/* 2008-10-21: File added and changed by Sony Corporation */
/*
 * 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 long 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 (unsigned char)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 long 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 (unsigned char)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);
}

#if defined(CONFIG_MACH_MX3KZ) /* { */

#include "../dam/dam.h"

/* 音のテスト */
int kmc_test_flag = 0;


int kmc_get_data;
int kmc_get_command_data;
int kmc_get_tag_data;
int kmc_intst;
unsigned int *kmc_digi_data;
unsigned int *kmc_digi_datap;
int kmc_digi_counter;
#define KMC_DIGI_SIZE		0x4000
static write_command(int cmd,int data)
{
    kmc_intst = ssi_get_status(0);
    msleep(1);
    ssi_ac97_set_command_address_register(0,cmd << 12);	/* command 12-18 */
    ssi_ac97_set_command_data_register(0,data << 4);	/* data 4-19 */

    ssi_set_data(0,ssi_fifo_0,0xe000);			/* valid data & valid address */
    ssi_ac97_set_tag_register(0,0xe000);		/* valid data & valid address (SATAG write) */
    ssi_ac97_write_command(0, true);
    msleep(1);
    kmc_get_command_data = ssi_ac97_get_command_data_register(0);

}

static read_command(int cmd)
{
    kmc_intst = ssi_get_status(0);
    msleep(1);
    ssi_ac97_set_command_address_register(0,cmd << 12);	/* command 12-18 */

    ssi_set_data(0,ssi_fifo_0,0xc000);			/* valid address */
    ssi_ac97_set_tag_register(0,0xc000);		/* valid address (SATAG write) */
    ssi_ac97_read_command(0, true);
    msleep(1);
    kmc_get_command_data = ssi_ac97_get_command_data_register(0);

    return kmc_get_command_data >> 4;
}




