/*
 * Copyright 2005 Freescale Semiconductor, Inc. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * CMOS Sensor related functions for the Freescale Semiconductor MXC IPU
 *
 */

/*!
 * @file ipu_csi.c
 *
 * @brief IPU CMOS Sensor interface functions
 *
 * @ingroup IPU 
 */
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include "ipu.h"
#include "ipu_prv.h"
#include "ipu_regs.h"
#include "asm-arm/io.h"

/*!
 * ipu_csi_init_interface  
 *    Sets initial values for the CSI registers.
 *    The width and height of the sensor and the actual frame size will be
 *    set to the same values.
 *    The value of p_mclk_freq is requested freq on input and 
 *    actual freq on output.
 * @param       width        Sensor width
 * @param       height       Sensor height
 * @param       pixel_fmt    pixel format   
 * @param       p_mclk_freq  mclk frequence
 * @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,
                               uint32_t * p_mclk_freq, ipu_csi_signal_cfg_t sig)
{
        uint32_t div;
        uint32_t data = 0;

        FUNC_START;

        /* 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;
        }

        // TODO: Support different clock sources    
        // Calculate the divider using the requested, minimum mclk freq
        div = g_ipu_clk / *p_mclk_freq;

        /* Calculate and return the actual mclk frequency.
        The integer division error/truncation will ensure the actual freq is
        greater than the requested freq.
        */
        if (*p_mclk_freq < g_ipu_clk / div) {
                div++;
        }

        *p_mclk_freq = g_ipu_clk / div;
        

        data |= (div - 1) << 16; // Register value is minus 1

        // 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;

        writel(data, CSI_SENS_CONF);

        // Set CSI_OUT_FRM_CTRL Register (TODO:  Allow caller to set?)
        writel(0, CSI_OUT_FRM_CTRL);
        //mNode->m_hw_if->Write32(CSI_OUT_FRM_CTRL, 0x964);
        // Virtio: HSC=9, VSC=0x64 Is this camera specific?

        // Setup frame size
        writel(width | height << 16, CSI_ACT_FRM_SIZE);
        writel(width | height << 16, CSI_SENS_FRM_SIZE);

        //Set CCIR registers
        if ((sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) ||
            (sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED)) {
                writel(0x40030, CSI_CCIR_CODE_1);
                writel(0xFF0000, CSI_CCIR_CODE_3);
        }

        printk("mclk frequency = %d\n", *p_mclk_freq);
        printk("CSI_SENS_CONF = 0x%08X\n", readl(CSI_SENS_CONF));
        printk("CSI_ACT_FRM_SIZE = 0x%08X\n", readl(CSI_ACT_FRM_SIZE));

        writel(readl(IPU_CONF) | IPU_CONF_CSI_EN | IPU_CONF_IC_EN, IPU_CONF);

        FUNC_END;
        return 0;
}

/*!
 * ipu_csi_enable_mclk     
 *
 * @param       flag        true to enable mclk, false to disable mclk
 *
 * @return      0 for success
 */
int32_t 
ipu_csi_enable_mclk(bool flag)
{
        if (flag == true)
                writel(readl(IPU_CONF) | IPU_CONF_CSI_EN, IPU_CONF);
        else
                writel(readl(IPU_CONF) & ~IPU_CONF_CSI_EN, IPU_CONF);
        return 0;
}
