diff options
47 files changed, 15345 insertions, 11 deletions
diff --git a/drivers/Makefile b/drivers/Makefile index adad2f3d438a..6e3a8a854db4 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_EDAC) += edac/ obj-$(CONFIG_MCA) += mca/ obj-$(CONFIG_EISA) += eisa/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ +obj-$(CONFIG_ARCH_MXC) += mxc/ obj-$(CONFIG_MMC) += mmc/ obj-$(CONFIG_NEW_LEDS) += leds/ obj-$(CONFIG_INFINIBAND) += infiniband/ diff --git a/drivers/mxc/Kconfig b/drivers/mxc/Kconfig new file mode 100644 index 000000000000..f913e03af789 --- /dev/null +++ b/drivers/mxc/Kconfig @@ -0,0 +1,18 @@ +# drivers/video/mxc/Kconfig + +if ARCH_MXC + +menu "MXC support drivers" + +source "drivers/mxc/ipu/Kconfig" +source "drivers/mxc/ssi/Kconfig" +source "drivers/mxc/dam/Kconfig" +source "drivers/mxc/pmic/Kconfig" +source "drivers/mxc/pm/Kconfig" +source "drivers/mxc/security/Kconfig" +source "drivers/mxc/hmp4e/Kconfig" +source "drivers/mxc/vpu/Kconfig" + +endmenu + +endif diff --git a/drivers/mxc/Makefile b/drivers/mxc/Makefile new file mode 100644 index 000000000000..4a7029df5200 --- /dev/null +++ b/drivers/mxc/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_MXC_IPU) += ipu/ +obj-$(CONFIG_MXC_SSI) += ssi/ +obj-$(CONFIG_MXC_DAM) += dam/ + +obj-$(CONFIG_MXC_PMIC) += pmic/ + +obj-$(CONFIG_MXC_DPTC) += pm/ +obj-$(CONFIG_MX27_DPTC) += pm/ +obj-$(CONFIG_MXC_HMP4E) += hmp4e/ +obj-y += security/ +obj-$(CONFIG_MXC_VPU) += vpu/ diff --git a/drivers/mxc/dam/Kconfig b/drivers/mxc/dam/Kconfig new file mode 100644 index 000000000000..3df2f8da2ecd --- /dev/null +++ b/drivers/mxc/dam/Kconfig @@ -0,0 +1,14 @@ +# +# 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..0596557c99b6 --- /dev/null +++ b/drivers/mxc/dam/Makefile @@ -0,0 +1,11 @@ +# +# 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..54c7dbc78625 --- /dev/null +++ b/drivers/mxc/dam/dam.c @@ -0,0 +1,427 @@ +/* + * 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 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 <asm/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..ee5575213e65 --- /dev/null +++ b/drivers/mxc/dam/dam_v1.c @@ -0,0 +1,616 @@ +/* + * 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 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 <asm/uaccess.h> +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include <asm/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 class_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 = 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"); + class_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 + class_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/hmp4e/Kconfig b/drivers/mxc/hmp4e/Kconfig new file mode 100644 index 000000000000..01b92a4714f1 --- /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..512f3e0f665a --- /dev/null +++ b/drivers/mxc/hmp4e/Makefile @@ -0,0 +1,9 @@ +# +# 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..459d2f34a3e0 --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.c @@ -0,0 +1,811 @@ +/* + * 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) + * + * 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 "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 = INT_MPEG4_ENC; + +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 class_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 = 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) +{ + class_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..53f173f1321e --- /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/ipu/Kconfig b/drivers/mxc/ipu/Kconfig new file mode 100644 index 000000000000..e13759c38425 --- /dev/null +++ b/drivers/mxc/ipu/Kconfig @@ -0,0 +1,17 @@ +menu "MXC IPU" + +config MXC_IPU + bool "MXC Image Processing Unit" + depends on ARCH_MXC + depends on !ARCH_MX27 + help + If you plan to use the Image Processing unit in the MXC, say + Y here. If unsure, select Y. + +config MXC_IPU_LPMC + bool + depends on MXC_IPU + +source "drivers/mxc/ipu/pf/Kconfig" + +endmenu diff --git a/drivers/mxc/ipu/Makefile b/drivers/mxc/ipu/Makefile new file mode 100644 index 000000000000..f94f0eb64dca --- /dev/null +++ b/drivers/mxc/ipu/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_MXC_IPU) += 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..85a53688b06e --- /dev/null +++ b/drivers/mxc/ipu/ipu_adc.c @@ -0,0 +1,677 @@ +/* + * 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_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 <asm/io.h> +#include <asm/arch/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; + } +} + +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..6abe712c5ba3 --- /dev/null +++ b/drivers/mxc/ipu/ipu_common.c @@ -0,0 +1,1800 @@ +/* + * 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_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 <asm/io.h> +#include <asm/arch/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); + + __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(0x00000070L, 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"); + break; + } + + /* 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; + uint32_t stride_bytes; + + dma_chan = channel_2_dma(channel, type); + stride_bytes = stride * bytes_per_pixel(pixel_fmt); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (stride_bytes % 4) { + dev_err(g_ipu_dev, + "Stride length must be 32-bit aligned, stride = %d, bytes = %d\n", + stride, stride_bytes); + return -EINVAL; + } + /* IC channels' stride must be multiple of 8 pixels */ + if ((dma_chan <= 13) && (stride % 8)) { + dev_err(g_ipu_dev, "Stride 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_bytes, + 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 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; + + /* 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) { + uint32_t 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) { + uint32_t timeout = 5; + spin_lock_irqsave(&ipu_lock, lock_flags); + ipu_clear_irq(IPU_IRQ_SDC_BG_EOF); + if (_ipu_sdc_bg_uninit() && wait_for_stop) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + while (!ipu_get_irq_status(IPU_IRQ_SDC_BG_EOF)) { + msleep(5); + timeout--; + if (timeout == 0) + break; + } + } else { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } + } else if (channel == MEM_SDC_FG) { + uint32_t timeout = 5; + spin_lock_irqsave(&ipu_lock, lock_flags); + ipu_clear_irq(IPU_IRQ_SDC_FG_EOF); + if (_ipu_sdc_fg_uninit() && wait_for_stop) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + while (!ipu_get_irq_status(IPU_IRQ_SDC_FG_EOF)) { + msleep(5); + timeout--; + if (timeout == 0) + break; + } + } else { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } + } + + 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); + + /* Reset to buffer 0 */ + __raw_writel(chan_mask, IPU_CHA_CUR_BUF); + + /* 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; + + if (g_ipu_irq[1]) { + disable_irq(g_ipu_irq[0]); + disable_irq(g_ipu_irq[1]); + } + + 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); + } + + if (g_ipu_irq[1]) { + enable_irq(g_ipu_irq[0]); + enable_irq(g_ipu_irq[1]); + } + 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; +} + +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; + } + } + 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; + } + } + 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..ece04f003f85 --- /dev/null +++ b/drivers/mxc/ipu/ipu_csi.c @@ -0,0 +1,217 @@ +/* + * 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_csi.c + * + * @brief IPU CMOS Sensor interface functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <asm/arch/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 + * + */ +void ipu_csi_get_window_size(uint32_t * width, uint32_t * height) +{ + 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 + * + */ +void ipu_csi_set_window_size(uint32_t width, uint32_t height) +{ + __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 + * + */ +void ipu_csi_set_window_pos(uint32_t left, uint32_t top) +{ + 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..c777d143146b --- /dev/null +++ b/drivers/mxc/ipu/ipu_device.c @@ -0,0 +1,600 @@ +/* + * 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_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 <asm/io.h> +#include <asm/arch/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; + +typedef struct _event_type { + int irq; + void *dev; +} event_type; + +event_type events[MAX_Q_SIZE]; + +int register_ipu_device(void); + +/* Static functions */ + +int get_events(event_type * p) +{ + unsigned long flags; + int ret = 0; + spin_lock_irqsave(&queue_lock, flags); + if (pending_events != 0) { + *p = events[read_ptr]; + read_ptr++; + pending_events--; + if (read_ptr >= MAX_Q_SIZE) + read_ptr = 0; + } else { + ret = -1; + } + + spin_unlock_irqrestore(&queue_lock, flags); + return ret; +} + +static irqreturn_t mxc_ipu_generic_handler(int irq, void *dev_id) +{ + event_type 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_sdc_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 */ + { + event_type ev; + int r = -1; + r = get_events(&ev); + if (r == -1) { + wait_event_interruptible(waitq, + (pending_events != 0)); + r = get_events(&ev); + } + ret = -1; + if (r == 0) { + if (!copy_to_user((event_type *) arg, &ev, + sizeof(event_type))) { + 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; + ipu_csi_get_window_size(&w.width, &w.height); + 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; + if (copy_from_user + (&w, (ipu_csi_window_size *) arg, sizeof(w))) + return -EFAULT; + ipu_csi_set_window_size(w.width, w.height); + } + break; + case IPU_CSI_SET_WINDOW: + { + ipu_csi_window p; + if (copy_from_user + (&p, (ipu_csi_window *) arg, sizeof(p))) + return -EFAULT; + ipu_csi_set_window_pos(p.left, p.top); + } + 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; + default: + break; + + } + return ret; +} + +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, + .release = mxc_ipu_release, + .ioctl = mxc_ipu_ioctl +}; + +int register_ipu_device() +{ + int ret = 0; + struct class_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 = + class_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..83fc349deb10 --- /dev/null +++ b/drivers/mxc/ipu/ipu_ic.c @@ -0,0 +1,579 @@ +/* + * 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_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 <asm/io.h> +#include <asm/arch/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); +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 (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..1da41734f36e --- /dev/null +++ b/drivers/mxc/ipu/ipu_prv.h @@ -0,0 +1,59 @@ +/* + * 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_PRV_H__ +#define __INCLUDE_IPU_PRV_H__ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <asm/arch/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..6feb24d63cfd --- /dev/null +++ b/drivers/mxc/ipu/ipu_sdc.c @@ -0,0 +1,355 @@ +/* + * 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_sdc.c + * + * @brief IPU SDC submodule API functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <asm/io.h> +#include <asm/arch/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 vSyncWidth, 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 ((vSyncWidth == 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_start_width + h_end_width - 1) << 16); + __raw_writel(reg, SDC_HOR_CONF); + + reg = ((uint32_t) (vSyncWidth - 1) << 26) | SDC_V_SYNC_WIDTH_L | + ((uint32_t) (height + v_start_width + v_end_width - 1) << 16); + __raw_writel(reg, SDC_VER_CONF); + + g_h_start_width = h_start_width; + g_v_start_width = v_start_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_sdc_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_sdc_set_window_pos); diff --git a/drivers/mxc/ipu/pf/Kconfig b/drivers/mxc/ipu/pf/Kconfig new file mode 100644 index 000000000000..02cda917fb7a --- /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 + 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..3fdccc453f24 --- /dev/null +++ b/drivers/mxc/ipu/pf/Makefile @@ -0,0 +1,2 @@ +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..fe5057ed3d2b --- /dev/null +++ b/drivers/mxc/ipu/pf/mxc_pf.c @@ -0,0 +1,995 @@ +/* + * 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 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 <asm/arch/ipu.h> +#include <asm/arch/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; + bool buffer_dirty; + 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; + + 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.buffer_dirty = true; + 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) +{ + if (pf_data.buffer_dirty) { + flush_cache_all(); +// l2_flush_all(); + } + pf_data.buffer_dirty = false; + + 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 class_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 = 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) { + class_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/pm/Kconfig b/drivers/mxc/pm/Kconfig new file mode 100644 index 000000000000..ccf444e057ea --- /dev/null +++ b/drivers/mxc/pm/Kconfig @@ -0,0 +1,40 @@ +# +# Power Managment devices +# + +menu "Advanced Power Management devices" + +config MXC_DPTC + bool "MXC DPTC driver" + depends on ARCH_MX3 && MXC_PMIC_MC13783 + default y + help + This selects the Freescale MXC Internal DPTC driver. + If unsure, say N. + +config MX27_DPTC + bool "MXC DPTC driver" + depends on ARCH_MX27 && MXC_PMIC_MC13783 + default y + help + This selects the Freescale MX27 Internal DPTC driver. + If unsure, say N. + +config MXC_DVFS + bool "MXC DVFS driver" + depends on ARCH_MX3 + default y + help + This selects the Freescale MXC Internal DVFS driver. + If unsure, say N. + +config MXC_DVFS_SDMA + bool "MXC DVFS SDMA support" + depends on MXC_DVFS + default n + help + This selects the Freescale MXC Internal DVFS driver SDMA support. + If unsure, say N. + +endmenu + diff --git a/drivers/mxc/pm/Makefile b/drivers/mxc/pm/Makefile new file mode 100644 index 000000000000..70e965813976 --- /dev/null +++ b/drivers/mxc/pm/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for Power Managment devices. +# + +# Module dependencies for the MXC driver +obj-$(CONFIG_MXC_DPTC) += dptc.o + +obj-$(CONFIG_MXC_DPTC) += dvfs_dptc.o + +obj-$(CONFIG_MXC_DVFS) += dvfs_dptc.o + +obj-$(CONFIG_MX27_DPTC) += dptc_mx27.o + diff --git a/drivers/mxc/pm/dptc.c b/drivers/mxc/pm/dptc.c new file mode 100644 index 000000000000..db28b95fc6f3 --- /dev/null +++ b/drivers/mxc/pm/dptc.c @@ -0,0 +1,890 @@ +/* + * 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 dptc.c + * + * @brief Driver for the Freescale Semiconductor MXC DPTC module. + * + * The DPTC driver + * driver is designed as a character driver which interacts with the MXC DPTC + * 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 voltage, increase voltage or emergency) and changes + * the CPU voltage according to translation table that is loaded into the driver + * (the voltage changes are done by calling some routines in the mc13783 driver). + * The driver read method is used to read the currently loaded DPTC translation + * table and the write method is used in-order to update the translation table. + * Driver ioctls are used to change driver parameters and enable/disable the + * DPTC operation. + * + * @ingroup PM_MX31 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <asm/uaccess.h> +#include <asm/hardware.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <asm/semaphore.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <asm/arch/sdma.h> +#include <asm/arch/pmic_power.h> + +/* + * Module header file + */ +#include <asm/arch/dptc.h> + +/* + * CRM registers + */ +#ifdef CONFIG_ARCH_MX3 +#include "../../../arch/arm/mach-mx3/crm_regs.h" +#endif + +/*! + * The dvfs_dptc_params structure holds all the internal DPTC driver parameters + * (current working point, current frequency, translation table and DPTC + * log buffer). + */ +static dvfs_dptc_params_s *dvfs_dptc_params; + +static struct delayed_work dptc_work; + +#ifndef CONFIG_MXC_DVFS_SDMA +static unsigned long ptvai; +#endif + +/*! + * Spinlock to protect CRM register accesses + */ +static DEFINE_SPINLOCK(mxc_crm_lock); + +/*! + * This function is called to read the contents of a CCM_MCU register + * + * @param reg_offset the CCM_MCU register that will read + * + * @return the register contents + */ +static unsigned long mxc_ccm_get_reg(unsigned int reg_offset) +{ + unsigned long reg; + + reg = __raw_readl(reg_offset); + return reg; +} + +/*! + * 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 + */ +static void mxc_ccm_modify_reg(unsigned int 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); +} + +/*! + * Enable DPTC hardware + */ +static void dptc_enable_dptc(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DPTEN, + MXC_CCM_PMCR0_DPTEN); +} + +/*! + * Disable DPTC hardware + */ +static void dptc_disable_dptc(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DPTEN, 0); +} + +/*! + * Mask DPTC interrupt + */ +static void dptc_mask_dptc_int(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_PTVAIM, + MXC_CCM_PMCR0_PTVAIM); +} + +/*! + * Unmask DPTC interrupt + */ +static void dptc_unmask_dptc_int(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_PTVAIM, 0); +} + +/*! + * Read the PTVAI bits from the CCM + * + * @return PTVAI bits value + */ +static unsigned long dptc_get_ptvai(void) +{ + return (mxc_ccm_get_reg(MXC_CCM_PMCR0) & MXC_CCM_PMCR0_PTVAI_MASK) + >> MXC_CCM_PMCR0_PTVAI_OFFSET; +} + +/*! + * Clear DCR bits of the CCM + */ +static void dptc_clear_dcr(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DCR, 0); +} + +/*! + * If value equal to PTVAI bits indicates working point decrease + */ +#define DPTC_DECREASE (unsigned long)0x1 + +/*! + * If value equal to PTVAI bits indicates working point increase + */ +#define DPTC_INCREASE (unsigned long)0x2 + +/*! + * If value equal to PTVAI bits indicates working point increase to maximum + */ +#define DPTC_EMERG (unsigned long)0x3 + +#ifdef CONFIG_MXC_DVFS +#ifndef CONFIG_MXC_DVFS_SDMA +/* + * MCU will get DPTC interrupt + */ +static void dptc_set_ptvis(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_PTVIS, + MXC_CCM_PMCR0_PTVIS); +} +#endif +#endif + +/*! + * 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) +{ + int ret_val; + + if (params->dptc_is_active == FALSE) { + params->rc_state = rc_state; + + if (rc_state & 0x1) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DRCE0, + MXC_CCM_PMCR0_DRCE0); + pr_debug("Ref circuit 0 enabled\n"); + } + if (rc_state & 0x2) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DRCE1, + MXC_CCM_PMCR0_DRCE1); + pr_debug("Ref circuit 1 enabled\n"); + } + if (rc_state & 0x4) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DRCE2, + MXC_CCM_PMCR0_DRCE2); + pr_debug("Ref circuit 2 enabled\n"); + } + if (rc_state & 0x8) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DRCE3, + MXC_CCM_PMCR0_DRCE3); + pr_debug("Ref circuit 3 enabled\n"); + } + + ret_val = 0; + } else { + ret_val = -EINVAL; + } + + return ret_val; +} + +/*! + * 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) +{ + int ret_val; + + if (params->dptc_is_active == FALSE) { + params->rc_state &= ~rc_state; + + if (rc_state & 0x1) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, + MXC_CCM_PMCR0_DRCE0, 0); + pr_debug("Ref circuit 0 disabled\n"); + } + if (rc_state & 0x2) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, + MXC_CCM_PMCR0_DRCE1, 0); + pr_debug("Ref circuit 1 disabled\n"); + } + if (rc_state & 0x4) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, + MXC_CCM_PMCR0_DRCE2, 0); + pr_debug("Ref circuit 2 disabled\n"); + } + if (rc_state & 0x8) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, + MXC_CCM_PMCR0_DRCE3, 0); + pr_debug("Ref circuit 3 disabled\n"); + } + + ret_val = 0; + } else { + ret_val = -EINVAL; + } + + return ret_val; +} + +static void dptc_workqueue_handler(struct work_struct *work) +{ + dvfs_dptc_params_s *params; + + params = (dvfs_dptc_params_s *) work_data_bits(work); + + pr_debug("In %s: PTVAI = %lu\n", __FUNCTION__, dptc_get_ptvai()); + pr_debug("PMCR0 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_PMCR0)); + pr_debug("DCVR0 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_DCVR0)); + pr_debug("DCVR1 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_DCVR1)); + pr_debug("DCVR2 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_DCVR2)); + pr_debug("DCVR3 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_DCVR3)); + pr_debug("PTVAI = 0x%lx\n", ptvai); +} + +/*! + * 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) +{ +#ifndef CONFIG_MXC_DVFS_SDMA + ptvai = dptc_get_ptvai(); + + pr_debug("ptvai = 0x%lx (0x%x)!!!!!!!\n", ptvai, + __raw_readl(MXC_CCM_PMCR0)); + if (ptvai != 0) { + dptc_mask_dptc_int(); + dptc_disable_dptc(); + + schedule_delayed_work(&dptc_work, 0); + } +#else + schedule_delayed_work(&dptc_work, 0); +#endif +} + +/*! + * This function updates the CPU voltage, produced by mc13783, by calling mc13783 + * driver functions. + * + * @param dvfs_dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + * frequency. + * + */ +void set_pmic_voltage(dvfs_dptc_tables_s * dvfs_dptc_tables_ptr, int wp) +{ + /* Call mc13783 functions */ + t_regulator_voltage volt; + + /* Normal mode setting */ + volt.sw1a = dvfs_dptc_tables_ptr->wp[wp].pmic_values[0]; + pmic_power_regulator_set_voltage(SW_SW1A, volt); + +#ifdef CONFIG_MXC_DVFS + volt.sw1a = dvfs_dptc_tables_ptr->wp[wp].pmic_values[1]; + pmic_power_switcher_set_dvs(SW_SW1A, volt); + + volt.sw1b = dvfs_dptc_tables_ptr->wp[wp].pmic_values[2]; + pmic_power_switcher_set_dvs(SW_SW1B, volt); + + volt.sw1b = dvfs_dptc_tables_ptr->wp[wp].pmic_values[3]; + pmic_power_switcher_set_stby(SW_SW1B, volt); +#endif + + if (cpu_is_mx31()) { + pr_debug("DPVV = 0x%lx (0x%lx)\n", + mxc_ccm_get_reg(MXC_CCM_PMCR0) & MXC_CCM_PMCR0_DPVV, + mxc_ccm_get_reg(MXC_CCM_PMCR0)); + } +} + +/*! + * This function enables the DPTC module. this function updates the DPTC + * thresholds, updates the mc13783, 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) +{ + int freq_index = 0; + + /* Check if DPTC module isn't already active */ + if (params->dptc_is_active == FALSE) { + + enable_ref_circuits(params, params->rc_state); + disable_ref_circuits(params, ~params->rc_state); + + /* + * Set the DPTC thresholds and mc13783 voltage to + * correspond to the current working point and frequency. + */ + set_pmic_voltage(params->dvfs_dptc_tables_ptr, + params->dvfs_dptc_tables_ptr->curr_wp); + +#ifdef CONFIG_MXC_DVFS + freq_index = dvfs_get_dvsup(); +#endif + + update_dptc_thresholds(params->dvfs_dptc_tables_ptr, + params->dvfs_dptc_tables_ptr->curr_wp, + freq_index); + + /* Mark DPCT module as active */ + params->dptc_is_active = TRUE; + + /* Enable the DPTC module and unmask the DPTC interrupt */ + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + + return 0; + } + + /* DPTC module already active return error */ + return -EINVAL; +} + +/*! + * 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) +{ + /* Check if DPTC module isn't already disabled */ + if (params->dptc_is_active != FALSE) { + + /* Disable the DPTC module and mask the DPTC interrupt */ + dptc_disable_dptc(); + dptc_mask_dptc_int(); + + /* Set working point 0 */ + set_dptc_wp(params, 0); + + /* Mark DPCT module as inactive */ + params->dptc_is_active = FALSE; + + return 0; + } + + /* DPTC module already disabled, return error */ + return -EINVAL; +} + +/*! + * 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) +{ + int freq_index = 0; + +#ifdef CONFIG_MXC_DVFS + freq_index = dvfs_get_dvsup(); +#endif + + /* + * Check if new index is smaller than the maximal working point + * index in the DPTC translation table and larger that 0. + */ + if ((new_wp < params->dvfs_dptc_tables_ptr->wp_num) + && (new_wp >= 0)) { + /* Set current working point index to new index */ + params->dvfs_dptc_tables_ptr->curr_wp = new_wp; + } + + /* + * Check if new index is larger than the maximal working point index in + * the DPTC translation table. + */ + if (new_wp >= params->dvfs_dptc_tables_ptr->wp_num) { + /* + * Set current working point index to maximal working point + * index in the DPTC translation table. + */ + params->dvfs_dptc_tables_ptr->curr_wp = + params->dvfs_dptc_tables_ptr->wp_num - 1; + } + + /* Check if new index is smaller than 0. */ + if (new_wp < 0) { + /* Set current working point index to 0 (minimal value) */ + params->dvfs_dptc_tables_ptr->curr_wp = 0; + } +#ifndef CONFIG_MXC_DVFS_SDMA + /* Update the DPTC hardware thresholds */ + update_dptc_thresholds(params->dvfs_dptc_tables_ptr, + params->dvfs_dptc_tables_ptr->curr_wp, + freq_index); +#else + params->prev_wp = params->dvfs_dptc_tables_ptr->curr_wp; +#endif + + /* Update the mc13783 voltage */ + set_pmic_voltage(params->dvfs_dptc_tables_ptr, + params->dvfs_dptc_tables_ptr->curr_wp); + + /* Write DPTC changes in the DPTC log buffer */ + add_dptc_log_entry(params, ¶ms->dptc_log_buffer, + params->dvfs_dptc_tables_ptr->curr_wp, freq_index); + + pr_debug("Current wp: %d\n", params->dvfs_dptc_tables_ptr->curr_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 * dvfs_dptc_tables_ptr, + int wp, int freq_index) +{ + dcvr_state *dcvr; + +#ifdef CONFIG_MXC_DVFS_SDMA + + dcvr_state **dcvr_arr; + dcvr_state *curr_freq_dcvr; + + dcvr_arr = dvfs_dptc_tables_ptr->dcvr; + dcvr_arr = sdma_phys_to_virt((unsigned long)dcvr_arr); + curr_freq_dcvr = dcvr_arr[freq_index]; + curr_freq_dcvr = sdma_phys_to_virt((unsigned long)curr_freq_dcvr); + dcvr = &curr_freq_dcvr[wp]; +#else + /* Calculate current table entry offset in the DPTC translation table */ + dcvr = &dvfs_dptc_tables_ptr->dcvr[freq_index][wp]; +#endif + /* Update DPTC threshold registers */ + mxc_ccm_modify_reg(MXC_CCM_DCVR0, 0xffffffff, dcvr->dcvr_reg[0].AsInt); + mxc_ccm_modify_reg(MXC_CCM_DCVR1, 0xffffffff, dcvr->dcvr_reg[1].AsInt); + mxc_ccm_modify_reg(MXC_CCM_DCVR2, 0xffffffff, dcvr->dcvr_reg[2].AsInt); + mxc_ccm_modify_reg(MXC_CCM_DCVR3, 0xffffffff, dcvr->dcvr_reg[3].AsInt); +} + +/*! + * This function increments a log buffer index (head or tail) + * by the value of val. + * + * @param index pointer to the DPTC log buffer index that + * we wish to change. + * @param val the value in which the index should be incremented. + * + */ +static void inc_log_index(int *index, int val) +{ + *index = (*index + val) % LOG_ENTRIES; +} + +/*! + * This function returns the number of entries in the DPTC log buffer. + * + * @param dptc_log pointer to the DPTC log buffer structure. + * + * @return number of log buffer entries. + * + */ +static int get_entry_count(dptc_log_s * dptc_log) +{ + return ((dptc_log->head - dptc_log->tail + LOG_ENTRIES) % LOG_ENTRIES); +} + +/*! + * This function is used by the proc file system to read the DPTC log buffer. + * Each time the DPTC proc file system file is read this function is called + * and returns the data written in the log buffer. + * + * @param buf pointer to the buffer the data should 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 current offset in the DPTC proc file. + * @param count number of bytes to read. + * @param eof pointer to eof flag. should be set to 1 when + * reaching eof. + * @param data driver specific data pointer. + * + * @return number byte read from the log buffer. + * + */ +static int read_log(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + int entries_to_read; + int num_of_entries; + int entries_to_end_of_buffer, entries_left; + void *entry_ptr; + char *buf_ptr; + dvfs_dptc_params_s *params; + + params = (dvfs_dptc_params_s *) data; + + /* Calculate number of log entries to read */ + entries_to_read = count / sizeof(dptc_log_entry_s); + /* Get the number of current log buffer entries */ + num_of_entries = get_entry_count(¶ms->dptc_log_buffer); + + /* + * If number of entries to read is larger that the number of entries + * in the log buffer set number of entries to read to number of + * entries in the log buffer and set eof flag to 1 + */ + if (num_of_entries < entries_to_read) { + entries_to_read = num_of_entries; + *eof = 1; + } + + /* + * Down the log buffer mutex to exclude others from reading and + * writing to the log buffer. + */ + if (down_interruptible(¶ms->dptc_log_buffer.mutex)) { + return -EAGAIN; + } + + if (num_of_entries == 0 && offset == 0) { + inc_log_index(¶ms->dptc_log_buffer.tail, -1); + num_of_entries++; + entries_to_read++; + } + + /* get the pointer of the last (oldest) entry in the log buffer */ + entry_ptr = (void *)¶ms->dptc_log_buffer. + entries[params->dptc_log_buffer.tail]; + + /* Check if tail index wraps during current read */ + if ((params->dptc_log_buffer.tail + entries_to_read) < LOG_ENTRIES) { + /* No tail wrap around copy data from log buffer to buf */ + memcpy(buf, entry_ptr, + (entries_to_read * sizeof(dptc_log_entry_s))); + } else { + /* + * Tail wrap around. + * First copy data from current position until end of buffer, + * after that copy the rest from start of the log buffer. + */ + entries_to_end_of_buffer = LOG_ENTRIES - + params->dptc_log_buffer.tail; + memcpy(buf, entry_ptr, + (entries_to_end_of_buffer * sizeof(dptc_log_entry_s))); + + entry_ptr = (void *)¶ms->dptc_log_buffer.entries[0]; + buf_ptr = buf + + (entries_to_end_of_buffer * sizeof(dptc_log_entry_s)); + entries_left = entries_to_read - entries_to_end_of_buffer; + memcpy(buf_ptr, entry_ptr, + (entries_left * sizeof(dptc_log_entry_s))); + } + + /* Increment the tail index by the number of entries read */ + inc_log_index(¶ms->dptc_log_buffer.tail, entries_to_read); + + /* Up the log buffer mutex to allow access to the log buffer */ + up(¶ms->dptc_log_buffer.mutex); + + /* set start of data to point to buf */ + *start = buf; + + return (entries_to_read * sizeof(dptc_log_entry_s)); +} + +/*! + * This function initializes the DPTC log buffer. + * + * @param params pointer to \b dvfs_dptc_params_s. + * @param dptc_log pointer to the DPTC log buffer structure. + * + */ +void init_dptc_log(dvfs_dptc_params_s * params, dptc_log_s * dptc_log) +{ + dptc_log->tail = 0; + dptc_log->head = 0; + + /* initialize log buffer mutex used for accessing the log buffer */ + sema_init(&dptc_log->mutex, 1); + + /* add the first log buffer entry */ + add_dptc_log_entry(params, dptc_log, + params->dvfs_dptc_tables_ptr->curr_wp, + params->current_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) +{ + /* + * Down the log buffer mutex to exclude others from reading and + * writing to the log buffer. + */ + if (down_interruptible(&dptc_log->mutex)) { + return; + } + + /* Write values to log buffer */ + dptc_log->entries[dptc_log->head].jiffies = jiffies; + dptc_log->entries[dptc_log->head].wp = wp; + + dptc_log->entries[dptc_log->head].voltage = wp; + +#ifdef CONFIG_MXC_DVFS + freq_index = dptc_get_ptvai(); +#endif + + dptc_log->entries[dptc_log->head].freq = freq_index; + + /* Increment the head index by 1 */ + inc_log_index(&dptc_log->head, 1); + /* If head index reaches the tail increment the tail by 1 */ + if (dptc_log->head == dptc_log->tail) { + inc_log_index(&dptc_log->tail, 1); + } + + /* Up the log buffer mutex to allow access to the log buffer */ + up(&dptc_log->mutex); +} + +/*! + * This function is called to put the DPTC in a low power state. + * + */ +void dptc_suspend(void) +{ +#ifdef CONFIG_MXC_DPTC + if (dvfs_dptc_params->dptc_is_active) { + dptc_disable_dptc(); + set_dptc_wp(dvfs_dptc_params, + dvfs_dptc_params-> + dvfs_dptc_tables_ptr->curr_wp - 1); + } +#endif +} + +/*! + * This function is called to resume the DPTC from a low power state. + * + */ +void dptc_resume(void) +{ +#ifdef CONFIG_MXC_DPTC + if (dvfs_dptc_params->dptc_is_active) { + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + } +#endif +} + +/*! + * This function initializes the DPTC hardware + * + * @param params pointer to the DPTC driver parameters structure. + * + */ +int __init init_dptc_controller(dvfs_dptc_params_s * params) +{ + dvfs_dptc_params = params; + + INIT_DELAYED_WORK(&dptc_work, dptc_workqueue_handler); + + if (create_proc_read_entry(PROC_NODE_NAME, 0, + NULL, read_log, params) == NULL) { + /* + * Error creating proc file system entry. + * Exit and return error code + */ + printk(KERN_ERR "DPTC: Unable create proc entry"); + return -EFAULT; + } + + /* Initialize the DPTC log buffer */ + init_dptc_log(params, ¶ms->dptc_log_buffer); + + set_dptc_curr_freq(params, 0); + set_dptc_wp(params, 0); + + /* By default all reference circuits are enabled */ + params->rc_state = DPTC_REF_CIRCUITS_STATUS; + + dptc_clear_dcr(); + + /* Disable DPTC hardware and mask DPTC interrupt */ + dptc_disable_dptc(); + dptc_mask_dptc_int(); + +#ifdef CONFIG_MXC_DVFS +#ifndef CONFIG_MXC_DVFS_SDMA + dptc_set_ptvis(); +#endif +#endif + printk(KERN_INFO "DPTC controller initialized\n"); + + return 0; +} + +/*! + * 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) +{ + /* + * Check if the new index value is a valid frequency index (smaller + * than the maximal index in the DPTC table) and if the DPTC module + * is disabled. + */ + if ((freq_index < params->dvfs_dptc_tables_ptr->dvfs_state_num) + && (params->dptc_is_active == FALSE)) { + /* + * Index is valid and DPTC module is + * disabled -> change frequency index. + */ + params->current_freq_index = freq_index; + add_dptc_log_entry(params, ¶ms->dptc_log_buffer, + params->dvfs_dptc_tables_ptr->curr_wp, + params->current_freq_index); + + return 0; + } + + /* Invalid index or DPTC module is active -> return error */ + return -EINVAL; +} + +#ifdef CONFIG_MXC_DVFS_SDMA +/* + * DPTC SDMA callback. + * Updates the mc13783 voltage + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + */ +void dptc_sdma_callback(dvfs_dptc_params_s * params) +{ + printk(KERN_INFO "In %s: params->dvfs_dptc_tables_ptr->curr_wp = %d\n", + __FUNCTION__, params->dvfs_dptc_tables_ptr->curr_wp); +} +#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) +{ + dptc_suspend(); + return 0; +} + +/*! + * 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 *pdev) +{ + dptc_resume(); + return 0; +} + +EXPORT_SYMBOL(dptc_suspend); +EXPORT_SYMBOL(dptc_resume); diff --git a/drivers/mxc/pm/dptc_mx27.c b/drivers/mxc/pm/dptc_mx27.c new file mode 100644 index 000000000000..138adc26a8a9 --- /dev/null +++ b/drivers/mxc/pm/dptc_mx27.c @@ -0,0 +1,1416 @@ +/* + * 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 dptc_mx27.c + * + * @brief Driver for the Freescale Semiconductor MX27 DPTC module. + * + * The DPTC driver is designed as a character driver which interacts with the + * MX27 DPTC 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 voltage, increase voltage or + * emergency) and changes the CPU voltage according to translation table that + * is loaded into the driver(the voltage changes are done by calling some + * routines in the mc13783 driver). The driver read method is used to read the + * currently loaded DPTC translation table and the write method is used + * in-order to update the translation table. Driver ioctls are used to change + * driver parameters and enable/disable the DPTC operation. + * + * @ingroup PM_MX27 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <asm/uaccess.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <asm/semaphore.h> +#include <linux/vmalloc.h> +#include <asm/arch/pmic_power.h> +#include <asm/arch/dvfs_dptc_struct.h> +#include "dvfs_dptc.h" + +#define MX27_PMCR_BASE_ADDR (SYSCTRL_BASE_ADDR + 0x60) +#define MX27_DCVR_BASE_ADDR (SYSCTRL_BASE_ADDR + 0x64) +#define MX27_DCVR0_OFFSET 0 +#define MX27_DCVR1_OFFSET 4 +#define MX27_DCVR2_OFFSET 8 +#define MX27_DCVR3_OFFSET 12 + +#define MX27_PMCR_MC_STATUS_OFFSET 31 +#define MX27_PMCR_MC_STATUS_MASK (1 << 31) +#define MX27_PMCR_EM_INTR_OFFSET 30 +#define MX27_PMCR_EM_INTR_MASK (1 << 30) +#define MX27_PMCR_UP_INTR_OFFSET 29 +#define MX27_PMCR_UP_INTR_MASK (1 << 29) +#define MX27_PMCR_LO_INTR_OFFSET 28 +#define MX27_PMCR_LO_INTR_MASK (1 << 28) +#define MX27_PMCR_REFCNT_OFFSET 16 +#define MX27_PMCR_REFCNT_MASK (0x7FF << 16) +#define MX27_PMCR_DCR_OFFSET 9 +#define MX27_PMCR_DCR_MASK (1 << 9) +#define MX27_PMCR_RCLKON_OFFSET 8 +#define MX27_PMCR_RCLKON_MASK (1 << 8) +#define MX27_PMCR_DRCE3_OFFSET 7 +#define MX27_PMCR_DRCE3_MASK (1 << 7) +#define MX27_PMCR_DRCE2_OFFSET 6 +#define MX27_PMCR_DRCE2_MASK (1 << 6) +#define MX27_PMCR_DRCE1_OFFSET 5 +#define MX27_PMCR_DRCE1_MASK (1 << 5) +#define MX27_PMCR_DRCE0_OFFSET 4 +#define MX27_PMCR_DRCE0_MASK (1 << 4) +#define MX27_PMCR_DIM_OFFSET 2 +#define MX27_PMCR_DIM_MASK (0x3 << 2) +#define MX27_PMCR_DIE_OFFSET 1 +#define MX27_PMCR_DIE_MASK (1 << 1) +#define MX27_PMCR_DPTEN_OFFSET 0 +#define MX27_PMCR_DPTEN_MASK (1 << 0) + +/*! + * DPTC proc file system entry name + */ +#define PROC_NODE_NAME "dptc" + +/* + * Prototypes + */ +static int dptc_mx27_open(struct inode *inode, struct file *filp); +static int dptc_mx27_release(struct inode *inode, struct file *filp); +static int dptc_mx27_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg); +static irqreturn_t dptc_mx27_irq(int irq, void *dev_id); + +/* + * Global variables + */ + +/*! + * The dvfs_dptc_params structure holds all the internal DPTC driver parameters + * (current working point, current frequency, translation table and DPTC + * log buffer). + */ +static dvfs_dptc_params_s dptc_params; + +static struct work_struct dptc_work; +static volatile u32 dptc_intr_status; +static u32 dptc_initial_wp; + +/*! + * Holds the automatically selected DPTC driver major number. + */ +static int major; + +static struct class *mxc_dvfs_dptc_class; + +/* + * This mutex makes the Read,Write and IOCTL command mutual exclusive. + */ +DECLARE_MUTEX(access_mutex); + +/*! + * This structure contains pointers for device driver entry point. + * The driver register function in init module will call this + * structure. + */ +static struct file_operations fops = { + .open = dptc_mx27_open, + .release = dptc_mx27_release, + .ioctl = dptc_mx27_ioctl, +}; + +static +void mx27_pmcr_modify_reg(u32 mask, u32 val) +{ + volatile u32 reg; + reg = __raw_readl(IO_ADDRESS(MX27_PMCR_BASE_ADDR)); + reg = (reg & (~mask)) | val; + __raw_writel(reg, IO_ADDRESS(MX27_PMCR_BASE_ADDR)); +} + +static +void mx27_dcvr_modify_reg(u32 offset, u32 val) +{ + __raw_writel(val, IO_ADDRESS(MX27_DCVR_BASE_ADDR) + offset); +} + +/*! + * Enable DPTC hardware + */ +static void dptc_enable_dptc(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DPTEN_MASK, 1); +} + +/*! + * Disable DPTC hardware + */ +static void dptc_disable_dptc(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DPTEN_MASK, 0); +} + +/*! + * Mask DPTC interrupt + */ +static void dptc_mask_dptc_int(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DIE_MASK, (0 << MX27_PMCR_DIE_OFFSET)); +} + +/*! + * Unmask DPTC interrupt + */ +static void dptc_unmask_dptc_int(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DIE_MASK, (1 << MX27_PMCR_DIE_OFFSET)); +} + +/*! + * Clear DCR bits of the CCM + */ +static void dptc_clear_dcr(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DCR_MASK, (0 << MX27_PMCR_DCR_OFFSET)); +} + +/*! + * This function enables the DPTC reference circuits. + * + * @param rc_state each high bit specifies which + * reference circuite to enable + * @return 0 on success, error code on failure + */ +static int enable_ref_circuits(unsigned char rc_state) +{ + int ret_val; + + if (dptc_params.dptc_is_active == FALSE) { + dptc_params.rc_state = rc_state; + + if (rc_state & 0x1) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE0_MASK, + (1 << MX27_PMCR_DRCE0_OFFSET)); + } + if (rc_state & 0x2) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE1_MASK, + (1 << MX27_PMCR_DRCE1_OFFSET)); + } + if (rc_state & 0x4) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE2_MASK, + (1 << MX27_PMCR_DRCE2_OFFSET)); + } + if (rc_state & 0x8) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE3_MASK, + (1 << MX27_PMCR_DRCE3_OFFSET)); + } + + ret_val = 0; + } else { + ret_val = -EINVAL; + } + + return ret_val; +} + +/*! + * This function disables the DPTC reference circuits. + * + * @param rc_state each high bit specifies which + * reference circuite to disable + * @return 0 on success, error code on failure + */ +static int disable_ref_circuits(unsigned char rc_state) +{ + int ret_val; + + if (dptc_params.dptc_is_active == FALSE) { + dptc_params.rc_state &= ~rc_state; + + if (rc_state & 0x1) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE0_MASK, + (0 << MX27_PMCR_DRCE0_OFFSET)); + } + if (rc_state & 0x2) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE1_MASK, + (0 << MX27_PMCR_DRCE1_OFFSET)); + } + if (rc_state & 0x4) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE2_MASK, + (0 << MX27_PMCR_DRCE2_OFFSET)); + } + if (rc_state & 0x8) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE3_MASK, + (0 << MX27_PMCR_DRCE3_OFFSET)); + } + + ret_val = 0; + } else { + ret_val = -EINVAL; + } + + return ret_val; +} + +/*! + * This function updates the CPU voltage, produced by MC13783, by calling + * MC13783 driver functions. + * + * @param dvfs_dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + * + */ +static void set_pmic_voltage(dvfs_dptc_tables_s * dvfs_dptc_tables_ptr, int wp) +{ + /* Call MC13783 functions */ + t_regulator_voltage volt; + + volt.sw1a = dvfs_dptc_tables_ptr->wp[wp].pmic_values[0]; + pmic_power_regulator_set_voltage(SW_SW1A, volt); +} + +/*! + * This function updates the DPTC threshold registers. + * + * @param dvfs_dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + * + */ +static void update_dptc_thresholds(dvfs_dptc_tables_s * dvfs_dptc_tables_ptr, + int wp) +{ + dcvr_state *dcvr; + + /* Calculate current table entry offset in the DPTC translation table */ + dcvr = &dvfs_dptc_tables_ptr->dcvr[0][wp]; + + /* Update DPTC threshold registers */ + mx27_dcvr_modify_reg(MX27_DCVR0_OFFSET, dcvr->dcvr_reg[0].AsInt); + mx27_dcvr_modify_reg(MX27_DCVR1_OFFSET, dcvr->dcvr_reg[1].AsInt); + mx27_dcvr_modify_reg(MX27_DCVR2_OFFSET, dcvr->dcvr_reg[2].AsInt); + mx27_dcvr_modify_reg(MX27_DCVR3_OFFSET, dcvr->dcvr_reg[3].AsInt); +} + +/*! + * This function increments a log buffer index (head or tail) + * by the value of val. + * + * @param index pointer to the DPTC log buffer index that + * we wish to change. + * @param val the value in which the index should be incremented. + * + */ +static void inc_log_index(int *index, int val) +{ + *index = (*index + val) % LOG_ENTRIES; +} + +/*! + * This function adds a new entry to the DPTC log buffer. + * + * @param dptc_log pointer to the DPTC log buffer structure. + * @param wp value of the working point index written + * to the log buffer. + * + * @return number of log buffer entries. + * + */ +static void add_dptc_log_entry(dptc_log_s * dptc_log, int wp) +{ + /* + * Down the log buffer mutex to exclude others from reading and + * writing to the log buffer. + */ + if (down_interruptible(&dptc_log->mutex)) { + return; + } + + /* Write values to log buffer */ + dptc_log->entries[dptc_log->head].jiffies = jiffies; + dptc_log->entries[dptc_log->head].wp = wp; + + dptc_log->entries[dptc_log->head].voltage = wp; + dptc_log->entries[dptc_log->head].freq = 0; + + /* Increment the head index by 1 */ + inc_log_index(&dptc_log->head, 1); + /* If head index reaches the tail increment the tail by 1 */ + if (dptc_log->head == dptc_log->tail) { + inc_log_index(&dptc_log->tail, 1); + } + + /* Up the log buffer mutex to allow access to the log buffer */ + up(&dptc_log->mutex); +} + +/*! + * 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 new_wp New working point index value to be set. + * + */ +static void set_dptc_wp(int new_wp) +{ + /* + * Check if new index is smaller than the maximal working point + * index in the DPTC translation table and larger that 0. + */ + if ((new_wp < dptc_params.dvfs_dptc_tables_ptr->wp_num) + && (new_wp >= 0)) { + /* Set current working point index to new index */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = new_wp; + } + + /* + * Check if new index is larger than the maximal working point index in + * the DPTC translation table. + */ + if (new_wp >= dptc_params.dvfs_dptc_tables_ptr->wp_num) { + /* + * Set current working point index to maximal working point + * index in the DPTC translation table. + */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = + dptc_params.dvfs_dptc_tables_ptr->wp_num - 1; + } + + /* Check if new index is smaller than 0. */ + if (new_wp < 0) { + /* Set current working point index to 0 (minimal value) */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = 0; + } + + /* Update the DPTC hardware thresholds */ + update_dptc_thresholds(dptc_params.dvfs_dptc_tables_ptr, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); + + /* Update the MC13783 voltage */ + set_pmic_voltage(dptc_params.dvfs_dptc_tables_ptr, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); + + /* Write DPTC changes in the DPTC log buffer */ + add_dptc_log_entry(&dptc_params.dptc_log_buffer, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); + +} + +static void dptc_workqueue_handler(struct work_struct *work) +{ + if (dptc_intr_status & 0x4) { + /* Chip working point has increased dramatically, + * raise working point to maximum */ + set_dptc_wp(dptc_params.dvfs_dptc_tables_ptr->curr_wp - 2); + } else if (dptc_intr_status & 0x2) { + /* Chip working point has increased, raise working point + * by one */ + set_dptc_wp(dptc_params.dvfs_dptc_tables_ptr->curr_wp + 1); + } else { + /* Chip working point has decreased, lower working point + * by one */ + set_dptc_wp(dptc_params.dvfs_dptc_tables_ptr->curr_wp - 1); + } + + /* + * If the DPTC module is still active, re-enable + * the DPTC hardware + */ + if (dptc_params.dptc_is_active) { + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + } +} + +/*! + * This function enables the DPTC module. this function updates the DPTC + * thresholds, updates the MC13783, unmasks the DPTC interrupt and enables + * the DPTC module + * + * @return 0 if DPTC module was enabled else returns -EINVAL. + */ +static int start_dptc(void) +{ + /* Check if DPTC module isn't already active */ + if (dptc_params.dptc_is_active == FALSE) { + + enable_ref_circuits(dptc_params.rc_state); + disable_ref_circuits(~dptc_params.rc_state); + + /* + * Set the DPTC thresholds and MC13783 voltage to + * correspond to the current working point and frequency. + */ + set_pmic_voltage(dptc_params.dvfs_dptc_tables_ptr, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); + + update_dptc_thresholds(dptc_params.dvfs_dptc_tables_ptr, + dptc_params.dvfs_dptc_tables_ptr-> + curr_wp); + + /* Mark DPTC module as active */ + dptc_params.dptc_is_active = TRUE; + + /* Enable the DPTC module and unmask the DPTC interrupt */ + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + + return 0; + } + + /* DPTC module already active return error */ + return -EINVAL; +} + +/*! + * This function disables the DPTC module. + * + * @return 0 if DPTC module was disabled else returns -EINVAL. + */ +static int stop_dptc(void) +{ + /* Check if DPTC module isn't already disabled */ + if (dptc_params.dptc_is_active != FALSE) { + + /* Disable the DPTC module and mask the DPTC interrupt */ + dptc_disable_dptc(); + dptc_mask_dptc_int(); + + /* Set working point to default */ + set_dptc_wp(dptc_initial_wp); + + /* Mark DPCT module as inactive */ + dptc_params.dptc_is_active = FALSE; + + return 0; + } + + /* DPTC module already disabled, return error */ + return -EINVAL; +} + +static void init_dptc_wp(void) +{ + /* Call MC13783 functions */ + t_regulator_voltage volt; + int i; + + /* Normal mode setting */ + pmic_power_regulator_get_voltage(SW_SW1A, &volt); + for (i = 0; i < dptc_params.dvfs_dptc_tables_ptr->wp_num; i++) { + if (volt.sw1a == + dptc_params.dvfs_dptc_tables_ptr->wp[i].pmic_values[0]) { + break; + } + } + + /* + * Check if new index is smaller than the maximal working point + * index in the DPTC translation table and larger that 0. + */ + if ((i < dptc_params.dvfs_dptc_tables_ptr->wp_num) && (i >= 0)) { + /* Set current working point index to new index */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = i; + } + + /* + * Check if new index is larger than the maximal working point index in + * the DPTC translation table. + */ + if (i >= dptc_params.dvfs_dptc_tables_ptr->wp_num) { + /* + * Set current working point index to maximal working point + * index in the DPTC translation table. + */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = + dptc_params.dvfs_dptc_tables_ptr->wp_num - 1; + } + + dptc_initial_wp = dptc_params.dvfs_dptc_tables_ptr->curr_wp; + + /* Initialize the log buffer */ + add_dptc_log_entry(&dptc_params.dptc_log_buffer, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); +} + +/*! + * This function returns the number of entries in the DPTC log buffer. + * + * @param dptc_log pointer to the DPTC log buffer structure. + * + * @return number of log buffer entries. + * + */ +static int get_entry_count(dptc_log_s * dptc_log) +{ + return ((dptc_log->head - dptc_log->tail + LOG_ENTRIES) % LOG_ENTRIES); +} + +/*! + * This function is used by the proc file system to read the DPTC log buffer. + * Each time the DPTC proc file system file is read this function is called + * and returns the data written in the log buffer. + * + * @param buf pointer to the buffer the data should 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 current offset in the DPTC proc file. + * @param count number of bytes to read. + * @param eof pointer to eof flag. should be set to 1 when + * reaching eof. + * @param data driver specific data pointer. + * + * @return number byte read from the log buffer. + * + */ +static int read_log(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + int entries_to_read; + int num_of_entries; + int entries_to_end_of_buffer, entries_left; + void *entry_ptr; + char *buf_ptr; + dvfs_dptc_params_s *params; + + params = (dvfs_dptc_params_s *) data; + + /* Calculate number of log entries to read */ + entries_to_read = count / sizeof(dptc_log_entry_s); + /* Get the number of current log buffer entries */ + num_of_entries = get_entry_count(¶ms->dptc_log_buffer); + + /* + * If number of entries to read is larger that the number of entries + * in the log buffer set number of entries to read to number of + * entries in the log buffer and set eof flag to 1 + */ + if (num_of_entries < entries_to_read) { + entries_to_read = num_of_entries; + *eof = 1; + } + + /* + * Down the log buffer mutex to exclude others from reading and + * writing to the log buffer. + */ + if (down_interruptible(¶ms->dptc_log_buffer.mutex)) { + return -EAGAIN; + } + + if (num_of_entries == 0 && offset == 0) { + inc_log_index(¶ms->dptc_log_buffer.tail, -1); + num_of_entries++; + entries_to_read++; + } + + /* get the pointer of the last (oldest) entry in the log buffer */ + entry_ptr = (void *)¶ms->dptc_log_buffer. + entries[params->dptc_log_buffer.tail]; + + /* Check if tail index wraps during current read */ + if ((params->dptc_log_buffer.tail + entries_to_read) < LOG_ENTRIES) { + /* No tail wrap around copy data from log buffer to buf */ + memcpy(buf, entry_ptr, + (entries_to_read * sizeof(dptc_log_entry_s))); + } else { + /* + * Tail wrap around. + * First copy data from current position until end of buffer, + * after that copy the rest from start of the log buffer. + */ + entries_to_end_of_buffer = LOG_ENTRIES - + params->dptc_log_buffer.tail; + memcpy(buf, entry_ptr, + (entries_to_end_of_buffer * sizeof(dptc_log_entry_s))); + + entry_ptr = (void *)¶ms->dptc_log_buffer.entries[0]; + buf_ptr = buf + + (entries_to_end_of_buffer * sizeof(dptc_log_entry_s)); + entries_left = entries_to_read - entries_to_end_of_buffer; + memcpy(buf_ptr, entry_ptr, + (entries_left * sizeof(dptc_log_entry_s))); + } + + /* Increment the tail index by the number of entries read */ + inc_log_index(¶ms->dptc_log_buffer.tail, entries_to_read); + + /* Up the log buffer mutex to allow access to the log buffer */ + up(¶ms->dptc_log_buffer.mutex); + + /* set start of data to point to buf */ + *start = buf; + + return (entries_to_read * sizeof(dptc_log_entry_s)); +} + +/*! + * This function initializes the DPTC log buffer. + * + * @param dptc_log pointer to the DPTC log buffer structure. + * + */ +static void init_dptc_log(dptc_log_s * dptc_log) +{ + dptc_log->tail = 0; + dptc_log->head = 0; + + /* initialize log buffer mutex used for accessing the log buffer */ + sema_init(&dptc_log->mutex, 1); +} + +/*! + * This function initializes the DPTC hardware + * + */ +static int init_dptc_controller(void) +{ + INIT_WORK(&dptc_work, dptc_workqueue_handler); + + if (create_proc_read_entry(PROC_NODE_NAME, 0, + NULL, read_log, &dptc_params) == NULL) { + /* + * Error creating proc file system entry. + * Exit and return error code + */ + printk(KERN_ERR "DPTC: Unable create proc entry"); + return -EFAULT; + } + + /* Initialize the DPTC log buffer */ + init_dptc_log(&dptc_params.dptc_log_buffer); + + init_dptc_wp(); + + /* By default all reference circuits are enabled */ + dptc_params.rc_state = DPTC_REF_CIRCUITS_STATUS; + + dptc_clear_dcr(); + + /* Disable DPTC hardware and mask DPTC interrupt */ + dptc_disable_dptc(); + dptc_mask_dptc_int(); + + printk(KERN_INFO "DPTC controller initialized\n"); + return 0; +} + +/*! + * 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) +{ + if (dptc_params.dptc_is_active) { + dptc_disable_dptc(); + set_dptc_wp(dptc_params.dvfs_dptc_tables_ptr->curr_wp - 1); + } + + dptc_params.suspended = 1; + + return 0; +} + +/*! + * 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 *pdev) +{ + dptc_params.suspended = 0; + + if (dptc_params.dptc_is_active) { + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + } + + return 0; +} + +/*! + * This function frees power management table structures + */ +static void free_dvfs_dptc_table(void) +{ + int i; + + for (i = 0; i < dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + kfree(dptc_params.dvfs_dptc_tables_ptr->dcvr[i]); + } + + kfree(dptc_params.dvfs_dptc_tables_ptr->dcvr); + kfree(dptc_params.dvfs_dptc_tables_ptr->table); + kfree(dptc_params.dvfs_dptc_tables_ptr->wp); + + kfree(dptc_params.dvfs_dptc_tables_ptr); + + dptc_params.dvfs_dptc_tables_ptr = 0; +} + +/* + * DVFS & DPTC table parsing function + * reads the next line of the table in text format + * + * @param str pointer to the previous line + * + * @return pointer to the next line + */ +static char *pm_table_get_next_line(char *str) +{ + char *line_ptr; + int flag = 0; + + if (strlen(str) == 0) + return str; + + line_ptr = strchr(str, '\n') + 1; + + while (!flag) { + if (strlen(line_ptr) == 0) { + flag = 1; + } else if (line_ptr[0] == '\n') { + line_ptr++; + } else if (line_ptr[0] == '#') { + line_ptr = pm_table_get_next_line(line_ptr); + } else { + flag = 1; + } + } + + return line_ptr; +} + +/* + * DVFS & DPTC table parsing function + * sets the values of DVFS & DPTC tables from + * table in text format + * + * @param pm_table pointer to the table in binary format + * @param pm_str pointer to the table in text format + * + * @return 0 on success, error code on failure + */ +static int dvfs_dptc_parse_table(dvfs_dptc_tables_s * pm_table, char *pm_str) +{ + char *pm_str_ptr; + int i, j, n; + dptc_wp *wp; + + pm_str_ptr = pm_str; + + n = sscanf(pm_str_ptr, "WORKING POINT %d\n", &pm_table->wp_num); + + if (n != 1) { + printk(KERN_WARNING "Failed read WORKING POINT number\n"); + return -1; + } + + pm_table->curr_wp = 0; + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + pm_table->dvfs_state_num = 1; + + pm_table->wp = + (dptc_wp *) kmalloc(sizeof(dptc_wp) * pm_table->wp_num, GFP_KERNEL); + if (!pm_table->wp) { + printk(KERN_ERR "Failed allocating memory\n"); + return -ENOMEM; + } + + for (i = 0; i < pm_table->wp_num; i++) { + + wp = &pm_table->wp[i]; + + wp->wp_index = i; + + n = sscanf(pm_str_ptr, "WP 0x%x\n", + (unsigned int *)&wp->pmic_values[0]); + + if (n != 1) { + printk(KERN_WARNING "Failed read WP %d\n", i); + kfree(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + + } + + pm_table->table = + (dvfs_state *) kmalloc(sizeof(dvfs_state) * + pm_table->dvfs_state_num, GFP_KERNEL); + + if (!pm_table->table) { + printk(KERN_WARNING "Failed allocating memory\n"); + kfree(pm_table->wp); + return -ENOMEM; + } + + pm_table->dcvr = + (dcvr_state **) kmalloc(sizeof(dcvr_state *) * + pm_table->dvfs_state_num, GFP_KERNEL); + + if (!pm_table->dcvr) { + printk(KERN_WARNING "Failed allocating memory\n"); + kfree(pm_table->table); + kfree(pm_table->wp); + return -ENOMEM; + } + + for (i = 0; i < pm_table->dvfs_state_num; i++) { + pm_table->dcvr[i] = + (dcvr_state *) kmalloc(sizeof(dcvr_state) * + pm_table->wp_num, GFP_KERNEL); + + if (!pm_table->dcvr[i]) { + printk(KERN_WARNING "Failed allocating memory\n"); + + for (j = i - 1; j >= 0; j--) { + kfree(pm_table->dcvr[j]); + } + + kfree(pm_table->dcvr); + return -ENOMEM; + } + + for (j = 0; j < pm_table->wp_num; j++) { + + n = sscanf(pm_str_ptr, "DCVR 0x%x 0x%x 0x%x 0x%x\n", + &pm_table->dcvr[i][j].dcvr_reg[0].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[1].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[2].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[3].AsInt); + + if (n != 4) { + printk(KERN_WARNING "Failed read FREQ %d\n", i); + + for (j = i; j >= 0; j--) { + kfree(pm_table->dcvr[j]); + } + kfree(pm_table->dcvr); + kfree(pm_table->table); + kfree(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + } + } + + return 0; +} + +/* + * Initializes the default values of DVFS & DPTC table + * + * @return 0 on success, error code on failure + */ +static int __init dvfs_dptc_init_default_table(void) +{ + int res = 0; + char *table_str; + + dvfs_dptc_tables_s *default_table; + + default_table = kmalloc(sizeof(dvfs_dptc_tables_s), GFP_KERNEL); + + if (!default_table) { + return -ENOMEM; + } + + table_str = default_table_str; + + memset(default_table, 0, sizeof(dvfs_dptc_tables_s)); + res = dvfs_dptc_parse_table(default_table, table_str); + + if (res == 0) { + dptc_params.dvfs_dptc_tables_ptr = default_table; + } + + return res; +} + +/*! + * This function is called when the driver is opened. This function + * checks if the user that open the device has root privileges. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dptc_mx27_open(struct inode *inode, struct file *filp) +{ + /* + * check if the program that opened the driver has root + * privileges, if not return error. + */ + if (!capable(CAP_SYS_ADMIN)) { + return -EACCES; + } + + if (dptc_params.suspended) { + return -EPERM; + } + + return 0; +} + +/*! + * This function is called when the driver is close. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + * + */ +static int dptc_mx27_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +/*! + * This function dumps dptc translation table into string pointer + * + * @param str string pointer + */ +static void dvfs_dptc_dump_table(char *str) +{ + int i, j; + dcvr_state **dcvr_arr; + dcvr_state *dcvr_row; + + memset(str, 0, MAX_TABLE_SIZE); + + sprintf(str, "WORKING POINT %d\n", + dptc_params.dvfs_dptc_tables_ptr->wp_num); + str += strlen(str); + + for (i = 0; i < dptc_params.dvfs_dptc_tables_ptr->wp_num; i++) { + sprintf(str, "WP 0x%x\n", (unsigned int) + dptc_params.dvfs_dptc_tables_ptr->wp[i].pmic_values[0]); + str += strlen(str); + } + + dcvr_arr = dptc_params.dvfs_dptc_tables_ptr->dcvr; + for (i = 0; i < dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + dcvr_row = dcvr_arr[i]; + + for (j = 0; j < dptc_params.dvfs_dptc_tables_ptr->wp_num; j++) { + sprintf(str, + "DCVR 0x%x 0x%x 0x%x 0x%x\n", + dcvr_row[j].dcvr_reg[0].AsInt, + dcvr_row[j].dcvr_reg[1].AsInt, + dcvr_row[j].dcvr_reg[2].AsInt, + dcvr_row[j].dcvr_reg[3].AsInt); + + str += strlen(str); + } + } +} + +/*! + * This function reads DVFS & DPTC translation table from user + * + * @param user_table pointer to user table + * @return 0 on success, error code on failure + */ +static int dvfs_dptc_set_table(char *user_table) +{ + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + char *tmp_str_ptr; + dvfs_dptc_tables_s *dptc_table; + + if (dptc_params.dptc_is_active == TRUE) { + ret_val = -EINVAL; + return ret_val; + } + + tmp_str = vmalloc(MAX_TABLE_SIZE); + + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + memset(tmp_str, 0, MAX_TABLE_SIZE); + tmp_str_ptr = tmp_str; + + /* + * read num_of_wp and dvfs_state_num + * parameters from new table + */ + while (tmp_str_ptr - tmp_str < MAX_TABLE_SIZE && + (!copy_from_user(tmp_str_ptr, user_table, 1)) && + tmp_str_ptr[0] != 0) { + tmp_str_ptr++; + user_table++; + } + if (tmp_str_ptr == tmp_str) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +didn't copy a character\n"); + ret_val = -EFAULT; + } else if (tmp_str_ptr - tmp_str == MAX_TABLE_SIZE) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +read more than %d\n", MAX_TABLE_SIZE); + ret_val = -EFAULT; + } else { + /* + * copy table from user and set it as + * the current DPTC table + */ + dptc_table = kmalloc(sizeof(dvfs_dptc_tables_s), + GFP_KERNEL); + + if (!dptc_table) { + ret_val = -ENOMEM; + } else { + ret_val = + dvfs_dptc_parse_table(dptc_table, tmp_str); + + if (ret_val == 0) { + free_dvfs_dptc_table(); + dptc_params.dvfs_dptc_tables_ptr = + dptc_table; + + set_dptc_wp(dptc_initial_wp); + } + } + + } + + vfree(tmp_str); + } + + return ret_val; +} + +/*! + * This function is called when a ioctl call is made from user space. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * @param cmd Ioctl command + * @param arg Ioctl argument + * + * Following are the ioctl commands for user to use:\n + * DPTC_IOCTENABLE : Enables the DPTC module.\n + * DPTC_IOCTDISABLE : Disables the DPTC module.\n + * DPTC_IOCSENABLERC : Enables DPTC reference circuits.\n + * DPTC_IOCSDISABLERC : Disables DPTC reference circuits.\n + * DPTC_IOCGETSTATE : Returns 1 if the DPTC module is enabled, + * returns 0 if the DPTC module is disabled.\n + * DPTC_IOCSWP : Sets working point.\n + * PM_IOCSTABLE : Sets translation table.\n + * PM_IOCGTABLE : Gets translation table.\n + * PM_IOCGFREQ : Returns current CPU frequency in Hz + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dptc_mx27_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct clk *clk; + unsigned int tmp; + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + + tmp = arg; + + if (dptc_params.suspended) { + return -EPERM; + } + + down(&access_mutex); + + pr_debug("DVFS_DPTC ioctl (%d)\n", cmd); + + switch (cmd) { + /* Enable the DPTC module */ + case DPTC_IOCTENABLE: + ret_val = start_dptc(); + break; + + /* Disable the DPTC module */ + case DPTC_IOCTDISABLE: + ret_val = stop_dptc(); + break; + + case DPTC_IOCSENABLERC: + ret_val = enable_ref_circuits(tmp); + break; + + case DPTC_IOCSDISABLERC: + ret_val = disable_ref_circuits(tmp); + break; + /* + * Return the DPTC module current state. + * Returns 1 if the DPTC module is enabled, else returns 0 + */ + case DPTC_IOCGSTATE: + ret_val = dptc_params.dptc_is_active; + break; + case DPTC_IOCSWP: + if (dptc_params.dptc_is_active == FALSE) { + if (arg >= 0 && + arg < dptc_params.dvfs_dptc_tables_ptr->wp_num) { + set_dptc_wp(arg); + ret_val = 0; + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + break; + + /* Update DPTC table */ + case PM_IOCSTABLE: + ret_val = dvfs_dptc_set_table((char *)arg); + break; + + case PM_IOCGTABLE: + tmp_str = vmalloc(MAX_TABLE_SIZE); + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + dvfs_dptc_dump_table(tmp_str); + if (copy_to_user((char *)tmp, tmp_str, strlen(tmp_str))) { + printk(KERN_ERR + "Failed copy %d characters to 0x%x\n", + strlen(tmp_str), tmp); + ret_val = -EFAULT; + } else { + ret_val = 0; + } + vfree(tmp_str); + } + break; + + case PM_IOCGFREQ: + clk = clk_get(NULL, "cpu_clk"); + ret_val = clk_get_rate(clk); + break; + + /* Unknown ioctl command -> return error */ + default: + printk(KERN_ERR "Unknown ioctl command 0x%x\n", cmd); + ret_val = -ENOIOCTLCMD; + } + + up(&access_mutex); + + return ret_val; +} + +/*! + * This function is the DPTC Interrupt handler. + * This function wakes-up the dptc_workqueue_handler function that handles the + * DPTC interrupt. + * + * @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 dptc_mx27_irq(int irq, void *dev_id) +{ + if (dptc_params.dptc_is_active == TRUE) { + dptc_intr_status = __raw_readl(IO_ADDRESS(MX27_PMCR_BASE_ADDR)); + + /* Acknowledge the interrupt */ + __raw_writel(dptc_intr_status, IO_ADDRESS(MX27_PMCR_BASE_ADDR)); + + /* Extract the interrupt cause */ + dptc_intr_status = + (dptc_intr_status >> MX27_PMCR_LO_INTR_OFFSET) & 0x7; + + if (dptc_intr_status != 0) { + dptc_mask_dptc_int(); + dptc_disable_dptc(); + schedule_work(&dptc_work); + } + } + + return IRQ_RETVAL(1); +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_dptc_driver = { + .driver = { + .name = "mxc_dptc", + .bus = &platform_bus_type, + }, + .suspend = mxc_dptc_suspend, + .resume = mxc_dptc_resume, +}; + +/*! + * This is platform device structure for adding MU + */ +static struct platform_device mxc_dptc_device = { + .name = "mxc_dptc", + .id = 0, +}; + +/*! + * This function is called for module initialization. + * It initializes the driver data structures, sets up the DPTC hardware, + * registers the DPTC driver, creates a proc file system read entry and + * attaches the driver to the DPTC interrupt. + * + * @return 0 to indicate success else returns a negative number. + * + */ +static int __init dptc_mx27_init(void) +{ + int res; + struct class_device *temp_class; + + res = dvfs_dptc_init_default_table(); + + if (res < 0) { + printk(KERN_WARNING "Failed parsing default DPTC table\n"); + return res; + } + + /* Initialize DPTC hardware */ + res = init_dptc_controller(); + if (res < 0) { + free_dvfs_dptc_table(); + } + + /* Initialize internal driver structures */ + dptc_params.dptc_is_active = FALSE; + + /* + * Register DPTC driver as a char driver with an automatically allocated + * major number. + */ + major = register_chrdev(0, DEVICE_NAME, &fops); + + /* + * Return error if a negative major number is returned. + */ + if (major < 0) { + printk(KERN_ERR + "DPTC: Registering driver failed with %d\n", major); + free_dvfs_dptc_table(); + return major; + } + + mxc_dvfs_dptc_class = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(mxc_dvfs_dptc_class)) { + printk(KERN_ERR "DPTC: Error creating class.\n"); + goto err_out; + } + + temp_class = + class_device_create(mxc_dvfs_dptc_class, NULL, MKDEV(major, 0), + NULL, DEVICE_NAME); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "DPTC: Error creating class device.\n"); + goto err_out; + } + + /* request the DPTC interrupt */ + res = request_irq(INT_CCM, dptc_mx27_irq, 0, DEVICE_NAME, NULL); + + /* + * If res is not 0, then where was an error + * during attaching to DPTC interrupt. + * Exit and return error code. + */ + if (res) { + printk(KERN_ERR "DPTC: Unable to attach to DPTC interrupt"); + free_dvfs_dptc_table(); + goto err_out; + } + + /* Register low power modes functions */ + res = platform_driver_register(&mxc_dptc_driver); + if (res == 0) { + res = platform_device_register(&mxc_dptc_device); + if (res != 0) { + free_dvfs_dptc_table(); + goto err_out; + } + } + + dptc_params.suspended = FALSE; + + return 0; + + err_out: + printk(KERN_WARNING "MX27 DPTC driver was not initialized\n"); + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + class_destroy(mxc_dvfs_dptc_class); + unregister_chrdev(major, DEVICE_NAME); + return -1; +} + +/*! + * This function is called whenever the module is removed from the kernel. It + * unregisters the DVFS & DPTC driver from kernel, frees the irq number + * and removes the proc file system entry. + */ +static void __exit dptc_mx27_cleanup(void) +{ + free_dvfs_dptc_table(); + + /* Un-register the driver and remove its node */ + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + class_destroy(mxc_dvfs_dptc_class); + unregister_chrdev(major, DEVICE_NAME); + + /* release the DPTC interrupt */ + free_irq(INT_CCM, NULL); + + /* Unregister low power modes functions */ + platform_driver_unregister(&mxc_dptc_driver); + platform_device_unregister(&mxc_dptc_device); + + /* remove the DPTC proc file system entry */ + remove_proc_entry(PROC_NODE_NAME, NULL); +} + +module_init(dptc_mx27_init); +module_exit(dptc_mx27_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX27 DPTC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pm/dvfs_dptc.c b/drivers/mxc/pm/dvfs_dptc.c new file mode 100644 index 000000000000..1bd465f02abe --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc.c @@ -0,0 +1,1248 @@ +/* + * 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 dvfs_dptc.c + * + * @brief Driver for the Freescale Semiconductor MXC DVFS & DPTC module. + * + * The DVFS & DPTC driver + * driver is designed as a character driver which interacts with the MXC + * DVFS & DPTC hardware. Upon initialization, the DVFS & DPTC driver initializes + * the DVFS & DPTC hardware sets up driver nodes attaches to the DVFS & DPTC + * interrupts and initializes internal data structures. When the DVFS or DPTC + * interrupt occurs the driver checks the cause of the interrupt + * (lower voltage/frequency, increase voltage/frequency or emergency) and changes + * the CPU voltage and/or frequency according to translation table that is loaded + * into the driver (the voltage changes are done by calling some routines + * of the mc13783 driver). + * + * @ingroup PM_MX31 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <asm/uaccess.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <asm/semaphore.h> +#include <linux/jiffies.h> +#include <linux/vmalloc.h> +#include <asm/arch/hardware.h> +#include <asm/arch/pmic_external.h> + +/* + * Module header files + */ +#include "dvfs_dptc.h" +#include <asm/arch/dptc.h> + +#ifdef CONFIG_MXC_DVFS +#include <asm/arch/dvfs.h> +#endif + +/* + * Prototypes + */ +static int dvfs_dptc_open(struct inode *inode, struct file *filp); +static int dvfs_dptc_release(struct inode *inode, struct file *filp); +static int dvfs_dptc_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg); +#ifdef CONFIG_MXC_DVFS_SDMA +static ssize_t dvfs_dptc_read(struct file *filp, char __user * buf, + size_t count, loff_t * ppos); +#endif + +#ifndef CONFIG_MXC_DVFS_SDMA +static irqreturn_t dvfs_dptc_irq(int irq, void *dev_id); +#else +static void dvfs_dptc_sdma_callback(dvfs_dptc_params_s * params); +#endif + +#ifdef CONFIG_MXC_DPTC +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_dptc_driver = { + .driver = { + .name = "mxc_dptc", + }, + .suspend = mxc_dptc_suspend, + .resume = mxc_dptc_resume, +}; + +/*! + * This is platform device structure for adding MU + */ +static struct platform_device mxc_dptc_device = { + .name = "mxc_dptc", + .id = 0, +}; +#endif + +/* + * Global variables + */ + +/*! + * The dvfs_dptc_params structure holds all the internal DPTC driver parameters + * (current working point, current frequency, translation table and DPTC + * log buffer). + */ +static dvfs_dptc_params_s dvfs_dptc_params; + +/*! + * Holds the automatically selected DPTC driver major number. + */ +static int major; + +static struct class *mxc_dvfs_dptc_class; + +/* + * This mutex makes the Read,Write and IOCTL command mutual exclusive. + */ +DECLARE_MUTEX(access_mutex); + +/*! + * This structure contains pointers for device driver entry point. + * The driver register function in init module will call this + * structure. + */ +static struct file_operations fops = { + .open = dvfs_dptc_open, + .release = dvfs_dptc_release, + .ioctl = dvfs_dptc_ioctl, +#ifdef CONFIG_MXC_DVFS_SDMA + .read = dvfs_dptc_read, +#endif +}; + +#ifdef CONFIG_MXC_DVFS_SDMA +/* + * Update pointers to physical addresses of DVFS & DPTC table + * for SDMA usage + * + * @param dvfs_dptc_tables_ptr pointer to the DVFS & + * DPTC translation table. + */ +static void dvfs_dptc_virt_2_phys(dvfs_dptc_tables_s * dvfs_dptc_table) +{ + int i; + + /* Update DCVR pointers */ + for (i = 0; i < dvfs_dptc_table->dvfs_state_num; i++) { + dvfs_dptc_table->dcvr[i] = (dcvr_state *) + sdma_virt_to_phys(dvfs_dptc_table->dcvr[i]); + } + dvfs_dptc_table->dcvr = (dcvr_state **) + sdma_virt_to_phys(dvfs_dptc_table->dcvr); + dvfs_dptc_table->table = (dvfs_state *) + sdma_virt_to_phys(dvfs_dptc_table->table); +} + +/* + * Update pointers to virtual addresses of DVFS & DPTC table + * for ARM usage + * + * @param dvfs_dptc_tables_ptr pointer to the DVFS & + * DPTC translation table. + */ +static void dvfs_dptc_phys_2_virt(dvfs_dptc_tables_s * dvfs_dptc_table) +{ + int i; + + dvfs_dptc_table->table = sdma_phys_to_virt + ((unsigned long)dvfs_dptc_table->table); + dvfs_dptc_table->dcvr = sdma_phys_to_virt + ((unsigned long)dvfs_dptc_table->dcvr); + + /* Update DCVR pointers */ + for (i = 0; i < dvfs_dptc_table->dvfs_state_num; i++) { + dvfs_dptc_table->dcvr[i] = + sdma_phys_to_virt((unsigned long)dvfs_dptc_table->dcvr[i]); + } +} +#endif + +/*! + * This function frees power management table structures + */ +static void free_dvfs_dptc_table(void) +{ + int i; + +#ifdef CONFIG_MXC_DVFS_SDMA + dvfs_dptc_phys_2_virt(dvfs_dptc_params.dvfs_dptc_tables_ptr); +#endif + + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr[i]); + } + + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr); + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->table); + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->wp); + + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr); + + dvfs_dptc_params.dvfs_dptc_tables_ptr = 0; +} + +/* + * DVFS & DPTC table parsing function + * reads the next line of the table in text format + * + * @param str pointer to the previous line + * + * @return pointer to the next line + */ +static char *pm_table_get_next_line(char *str) +{ + char *line_ptr; + int flag = 0; + + if (strlen(str) == 0) + return str; + + line_ptr = strchr(str, '\n') + 1; + + while (!flag) { + if (strlen(line_ptr) == 0) { + flag = 1; + } else if (line_ptr[0] == '\n') { + line_ptr++; + } else if (line_ptr[0] == '#') { + line_ptr = pm_table_get_next_line(line_ptr); + } else { + flag = 1; + } + } + + return line_ptr; +} + +/* + * DVFS & DPTC table parsing function + * sets the values of DVFS & DPTC tables from + * table in text format + * + * @param pm_table pointer to the table in binary format + * @param pm_str pointer to the table in text format + * + * @return 0 on success, error code on failure + */ +static int dvfs_dptc_parse_table(dvfs_dptc_tables_s * pm_table, char *pm_str) +{ + char *pm_str_ptr; + int i, j, n; + dptc_wp *wp; + dvfs_state *table; + + pm_str_ptr = pm_str; + + n = sscanf(pm_str_ptr, "WORKING POINT %d\n", &pm_table->wp_num); + + if (n != 1) { + printk(KERN_WARNING "Failed read WORKING POINT number\n"); + return -1; + } + + pm_table->curr_wp = 0; + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + + if (cpu_is_mx31() || cpu_is_mx32()) { + pm_table->dvfs_state_num = 4; + pm_table->use_four_freq = 1; + } else { + pm_table->dvfs_state_num = 1; + } + + pm_table->wp = + (dptc_wp *) sdma_malloc(sizeof(dptc_wp) * pm_table->wp_num); + if (!pm_table->wp) { + printk(KERN_ERR "Failed allocating memory\n"); + return -ENOMEM; + } + + for (i = 0; i < pm_table->wp_num; i++) { + + wp = &pm_table->wp[i]; + + wp->wp_index = i; + + if (cpu_is_mx31() || cpu_is_mx32()) { + n = sscanf(pm_str_ptr, "WP 0x%x 0x%x 0x%x 0x%x\n", + (unsigned int *)&wp->pmic_values[0], + (unsigned int *)&wp->pmic_values[1], + (unsigned int *)&wp->pmic_values[2], + (unsigned int *)&wp->pmic_values[3]); + + if (n != 4) { + printk(KERN_WARNING "Failed read WP %d\n", i); + sdma_free(pm_table->wp); + return -1; + } + } else { + n = sscanf(pm_str_ptr, "WP 0x%x\n", + (unsigned int *)&wp->pmic_values[0]); + + if (n != 1) { + printk(KERN_WARNING "Failed read WP %d\n", i); + sdma_free(pm_table->wp); + return -1; + } + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + + } + + pm_table->table = + (dvfs_state *) sdma_malloc(sizeof(dvfs_state) * + pm_table->dvfs_state_num); + + if (!pm_table->table) { + printk(KERN_WARNING "Failed allocating memory\n"); + sdma_free(pm_table->wp); + return -ENOMEM; + } + + if (cpu_is_mx31() || cpu_is_mx32()) { + for (i = 0; i < pm_table->dvfs_state_num; i++) { + table = &pm_table->table[i]; + + n = sscanf(pm_str_ptr, + "FREQ %d %d 0x%x 0x%x 0x%x 0x%x %d\n", + (unsigned int *)&table->pll_sw_up, + (unsigned int *)&table->pll_sw_down, + (unsigned int *)&table->pdr0_up, + (unsigned int *)&table->pdr0_down, + (unsigned int *)&table->pll_up, + (unsigned int *)&table->pll_down, + (unsigned int *)&table->vscnt); + + if (n != 7) { + printk(KERN_WARNING "Failed read FREQ %d\n", i); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + } + } + + pm_table->dcvr = + (dcvr_state **) sdma_malloc(sizeof(dcvr_state *) * + pm_table->dvfs_state_num); + + if (!pm_table->dcvr) { + printk(KERN_WARNING "Failed allocating memory\n"); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -ENOMEM; + } + + for (i = 0; i < pm_table->dvfs_state_num; i++) { + pm_table->dcvr[i] = + (dcvr_state *) sdma_malloc(sizeof(dcvr_state) * + pm_table->wp_num); + + if (!pm_table->dcvr[i]) { + printk(KERN_WARNING "Failed allocating memory\n"); + + for (j = i - 1; j >= 0; j--) { + sdma_free(pm_table->dcvr[j]); + } + + sdma_free(pm_table->dcvr); + return -ENOMEM; + } + + for (j = 0; j < pm_table->wp_num; j++) { + + n = sscanf(pm_str_ptr, "DCVR 0x%x 0x%x 0x%x 0x%x\n", + &pm_table->dcvr[i][j].dcvr_reg[0].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[1].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[2].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[3].AsInt); + + if (n != 4) { + printk(KERN_WARNING "Failed read FREQ %d\n", i); + + for (j = i; j >= 0; j--) { + sdma_free(pm_table->dcvr[j]); + } + sdma_free(pm_table->dcvr); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + } + } + + return 0; +} + +/* + * Initializes the default values of DVFS & DPTC table + * + * @return 0 on success, error code on failure + */ +static int __init dvfs_dptc_init_default_table(void) +{ + int res = 0; + char *table_str; + struct clk *clk; + + dvfs_dptc_tables_s *default_table; + + default_table = sdma_malloc(sizeof(dvfs_dptc_tables_s)); + + if (!default_table) { + return -ENOMEM; + } + + table_str = default_table_str; + if (cpu_is_mx31() || cpu_is_mx32()) { + if (cpu_is_mx31_rev(CHIP_REV_2_0) < 0) { + clk = clk_get(NULL, "ckih"); + if (clk_get_rate(clk) == 27000000) { + printk(KERN_INFO + "DVFS & DPTC: using 27MHz CKIH table\n"); +#ifdef CONFIG_ARCH_MX3 + table_str = default_table_str_27ckih; +#endif + } + } else { +#ifdef CONFIG_ARCH_MX3 + table_str = default_table_str_rev2; +#endif + } + clk_put(clk); + } + + memset(default_table, 0, sizeof(dvfs_dptc_tables_s)); + res = dvfs_dptc_parse_table(default_table, table_str); + + if (res == 0) { + dvfs_dptc_params.dvfs_dptc_tables_ptr = default_table; + } + + return res; +} + +#ifdef CONFIG_MXC_DVFS_SDMA +/*! + * This function is called for SDMA channel initialization. + * + * @param params pointer to the DPTC driver parameters structure. + * + * @return 0 to indicate success else returns a negative number. + */ +static int init_sdma_channel(dvfs_dptc_params_s * params) +{ + dma_channel_params sdma_params; + dma_request_t sdma_request; + int i; + int res = 0; + + params->sdma_channel = 0; + res = mxc_request_dma(¶ms->sdma_channel, "DVFS_DPTC"); + if (res < 0) { + printk(KERN_ERR "Failed allocate SDMA channel for DVFS_DPTC\n"); + return res; + } + + memset(&sdma_params, 0, sizeof(dma_channel_params)); + sdma_params.peripheral_type = CCM; + sdma_params.transfer_type = per_2_emi; + sdma_params.event_id = DMA_REQ_CCM; + sdma_params.callback = (dma_callback_t) dvfs_dptc_sdma_callback; + sdma_params.arg = params; + sdma_params.per_address = CCM_BASE_ADDR; + sdma_params.watermark_level = + sdma_virt_to_phys(params->dvfs_dptc_tables_ptr); + sdma_params.bd_number = 2; + + res = mxc_dma_setup_channel(params->sdma_channel, &sdma_params); + + if (res == 0) { + memset(&sdma_request, 0, sizeof(dma_request_t)); + + for (i = 0; i < DVFS_LB_SDMA_BD; i++) { + sdma_request.destAddr = (__u8 *) + (params->dvfs_log_buffer_phys + + i * (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) / + DVFS_LB_SDMA_BD); + sdma_request.count = DVFS_LB_SIZE / DVFS_LB_SDMA_BD; + sdma_request.bd_cont = 1; + + mxc_dma_set_config(params->sdma_channel, &sdma_request, + i); + } + + mxc_dma_start(params->sdma_channel); + } + + return res; +} +#endif + +/*! + * This function is called for module initialization. + * It initializes the driver data structures, sets up the DPTC hardware, + * registers the DPTC driver, creates a proc file system read entry and + * attaches the driver to the DPTC interrupt. + * + * @return 0 to indicate success else returns a negative number. + * + */ +static int __init dvfs_dptc_init(void) +{ + int res = 0; + struct class_device *temp_class; + + res = dvfs_dptc_init_default_table(); + + if (res < 0) { + printk(KERN_WARNING "Failed parsing default DPTC table\n"); + return res; + } +#ifdef CONFIG_MXC_DPTC + /* Initialize DPTC hardware */ + res = init_dptc_controller(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } +#endif + +#ifdef CONFIG_MXC_DVFS + /* Initialize DVFS hardware */ + res = init_dvfs_controller(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } + + /* Enable 4 mc13783 output voltages */ + pmic_write_reg(REG_ARBITRATION_SWITCHERS, 1, (1 << 5)); + + /* Enable mc13783 voltage ready signal */ + pmic_write_reg(REG_INTERRUPT_MASK_1, 0, (1 << 11)); + + /* Set mc13783 DVS speed 25mV each 4us */ + pmic_write_reg(REG_SWITCHERS_4, 1, (1 << 6)); + pmic_write_reg(REG_SWITCHERS_4, 0, (1 << 7)); + + dvfs_update_freqs_table(dvfs_dptc_params.dvfs_dptc_tables_ptr); +#endif + +#ifdef CONFIG_MXC_DVFS_SDMA + /* Update addresses to physical */ + if (res == 0) { + dvfs_dptc_virt_2_phys(dvfs_dptc_params.dvfs_dptc_tables_ptr); + } + + res = init_sdma_channel(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } +#endif + + /* Initialize internal driver structures */ + dvfs_dptc_params.dptc_is_active = FALSE; + +#ifdef CONFIG_MXC_DVFS + dvfs_dptc_params.dvfs_is_active = FALSE; +#endif + + /* + * Register DPTC driver as a char driver with an automatically allocated + * major number. + */ + major = register_chrdev(0, DEVICE_NAME, &fops); + + /* + * Return error if a negative major number is returned. + */ + if (major < 0) { + printk(KERN_ERR + "DPTC: Registering driver failed with %d\n", major); + free_dvfs_dptc_table(); + return major; + } + + mxc_dvfs_dptc_class = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(mxc_dvfs_dptc_class)) { + printk(KERN_ERR "DPTC: Error creating class.\n"); + res = PTR_ERR(mxc_dvfs_dptc_class); + goto err_out1; + } + + temp_class = + class_device_create(mxc_dvfs_dptc_class, NULL, MKDEV(major, 0), + NULL, DEVICE_NAME); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "DPTC: Error creating class device.\n"); + res = PTR_ERR(temp_class); + goto err_out2; + } +#ifndef CONFIG_MXC_DVFS_SDMA + /* request the DPTC interrupt */ + res = request_irq(INT_CCM, dvfs_dptc_irq, 0, DEVICE_NAME, NULL); + /* + * If res is not 0, then where was an error + * during attaching to DPTC interrupt. + * Exit and return error code. + */ + if (res) { + printk(KERN_ERR "DPTC: Unable to attach to DPTC interrupt"); + goto err_out3; + } + /* request the DVFS interrupt */ + res = request_irq(INT_DVFS, dvfs_dptc_irq, 0, DEVICE_NAME, NULL); + if (res) { + printk(KERN_ERR "DVFS: Unable to attach to DVFS interrupt"); + goto err_out4; + } +#endif + +#ifdef CONFIG_MXC_DPTC + /* Register low power modes functions */ + res = platform_driver_register(&mxc_dptc_driver); + if (res == 0) { + res = platform_device_register(&mxc_dptc_device); + if (res != 0) { + goto err_out5; + } + } +#endif + dvfs_dptc_params.suspended = FALSE; + + return res; + + err_out5: + free_irq(INT_DVFS, NULL); +#ifndef CONFIG_MXC_DVFS_SDMA + err_out4: +#endif + free_irq(INT_CCM, NULL); +#ifndef CONFIG_MXC_DVFS_SDMA + err_out3: +#endif + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + err_out2: + class_destroy(mxc_dvfs_dptc_class); + err_out1: + unregister_chrdev(major, DEVICE_NAME); + free_dvfs_dptc_table(); + printk(KERN_ERR "DVFS&DPTC driver was not initialized\n"); + return res; +} + +/*! + * This function is called whenever the module is removed from the kernel. It + * unregisters the DVFS & DPTC driver from kernel, frees the irq number + * and removes the proc file system entry. + */ +static void __exit dvfs_dptc_cleanup(void) +{ +#ifdef CONFIG_MXC_DPTC + /* Unregister low power modes functions */ + platform_driver_unregister(&mxc_dptc_driver); + platform_device_unregister(&mxc_dptc_device); +#endif + + free_dvfs_dptc_table(); + + /* Un-register the driver and remove its node */ + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + class_destroy(mxc_dvfs_dptc_class); + unregister_chrdev(major, DEVICE_NAME); + + /* release the DPTC interrupt */ + free_irq(INT_CCM, NULL); + /* release the DVFS interrupt */ + free_irq(INT_DVFS, NULL); + + /* remove the DPTC proc file system entry */ + remove_proc_entry(PROC_NODE_NAME, NULL); +} + +/*! + * This function is called when the driver is opened. This function + * checks if the user that open the device has root privileges. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dvfs_dptc_open(struct inode *inode, struct file *filp) +{ + /* + * check if the program that opened the driver has root + * privileges, if not return error. + */ + if (!capable(CAP_SYS_ADMIN)) { + return -EACCES; + } + + if (dvfs_dptc_params.suspended) { + return -EPERM; + } + + return 0; +} + +/*! + * This function is called when the driver is close. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + * + */ +static int dvfs_dptc_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +/*! + * This function dumps dptc translation table into string pointer + * + * @param str string pointer + */ +static void dvfs_dptc_dump_table(char *str) +{ + int i, j; + dcvr_state **dcvr_arr; + dcvr_state *dcvr_row; + dvfs_state *table; + + memset(str, 0, MAX_TABLE_SIZE); + + sprintf(str, "WORKING POINT %d\n", + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num); + str += strlen(str); + + for (i = 0; i < dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num; i++) { + if (cpu_is_mx31() || cpu_is_mx32()) { + sprintf(str, "WP 0x%x 0x%x 0x%x 0x%x\n", (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[0], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[1], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[2], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[3]); + } else { + sprintf(str, "WP 0x%x\n", (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[0]); + } + + str += strlen(str); + } + + if (cpu_is_mx31() || cpu_is_mx32()) { + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; + i++) { + table = dvfs_dptc_params.dvfs_dptc_tables_ptr->table; +#ifdef CONFIG_MXC_DVFS_SDMA + table = sdma_phys_to_virt((unsigned long)table); +#endif + sprintf(str, + "FREQ %d %d 0x%x 0x%x 0x%x 0x%x %d\n", + (unsigned int)table[i].pll_sw_up, + (unsigned int)table[i].pll_sw_down, + (unsigned int)table[i].pdr0_up, + (unsigned int)table[i].pdr0_down, + (unsigned int)table[i].pll_up, + (unsigned int)table[i].pll_down, + (unsigned int)table[i].vscnt); + + str += strlen(str); + } + } + + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + dcvr_arr = dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr; +#ifdef CONFIG_MXC_DVFS_SDMA + dcvr_arr = sdma_phys_to_virt((unsigned long)dcvr_arr); +#endif + dcvr_row = dcvr_arr[i]; +#ifdef CONFIG_MXC_DVFS_SDMA + dcvr_row = sdma_phys_to_virt((unsigned long)dcvr_row); +#endif + + for (j = 0; + j < dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num; j++) { + sprintf(str, + "DCVR 0x%x 0x%x 0x%x 0x%x\n", + dcvr_row[j].dcvr_reg[0].AsInt, + dcvr_row[j].dcvr_reg[1].AsInt, + dcvr_row[j].dcvr_reg[2].AsInt, + dcvr_row[j].dcvr_reg[3].AsInt); + + str += strlen(str); + } + } +} + +/*! + * This function reads DVFS & DPTC translation table from user + * + * @param user_table pointer to user table + * @return 0 on success, error code on failure + */ +int dvfs_dptc_set_table(char *user_table) +{ + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + char *tmp_str_ptr; + dvfs_dptc_tables_s *dptc_table; + + if ((cpu_is_mx31() || cpu_is_mx32()) && + (dvfs_dptc_params.dptc_is_active == TRUE || + dvfs_dptc_params.dvfs_is_active == TRUE)) { + ret_val = -EINVAL; + return ret_val; + } else if (dvfs_dptc_params.dptc_is_active == TRUE) { + ret_val = -EINVAL; + return ret_val; + } + + tmp_str = vmalloc(MAX_TABLE_SIZE); + + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + memset(tmp_str, 0, MAX_TABLE_SIZE); + tmp_str_ptr = tmp_str; + + /* + * read num_of_wp and dvfs_state_num + * parameters from new table + */ + while (tmp_str_ptr - tmp_str < MAX_TABLE_SIZE && + (!copy_from_user(tmp_str_ptr, user_table, 1)) && + tmp_str_ptr[0] != 0) { + tmp_str_ptr++; + user_table++; + } + if (tmp_str_ptr == tmp_str) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +didn't copy a character\n"); + ret_val = -EFAULT; + } else if (tmp_str_ptr - tmp_str == MAX_TABLE_SIZE) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +read more than %d\n", MAX_TABLE_SIZE); + ret_val = -EFAULT; + } else { + /* + * copy table from user and set it as + * the current DPTC table + */ + dptc_table = sdma_malloc(sizeof(dvfs_dptc_tables_s)); + + if (!dptc_table) { + ret_val = -ENOMEM; + } else { + ret_val = + dvfs_dptc_parse_table(dptc_table, tmp_str); + + if (ret_val == 0) { + free_dvfs_dptc_table(); + dvfs_dptc_params.dvfs_dptc_tables_ptr = + dptc_table; + +#ifdef CONFIG_MXC_DVFS + dvfs_update_freqs_table + (dvfs_dptc_params. + dvfs_dptc_tables_ptr); +#endif + +#ifdef CONFIG_MXC_DVFS_SDMA + /* Update addresses to physical */ + dvfs_dptc_virt_2_phys(dvfs_dptc_params. + dvfs_dptc_tables_ptr); + mxc_free_dma(dvfs_dptc_params. + sdma_channel); + init_sdma_channel(&dvfs_dptc_params); +#endif +#ifdef CONFIG_MXC_DPTC + set_dptc_curr_freq(&dvfs_dptc_params, + 0); + set_dptc_wp(&dvfs_dptc_params, 0); +#endif + } + } + + } + + vfree(tmp_str); + } + + return ret_val; +} + +#ifdef CONFIG_MXC_DVFS_SDMA +static ssize_t dvfs_dptc_read(struct file *filp, char __user * buf, + size_t count, loff_t * ppos) +{ + size_t count0, count1; + + while (dvfs_dptc_params.chars_in_buffer < count) { + //count = dvfs_dptc_params.chars_in_buffer; + waitqueue_active(&dvfs_dptc_params.dvfs_pred_wait); + wake_up(&dvfs_dptc_params.dvfs_pred_wait); + schedule(); + } + + if (dvfs_dptc_params.read_ptr + count < + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) { + count0 = count; + count1 = 0; + } else { + count0 = + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8 - + dvfs_dptc_params.read_ptr; + count1 = count - count0; + } + + copy_to_user(buf, dvfs_dptc_params.read_ptr, count0); + copy_to_user(buf + count0, dvfs_dptc_params.dvfs_log_buffer, count1); + + if (count1 == 0) { + dvfs_dptc_params.read_ptr += count; + } else { + dvfs_dptc_params.read_ptr = + dvfs_dptc_params.dvfs_log_buffer + count1; + } + + if (dvfs_dptc_params.read_ptr == + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) { + dvfs_dptc_params.read_ptr = dvfs_dptc_params.dvfs_log_buffer; + } + + dvfs_dptc_params.chars_in_buffer -= count; + + return count; +} +#endif + +/*! + * This function is called when a ioctl call is made from user space. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * @param cmd Ioctl command + * @param arg Ioctl argument + * + * Following are the ioctl commands for user to use:\n + * DPTC_IOCTENABLE : Enables the DPTC module.\n + * DPTC_IOCTDISABLE : Disables the DPTC module.\n + * DPTC_IOCSENABLERC : Enables DPTC reference circuits.\n + * DPTC_IOCSDISABLERC : Disables DPTC reference circuits.\n + * DPTC_IOCGETSTATE : Returns 1 if the DPTC module is enabled, + * returns 0 if the DPTC module is disabled.\n + * DPTC_IOCSWP : Sets working point.\n + * PM_IOCSTABLE : Sets translation table.\n + * PM_IOCGTABLE : Gets translation table.\n + * DVFS_IOCTENABLE : Enables DVFS + * DVFS_IOCTDISABLE : Disables DVFS + * DVFS_IOCGSTATE : Returns 1 if the DVFS module is enabled, + * returns 0 if the DVFS module is disabled.\n + * DVFS_IOCSSWGP : Sets the value of DVFS SW general + * purpose bits.\n + * DVFS_IOCSWFI : Sets the status of WFI monitoring.\n + * PM_IOCGFREQ : Returns current CPU frequency in Hz + * DVFS_IOCSFREQ : Sets DVFS frequency when DVFS\n + * HW is disabled.\n + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dvfs_dptc_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct clk *clk; + unsigned int tmp; + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + + tmp = arg; + + if (dvfs_dptc_params.suspended) { + return -EPERM; + } + + down(&access_mutex); + + pr_debug("DVFS_DPTC ioctl (%d)\n", cmd); + + switch (cmd) { +#ifdef CONFIG_MXC_DPTC + /* Enable the DPTC module */ + case DPTC_IOCTENABLE: + ret_val = start_dptc(&dvfs_dptc_params); + break; + + /* Disable the DPTC module */ + case DPTC_IOCTDISABLE: + ret_val = stop_dptc(&dvfs_dptc_params); + break; + + case DPTC_IOCSENABLERC: + ret_val = enable_ref_circuits(&dvfs_dptc_params, tmp); + break; + + case DPTC_IOCSDISABLERC: + ret_val = disable_ref_circuits(&dvfs_dptc_params, tmp); + break; + /* + * Return the DPTC module current state. + * Returns 1 if the DPTC module is enabled, else returns 0 + */ + case DPTC_IOCGSTATE: + ret_val = dvfs_dptc_params.dptc_is_active; + break; + case DPTC_IOCSWP: + if (dvfs_dptc_params.dptc_is_active == FALSE) { + if (arg >= 0 && + arg < + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num) { + set_dptc_wp(&dvfs_dptc_params, arg); + ret_val = 0; + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + break; + +#endif /* CONFIG_MXC_DPTC */ + + /* Update DPTC table */ + case PM_IOCSTABLE: + ret_val = dvfs_dptc_set_table((char *)arg); + break; + + case PM_IOCGTABLE: + tmp_str = vmalloc(MAX_TABLE_SIZE); + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + dvfs_dptc_dump_table(tmp_str); + if (copy_to_user((char *)tmp, tmp_str, strlen(tmp_str))) { + printk(KERN_ERR + "Failed copy %d characters to 0x%x\n", + strlen(tmp_str), tmp); + ret_val = -EFAULT; + } else { + ret_val = 0; + } + vfree(tmp_str); + } + break; + +#ifdef CONFIG_MXC_DVFS + /* Enable the DVFS module */ + case DVFS_IOCTENABLE: + ret_val = start_dvfs(&dvfs_dptc_params); + break; + + /* Disable the DVFS module */ + case DVFS_IOCTDISABLE: + ret_val = stop_dvfs(&dvfs_dptc_params); + break; + /* + * Return the DVFS module current state. + * Returns 1 if the DPTC module is enabled, else returns 0 + */ + case DVFS_IOCGSTATE: + ret_val = dvfs_dptc_params.dvfs_is_active; + break; + case DVFS_IOCSSWGP: + ret_val = set_sw_gp((unsigned char)arg); + break; + case DVFS_IOCSWFI: + ret_val = set_wfi((unsigned char)arg); + break; + case DVFS_IOCSFREQ: + if (dvfs_dptc_params.dvfs_is_active == FALSE || + dvfs_dptc_params.dvfs_mode == DVFS_PRED_MODE) { + if (arg >= 0 && + arg < + dvfs_dptc_params.dvfs_dptc_tables_ptr-> + dvfs_state_num) { + ret_val = dvfs_set_state(arg); + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + break; + case DVFS_IOCSMODE: +#ifdef CONFIG_MXC_DVFS_SDMA + if (dvfs_dptc_params.dvfs_is_active == FALSE) { + if ((unsigned int)arg == DVFS_HW_MODE || + (unsigned int)arg == DVFS_PRED_MODE) { + dvfs_dptc_params.dvfs_mode = (unsigned int)arg; + ret_val = 0; + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } +#else + /* Predictive mode is supported only in SDMA mode */ + ret_val = -EINVAL; +#endif + break; +#endif /* CONFIG_MXC_DVFS */ + case PM_IOCGFREQ: + clk = clk_get(NULL, "cpu_clk"); + ret_val = clk_get_rate(clk); + break; + + /* Unknown ioctl command -> return error */ + default: + printk(KERN_ERR "Unknown ioctl command 0x%x\n", cmd); + ret_val = -ENOIOCTLCMD; + } + + up(&access_mutex); + + return ret_val; +} + +#ifndef CONFIG_MXC_DVFS_SDMA +/*! + * This function is the DPTC & DVFS Interrupt handler. + * This function wakes-up the dvfs_dptc_workqueue_handler function that handles the + * DPTC interrupt. + * + * @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 dvfs_dptc_irq(int irq, void *dev_id) +{ + +#ifdef CONFIG_MXC_DPTC + if (dvfs_dptc_params.dptc_is_active == TRUE) { + dptc_irq(); + } +#endif + +#ifdef CONFIG_MXC_DVFS + if (dvfs_dptc_params.dvfs_is_active == TRUE) { + dvfs_irq(&dvfs_dptc_params); + } +#endif + + return IRQ_RETVAL(1); +} +#else +/*! + * This function is the DPTC & DVFS SDMA callback. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + */ +static void dvfs_dptc_sdma_callback(dvfs_dptc_params_s * params) +{ + dma_request_t sdma_request_params; + int i; + + for (i = 0; i < DVFS_LB_SDMA_BD; i++) { + mxc_dma_get_config(params->sdma_channel, + &sdma_request_params, i); + + if (sdma_request_params.bd_error == 1) { + printk(KERN_WARNING + "Error in DVFS-DPTC buffer descriptor\n"); + } + + if (sdma_request_params.bd_done == 0) { + params->chars_in_buffer += + (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) / + DVFS_LB_SDMA_BD; + + if (params->chars_in_buffer > + (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8)) { + params->chars_in_buffer = + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8; + params->read_ptr = params->dvfs_log_buffer; + } + + sdma_request_params.destAddr = + (__u8 *) (params->dvfs_log_buffer_phys + + i * (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / + 8) / DVFS_LB_SDMA_BD); + sdma_request_params.count = + DVFS_LB_SIZE / DVFS_LB_SDMA_BD; + sdma_request_params.bd_cont = 1; + mxc_dma_set_config(params->sdma_channel, + &sdma_request_params, i); + + if (params->dvfs_mode == DVFS_PRED_MODE) { + wake_up_interruptible(¶ms->dvfs_pred_wait); + } + } + } + +#ifdef CONFIG_MXC_DPTC + if (params->prev_wp != params->dvfs_dptc_tables_ptr->curr_wp) { + dptc_irq(); + } +#endif +} +#endif + +module_init(dvfs_dptc_init); +module_exit(dvfs_dptc_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("DVFS & DPTC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pm/dvfs_dptc.h b/drivers/mxc/pm/dvfs_dptc.h new file mode 100644 index 000000000000..a3d8b1b31a15 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc.h @@ -0,0 +1,80 @@ +/* + * 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 dvfs_dptc.h + * + * @brief MXC dvfs & dptc header file. + * + * @ingroup PM_MX27 PM_MX31 + */ +#ifndef __DVFS_DPTC_H__ +#define __DVFS_DPTC_H__ + +#include <asm/arch/pm_api.h> +#include <asm/hardware.h> +#include <asm/arch/dvfs_dptc_struct.h> + +#ifdef CONFIG_ARCH_MX27 +#include <asm/arch/dma.h> +#else +#include <asm/arch/sdma.h> +#endif + +#ifdef CONFIG_ARCH_MX3 +#include "dvfs_dptc_table_mx31.h" +#include "dvfs_dptc_table_mx31_27ckih.h" +#include "dvfs_dptc_table_mx31_rev2.h" +#endif +#ifdef CONFIG_ARCH_MX27 +#include "dvfs_dptc_table_mx27.h" +#endif + +#ifdef CONFIG_MXC_DVFS +#ifndef CONFIG_MXC_DPTC +/*! + * DPTC Module Name + */ +#define DEVICE_NAME "dvfs" + +/*! + * DPTC driver node Name + */ +#define NODE_NAME "dvfs" +#endif /* ifndef CONFIG_MXC_DPTC */ +#ifdef CONFIG_MXC_DPTC +/*! + * DPTC Module Name + */ +#define DEVICE_NAME "dvfs_dptc" + +/*! + * DPTC driver node Name + */ +#define NODE_NAME "dvfs_dptc" +#endif /* ifdef CONFIG_MXC_DPTC */ +#else /* ifdef CONFIG_MXC_DVFS */ +/*! + * DPTC Module Name + */ +#define DEVICE_NAME "dptc" + +/*! + * DPTC driver node Name + */ +#define NODE_NAME "dptc" +#endif /* ifdef CONFIG_MXC_DVFS */ + +#define MAX_TABLE_SIZE 8192 + +#endif /* __DPTC_H__ */ diff --git a/drivers/mxc/pm/dvfs_dptc_table_mx27.h b/drivers/mxc/pm/dvfs_dptc_table_mx27.h new file mode 100644 index 000000000000..83438707ee92 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mx27.h @@ -0,0 +1,69 @@ +/* + * 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 dptc.h + * + * @brief i.MX27 dptc table file. + * + * @ingroup PM_MX27 + */ +#ifndef __DVFS_DPTC_TABLE_MX27_H__ +#define __DVFS_DPTC_TABLE_MX27_H__ + +/*! + * Default DPTC table definition + */ +#define NUM_OF_FREQS 1 +#define NUM_OF_WP 17 + +static char *default_table_str = "WORKING POINT 17\n\ +\n\ +WP 0x1c\n\ +WP 0x1b\n\ +WP 0x1a\n\ +WP 0x19\n\ +WP 0x18\n\ +WP 0x17\n\ +WP 0x16\n\ +WP 0x15\n\ +WP 0x14\n\ +WP 0x13\n\ +WP 0x12\n\ +WP 0x11\n\ +WP 0x10\n\ +WP 0xf\n\ +WP 0xe\n\ +WP 0xd\n\ +WP 0xc\n\ +\n\ +DCVR 0xffe00000 0x18e2e85b 0xffe00000 0x25c4688a \n\ +DCVR 0xffe00000 0x18e2e85b 0xffe00000 0x25c4688a \n\ +DCVR 0xffe00000 0x1902e85b 0xffe00000 0x25e4688a \n\ +DCVR 0xffe00000 0x1922e85b 0xffe00000 0x25e4688a \n\ +DCVR 0xffe00000 0x1942ec5b 0xffe00000 0x2604688a \n\ +DCVR 0xffe00000 0x1942ec5b 0xffe00000 0x26646c8a \n\ +DCVR 0xffe00000 0x1962ec5b 0xffe00000 0x26c4708b \n\ +DCVR 0xffe00000 0x1962ec5b 0xffe00000 0x26e4708b \n\ +DCVR 0xffe00000 0x1982f05c 0xffe00000 0x2704748b \n\ +DCVR 0xffe00000 0x19c2f05c 0xffe00000 0x2744748b \n\ +DCVR 0xffe00000 0x1a02f45c 0xffe00000 0x2784788b \n\ +DCVR 0xffe00000 0x1a42f45c 0xffe00000 0x27c47c8b \n\ +DCVR 0xffe00000 0x1a82f85c 0xffe00000 0x2824808c \n\ +DCVR 0xffe00000 0x1aa2f85c 0xffe00000 0x2884848c \n\ +DCVR 0xffe00000 0x1ac2fc5c 0xffe00000 0x28e4888c \n\ +DCVR 0xffe00000 0x1ae2fc5c 0xffe00000 0x2924888c \n\ +DCVR 0xffe00000 0x1b23005d 0xffe00000 0x29648c8c \n\ +"; + +#endif diff --git a/drivers/mxc/pm/dvfs_dptc_table_mx31.h b/drivers/mxc/pm/dvfs_dptc_table_mx31.h new file mode 100644 index 000000000000..81fe80f28af8 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mx31.h @@ -0,0 +1,157 @@ +/* + * 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 + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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_dptc_table_mx31.h + * + * @brief MX31 dvfs & dptc table file for CKIH clock 26MHz. + * + * @ingroup PM_MX31 + */ +#ifndef __DVFS_DPTC_TABLE_MX31_H__ +#define __DVFS_DPTC_TABLE_MX31_H__ + +#define NUM_OF_FREQS 4 +#define NUM_OF_WP 17 + +/*! + * Default DPTC table definition. + * The table doesn't use PLL switch, because on DDR boards + * PLL switch is not possible due to HW issue. + * For SDR boards new table can be loaded. + * + * The table keeps the same voltage of 3.5V for frequencies lower than 399MHz. + * Theoretically we don't need DPTC for these frequencies, + * but we have to keep DPTC enabled for fluent DVFS switching + * back to high frequency. + */ +static char *default_table_str = "WORKING POINT 17\n\ +\n\ +# mc13783 switcher SW values for each working point\n\ +# The first line is for WP of highest voltage\n\ +# The first column is for highest frequency\n\ +# SW1A SW1A DVS SW1B DVS SW1B STANDBY\n\ +WP 0x1d 0x12 0x12 0x12\n\ +WP 0x1c 0x12 0x12 0x12\n\ +WP 0x1b 0x12 0x12 0x12\n\ +WP 0x1a 0x12 0x12 0x12\n\ +WP 0x19 0x12 0x12 0x12\n\ +WP 0x18 0x12 0x12 0x12\n\ +WP 0x17 0x12 0x12 0x12\n\ +WP 0x16 0x12 0x12 0x12\n\ +WP 0x15 0x12 0x12 0x12\n\ +WP 0x14 0x12 0x12 0x12\n\ +WP 0x13 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +\n\ +# pll_sw_up pll_sw_down pdr_up pdr_down pll_up pll_down vscnt\n\ +# 532MHz\n\ +FREQ 0 0 0xff871e58 0xff871e59 0x33280c 0x33280c 7\n\ +# 266MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x33280c 0x33280c 7\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x33280c 0x33280c 7\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x33280c 0x33280c 7\n\ +# 532MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95e3e8e4 0xffc00000 0xe5b6fda0\n\ +DCVR 0xffc00000 0x95e3e8e4 0xffc00000 0xe5b6fda0\n\ +DCVR 0xffc00000 0x95e3e8e8 0xffc00000 0xe5f70da4\n\ +DCVR 0xffc00000 0x9623f8e8 0xffc00000 0xe6371da8\n\ +DCVR 0xffc00000 0x966408f0 0xffc00000 0xe6b73db0\n\ +DCVR 0xffc00000 0x96e428f4 0xffc00000 0xe7776dbc\n\ +DCVR 0xffc00000 0x976448fc 0xffc00000 0xe8379dc8\n\ +DCVR 0xffc00000 0x97e46904 0xffc00000 0xe977ddd8\n\ +DCVR 0xffc00000 0x98a48910 0xffc00000 0xeab81de8\n\ +DCVR 0xffc00000 0x9964b918 0xffc00000 0xebf86df8\n\ +DCVR 0xffc00000 0xffe4e924 0xffc00000 0xfff8ae08\n\ +DCVR 0xffc00000 0xffe5192c 0xffc00000 0xfff8fe1c\n\ +DCVR 0xffc00000 0xffe54938 0xffc00000 0xfff95e2c\n\ +DCVR 0xffc00000 0xffe57944 0xffc00000 0xfff9ae44\n\ +DCVR 0xffc00000 0xffe5b954 0xffc00000 0xfffa0e58\n\ +DCVR 0xffc00000 0xffe5e960 0xffc00000 0xfffa6e70\n\ +\n\ +# 266MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +"; + +#endif diff --git a/drivers/mxc/pm/dvfs_dptc_table_mx31_27ckih.h b/drivers/mxc/pm/dvfs_dptc_table_mx31_27ckih.h new file mode 100644 index 000000000000..ff8b520776c0 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mx31_27ckih.h @@ -0,0 +1,152 @@ +/* + * 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 + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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.h + * + * @brief MX31 dvfs & dptc table file for CKIH clock 27MHz. + * + * @ingroup PM_MX31 + */ +#ifndef __DVFS_DPTC_TABLE_MX31_27CKIH_H__ +#define __DVFS_DPTC_TABLE_MX31_27CKIH_H__ + +#define NUM_OF_FREQS 4 +#define NUM_OF_WP 17 + +/*! + * Default DPTC table definition. + * The table doesn't use PLL switch, because on DDR boards + * PLL switch is not possible due to HW issue. + * For SDR boards new table can be loaded. + * + * The table keeps the same voltage of 1.35V for frequencies lower than 399MHz. + * Theoretically we don't need DPTC for these frequencies, + * but we have to keep DPTC enabled for fluent DVFS switching + * back to high frequency. + */ +static char *default_table_str_27ckih = "WORKING POINT 17\n\ +WP 0x1d 0x12 0x12 0x12\n\ +WP 0x1c 0x12 0x12 0x12\n\ +WP 0x1b 0x12 0x12 0x12\n\ +WP 0x1a 0x12 0x12 0x12\n\ +WP 0x19 0x12 0x12 0x12\n\ +WP 0x18 0x12 0x12 0x12\n\ +WP 0x17 0x12 0x12 0x12\n\ +WP 0x16 0x12 0x12 0x12\n\ +WP 0x15 0x12 0x12 0x12\n\ +WP 0x14 0x12 0x12 0x12\n\ +WP 0x13 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x11 0x12 0x12 0x12\n\ +WP 0x10 0x12 0x12 0x12\n\ +WP 0xf 0x12 0x12 0x12\n\ +WP 0xe 0x12 0x12 0x12\n\ +WP 0xd 0x12 0x12 0x12\n\ +\n\ +# pll_sw_up pll_sw_down pdr_up pdr_down pll_up pll_down vscnt\n\ +# 532MHz\n\ +FREQ 0 0 0xff871e58 0xff871e59 0xe240d 0xe240d 7\n\ +# 266MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0xe240d 0xe240d 7\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0xe240d 0xe240d 7\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0xe240d 0xe240d 7\n\ +# 532MHz\n\ +DCVR 0xffc00000 0x90400000 0xffc00000 0xdd000000\n\ +DCVR 0xffc00000 0x90629890 0xffc00000 0xdd34ed20\n\ +DCVR 0xffc00000 0x90629890 0xffc00000 0xdd34ed20\n\ +DCVR 0xffc00000 0x90629894 0xffc00000 0xdd74fd24\n\ +DCVR 0xffc00000 0x90a2a894 0xffc00000 0xddb50d28\n\ +DCVR 0xffc00000 0x90e2b89c 0xffc00000 0xde352d30\n\ +DCVR 0xffc00000 0x9162d8a0 0xffc00000 0xdef55d38\n\ +DCVR 0xffc00000 0x91e2f8a8 0xffc00000 0xdfb58d44\n\ +DCVR 0xffc00000 0x926308b0 0xffc00000 0xe0b5cd54\n\ +DCVR 0xffc00000 0x92e328bc 0xffc00000 0xe1f60d64\n\ +DCVR 0xffc00000 0x93a358c0 0xffc00000 0xe3365d74\n\ +DCVR 0xffc00000 0xf66388cc 0xffc00000 0xf6768d84\n\ +DCVR 0xffc00000 0xf663b8d4 0xffc00000 0xf676dd98\n\ +DCVR 0xffc00000 0xf663e8e0 0xffc00000 0xf6773da4\n\ +DCVR 0xffc00000 0xf66418ec 0xffc00000 0xf6778dbc\n\ +DCVR 0xffc00000 0xf66458fc 0xffc00000 0xf677edd0\n\ +DCVR 0xffc00000 0xf6648908 0xffc00000 0xf6783de8\n\ +\n\ +# 266MHz\n\ +DCVR 0xffc00000 0x90400000 0xffc00000 0xdd000000\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd4d4348\n\ +DCVR 0xffc00000 0x9088b228 0xffc00000 0xdd8d434c\n\ +DCVR 0xffc00000 0x90c8b228 0xffc00000 0xde0d534c\n\ +DCVR 0xffc00000 0x9148b228 0xffc00000 0xdecd5350\n\ +DCVR 0xffc00000 0x91c8c22c 0xffc00000 0xdf8d6354\n\ +DCVR 0xffc00000 0x9248d22c 0xffc00000 0xe08d7354\n\ +DCVR 0xffc00000 0x92c8d230 0xffc00000 0xe1cd8358\n\ +DCVR 0xffc00000 0x9388e234 0xffc00000 0xe30d935c\n\ +DCVR 0xffc00000 0xf648e234 0xffc00000 0xf64db364\n\ +DCVR 0xffc00000 0xf648f238 0xffc00000 0xf64dc368\n\ +DCVR 0xffc00000 0xf648f23c 0xffc00000 0xf64dd36c\n\ +DCVR 0xffc00000 0xf649023c 0xffc00000 0xf64de370\n\ +DCVR 0xffc00000 0xf649123c 0xffc00000 0xf64df374\n\ +DCVR 0xffc00000 0xf6492240 0xffc00000 0xf64e1378\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x90400000 0xffc00000 0xdd000000\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd4d4348\n\ +DCVR 0xffc00000 0x9088b228 0xffc00000 0xdd8d434c\n\ +DCVR 0xffc00000 0x90c8b228 0xffc00000 0xde0d534c\n\ +DCVR 0xffc00000 0x9148b228 0xffc00000 0xdecd5350\n\ +DCVR 0xffc00000 0x91c8c22c 0xffc00000 0xdf8d6354\n\ +DCVR 0xffc00000 0x9248d22c 0xffc00000 0xe08d7354\n\ +DCVR 0xffc00000 0x92c8d230 0xffc00000 0xe1cd8358\n\ +DCVR 0xffc00000 0x9388e234 0xffc00000 0xe30d935c\n\ +DCVR 0xffc00000 0xf648e234 0xffc00000 0xf64db364\n\ +DCVR 0xffc00000 0xf648f238 0xffc00000 0xf64dc368\n\ +DCVR 0xffc00000 0xf648f23c 0xffc00000 0xf64dd36c\n\ +DCVR 0xffc00000 0xf649023c 0xffc00000 0xf64de370\n\ +DCVR 0xffc00000 0xf649123c 0xffc00000 0xf64df374\n\ +DCVR 0xffc00000 0xf6492240 0xffc00000 0xf64e1378\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x90400000 0xffc00000 0xdd000000\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd4d4348\n\ +DCVR 0xffc00000 0x9088b228 0xffc00000 0xdd8d434c\n\ +DCVR 0xffc00000 0x90c8b228 0xffc00000 0xde0d534c\n\ +DCVR 0xffc00000 0x9148b228 0xffc00000 0xdecd5350\n\ +DCVR 0xffc00000 0x91c8c22c 0xffc00000 0xdf8d6354\n\ +DCVR 0xffc00000 0x9248d22c 0xffc00000 0xe08d7354\n\ +DCVR 0xffc00000 0x92c8d230 0xffc00000 0xe1cd8358\n\ +DCVR 0xffc00000 0x9388e234 0xffc00000 0xe30d935c\n\ +DCVR 0xffc00000 0xf648e234 0xffc00000 0xf64db364\n\ +DCVR 0xffc00000 0xf648f238 0xffc00000 0xf64dc368\n\ +DCVR 0xffc00000 0xf648f23c 0xffc00000 0xf64dd36c\n\ +DCVR 0xffc00000 0xf649023c 0xffc00000 0xf64de370\n\ +DCVR 0xffc00000 0xf649123c 0xffc00000 0xf64df374\n\ +DCVR 0xffc00000 0xf6492240 0xffc00000 0xf64e1378\n\ +"; + +#endif diff --git a/drivers/mxc/pm/dvfs_dptc_table_mx31_rev2.h b/drivers/mxc/pm/dvfs_dptc_table_mx31_rev2.h new file mode 100644 index 000000000000..cb0f64c98749 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mx31_rev2.h @@ -0,0 +1,158 @@ +/* + * 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 + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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_dptc_table_mx31_rev2.h + * + * @brief MX31 dvfs & dptc table file for MX31 2.0 + * + * @ingroup PM_MX31 + */ +#ifndef __DVFS_DPTC_TABLE_MX3_REV2_H__ +#define __DVFS_DPTC_TABLE_MX3_REV2_H__ + +#define NUM_OF_FREQS 4 +#define NUM_OF_WP 17 + +/*! + * Default DPTC table definition. + * The table doesn't use PLL switch, because on DDR boards + * PLL switch is not possible due to HW issue. + * For SDR boards new table can be loaded. + * + * The table keeps the same voltage of 3.5V for frequencies lower than 399MHz. + * Theoretically we don't need DPTC for these frequencies, + * but we have to keep DPTC enabled for fluent DVFS switching + * back to high frequency. + */ +static char *default_table_str_rev2 = "WORKING POINT 17\n\ +\n\ +# mc13783 switcher SW values for each working point\n\ +# The first line is for WP of highest voltage\n\ +# The first column is for highest frequency\n\ +# SW1A SW1A DVS SW1B DVS SW1B STANDBY\n\ +WP 0x1d 0xc 0xc 0xc\n\ +WP 0x1c 0xc 0xc 0xc\n\ +WP 0x1b 0xc 0xc 0xc\n\ +WP 0x1a 0xc 0xc 0xc\n\ +WP 0x19 0xc 0xc 0xc\n\ +WP 0x18 0xc 0xc 0xc\n\ +WP 0x17 0xc 0xc 0xc\n\ +WP 0x16 0xc 0xc 0xc\n\ +WP 0x15 0xc 0xc 0xc\n\ +WP 0x14 0xc 0xc 0xc\n\ +WP 0x13 0xc 0xc 0xc\n\ +WP 0x12 0xc 0xc 0xc\n\ +WP 0x11 0xc 0xc 0xc\n\ +WP 0x10 0xc 0xc 0xc\n\ +WP 0xf 0xc 0xc 0xc\n\ +WP 0xe 0xc 0xc 0xc\n\ +WP 0xd 0xc 0xc 0xc\n\ +\n\ +# pll_sw_up pll_sw_down pdr_up pdr_down pll_up pll_down vscnt\n\ +# 532MHz\n\ +FREQ 0 1 0xff871e58 0xff871650 0x0033280c 0x00331c23 1\n\ +# 399MHz\n\ +FREQ 1 1 0xff871e58 0xff871e59 0x0033280c 0x0033280c 4\n\ +# 266MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x0033280c 0x0033280c 4\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x0033280c 0x0033280c 4\n\ +\n\ +# 532MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95e3e8e4 0xffc00000 0xe5b6fda0\n\ +DCVR 0xffc00000 0x95e3e8e4 0xffc00000 0xe5b6fda0\n\ +DCVR 0xffc00000 0x95e3e8e8 0xffc00000 0xe5f70da4\n\ +DCVR 0xffc00000 0x9623f8e8 0xffc00000 0xe6371da8\n\ +DCVR 0xffc00000 0x966408f0 0xffc00000 0xe6b73db0\n\ +DCVR 0xffc00000 0x96e428f4 0xffc00000 0xe7776dbc\n\ +DCVR 0xffc00000 0x976448fc 0xffc00000 0xe8379dc8\n\ +DCVR 0xffc00000 0x97e46904 0xffc00000 0xe977ddd8\n\ +DCVR 0xffc00000 0x98a48910 0xffc00000 0xeab81de8\n\ +DCVR 0xffc00000 0x9964b918 0xffc00000 0xebf86df8\n\ +DCVR 0xffc00000 0xffe4e924 0xffc00000 0xfff8ae08\n\ +DCVR 0xffc00000 0xffe5192c 0xffc00000 0xfff8fe1c\n\ +DCVR 0xffc00000 0xffe54938 0xffc00000 0xfff95e2c\n\ +DCVR 0xffc00000 0xffe57944 0xffc00000 0xfff9ae44\n\ +DCVR 0xffc00000 0xffe5b954 0xffc00000 0xfffa0e58\n\ +DCVR 0xffc00000 0xffe5e960 0xffc00000 0xfffa6e70\n\ +\n\ +# 266MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +"; + +#endif diff --git a/drivers/mxc/ssi/Kconfig b/drivers/mxc/ssi/Kconfig new file mode 100644 index 000000000000..f4b8803bec52 --- /dev/null +++ b/drivers/mxc/ssi/Kconfig @@ -0,0 +1,13 @@ +# +# 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..c0c88729026d --- /dev/null +++ b/drivers/mxc/ssi/Makefile @@ -0,0 +1,9 @@ +# +# 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..ac91de8ea49f --- /dev/null +++ b/drivers/mxc/ssi/registers.h @@ -0,0 +1,190 @@ +/* + * 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/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 <asm/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 + +/*! 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 + +/*! 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 + +/*! + * 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 + +/*! + * 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 +/*! + * SISR Register definition + */ +#define SSI_IRQ_STATUS_NUMBER 19 +/*! + * 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..244bd9ad818d --- /dev/null +++ b/drivers/mxc/ssi/ssi.c @@ -0,0 +1,1155 @@ +/* + * 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.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/slab.h> +#include <asm/uaccess.h> +#include <asm/arch/clock.h> + +#include "registers.h" +#include "ssi.h" + +static spinlock_t ssi_lock; + +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); + +unsigned int get_ssi_base_addr(unsigned int ssi) +{ + unsigned int base_addr; + base_addr = (ssi == SSI1) ? IO_ADDRESS(SSI1_BASE_ADDR) : + IO_ADDRESS(SSI2_BASE_ADDR); + return base_addr; +} + +void set_register_bits(unsigned int mask, unsigned int data, + unsigned int offset, unsigned int ssi) +{ + volatile unsigned long reg = 0; + unsigned int base_addr = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + base_addr = get_ssi_base_addr(ssi); + 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) +{ + volatile unsigned long reg = 0; + unsigned int base_addr = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + base_addr = get_ssi_base_addr(ssi); + reg = __raw_readl(base_addr + offset); + spin_unlock_irqrestore(&ssi_lock, flags); + + return reg; +} + +void set_register(unsigned int data, unsigned int offset, unsigned int ssi) +{ + unsigned int base_addr = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + base_addr = get_ssi_base_addr(ssi); + __raw_writel(data, base_addr + offset); + spin_unlock_irqrestore(&ssi_lock, flags); + +} + +/*! + * 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 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); + printk(KERN_INFO "SSI module loaded successfully\n"); + return 0; +} + +/*! + * 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) +{ + 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..01078ae87325 --- /dev/null +++ b/drivers/mxc/ssi/ssi.h @@ -0,0 +1,565 @@ +/* + * 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 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..793faf9be7e5 --- /dev/null +++ b/drivers/mxc/ssi/ssi_types.h @@ -0,0 +1,359 @@ +/* + * 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 + */ + 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_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..c4e64197074e --- /dev/null +++ b/drivers/mxc/vpu/Kconfig @@ -0,0 +1,22 @@ +# +# 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) + default y + ---help--- + The VPU codec device provides codec function for H.264/MPEG4/H.263 + +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..cd062d28aef5 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vl2cc.c @@ -0,0 +1,123 @@ +/* + * 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 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 <asm/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..be9e443f83a8 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vpu.c @@ -0,0 +1,489 @@ +/* + * Copyright 2006-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_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 <asm/hardware.h> + +#include <asm/arch/mxc_vpu.h> + +#define BIT_INT_CLEAR 0x00C +#define BIT_INT_STATUS 0x010 +#define BIT_INT_ENABLE 0x170 + +typedef struct vpu_t { + struct fasync_struct *async_queue; +} vpu_t; + +/* To track the allocated memory buffer */ +typedef struct memalloc_record { + struct list_head list; + vpu_mem_desc mem; +} memalloc_record; + +static DEFINE_SPINLOCK(vpu_lock); +static LIST_HEAD(head); + +static int vpu_major = 0; +static struct class *vpu_class; +static struct vpu_t vpu_data; +static u8 open_count = 0; +static struct clk *vpu_clk; + +/* implement the blocking ioctl */ +static int codec_done = 0; +static wait_queue_head_t vpu_queue; + +/*! + * Private function to free buffers + * @return status 0 success. + */ +static int vpu_free_buffers(void) +{ + struct memalloc_record *rec, *n; + vpu_mem_desc mem; + + spin_lock(&vpu_lock); + list_for_each_entry_safe(rec, n, &head, list) { + mem = rec->mem; + if (mem.cpu_addr != 0) { + dma_free_coherent(0, PAGE_ALIGN(mem.size), + (void *)mem.cpu_addr, mem.phy_addr); + pr_debug("[FREE] freed paddr=0x%08X\n", mem.phy_addr); + + /* delete from list */ + list_del(&rec->list); + kfree(rec); + } + } + spin_unlock(&vpu_lock); + + return 0; +} + +/*! + * @brief vpu interrupt handler + */ +static irqreturn_t vpu_irq_handler(int irq, void *dev_id) +{ + struct vpu_t *dev; + dev = (struct vpu_t *)dev_id; + __raw_readl(IO_ADDRESS(VPU_BASE_ADDR + BIT_INT_STATUS)); + __raw_writel(0x1, IO_ADDRESS(VPU_BASE_ADDR + BIT_INT_CLEAR)); + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + codec_done = 1; + wake_up_interruptible(&vpu_queue); + + return IRQ_HANDLED; +} + +/*! + * @brief vpu hardware enable function + * + * @return 0 on success or negative error code on error + */ +static int vpu_hardware_enable(void) +{ + if (cpu_is_mx32()) { + vl2cc_enable(); + } + clk_enable(vpu_clk); + return 0; +} + +/*! + * @brief vpu hardware disable function + * + * @return 0 on success or negative error code on error + */ +static int vpu_hardware_disable(void) +{ + if (cpu_is_mx32()) { + vl2cc_disable(); + } + clk_disable(vpu_clk); + return 0; + +} + +/*! + * @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) +{ + if (open_count++ == 0) { + filp->private_data = (void *)(&vpu_data); + vpu_hardware_enable(); + } else { + printk(KERN_ERR "VPU has already been opened.\n"); + return -EACCES; + } + + 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), (vpu_mem_desc *) arg, + sizeof(vpu_mem_desc)); + if (ret) { + kfree(rec); + return -EFAULT; + } + + pr_debug("[ALLOC] mem alloc size = 0x%x\n", + rec->mem.size); + rec->mem.cpu_addr = (unsigned long) + dma_alloc_coherent(NULL, + PAGE_ALIGN(rec->mem.size), + (dma_addr_t + *) (&(rec->mem.phy_addr)), + GFP_DMA | GFP_KERNEL); + pr_debug("[ALLOC] mem alloc cpu_addr = 0x%x\n", + rec->mem.cpu_addr); + if ((void *)(rec->mem.cpu_addr) == NULL) { + kfree(rec); + printk(KERN_ERR + "Physical memory allocation error!\n"); + ret = -1; + break; + } + ret = copy_to_user((void __user *)arg, &(rec->mem), + sizeof(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; + vpu_mem_desc vpu_mem; + + ret = copy_from_user(&vpu_mem, (vpu_mem_desc *) arg, + sizeof(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) { + dma_free_coherent(NULL, + PAGE_ALIGN(vpu_mem.size), + (void *)vpu_mem.cpu_addr, + (dma_addr_t) vpu_mem. + phy_addr); + } + + 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_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) +{ + if (--open_count == 0) { + vpu_free_buffers(); + vpu_hardware_disable(); + } + + 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_t *dev = (struct vpu_t *)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 class_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 -ENOENT; + } + + err = vl2cc_init(res->start); + if (err != 0) + return err; + } + + 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 = 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(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: + 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; +} + +/*! Driver definition + * + */ +static struct platform_driver mxcvpu_driver = { + .driver = { + .name = "mxc_vpu", + }, + .probe = vpu_dev_probe, +}; + +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(INT_VPU, (void *)(&vpu_data)); + if (vpu_major > 0) { + class_device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + if (unregister_chrdev(vpu_major, "mxc_vpu") < 0) { + printk(KERN_ERR + "Failed to unregister vpu from devfs\n"); + return; + } + vpu_major = 0; + } + + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + + clk_put(vpu_clk); + + platform_driver_unregister(&mxcvpu_driver); + return; +} + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX27"); +MODULE_LICENSE("GPL"); + +module_init(vpu_init); +module_exit(vpu_exit); diff --git a/include/linux/fsl_devices.h b/include/linux/fsl_devices.h index 73710d617775..a6814f02ead6 100644 --- a/include/linux/fsl_devices.h +++ b/include/linux/fsl_devices.h @@ -71,8 +71,8 @@ struct gianfar_mdio_data { #define FSL_GIANFAR_DEV_HAS_PADDING 0x00000080 /* Flags in gianfar_platform_data */ -#define FSL_GIANFAR_BRD_HAS_PHY_INTR 0x00000001 /* set or use a timer */ -#define FSL_GIANFAR_BRD_IS_REDUCED 0x00000002 /* Set if RGMII, RMII */ +#define FSL_GIANFAR_BRD_HAS_PHY_INTR 0x00000001 /* set or use a timer */ +#define FSL_GIANFAR_BRD_IS_REDUCED 0x00000002 /* Set if RGMII, RMII */ struct fsl_i2c_platform_data { /* device specific information */ @@ -100,9 +100,30 @@ enum fsl_usb2_phy_modes { 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; + enum fsl_usb2_operating_modes operating_mode; + enum fsl_usb2_phy_modes phy_mode; + unsigned int port_enables; + + /* DDD this could arguably be moved to a separate + * fsl usb2 device header file + */ + char *name; /* pretty print */ + int (*platform_init) (struct platform_device *); + void (*platform_uninit) (struct fsl_usb2_platform_data *); + int (*platform_verify) (struct platform_device *); + u32 xcvr_type; /* PORTSC_PTS_* */ + char *transceiver; /* transceiver name */ + // DDD combine usbmode and view into 1 register-base variable + u32 usbmode; /* address of usbmode register */ + u32 viewport; /* address of ulpiview register */ + u32 r_start; /* start of MEM resource */ + u32 r_len; /* length of MEM resource */ + void __iomem *regs; /* ioremap'd register base */ + int does_otg; + unsigned power_budget; /* for hcd->power_budget */ + struct fsl_xcvr_ops *xcvr_ops; + int (*gpio_usb_active) (void); + void (*gpio_usb_inactive) (void); }; /* Flags in fsl_usb2_mph_platform_data */ @@ -110,14 +131,14 @@ struct fsl_usb2_platform_data { #define FSL_USB2_PORT1_ENABLED 0x00000002 struct fsl_spi_platform_data { - u32 initial_spmode; /* initial SPMODE value */ - u16 bus_num; + u32 initial_spmode; /* initial SPMODE value */ + u16 bus_num; /* board specific information */ - u16 max_chipselect; - void (*activate_cs)(u8 cs, u8 polarity); - void (*deactivate_cs)(u8 cs, u8 polarity); - u32 sysclk; + u16 max_chipselect; + void (*activate_cs) (u8 cs, u8 polarity); + void (*deactivate_cs) (u8 cs, u8 polarity); + u32 sysclk; }; #endif /* _FSL_DEVICE_H_ */ |