int ac97_test()
{
#if 0 /* { */
#if 0 /* { */
	int source_port = port_1;
	int target_port = port_4;
	int dummy = 0;

	/* TX MSB trans  STCR[4]=0 */
	/* RX MSB Recv   SRCR[4]=0 */
	/* TX rising edge STCR[3]=0 */
	/* RX latched faillig edge SRCR[3]=0 */
	/* TX frame sync active high STCR[2]=0 */
	/* RX frame sync active high SRCR[2]=0 */

	ssi_rx_word_length(0, 9);		/* ワード長 9:20bit 7:16bit */
	ssi_rx_frame_rate(0, 13);		/* 13個受信 セットは12 */
	ssi_tx_word_length(0, 9);		/* ワード長 9:20bit 7:16bit */
	ssi_tx_frame_rate(0, 13);		/* 13個送信 セットは12 */


	dam_reset_register(source_port);
        dam_reset_register(target_port);

        dam_select_mode(source_port, normal_mode);
        dam_select_mode(target_port, normal_mode);

        dam_set_synchronous(source_port, true);
        dam_set_synchronous(target_port, true);

        dam_select_RxD_source(source_port, target_port);
        dam_select_RxD_source(target_port, source_port);

        dam_select_TxFS_direction(source_port, signal_in);
        dam_select_TxFS_direction(target_port, signal_out);
	dam_select_TxFS_source(source_port, false, target_port);

	dam_select_TxClk_direction(source_port, signal_out);
	dam_select_TxClk_source(source_port, false, target_port);

	ssi_tx_frame_direction(0, ssi_tx_rx_internally);

	ssi_tx_fifo_enable(0, ssi_fifo_0, true);
	ssi_rx_fifo_enable(0, ssi_fifo_0, true);

//	ssi_receive_enable(0,true);
//	ssi_transmit_enable(0,true);

//	ssi_enable(0,true);

//        kmc_digi_data = (void *)kmalloc(KMC_DIGI_SIZE*4, GFP_KERNEL);

//	ssi_ac97_mode_enable(0,true);
//	msleep(1);
//	write_command(0x0,0x0000);				/* Master Vol 0db and Mute off and r-mute off */
//	msleep(1);

//	    write_command(0x2,0x0000);				/* Master Vol 0db and Mute off and r-mute off */

//	    write_command(0x4,0x0000);				/* Headphone(AUX) Vol 0db and Mute off and r-mute off */

//	    write_command(0x6,0x0000);				/* Mono Vol 0db and Mute off and r-mute off */

//	    write_command(0xc,0x0000);				/* phone Vol 0db and Mute off and r-mute off */

//	    write_command(0xe,0x0000);				/* mic Vol 0db and Mute off and r-mute off */

//	    write_command(0x10,0x0000);				/* Line-in Vol Mute off */

//	    write_command(0x12,0x0000);				/* CD Vol Mute off */

//	    write_command(0x16,0x0000);				/* AUX Vol Mute off */

//	    write_command(0x1a,0x404);				/* Rec sel Line-in  */

//	    write_command(0x1c,0x0808);				/* Rec Gain */




//	    write_command(0x64,0x808);				/* MIXER ADC */

//		write_command(0x76,0x0800);				/* DAM ON */

//		write_command(0x18,0x0);				/* PCM_VOL ON */

//		ssi_ac97_set_tag_register(0,0x9800);			/* lift/right data send */

#else /* }{ */
	int source_port = port_1;
	int target_port = port_4;
	int dummy = 0;

	/* TX MSB trans  STCR[4]=0 */
	/* RX MSB Recv   SRCR[4]=0 */
	/* TX rising edge STCR[3]=0 */
	/* RX latched faillig edge SRCR[3]=0 */
	/* TX frame sync active high STCR[2]=0 */
	/* RX frame sync active high SRCR[2]=0 */

	ssi_rx_word_length(0, 9);		/* ワード長 9:20bit 7:16bit */
	ssi_rx_frame_rate(0, 13);		/* 13個受信 セットは12 */
	ssi_tx_word_length(0, 9);		/* ワード長 9:20bit 7:16bit */
	ssi_tx_frame_rate(0, 13);		/* 13個送信 セットは12 */


	dam_reset_register(source_port);
        dam_reset_register(target_port);

        dam_select_mode(source_port, normal_mode);
        dam_select_mode(target_port, normal_mode);

        dam_set_synchronous(source_port, true);
        dam_set_synchronous(target_port, true);

        dam_select_RxD_source(source_port, target_port);
        dam_select_RxD_source(target_port, source_port);

#if 0
        dam_select_TxFS_direction(source_port, signal_out);
	dam_select_TxFS_source(source_port, false, target_port);

	dam_select_TxClk_direction(target_port, signal_out);
	dam_select_TxClk_source(target_port, false, source_port);
#else
        dam_select_TxFS_direction(source_port, signal_in);
        dam_select_TxFS_direction(target_port, signal_out);
	dam_select_TxFS_source(source_port, false, target_port);
//        dam_select_TxFS_direction(source_port, signal_out);
//	dam_select_TxFS_source(source_port, false, target_port);

	dam_select_TxClk_direction(source_port, signal_out);
	dam_select_TxClk_source(source_port, false, target_port);
#endif


/*
//	dam_select_RxFS_direction(source_port, signal_out);
//	dam_select_RxFS_source(source_port, false, target_port);

//	dam_select_RxClk_direction(source_port, signal_out);
//	dam_select_RxClk_source(source_port, false, target_port);
*/

	ssi_tx_frame_direction(0, ssi_tx_rx_internally);

	ssi_tx_fifo_enable(0, ssi_fifo_0, true);
	ssi_rx_fifo_enable(0, ssi_fifo_0, true);

	ssi_receive_enable(0,true);
	ssi_transmit_enable(0,true);

	ssi_enable(0,true);

        kmc_digi_data = (void *)kmalloc(KMC_DIGI_SIZE*4, GFP_KERNEL);

	ssi_ac97_mode_enable(0,true);
	msleep(1);
	write_command(0x0,0x0000);				/* Master Vol 0db and Mute off and r-mute off */
	msleep(1);

	while(kmc_test_flag){
	    write_command(0x2,0x0000);				/* Master Vol 0db and Mute off and r-mute off */
	    kmc_get_command_data = read_command(2);

	    write_command(0x4,0x0000);				/* Headphone(AUX) Vol 0db and Mute off and r-mute off */
	    kmc_get_command_data = read_command(4);

	    write_command(0x6,0x0000);				/* Mono Vol 0db and Mute off and r-mute off */
	    kmc_get_command_data = read_command(6);

	    write_command(0xc,0x0000);				/* phone Vol 0db and Mute off and r-mute off */
	    kmc_get_command_data = read_command(0xc);

	    write_command(0xe,0x0000);				/* mic Vol 0db and Mute off and r-mute off */
	    kmc_get_command_data = read_command(0xe);

	    write_command(0x10,0x0000);				/* Line-in Vol Mute off */
	    kmc_get_command_data = read_command(0x10);

	    write_command(0x12,0x0000);				/* CD Vol Mute off */
	    kmc_get_command_data = read_command(0x12);

	    write_command(0x16,0x0000);				/* AUX Vol Mute off */
	    kmc_get_command_data = read_command(0x16);

	    write_command(0x1a,0x404);				/* Rec sel Line-in  */
	    kmc_get_command_data = read_command(0x1a);

	    write_command(0x1c,0x0808);				/* Rec Gain */
	    kmc_get_command_data = read_command(0x1c);



//	    write_command(0x20,0x80);				/* Loop Back */
//	    kmc_get_command_data = read_command(0x20);

	    write_command(0x64,0x808);				/* MIXER ADC */
	    kmc_get_command_data = read_command(0x20);

	    kmc_get_command_data = read_command(0x26);		/* get staus/power control */
	    kmc_get_command_data = read_command(0x76);		/* get miscellaneous control */
	    kmc_get_command_data = read_command(0x72);		/* get JACK_SENCE */

	    while(kmc_test_flag){
		int cnt,i;
	        kmc_digi_datap = kmc_digi_data;
		for(kmc_digi_counter=0;kmc_digi_counter < (KMC_DIGI_SIZE - 8);){
		    cnt =  ssi_rx_fifo_counter(0,ssi_fifo_0);
		    for(i=0;i < cnt;i++){
			*kmc_digi_datap++ = getreg_value(MXC_SSISRX0,0);
			kmc_digi_counter++;
		    }
		}

		write_command(0x76,0x0800);				/* DAM ON */

		write_command(0x18,0x0);				/* PCM_VOL ON */

		ssi_ac97_set_tag_register(0,0x9800);			/* lift/right data send */

	        kmc_digi_datap = kmc_digi_data;
		kmc_digi_counter = 0;
		for(kmc_digi_counter=0;kmc_digi_counter < (KMC_DIGI_SIZE - 8);){
		    cnt =  8 - ssi_tx_fifo_counter(0,ssi_fifo_0);
		    for(i=0;i < cnt;i++){
			set_register(*kmc_digi_datap++,MXC_SSISTX0,0);
			kmc_digi_counter++;
		    }
		}




	        kmc_digi_datap = kmc_digi_data;
		kmc_digi_counter = 0;
		kmc_get_command_data = read_command(0x72);		/* get JACK_SENCE */
//		kmc_get_tag_data = ssi_ac97_get_tag_register(0);
	    }
	}
#endif /* } */
#endif /* } */
}


#endif /* } */


/*!
 * 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);
#if defined(CONFIG_MACH_MX3KZ) /* { */
	ac97_test();
#endif /* } */
	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");
