From: Jason Chen Date: Wed, 23 Feb 2011 09:31:54 +0000 (+0800) Subject: ENGR00141217-5 IPU\VPU\GPU: upgrade to 2.6.38 X-Git-Tag: v3.0.35-fsl~2475 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=c5879fb33676d3aab317206e93a3fe15eb5f2a34;p=karo-tx-linux.git ENGR00141217-5 IPU\VPU\GPU: upgrade to 2.6.38 Add drivers/mxc Add drivers/video/mxc Add drivers/media/mxc fb device: change acquire_console_sem to console_lock And release_console_sem to console_unlock Add DMA Zone support Add TVE driver, add regulator Add hdmi support Add VPU Fix build error ioctl --> unlocked_ioctl DECLARE_MUTEX --> DEFINE_SEMAPHORE Signed-off-by: Jason Chen Signed-off-by: Richard Zhao --- diff --git a/drivers/Kconfig b/drivers/Kconfig index 3bb154d8c8cc..65f7142817a6 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -126,4 +126,6 @@ source "drivers/hwspinlock/Kconfig" source "drivers/clocksource/Kconfig" +source "drivers/mxc/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 09f3232bcdcd..3c66d7069ec7 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -96,6 +96,7 @@ obj-y += lguest/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ obj-$(CONFIG_MMC) += mmc/ +obj-$(CONFIG_ARCH_MXC) += mxc/ obj-$(CONFIG_MEMSTICK) += memstick/ obj-y += leds/ obj-$(CONFIG_INFINIBAND) += infiniband/ diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index bb53de7fe408..0597976f5e53 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -618,6 +618,56 @@ config VIDEO_W9966 Check out for more information. +config VIDEO_MXC_CAMERA + tristate "MXC Video For Linux Camera" + depends on VIDEO_DEV && ARCH_MXC + default y + ---help--- + This is the video4linux2 capture driver based on MXC IPU/eMMA module. + +source "drivers/media/video/mxc/capture/Kconfig" + +config VIDEO_MXC_OUTPUT + tristate "MXC Video For Linux Video Output" + depends on VIDEO_DEV && ARCH_MXC + default y + ---help--- + This is the video4linux2 output driver based on MXC IPU/eMMA module. + +source "drivers/media/video/mxc/output/Kconfig" + +config VIDEO_MXS_PXP + tristate "MXS PxP" + depends on VIDEO_DEV && VIDEO_V4L2 && ARCH_MXS + select VIDEOBUF_DMA_CONTIG + ---help--- + This is a video4linux driver for the Freescale PxP + (Pixel Pipeline). This module supports output overlay of + the MXS framebuffer on a video stream. + + To compile this driver as a module, choose M here: the + module will be called pxp. + +config VIDEO_MXC_PXP_V4L2 + tristate "MXC PxP V4L2 driver" + depends on VIDEO_DEV && VIDEO_V4L2 && ARCH_MX5 + select VIDEOBUF_DMA_CONTIG + ---help--- + This is a video4linux driver for the Freescale PxP + (Pixel Pipeline). This module supports output overlay of + the MXC framebuffer on a video stream. + + To compile this driver as a module, choose M here. + +config VIDEO_MXC_OPL + tristate + depends on VIDEO_DEV && ARCH_MXC + default n + ---help--- + This is the ARM9-optimized OPL (Open Primitives Library) software + rotation/mirroring implementation. It may be used by eMMA video + capture or output device. + source "drivers/media/video/cpia2/Kconfig" config VIDEO_VINO diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index f0fecd6f6a33..e268ad4dfedd 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -87,6 +87,15 @@ obj-$(CONFIG_SOC_CAMERA_TW9910) += tw9910.o # And now the v4l2 drivers: +obj-$(CONFIG_VIDEO_MXC_IPU_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_CSI_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_IPU_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_EMMA_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_PXP_V4L2) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_OPL) += mxc/opl/ +obj-$(CONFIG_VIDEO_PXP) += pxp.o +obj-$(CONFIG_VIDEO_MXS_PXP) += mxs_pxp.o obj-$(CONFIG_VIDEO_BT848) += bt8xx/ obj-$(CONFIG_VIDEO_ZORAN) += zoran/ obj-$(CONFIG_VIDEO_CQCAM) += c-qcam.o diff --git a/drivers/media/video/mxc/capture/Kconfig b/drivers/media/video/mxc/capture/Kconfig new file mode 100644 index 000000000000..2e3cb56de14c --- /dev/null +++ b/drivers/media/video/mxc/capture/Kconfig @@ -0,0 +1,125 @@ +if VIDEO_MXC_CAMERA + +menu "MXC Camera/V4L2 PRP Features support" +config VIDEO_MXC_IPU_CAMERA + bool + depends on VIDEO_MXC_CAMERA && MXC_IPU + default y + +config VIDEO_MXC_EMMA_CAMERA + tristate "MX27 eMMA support" + depends on VIDEO_MXC_CAMERA && MXC_EMMA && FB_MXC_SYNC_PANEL + select VIDEO_MXC_OPL + default y + +config VIDEO_MXC_CSI_CAMERA + tristate "MX25 CSI camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + +config VIDEO_MXC_CSI_DMA + bool "CSI-DMA Still Image Capture support" + depends on VIDEO_MXC_EMMA_CAMERA + default n + ---help--- + Use CSI-DMA method instead of CSI-PrP link to capture still image. This allows + to use less physical contiguous memory to capture big resolution still image. But + with this method the CSC (Color Space Conversion) and resize are not supported. + If unsure, say N. + +choice + prompt "Select Camera/TV Decoder" + default MXC_CAMERA_OV3640 + depends on VIDEO_MXC_CAMERA + +config MXC_CAMERA_MC521DA + tristate "Magnachip mc521da camera support" + select I2C_MXC + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mc521da Camera with your MXC system, say Y here. + +config MXC_EMMA_CAMERA_MICRON111 + tristate "Micron mt9v111 camera support with eMMA" + select I2C_MXC + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mt9v111 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV2640_EMMA + tristate "OmniVision ov2640 camera support with eMMA" + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov2640 Camera with your MXC system, say Y here. + +config MXC_CAMERA_MICRON111 + tristate "Micron mt9v111 camera support" + select I2C_MXC + depends on ! VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mt9v111 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV2640 + tristate "OmniVision ov2640 camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov2640 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV3640 + tristate "OmniVision ov3640 camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov3640 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV5640 + tristate "OmniVision ov5640 camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov5640 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV5642 + tristate "OmniVision ov5642 camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov5642 Camera with your MXC system, say Y here. + +config MXC_TVIN_ADV7180 + tristate "Analog Device adv7180 TV Decoder Input support" + depends on (MACH_MX35_3DS || MACH_MX51_3DS) + ---help--- + If you plan to use the adv7180 video decoder with your MXC system, say Y here. + +endchoice + +config MXC_IPU_PRP_VF_SDC + tristate "Pre-Processor VF SDC library" + depends on VIDEO_MXC_IPU_CAMERA && FB_MXC_SYNC_PANEL + default y + ---help--- + Use case PRP_VF_SDC: + Preprocessing image from smart sensor for viewfinder and + displaying it on synchronous display with SDC use case. + If SDC BG is selected, Rotation will not be supported. + CSI -> IC (PRP VF) -> MEM + MEM -> IC (ROT) -> MEM + MEM -> SDC (FG/BG) + +config MXC_IPU_PRP_ENC + tristate "Pre-processor Encoder library" + depends on VIDEO_MXC_IPU_CAMERA + default y + ---help--- + Use case PRP_ENC: + Preprocessing image from smart sensor for encoder. + CSI -> IC (PRP ENC) -> MEM + +config MXC_IPU_CSI_ENC + tristate "IPU CSI Encoder library" + depends on VIDEO_MXC_IPU_CAMERA + default y + ---help--- + Use case IPU_CSI_ENC: + Get raw image with CSI from smart sensor for encoder. + CSI -> MEM +endmenu + +endif diff --git a/drivers/media/video/mxc/capture/Makefile b/drivers/media/video/mxc/capture/Makefile new file mode 100644 index 000000000000..fda685d63d7b --- /dev/null +++ b/drivers/media/video/mxc/capture/Makefile @@ -0,0 +1,44 @@ +ifeq ($(CONFIG_VIDEO_MXC_IPU_CAMERA),y) + obj-$(CONFIG_VIDEO_MXC_CAMERA) += mxc_v4l2_capture.o + obj-$(CONFIG_MXC_IPU_PRP_VF_SDC) += ipu_prp_vf_sdc.o ipu_prp_vf_sdc_bg.o + obj-$(CONFIG_MXC_IPU_PRP_ENC) += ipu_prp_enc.o ipu_still.o + obj-$(CONFIG_MXC_IPU_CSI_ENC) += ipu_csi_enc.o ipu_still.o +endif + +obj-$(CONFIG_VIDEO_MXC_CSI_CAMERA) += fsl_csi.o csi_v4l2_capture.o + +mx27_capture-objs := mx27_prphw.o mx27_prpsw.o emma_v4l2_capture.o +obj-$(CONFIG_VIDEO_MXC_EMMA_CAMERA) += mx27_csi.o mx27_capture.o + +mc521da_camera-objs := mc521da.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_MC521DA) += mc521da_camera.o + +emma_mt9v111_camera-objs := emma_mt9v111.o sensor_clock.o +obj-$(CONFIG_MXC_EMMA_CAMERA_MICRON111) += emma_mt9v111_camera.o + +mt9v111_camera-objs := mt9v111.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_MICRON111) += mt9v111_camera.o + +hv7161_camera-objs := hv7161.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_HV7161) += hv7161_camera.o + +s5k3aaex_camera-objs := s5k3aaex.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_S5K3AAEX) += s5k3aaex_camera.o + +emma_ov2640_camera-objs := emma_ov2640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV2640_EMMA) += emma_ov2640_camera.o + +ov2640_camera-objs := ov2640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV2640) += ov2640_camera.o + +ov3640_camera-objs := ov3640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV3640) += ov3640_camera.o + +ov5640_camera-objs := ov5640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV5640) += ov5640_camera.o + +ov5642_camera-objs := ov5642.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV5642) += ov5642_camera.o + +adv7180_tvin-objs := adv7180.o +obj-$(CONFIG_MXC_TVIN_ADV7180) += adv7180_tvin.o diff --git a/drivers/media/video/mxc/capture/adv7180.c b/drivers/media/video/mxc/capture/adv7180.c new file mode 100644 index 000000000000..3e811d48f097 --- /dev/null +++ b/drivers/media/video/mxc/capture/adv7180.c @@ -0,0 +1,1024 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file adv7180.c + * + * @brief Analog Device ADV7180 video decoder functions + * + * @ingroup Camera + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxc_v4l2_capture.h" + +static struct regulator *dvddio_regulator; +static struct regulator *dvdd_regulator; +static struct regulator *avdd_regulator; +static struct regulator *pvdd_regulator; +static struct mxc_tvin_platform_data *tvin_plat; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +static int adv7180_probe(struct i2c_client *adapter, + const struct i2c_device_id *id); +static int adv7180_detach(struct i2c_client *client); + +static const struct i2c_device_id adv7180_id[] = { + {"adv7180", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, adv7180_id); + +static struct i2c_driver adv7180_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "adv7180", + }, + .probe = adv7180_probe, + .remove = adv7180_detach, + .id_table = adv7180_id, +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + v4l2_std_id std_id; +} adv7180_data; + +/*! List of input video formats supported. The video formats is corresponding + * with v4l2 id in video_fmt_t + */ +typedef enum { + ADV7180_NTSC = 0, /*!< Locked on (M) NTSC video signal. */ + ADV7180_PAL, /*!< (B, G, H, I, N)PAL video signal. */ + ADV7180_NOT_LOCKED, /*!< Not locked on a signal. */ +} video_fmt_idx; + +/*! Number of video standards supported (including 'not locked' signal). */ +#define ADV7180_STD_MAX (ADV7180_PAL + 1) + +/*! Video format structure. */ +typedef struct { + int v4l2_id; /*!< Video for linux ID. */ + char name[16]; /*!< Name (e.g., "NTSC", "PAL", etc.) */ + u16 raw_width; /*!< Raw width. */ + u16 raw_height; /*!< Raw height. */ + u16 active_width; /*!< Active width. */ + u16 active_height; /*!< Active height. */ +} video_fmt_t; + +/*! Description of video formats supported. + * + * PAL: raw=720x625, active=720x576. + * NTSC: raw=720x525, active=720x480. + */ +static video_fmt_t video_fmts[] = { + { /*! NTSC */ + .v4l2_id = V4L2_STD_NTSC, + .name = "NTSC", + .raw_width = 720, /* SENS_FRM_WIDTH */ + .raw_height = 525, /* SENS_FRM_HEIGHT */ + .active_width = 720, /* ACT_FRM_WIDTH plus 1 */ + .active_height = 480, /* ACT_FRM_WIDTH plus 1 */ + }, + { /*! (B, G, H, I, N) PAL */ + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 576, + }, + { /*! Unlocked standard */ + .v4l2_id = V4L2_STD_ALL, + .name = "Autodetect", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 576, + }, +}; + +/*!* Standard index of ADV7180. */ +static video_fmt_idx video_idx = ADV7180_PAL; + +/*! @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * and variables that were defined above. + */ +static DECLARE_MUTEX(mutex); + +#define IF_NAME "adv7180" +#define ADV7180_INPUT_CTL 0x00 /* Input Control */ +#define ADV7180_STATUS_1 0x10 /* Status #1 */ +#define ADV7180_BRIGHTNESS 0x0a /* Brightness */ +#define ADV7180_IDENT 0x11 /* IDENT */ +#define ADV7180_VSYNC_FIELD_CTL_1 0x31 /* VSYNC Field Control #1 */ +#define ADV7180_MANUAL_WIN_CTL 0x3d /* Manual Window Control */ +#define ADV7180_SD_SATURATION_CB 0xe3 /* SD Saturation Cb */ +#define ADV7180_SD_SATURATION_CR 0xe4 /* SD Saturation Cr */ +#define ADV7180_PWR_MNG 0x0f /* Power Management */ + +/* supported controls */ +/* This hasn't been fully implemented yet. + * This is how it should work, though. */ +static struct v4l2_queryctrl adv7180_qctrl[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, /* check this value */ + .maximum = 255, /* check this value */ + .step = 1, /* check this value */ + .default_value = 127, /* check this value */ + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, /* check this value */ + .maximum = 255, /* check this value */ + .step = 0x1, /* check this value */ + .default_value = 127, /* check this value */ + .flags = 0, + } +}; + +/*********************************************************************** + * I2C transfert. + ***********************************************************************/ + +/*! Read one register from a ADV7180 i2c slave device. + * + * @param *reg register in the device we wish to access. + * + * @return 0 if success, an error code otherwise. + */ +static inline int adv7180_read(u8 reg) +{ + int val; + val = i2c_smbus_read_byte_data(adv7180_data.i2c_client, reg); + if (val < 0) { + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:read reg error: reg=%2x \n", __func__, reg); + return -1; + } + return val; +} + +/*! Write one register of a ADV7180 i2c slave device. + * + * @param *reg register in the device we wish to access. + * + * @return 0 if success, an error code otherwise. + */ +static int adv7180_write_reg(u8 reg, u8 val) +{ + if (i2c_smbus_write_byte_data(adv7180_data.i2c_client, reg, val) < 0) { + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:write reg error:reg=%2x,val=%2x\n", __func__, + reg, val); + return -1; + } + return 0; +} + +/*********************************************************************** + * mxc_v4l2_capture interface. + ***********************************************************************/ + +/*! + * Return attributes of current video standard. + * Since this device autodetects the current standard, this function also + * sets the values that need to be changed if the standard changes. + * There is no set std equivalent function. + * + * @return None. + */ +static void adv7180_get_std(v4l2_std_id *std) +{ + int tmp; + int idx; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_get_std\n"); + + /* Make sure power on */ + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + /* Read the AD_RESULT to get the detect output video standard */ + tmp = adv7180_read(ADV7180_STATUS_1) & 0x70; + + down(&mutex); + if (tmp == 0x40) { + /* PAL */ + *std = V4L2_STD_PAL; + idx = ADV7180_PAL; + } else if (tmp == 0) { + /*NTSC*/ + *std = V4L2_STD_NTSC; + idx = ADV7180_NTSC; + } else { + *std = V4L2_STD_ALL; + idx = ADV7180_NOT_LOCKED; + dev_dbg(&adv7180_data.i2c_client->dev, + "Got invalid video standard! \n"); + } + up(&mutex); + + /* This assumes autodetect which this device uses. */ + if (*std != adv7180_data.std_id) { + video_idx = idx; + adv7180_data.std_id = *std; + adv7180_data.pix.width = video_fmts[video_idx].raw_width; + adv7180_data.pix.height = video_fmts[video_idx].raw_height; + } +} + +/*********************************************************************** + * IOCTL Functions from v4l2_int_ioctl_desc. + ***********************************************************************/ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + /* Initialize structure to 0s then set any non-0 values. */ + memset(p, 0, sizeof(*p)); + p->if_type = V4L2_IF_TYPE_BT656; /* This is the only possibility. */ + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.nobt_hs_inv = 1; + + /* ADV7180 has a dedicated clock so no clock settings needed. */ + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on open, close, suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_power\n"); + + if (on && !sensor->on) { + gpio_sensor_active(); + + /* Make sure pwoer on */ + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + if (adv7180_write_reg(ADV7180_PWR_MNG, 0) != 0) + return -EIO; + } else if (!on && sensor->on) { + if (adv7180_write_reg(ADV7180_PWR_MNG, 0x24) != 0) + return -EIO; + gpio_sensor_inactive(); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_parm\n"); + + switch (a->type) { + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + break; + + default: + pr_debug("ioctl_g_parm:type is unknown %d\n", a->type); + break; + } + + return 0; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + * + * This driver cannot change these settings. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_parm\n"); + + switch (a->type) { + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + break; + } + + return 0; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_fmt_cap\n"); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" Returning size of %dx%d\n", + sensor->pix.width, sensor->pix.height); + f->fmt.pix = sensor->pix; + break; + + case V4L2_BUF_TYPE_PRIVATE: { + v4l2_std_id std; + adv7180_get_std(&std); + f->fmt.pix.pixelformat = (u32)std; + } + break; + + default: + f->fmt.pix = sensor->pix; + break; + } + + return 0; +} + +/*! + * ioctl_queryctrl - V4L2 sensor interface handler for VIDIOC_QUERYCTRL ioctl + * @s: pointer to standard V4L2 device structure + * @qc: standard V4L2 VIDIOC_QUERYCTRL ioctl structure + * + * If the requested control is supported, returns the control information + * from the video_control[] array. Otherwise, returns -EINVAL if the + * control is not supported. + */ +static int ioctl_queryctrl(struct v4l2_int_device *s, + struct v4l2_queryctrl *qc) +{ + int i; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_queryctrl\n"); + + for (i = 0; i < ARRAY_SIZE(adv7180_qctrl); i++) + if (qc->id && qc->id == adv7180_qctrl[i].id) { + memcpy(qc, &(adv7180_qctrl[i]), + sizeof(*qc)); + return 0; + } + + return -EINVAL; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_ctrl\n"); + + /* Make sure power on */ + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BRIGHTNESS\n"); + adv7180_data.brightness = adv7180_read(ADV7180_BRIGHTNESS); + vc->value = adv7180_data.brightness; + break; + case V4L2_CID_CONTRAST: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_CONTRAST\n"); + vc->value = adv7180_data.contrast; + break; + case V4L2_CID_SATURATION: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_SATURATION\n"); + adv7180_data.saturation = adv7180_read(ADV7180_SD_SATURATION_CB); + vc->value = adv7180_data.saturation; + break; + case V4L2_CID_HUE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HUE\n"); + vc->value = adv7180_data.hue; + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_RED_BALANCE\n"); + vc->value = adv7180_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BLUE_BALANCE\n"); + vc->value = adv7180_data.blue; + break; + case V4L2_CID_GAMMA: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_EXPOSURE\n"); + vc->value = adv7180_data.ae_mode; + break; + case V4L2_CID_AUTOGAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_VFLIP\n"); + break; + default: + dev_dbg(&adv7180_data.i2c_client->dev, + " Default case\n"); + vc->value = 0; + ret = -EPERM; + break; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + u8 tmp; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_ctrl\n"); + + /* Make sure power on */ + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BRIGHTNESS\n"); + tmp = vc->value; + adv7180_write_reg(ADV7180_BRIGHTNESS, tmp); + adv7180_data.brightness = vc->value; + break; + case V4L2_CID_CONTRAST: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_SATURATION\n"); + tmp = vc->value; + adv7180_write_reg(ADV7180_SD_SATURATION_CB, tmp); + adv7180_write_reg(ADV7180_SD_SATURATION_CR, tmp); + adv7180_data.saturation = vc->value; + break; + case V4L2_CID_HUE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_EXPOSURE\n"); + break; + case V4L2_CID_AUTOGAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_VFLIP\n"); + break; + default: + dev_dbg(&adv7180_data.i2c_client->dev, + " Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_g_chip_ident - V4L2 sensor interface handler for + * VIDIOC_DBG_G_CHIP_IDENT ioctl + * @s: pointer to standard V4L2 device structure + * @id: pointer to int + * + * Return 0. + */ +static int ioctl_g_chip_ident(struct v4l2_int_device *s, int *id) +{ + ((struct v4l2_dbg_chip_ident *)id)->match.type = + V4L2_CHIP_MATCH_I2C_DRIVER; + strcpy(((struct v4l2_dbg_chip_ident *)id)->match.name, + "adv7180_decoder"); + ((struct v4l2_dbg_chip_ident *)id)->ident = V4L2_IDENT_ADV7180; + + return 0; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_init\n"); + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_dev_init\n"); + return 0; +} + +/*! + * This structure defines all the ioctls for this module. + */ +static struct v4l2_int_ioctl_desc adv7180_ioctl_desc[] = { + + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func*)ioctl_dev_init}, + + /*! + * Delinitialise the dev. at slave detach. + * The complement of ioctl_dev_init. + */ +/* {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func *)ioctl_dev_exit}, */ + + {vidioc_int_s_power_num, (v4l2_int_ioctl_func*)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func*)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func*)ioctl_init}, + + /*! + * VIDIOC_ENUM_FMT ioctl for the CAPTURE buffer type. + */ +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, */ + + /*! + * VIDIOC_TRY_FMT ioctl for the CAPTURE buffer type. + * This ioctl is used to negotiate the image capture size and + * pixel format without actually making it take effect. + */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func*)ioctl_g_fmt_cap}, + + /*! + * If the requested format is supported, configures the HW to use that + * format, returns error code if format not supported or HW can't be + * correctly configured. + */ +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func*)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func*)ioctl_s_parm}, + {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func*)ioctl_queryctrl}, + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func*)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func*)ioctl_s_ctrl}, + {vidioc_int_g_chip_ident_num, + (v4l2_int_ioctl_func *)ioctl_g_chip_ident}, +}; + +static struct v4l2_int_slave adv7180_slave = { + .ioctls = adv7180_ioctl_desc, + .num_ioctls = ARRAY_SIZE(adv7180_ioctl_desc), +}; + +static struct v4l2_int_device adv7180_int_device = { + .module = THIS_MODULE, + .name = "adv7180", + .type = v4l2_int_type_slave, + .u = { + .slave = &adv7180_slave, + }, +}; + + +/*********************************************************************** + * I2C client and driver. + ***********************************************************************/ + +/*! ADV7180 Reset function. + * + * @return None. + */ +static void adv7180_hard_reset(bool cvbs) +{ + dev_dbg(&adv7180_data.i2c_client->dev, + "In adv7180:adv7180_hard_reset\n"); + + if (cvbs) { + /* Set CVBS input on AIN1 */ + adv7180_write_reg(ADV7180_INPUT_CTL, 0x00); + } else { + /* + * Set YPbPr input on AIN1,4,5 and normal + * operations(autodection of all stds). + */ + adv7180_write_reg(ADV7180_INPUT_CTL, 0x09); + } + + /* Datasheet recommends */ + adv7180_write_reg(ADV7180_VSYNC_FIELD_CTL_1, 0x02); + adv7180_write_reg(ADV7180_MANUAL_WIN_CTL, 0xa2); +} + +/*! ADV7180 I2C attach function. + * + * @param *adapter struct i2c_adapter *. + * + * @return Error code indicating success or failure. + */ + +/*! + * ADV7180 I2C probe function. + * Function set in i2c_driver struct. + * Called by insmod. + * + * @param *adapter I2C adapter descriptor. + * + * @return Error code indicating success or failure. + */ +static int adv7180_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rev_id; + int ret = 0; + tvin_plat = client->dev.platform_data; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_probe\n"); + + if (tvin_plat->dvddio_reg) { + dvddio_regulator = + regulator_get(&client->dev, tvin_plat->dvddio_reg); + if (!IS_ERR_VALUE((unsigned long)dvddio_regulator)) { + regulator_set_voltage(dvddio_regulator, 3300000, 3300000); + if (regulator_enable(dvddio_regulator) != 0) + return -ENODEV; + } + } + + if (tvin_plat->dvdd_reg) { + dvdd_regulator = + regulator_get(&client->dev, tvin_plat->dvdd_reg); + if (!IS_ERR_VALUE((unsigned long)dvdd_regulator)) { + regulator_set_voltage(dvdd_regulator, 1800000, 1800000); + if (regulator_enable(dvdd_regulator) != 0) + return -ENODEV; + } + } + + if (tvin_plat->avdd_reg) { + avdd_regulator = + regulator_get(&client->dev, tvin_plat->avdd_reg); + if (!IS_ERR_VALUE((unsigned long)avdd_regulator)) { + regulator_set_voltage(avdd_regulator, 1800000, 1800000); + if (regulator_enable(avdd_regulator) != 0) + return -ENODEV; + } + } + + if (tvin_plat->pvdd_reg) { + pvdd_regulator = + regulator_get(&client->dev, tvin_plat->pvdd_reg); + if (!IS_ERR_VALUE((unsigned long)pvdd_regulator)) { + regulator_set_voltage(pvdd_regulator, 1800000, 1800000); + if (regulator_enable(pvdd_regulator) != 0) + return -ENODEV; + } + } + + + if (tvin_plat->reset) + tvin_plat->reset(); + + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + msleep(1); + + /* Set initial values for the sensor struct. */ + memset(&adv7180_data, 0, sizeof(adv7180_data)); + adv7180_data.i2c_client = client; + adv7180_data.streamcap.timeperframe.denominator = 30; + adv7180_data.streamcap.timeperframe.numerator = 1; + adv7180_data.std_id = V4L2_STD_ALL; + video_idx = ADV7180_NOT_LOCKED; + adv7180_data.pix.width = video_fmts[video_idx].raw_width; + adv7180_data.pix.height = video_fmts[video_idx].raw_height; + adv7180_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; /* YUV422 */ + adv7180_data.pix.priv = 1; /* 1 is used to indicate TV in */ + adv7180_data.on = true; + + gpio_sensor_active(); + + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:adv7180 probe i2c address is 0x%02X \n", + __func__, adv7180_data.i2c_client->addr); + + /*! Read the revision ID of the tvin chip */ + rev_id = adv7180_read(ADV7180_IDENT); + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:Analog Device adv7%2X0 detected! \n", __func__, + rev_id); + + /*! ADV7180 initialization. */ + adv7180_hard_reset(tvin_plat->cvbs); + + pr_debug(" type is %d (expect %d)\n", + adv7180_int_device.type, v4l2_int_type_slave); + pr_debug(" num ioctls is %d\n", + adv7180_int_device.u.slave->num_ioctls); + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the mt9v111_data structure here.*/ + adv7180_int_device.priv = &adv7180_data; + ret = v4l2_int_device_register(&adv7180_int_device); + + return ret; +} + +/*! + * ADV7180 I2C detach function. + * Called on rmmod. + * + * @param *client struct i2c_client*. + * + * @return Error code indicating success or failure. + */ +static int adv7180_detach(struct i2c_client *client) +{ + struct mxc_tvin_platform_data *plat_data = client->dev.platform_data; + + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:Removing %s video decoder @ 0x%02X from adapter %s \n", + __func__, IF_NAME, client->addr << 1, client->adapter->name); + + if (plat_data->pwdn) + plat_data->pwdn(1); + + if (dvddio_regulator) { + regulator_disable(dvddio_regulator); + regulator_put(dvddio_regulator); + } + + if (dvdd_regulator) { + regulator_disable(dvdd_regulator); + regulator_put(dvdd_regulator); + } + + if (avdd_regulator) { + regulator_disable(avdd_regulator); + regulator_put(avdd_regulator); + } + + if (pvdd_regulator) { + regulator_disable(pvdd_regulator); + regulator_put(pvdd_regulator); + } + + v4l2_int_device_unregister(&adv7180_int_device); + + return 0; +} + +/*! + * ADV7180 init function. + * Called on insmod. + * + * @return Error code indicating success or failure. + */ +static __init int adv7180_init(void) +{ + u8 err = 0; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_init\n"); + + /* Tells the i2c driver what functions to call for this driver. */ + err = i2c_add_driver(&adv7180_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * ADV7180 cleanup function. + * Called on rmmod. + * + * @return Error code indicating success or failure. + */ +static void __exit adv7180_clean(void) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_clean\n"); + i2c_del_driver(&adv7180_i2c_driver); + gpio_sensor_inactive(); +} + +module_init(adv7180_init); +module_exit(adv7180_clean); + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Anolog Device ADV7180 video decoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/csi_v4l2_capture.c b/drivers/media/video/mxc/capture/csi_v4l2_capture.c new file mode 100644 index 000000000000..eb824ddf5eb3 --- /dev/null +++ b/drivers/media/video/mxc/capture/csi_v4l2_capture.c @@ -0,0 +1,1466 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/capture/csi_v4l2_capture.c + * This file is derived from mxc_v4l2_capture.c + * + * @brief MX25 Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxc_v4l2_capture.h" +#include "fsl_csi.h" + +static int video_nr = -1; +static cam_data *g_cam; + +static int csi_v4l2_master_attach(struct v4l2_int_device *slave); +static void csi_v4l2_master_detach(struct v4l2_int_device *slave); +static u8 camera_power(cam_data *cam, bool cameraOn); + +/*! Information about this driver. */ +static struct v4l2_int_master csi_v4l2_master = { + .attach = csi_v4l2_master_attach, + .detach = csi_v4l2_master_detach, +}; + +static struct v4l2_int_device csi_v4l2_int_device = { + .module = THIS_MODULE, + .name = "csi_v4l2_cap", + .type = v4l2_int_type_master, + .u = { + .master = &csi_v4l2_master, + }, +}; + +/*! + * Camera V4l2 callback function. + * + * @param mask u32 + * @param dev void device structure + * + * @return none + */ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + cam_data *cam; + + cam = (cam_data *) dev; + if (cam == NULL) + return; + + if (list_empty(&cam->working_q)) { + pr_err("ERROR: v4l2 capture: %s: " + "working queue empty\n", __func__); + return; + } + + done_frame = + list_entry(cam->working_q.next, struct mxc_v4l_frame, queue); + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + if (list_empty(&cam->ready_q)) { + cam->skip_frame++; + } else { + ready_frame = list_entry(cam->ready_q.next, + struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, &cam->working_q); + + if (cam->ping_pong_csi == 1) { + __raw_writel(cam->frame[ready_frame->index]. + paddress, CSI_CSIDMASA_FB1); + } else { + __raw_writel(cam->frame[ready_frame->index]. + paddress, CSI_CSIDMASA_FB2); + } + } + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else { + pr_err("ERROR: v4l2 capture: %s: " + "buffer not queued\n", __func__); + } + + return; +} + +/*! + * Make csi ready for capture image. + * + * @param cam structure cam_data * + * + * @return status 0 success + */ +static int csi_cap_image(cam_data *cam) +{ + unsigned int value; + + value = __raw_readl(CSI_CSICR3); + __raw_writel(value | BIT_DMA_REFLASH_RFF | BIT_FRMCNT_RST, CSI_CSICR3); + value = __raw_readl(CSI_CSISR); + __raw_writel(value, CSI_CSISR); + + return 0; +} + +/*************************************************************************** + * Functions for handling Frame buffers. + **************************************************************************/ + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int csi_free_frame_buf(cam_data *cam) +{ + int i; + + pr_debug("MVC: In %s\n", __func__); + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data * + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int csi_allocate_frame_buf(cam_data *cam, int count) +{ + int i; + + pr_debug("In MVC:%s- size=%d\n", + __func__, cam->v2f.fmt.pix.sizeimage); + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = dma_alloc_coherent(0, PAGE_ALIGN + (cam->v2f.fmt. + pix.sizeimage), + &cam->frame[i]. + paddress, + GFP_DMA | + GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + pr_err("ERROR: v4l2 capture: " + "%s failed.\n", __func__); + csi_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = PAGE_ALIGN(cam->v2f.fmt. + pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void csi_free_frames(cam_data *cam) +{ + int i; + + pr_debug("In MVC: %s\n", __func__); + + for (i = 0; i < FRAME_NUM; i++) + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + return; +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int csi_v4l2_buffer_status(cam_data *cam, struct v4l2_buffer *buf) +{ + pr_debug("In MVC: %s\n", __func__); + + if (buf->index < 0 || buf->index >= FRAME_NUM) { + pr_err("ERROR: v4l2 capture: %s buffers " + "not allocated\n", __func__); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + + return 0; +} + +/*! + * Indicates whether the palette is supported. + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_UYVY or V4L2_PIX_FMT_YUV420 + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return (palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_UYVY) || (palette == V4L2_PIX_FMT_YUV420); +} + +/*! + * Start stream I/O + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int csi_streamon(cam_data *cam) +{ + struct mxc_v4l_frame *frame; + + pr_debug("In MVC: %s\n", __func__); + + if (NULL == cam) { + pr_err("ERROR: v4l2 capture: %s cam parameter is NULL\n", + __func__); + return -1; + } + + /* move the frame from readyq to workingq */ + if (list_empty(&cam->ready_q)) { + pr_err("ERROR: v4l2 capture: %s: " + "ready_q queue empty\n", __func__); + return -1; + } + frame = list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + __raw_writel(cam->frame[frame->index].paddress, CSI_CSIDMASA_FB1); + + if (list_empty(&cam->ready_q)) { + pr_err("ERROR: v4l2 capture: %s: " + "ready_q queue empty\n", __func__); + return -1; + } + frame = list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + __raw_writel(cam->frame[frame->index].paddress, CSI_CSIDMASA_FB2); + + cam->capture_pid = current->pid; + cam->capture_on = true; + csi_cap_image(cam); + csi_enable_int(1); + + return 0; +} + +/*! + * Stop stream I/O + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int csi_streamoff(cam_data *cam) +{ + unsigned int cr3; + + pr_debug("In MVC: %s\n", __func__); + + if (cam->capture_on == false) + return 0; + + csi_disable_int(); + cam->capture_on = false; + + /* set CSI_CSIDMASA_FB1 and CSI_CSIDMASA_FB2 to default value */ + __raw_writel(0, CSI_CSIDMASA_FB1); + __raw_writel(0, CSI_CSIDMASA_FB2); + cr3 = __raw_readl(CSI_CSICR3); + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + + csi_free_frames(cam); + csi_free_frame_buf(cam); + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data *cam) +{ + unsigned long fb_addr = (unsigned long)cam->v4l2_fb.base; + + __raw_writel(fb_addr, CSI_CSIDMASA_FB1); + __raw_writel(fb_addr, CSI_CSIDMASA_FB2); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + + csi_enable_int(0); + + return 0; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data *cam) +{ + csi_disable_int(); + + /* set CSI_CSIDMASA_FB1 and CSI_CSIDMASA_FB2 to default value */ + __raw_writel(0, CSI_CSIDMASA_FB1); + __raw_writel(0, CSI_CSIDMASA_FB2); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + + return 0; +} + +/*************************************************************************** + * VIDIOC Functions. + **************************************************************************/ + +/*! + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_g_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + f->fmt.pix = cam->v2f.fmt.pix; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + f->fmt.win = cam->win; + break; + default: + pr_debug(" type is invalid\n"); + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return retval; +} + +/*! + * V4L2 - csi_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_s_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + int *width, *height; + + pr_debug("In MVC: %s\n", __func__); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_err("ERROR: v4l2 capture: %s: format " + "not supported\n", __func__); + return -EINVAL; + } + + /* Handle case where size requested is larger than cuurent + * camera setting. */ + if ((f->fmt.pix.width > cam->crop_bounds.width) + || (f->fmt.pix.height > cam->crop_bounds.height)) { + /* Need the logic here, calling vidioc_s_param if + * camera can change. */ + pr_debug("csi_v4l2_s_fmt size changed\n"); + } + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &f->fmt.pix.width; + width = &f->fmt.pix.height; + } else { + width = &f->fmt.pix.width; + height = &f->fmt.pix.height; + } + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + pr_err("ERROR: v4l2 capture: width exceeds limit " + "resize to %d.\n", *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", *height); + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_RGB565: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + csi_set_16bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_UYVY: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + csi_set_16bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + csi_set_12bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_NV12: + default: + pr_debug(" case not supported\n"); + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) + f->fmt.pix.bytesperline = bytesperline; + else + bytesperline = f->fmt.pix.bytesperline; + + if (f->fmt.pix.sizeimage < size) + f->fmt.pix.sizeimage = size; + else + size = f->fmt.pix.sizeimage; + + cam->v2f.fmt.pix = f->fmt.pix; + + if (cam->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&cam->offset, + (void *)cam->v2f.fmt.pix.priv, + sizeof(cam->offset))) { + retval = -EFAULT; + break; + } + } + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return retval; +} + +/*! + * V4L2 - csi_v4l2_s_param function + * Allows setting of capturemode and frame rate. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct v4l2_streamparm currentparm; + int err = 0; + + pr_debug("In %s\n", __func__); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err(KERN_ERR "%s invalid type\n", __func__); + return -EINVAL; + } + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + currentparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* First check that this device can support the changes requested. */ + err = vidioc_int_g_parm(cam->sensor, ¤tparm); + if (err) { + pr_err("%s: vidioc_int_g_parm returned an error %d\n", + __func__, err); + goto exit; + } + + pr_debug(" Current capabilities are %x\n", + currentparm.parm.capture.capability); + pr_debug(" Current capturemode is %d change to %d\n", + currentparm.parm.capture.capturemode, + parm->parm.capture.capturemode); + pr_debug(" Current framerate is %d change to %d\n", + currentparm.parm.capture.timeperframe.denominator, + parm->parm.capture.timeperframe.denominator); + + err = vidioc_int_s_parm(cam->sensor, parm); + if (err) { + pr_err("%s: vidioc_int_s_parm returned an error %d\n", + __func__, err); + goto exit; + } + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pr_debug(" g_fmt_cap returns widthxheight of input as %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + +exit: + return err; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int csi_v4l_dqueue(cam_data *cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + unsigned long lock_flags; + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue timeout " + "enc_counter %x\n", cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue() " + "interrupt received\n"); + return -ERESTARTSYS; + } + + spin_lock_irqsave(&cam->dqueue_int_lock, lock_flags); + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not queued.\n"); + retval = -EINVAL; + } + + spin_unlock_irqrestore(&cam->dqueue_int_lock, lock_flags); + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + buf->m = cam->frame[frame->index].buffer.m; + + return retval; +} + +/*! + * V4L interface - open function + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int csi_v4l_open(struct file *file) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int err = 0; + + pr_debug(" device name is %s\n", dev->name); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + down(&cam->busy_lock); + err = 0; + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_init(cam->sensor); + } + + file->private_data = dev; + +oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param file struct file * + * + * @return 0 success + */ +static int csi_v4l_close(struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:%s\n", __func__); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + file->private_data = NULL; + csi_enable_mclk(CSI_MCLK_I2C, false, false); + } + + return err; +} + +/* + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t csi_v4l_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int err = 0; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + if (cam->still_buf_vaddr == NULL) { + cam->still_buf_vaddr = dma_alloc_coherent(0, + PAGE_ALIGN + (cam->v2f.fmt. + pix.sizeimage), + &cam-> + still_buf[0], + GFP_DMA | GFP_KERNEL); + if (cam->still_buf_vaddr == NULL) { + pr_err("alloc dma memory failed\n"); + return -ENOMEM; + } + cam->still_counter = 0; + __raw_writel(cam->still_buf[0], CSI_CSIDMASA_FB2); + __raw_writel(cam->still_buf[0], CSI_CSIDMASA_FB1); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, + CSI_CSICR3); + __raw_writel(__raw_readl(CSI_CSISR), CSI_CSISR); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, + CSI_CSICR3); + csi_enable_int(1); + } + + wait_event_interruptible(cam->still_queue, cam->still_counter); + csi_disable_int(); + err = copy_to_user(buf, cam->still_buf_vaddr, + cam->v2f.fmt.pix.sizeimage); + + if (cam->still_buf_vaddr != NULL) { + dma_free_coherent(0, PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + cam->still_buf_vaddr, cam->still_buf[0]); + cam->still_buf[0] = 0; + cam->still_buf_vaddr = NULL; + } + + if (cam->overlay_on == true) + start_preview(cam); + + up(&cam->busy_lock); + if (err < 0) + return err; + + return cam->v2f.fmt.pix.sizeimage - err; +} + +/*! + * V4L interface - ioctl function + * + * @param file struct file* + * + * @param ioctlnr unsigned int + * + * @param arg void* + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static long csi_v4l_do_ioctl(struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retval = 0; + unsigned long lock_flags; + + pr_debug("In MVC: %s, %x\n", __func__, ioctlnr); + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT:{ + struct v4l2_format *gf = arg; + pr_debug(" case VIDIOC_G_FMT\n"); + retval = csi_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT:{ + struct v4l2_format *sf = arg; + pr_debug(" case VIDIOC_S_FMT\n"); + retval = csi_v4l2_s_fmt(cam, sf); + vidioc_int_s_fmt_cap(cam->sensor, sf); + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY:{ + int *on = arg; + pr_debug(" case VIDIOC_OVERLAY\n"); + if (*on) { + cam->overlay_on = true; + cam->overlay_pid = current->pid; + start_preview(cam); + } + if (!*on) { + stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF:{ + struct v4l2_framebuffer *fb = arg; + *fb = cam->v4l2_fb; + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF:{ + struct v4l2_framebuffer *fb = arg; + cam->v4l2_fb = *fb; + break; + } + + case VIDIOC_G_PARM:{ + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_G_PARM\n"); + vidioc_int_g_parm(cam->sensor, parm); + break; + } + + case VIDIOC_S_PARM:{ + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_S_PARM\n"); + retval = csi_v4l2_s_param(cam, parm); + break; + } + + case VIDIOC_QUERYCAP:{ + struct v4l2_capability *cap = arg; + pr_debug(" case VIDIOC_QUERYCAP\n"); + strcpy(cap->driver, "csi_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_VIDEO_OUTPUT_OVERLAY | V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + break; + } + + case VIDIOC_S_CROP: + pr_debug(" case not supported\n"); + break; + + case VIDIOC_REQBUFS: { + struct v4l2_requestbuffers *req = arg; + pr_debug(" case VIDIOC_REQBUFS\n"); + + if (req->count > FRAME_NUM) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "not enough buffers\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "wrong buffer type\n"); + retval = -EINVAL; + break; + } + + csi_streamoff(cam); + csi_free_frame_buf(cam); + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + retval = csi_allocate_frame_buf(cam, req->count); + break; + } + + case VIDIOC_QUERYBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QUERYBUF\n"); + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + retval = csi_v4l2_buffer_status(cam, buf); + break; + } + + case VIDIOC_QBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QBUF\n"); + + spin_lock_irqsave(&cam->queue_int_lock, lock_flags); + cam->frame[index].buffer.m.offset = buf->m.offset; + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= V4L2_BUF_FLAG_QUEUED; + if (cam->skip_frame > 0) { + list_add_tail(&cam->frame[index].queue, + &cam->working_q); + cam->skip_frame = 0; + + if (cam->ping_pong_csi == 1) { + __raw_writel(cam->frame[index].paddress, + CSI_CSIDMASA_FB1); + } else { + __raw_writel(cam->frame[index].paddress, + CSI_CSIDMASA_FB2); + } + } else { + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } + } else if (cam->frame[index].buffer.flags & + V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "buffer already queued\n"); + retval = -EINVAL; + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->queue_int_lock, lock_flags); + + break; + } + + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = arg; + pr_debug(" case VIDIOC_DQBUF\n"); + + retval = csi_v4l_dqueue(cam, buf); + + break; + } + + case VIDIOC_STREAMON: { + pr_debug(" case VIDIOC_STREAMON\n"); + retval = csi_streamon(cam); + break; + } + + case VIDIOC_STREAMOFF: { + pr_debug(" case VIDIOC_STREAMOFF\n"); + retval = csi_streamoff(cam); + break; + } + + case VIDIOC_S_CTRL: + case VIDIOC_G_STD: + case VIDIOC_G_OUTPUT: + case VIDIOC_S_OUTPUT: + case VIDIOC_ENUMSTD: + case VIDIOC_G_CROP: + case VIDIOC_CROPCAP: + case VIDIOC_S_STD: + case VIDIOC_G_CTRL: + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + case VIDIOC_ENUMOUTPUT: + default: + pr_debug(" case not supported\n"); + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static long csi_v4l_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(file, cmd, arg, csi_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int csi_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("%s\n", __func__); + pr_debug("\npgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_err("ERROR: v4l2 capture: %s : " + "remap_pfn_range failed\n", __func__); + res = -ENOBUFS; + goto csi_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + +csi_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * This structure defines the functions to be called in this driver. + */ +static struct v4l2_file_operations csi_v4l_fops = { + .owner = THIS_MODULE, + .open = csi_v4l_open, + .release = csi_v4l_close, + .read = csi_v4l_read, + .ioctl = csi_v4l_ioctl, + .mmap = csi_mmap, +}; + +static struct video_device csi_v4l_template = { + .name = "Mx25 Camera", + .fops = &csi_v4l_fops, + .release = video_device_release, +}; + +/*! + * This function can be used to release any platform data on closing. + */ +static void camera_platform_release(struct device *device) +{ +} + +/*! Device Definition for csi v4l2 device */ +static struct platform_device csi_v4l2_devices = { + .name = "csi_v4l2", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data *cam) +{ + pr_debug("In MVC: %s\n", __func__); + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = csi_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&csi_v4l2_devices.dev, (void *)cam); + cam->video_dev->minor = -1; + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->standard_autodetect = true; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_OVERLAY; + + cam->v2f.fmt.pix.sizeimage = 480 * 640 * 2; + cam->v2f.fmt.pix.bytesperline = 640 * 2; + cam->v2f.fmt.pix.width = 640; + cam->v2f.fmt.pix.height = 480; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + cam->still_counter = 0; + + cam->enc_callback = camera_callback; + csi_start_callback(cam); + init_waitqueue_head(&cam->power_queue); + spin_lock_init(&cam->queue_int_lock); + spin_lock_init(&cam->dqueue_int_lock); +} + +/*! + * camera_power function + * Turns Sensor power On/Off + * + * @param cam cam data struct + * @param cameraOn true to turn camera on, false to turn off power. + * + * @return status + */ +static u8 camera_power(cam_data *cam, bool cameraOn) +{ + pr_debug("In MVC: %s on=%d\n", __func__, cameraOn); + + if (cameraOn == true) { + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_s_power(cam->sensor, 1); + } else { + csi_enable_mclk(CSI_MCLK_I2C, false, false); + vidioc_int_s_power(cam->sensor, 0); + } + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int csi_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC: %s\n", __func__); + + if (cam == NULL) + return -1; + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + + camera_power(cam, false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int csi_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC: %s\n", __func__); + + if (cam == NULL) + return -1; + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + camera_power(cam, true); + + if (cam->overlay_on == true) + start_preview(cam); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver csi_v4l2_driver = { + .driver = { + .name = "csi_v4l2", + }, + .probe = NULL, + .remove = NULL, +#ifdef CONFIG_PM + .suspend = csi_v4l2_suspend, + .resume = csi_v4l2_resume, +#endif + .shutdown = NULL, +}; + +/*! + * Initializes the camera driver. + */ +static int csi_v4l2_master_attach(struct v4l2_int_device *slave) +{ + cam_data *cam = slave->u.slave->master->priv; + struct v4l2_format cam_fmt; + + pr_debug("In MVC: %s\n", __func__); + pr_debug(" slave.name = %s\n", slave->name); + pr_debug(" master.name = %s\n", slave->u.slave->master->name); + + cam->sensor = slave; + if (slave == NULL) { + pr_err("ERROR: v4l2 capture: slave parameter not valid.\n"); + return -1; + } + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_dev_init(slave); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* Used to detect TV in (type 1) vs. camera (type 0) */ + cam->device_type = cam_fmt.fmt.pix.priv; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return 0; +} + +/*! + * Disconnects the camera driver. + */ +static void csi_v4l2_master_detach(struct v4l2_int_device *slave) +{ + pr_debug("In MVC: %s\n", __func__); + + vidioc_int_dev_exit(slave); +} + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + + /* Register the device driver structure. */ + err = platform_driver_register(&csi_v4l2_driver); + if (err != 0) { + pr_err("ERROR: v4l2 capture:camera_init: " + "platform_driver_register failed.\n"); + return err; + } + + /* Create g_cam and initialize it. */ + g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL); + if (g_cam == NULL) { + pr_err("ERROR: v4l2 capture: failed to register camera\n"); + platform_driver_unregister(&csi_v4l2_driver); + return -1; + } + init_camera_struct(g_cam); + + /* Set up the v4l2 device and register it */ + csi_v4l2_int_device.priv = g_cam; + /* This function contains a bug that won't let this be rmmod'd. */ + v4l2_int_device_register(&csi_v4l2_int_device); + + /* Register the platform device */ + err = platform_device_register(&csi_v4l2_devices); + if (err != 0) { + pr_err("ERROR: v4l2 capture: camera_init: " + "platform_device_register failed.\n"); + platform_driver_unregister(&csi_v4l2_driver); + kfree(g_cam); + g_cam = NULL; + return err; + } + + /* register v4l video device */ + if (video_register_device(g_cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + platform_device_unregister(&csi_v4l2_devices); + platform_driver_unregister(&csi_v4l2_driver); + kfree(g_cam); + g_cam = NULL; + pr_err("ERROR: v4l2 capture: video_register_device failed\n"); + return -1; + } + pr_debug(" Video device registered: %s #%d\n", + g_cam->video_dev->name, g_cam->video_dev->minor); + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + */ +static void __exit camera_exit(void) +{ + pr_debug("In MVC: %s\n", __func__); + + if (g_cam->open_count) { + pr_err("ERROR: v4l2 capture:camera open " + "-- setting ops to NULL\n"); + } else { + pr_info("V4L2 freeing image input device\n"); + v4l2_int_device_unregister(&csi_v4l2_int_device); + csi_stop_callback(g_cam); + video_unregister_device(g_cam->video_dev); + platform_driver_unregister(&csi_v4l2_driver); + platform_device_unregister(&csi_v4l2_devices); + + kfree(g_cam); + g_cam = NULL; + } +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mx25 based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/fsl_csi.c b/drivers/media/video/mxc/capture/fsl_csi.c new file mode 100644 index 000000000000..dba35c4499e2 --- /dev/null +++ b/drivers/media/video/mxc/capture/fsl_csi.c @@ -0,0 +1,287 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_csi.c, this file is derived from mx27_csi.c + * + * @brief mx25 CMOS Sensor interface functions + * + * @ingroup CSI + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mxc_v4l2_capture.h" +#include "fsl_csi.h" + +static bool g_csi_mclk_on; +static csi_irq_callback_t g_callback; +static void *g_callback_data; +static struct clk csi_mclk; + +static irqreturn_t csi_irq_handler(int irq, void *data) +{ + cam_data *cam = (cam_data *) data; + unsigned long status = __raw_readl(CSI_CSISR); + unsigned long cr3 = __raw_readl(CSI_CSICR3); + unsigned int frame_count = (cr3 >> 16) & 0xFFFF; + + __raw_writel(status, CSI_CSISR); + + if (status & BIT_SOF_INT) { + /* reflash the embeded DMA controller */ + if (frame_count % 2 == 1) + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + } + + if (status & BIT_DMA_TSF_DONE_FB1) { + if (cam->capture_on) { + cam->ping_pong_csi = 1; + cam->enc_callback(0, cam); + } else { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + } + + if (status & BIT_DMA_TSF_DONE_FB2) { + if (cam->capture_on) { + cam->ping_pong_csi = 2; + cam->enc_callback(0, cam); + } else { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + } + + if (g_callback) + g_callback(g_callback_data, status); + + pr_debug("CSI status = 0x%08lX\n", status); + + return IRQ_HANDLED; +} + +static void csihw_reset_frame_count(void) +{ + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, CSI_CSICR3); +} + +static void csihw_reset(void) +{ + csihw_reset_frame_count(); + __raw_writel(CSICR1_RESET_VAL, CSI_CSICR1); + __raw_writel(CSICR2_RESET_VAL, CSI_CSICR2); + __raw_writel(CSICR3_RESET_VAL, CSI_CSICR3); +} + +/*! + * csi_init_interface + * Init csi interface + */ +void csi_init_interface(void) +{ + unsigned int val = 0; + unsigned int imag_para; + + val |= BIT_SOF_POL; + val |= BIT_REDGE; + val |= BIT_GCLK_MODE; + val |= BIT_HSYNC_POL; + val |= BIT_PACK_DIR; + val |= BIT_FCC; + val |= BIT_SWAP16_EN; + val |= 1 << SHIFT_MCLKDIV; + __raw_writel(val, CSI_CSICR1); + + imag_para = (640 << 16) | 960; + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + val = 0x1010; + val |= BIT_DMA_REFLASH_RFF; + __raw_writel(val, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_init_interface); + +/*! + * csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + csi_mclk_enable(); + if (wait == true) + msleep(10); + pr_debug("Enable csi clock from source %d\n", src); + g_csi_mclk_on = true; + } else { + csi_mclk_disable(); + pr_debug("Disable csi clock from source %d\n", src); + g_csi_mclk_on = false; + } + + return 0; +} +EXPORT_SYMBOL(csi_enable_mclk); + +/*! + * csi_read_mclk_flag + * + * @return gcsi_mclk_source + */ +int csi_read_mclk_flag(void) +{ + return 0; +} +EXPORT_SYMBOL(csi_read_mclk_flag); + +void csi_start_callback(void *data) +{ + cam_data *cam = (cam_data *) data; + + if (request_irq(MXC_INT_CSI, csi_irq_handler, 0, "csi", cam) < 0) + pr_debug("CSI error: irq request fail\n"); + +} +EXPORT_SYMBOL(csi_start_callback); + +void csi_stop_callback(void *data) +{ + cam_data *cam = (cam_data *) data; + + free_irq(MXC_INT_CSI, cam); +} +EXPORT_SYMBOL(csi_stop_callback); + +void csi_enable_int(int arg) +{ + unsigned long cr1 = __raw_readl(CSI_CSICR1); + + cr1 |= BIT_SOF_INTEN; + if (arg == 1) { + /* still capture needs DMA intterrupt */ + cr1 |= BIT_FB1_DMA_DONE_INTEN; + cr1 |= BIT_FB2_DMA_DONE_INTEN; + } + __raw_writel(cr1, CSI_CSICR1); +} +EXPORT_SYMBOL(csi_enable_int); + +void csi_disable_int(void) +{ + unsigned long cr1 = __raw_readl(CSI_CSICR1); + + cr1 &= ~BIT_SOF_INTEN; + cr1 &= ~BIT_FB1_DMA_DONE_INTEN; + cr1 &= ~BIT_FB2_DMA_DONE_INTEN; + __raw_writel(cr1, CSI_CSICR1); +} +EXPORT_SYMBOL(csi_disable_int); + +void csi_set_16bit_imagpara(int width, int height) +{ + int imag_para = 0; + unsigned long cr3 = __raw_readl(CSI_CSICR3); + + imag_para = (width << 16) | (height * 2); + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + /* reflash the embeded DMA controller */ + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_set_16bit_imagpara); + +void csi_set_12bit_imagpara(int width, int height) +{ + int imag_para = 0; + unsigned long cr3 = __raw_readl(CSI_CSICR3); + + imag_para = (width << 16) | (height * 3 / 2); + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + /* reflash the embeded DMA controller */ + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_set_12bit_imagpara); + +static void csi_mclk_recalc(struct clk *clk) +{ + u32 div; + unsigned long rate; + + div = (__raw_readl(CSI_CSICR1) & BIT_MCLKDIV) >> SHIFT_MCLKDIV; + if (div == 0) + div = 1; + else + div = div * 2; + + rate = clk_get_rate(clk->parent) / div; + clk_set_rate(clk, rate); +} + +void csi_mclk_enable(void) +{ + __raw_writel(__raw_readl(CSI_CSICR1) | BIT_MCLKEN, CSI_CSICR1); +} + +void csi_mclk_disable(void) +{ + __raw_writel(__raw_readl(CSI_CSICR1) & ~BIT_MCLKEN, CSI_CSICR1); +} + +int32_t __init csi_init_module(void) +{ + int ret = 0; + struct clk *per_clk; + + csihw_reset(); + csi_init_interface(); + + per_clk = clk_get(NULL, "csi_clk"); + if (IS_ERR(per_clk)) + return PTR_ERR(per_clk); + + clk_put(per_clk); + csi_mclk.parent = per_clk; + clk_enable(per_clk); + csi_mclk_recalc(&csi_mclk); + + return ret; +} + +void __exit csi_cleanup_module(void) +{ + clk_disable(&csi_mclk); +} + +module_init(csi_init_module); +module_exit(csi_cleanup_module); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("fsl CSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/fsl_csi.h b/drivers/media/video/mxc/capture/fsl_csi.h new file mode 100644 index 000000000000..00c389892224 --- /dev/null +++ b/drivers/media/video/mxc/capture/fsl_csi.h @@ -0,0 +1,198 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_csi.h + * + * @brief mx25 CMOS Sensor interface functions + * + * @ingroup CSI + */ + +#ifndef MX25_CSI_H +#define MX25_CSI_H + +#include +#include + +/* reset values */ +#define CSICR1_RESET_VAL 0x40000800 +#define CSICR2_RESET_VAL 0x0 +#define CSICR3_RESET_VAL 0x0 + +/* csi control reg 1 */ +#define BIT_SWAP16_EN (0x1 << 31) +#define BIT_EXT_VSYNC (0x1 << 30) +#define BIT_EOF_INT_EN (0x1 << 29) +#define BIT_PRP_IF_EN (0x1 << 28) +#define BIT_CCIR_MODE (0x1 << 27) +#define BIT_COF_INT_EN (0x1 << 26) +#define BIT_SF_OR_INTEN (0x1 << 25) +#define BIT_RF_OR_INTEN (0x1 << 24) +#define BIT_SFF_DMA_DONE_INTEN (0x1 << 22) +#define BIT_STATFF_INTEN (0x1 << 21) +#define BIT_FB2_DMA_DONE_INTEN (0x1 << 20) +#define BIT_FB1_DMA_DONE_INTEN (0x1 << 19) +#define BIT_RXFF_INTEN (0x1 << 18) +#define BIT_SOF_POL (0x1 << 17) +#define BIT_SOF_INTEN (0x1 << 16) +#define BIT_MCLKDIV (0xF << 12) +#define BIT_HSYNC_POL (0x1 << 11) +#define BIT_CCIR_EN (0x1 << 10) +#define BIT_MCLKEN (0x1 << 9) +#define BIT_FCC (0x1 << 8) +#define BIT_PACK_DIR (0x1 << 7) +#define BIT_CLR_STATFIFO (0x1 << 6) +#define BIT_CLR_RXFIFO (0x1 << 5) +#define BIT_GCLK_MODE (0x1 << 4) +#define BIT_INV_DATA (0x1 << 3) +#define BIT_INV_PCLK (0x1 << 2) +#define BIT_REDGE (0x1 << 1) +#define BIT_PIXEL_BIT (0x1 << 0) + +#define SHIFT_MCLKDIV 12 + +/* control reg 3 */ +#define BIT_FRMCNT (0xFFFF << 16) +#define BIT_FRMCNT_RST (0x1 << 15) +#define BIT_DMA_REFLASH_RFF (0x1 << 14) +#define BIT_DMA_REFLASH_SFF (0x1 << 13) +#define BIT_DMA_REQ_EN_RFF (0x1 << 12) +#define BIT_DMA_REQ_EN_SFF (0x1 << 11) +#define BIT_STATFF_LEVEL (0x7 << 8) +#define BIT_HRESP_ERR_EN (0x1 << 7) +#define BIT_RXFF_LEVEL (0x7 << 4) +#define BIT_TWO_8BIT_SENSOR (0x1 << 3) +#define BIT_ZERO_PACK_EN (0x1 << 2) +#define BIT_ECC_INT_EN (0x1 << 1) +#define BIT_ECC_AUTO_EN (0x1 << 0) + +#define SHIFT_FRMCNT 16 + +/* csi status reg */ +#define BIT_SFF_OR_INT (0x1 << 25) +#define BIT_RFF_OR_INT (0x1 << 24) +#define BIT_DMA_TSF_DONE_SFF (0x1 << 22) +#define BIT_STATFF_INT (0x1 << 21) +#define BIT_DMA_TSF_DONE_FB2 (0x1 << 20) +#define BIT_DMA_TSF_DONE_FB1 (0x1 << 19) +#define BIT_RXFF_INT (0x1 << 18) +#define BIT_EOF_INT (0x1 << 17) +#define BIT_SOF_INT (0x1 << 16) +#define BIT_F2_INT (0x1 << 15) +#define BIT_F1_INT (0x1 << 14) +#define BIT_COF_INT (0x1 << 13) +#define BIT_HRESP_ERR_INT (0x1 << 7) +#define BIT_ECC_INT (0x1 << 1) +#define BIT_DRDY (0x1 << 0) + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 +#endif + +#define CSI_CSICR1 (IO_ADDRESS(CSI_BASE_ADDR)) +#define CSI_CSICR2 (IO_ADDRESS(CSI_BASE_ADDR + 0x4)) +#define CSI_CSICR3 (IO_ADDRESS(CSI_BASE_ADDR + 0x8)) +#define CSI_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0xC)) +#define CSI_CSIRXFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x10)) +#define CSI_CSIRXCNT (IO_ADDRESS(CSI_BASE_ADDR + 0x14)) +#define CSI_CSISR (IO_ADDRESS(CSI_BASE_ADDR + 0x18)) + +#define CSI_CSIDBG (IO_ADDRESS(CSI_BASE_ADDR + 0x1C)) +#define CSI_CSIDMASA_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x20)) +#define CSI_CSIDMATS_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x24)) +#define CSI_CSIDMASA_FB1 (IO_ADDRESS(CSI_BASE_ADDR + 0x28)) +#define CSI_CSIDMASA_FB2 (IO_ADDRESS(CSI_BASE_ADDR + 0x2C)) +#define CSI_CSIFBUF_PARA (IO_ADDRESS(CSI_BASE_ADDR + 0x30)) +#define CSI_CSIIMAG_PARA (IO_ADDRESS(CSI_BASE_ADDR + 0x34)) + +#define CSI_CSIRXFIFO_PHYADDR (CSI_BASE_ADDR + 0x10) + +static inline void csi_clear_status(unsigned long status) +{ + __raw_writel(status, CSI_CSISR); +} + +struct csi_signal_cfg_t { + unsigned data_width:3; + unsigned clk_mode:2; + unsigned ext_vsync:1; + unsigned Vsync_pol:1; + unsigned Hsync_pol:1; + unsigned pixclk_pol:1; + unsigned data_pol:1; + unsigned sens_clksrc:1; +}; + +struct csi_config_t { + /* control reg 1 */ + unsigned int swap16_en:1; + unsigned int ext_vsync:1; + unsigned int eof_int_en:1; + unsigned int prp_if_en:1; + unsigned int ccir_mode:1; + unsigned int cof_int_en:1; + unsigned int sf_or_inten:1; + unsigned int rf_or_inten:1; + unsigned int sff_dma_done_inten:1; + unsigned int statff_inten:1; + unsigned int fb2_dma_done_inten:1; + unsigned int fb1_dma_done_inten:1; + unsigned int rxff_inten:1; + unsigned int sof_pol:1; + unsigned int sof_inten:1; + unsigned int mclkdiv:4; + unsigned int hsync_pol:1; + unsigned int ccir_en:1; + unsigned int mclken:1; + unsigned int fcc:1; + unsigned int pack_dir:1; + unsigned int gclk_mode:1; + unsigned int inv_data:1; + unsigned int inv_pclk:1; + unsigned int redge:1; + unsigned int pixel_bit:1; + + /* control reg 3 */ + unsigned int frmcnt:16; + unsigned int frame_reset:1; + unsigned int dma_reflash_rff:1; + unsigned int dma_reflash_sff:1; + unsigned int dma_req_en_rff:1; + unsigned int dma_req_en_sff:1; + unsigned int statff_level:3; + unsigned int hresp_err_en:1; + unsigned int rxff_level:3; + unsigned int two_8bit_sensor:1; + unsigned int zero_pack_en:1; + unsigned int ecc_int_en:1; + unsigned int ecc_auto_en:1; + /* fifo counter */ + unsigned int rxcnt; +}; + +typedef void (*csi_irq_callback_t) (void *data, unsigned long status); + +int32_t csi_enable_mclk(int src, bool flag, bool wait); +void csi_init_interface(void); +void csi_set_16bit_imagpara(int width, int height); +void csi_set_12bit_imagpara(int width, int height); +int csi_read_mclk_flag(void); +void csi_start_callback(void *data); +void csi_stop_callback(void *data); +void csi_enable_int(int arg); +void csi_disable_int(void); +void csi_mclk_enable(void); +void csi_mclk_disable(void); diff --git a/drivers/media/video/mxc/capture/ipu_csi_enc.c b/drivers/media/video/mxc/capture/ipu_csi_enc.c new file mode 100644 index 000000000000..c8a4dab78cc7 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_csi_enc.c @@ -0,0 +1,357 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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_enc.c + * + * @brief CSI Use case for video capture + * + * @ingroup IPU + */ + +#include +#include +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#ifdef CAMERA_DBG + #define CAMERA_TRACE(x) (printk)x +#else + #define CAMERA_TRACE(x) +#endif + +/* + * Function definitions + */ + +/*! + * csi ENC callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t csi_enc_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + if (cam->enc_callback == NULL) + return IRQ_HANDLED; + + cam->enc_callback(irq, dev_id); + return IRQ_HANDLED; +} + +/*! + * CSI ENC enable channel setup function + * + * @param cam struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_setup(cam_data *cam) +{ + ipu_channel_params_t params; + u32 pixel_fmt; + int err = 0, sensor_protocol = 0; + dma_addr_t dummy = cam->dummy_frame.buffer.m.offset; + + CAMERA_TRACE("In csi_enc_setup\n"); + if (!cam) { + printk(KERN_ERR "cam private is NULL\n"); + return -ENXIO; + } + + memset(¶ms, 0, sizeof(ipu_channel_params_t)); + params.csi_mem.csi = cam->csi; + + sensor_protocol = ipu_csi_get_sensor_protocol(cam->csi); + switch (sensor_protocol) { + case IPU_CSI_CLK_MODE_GATED_CLK: + case IPU_CSI_CLK_MODE_NONGATED_CLK: + case IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE: + case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR: + case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR: + params.csi_mem.interlaced = false; + break; + case IPU_CSI_CLK_MODE_CCIR656_INTERLACED: + case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR: + case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR: + params.csi_mem.interlaced = true; + break; + default: + printk(KERN_ERR "sensor protocol unsupported\n"); + return -EINVAL; + } + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + pixel_fmt = IPU_PIX_FMT_YUV420P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + pixel_fmt = IPU_PIX_FMT_YUV422P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + pixel_fmt = IPU_PIX_FMT_UYVY; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + pixel_fmt = IPU_PIX_FMT_YUYV; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) + pixel_fmt = IPU_PIX_FMT_NV12; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) + pixel_fmt = IPU_PIX_FMT_BGR24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) + pixel_fmt = IPU_PIX_FMT_RGB24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) + pixel_fmt = IPU_PIX_FMT_RGB565; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) + pixel_fmt = IPU_PIX_FMT_BGR32; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) + pixel_fmt = IPU_PIX_FMT_RGB32; + else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, true, true); + + err = ipu_init_channel(CSI_MEM, ¶ms); + if (err != 0) { + printk(KERN_ERR "ipu_init_channel %d\n", err); + return err; + } + + err = ipu_init_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + pixel_fmt, cam->v2f.fmt.pix.width, + cam->v2f.fmt.pix.height, + cam->v2f.fmt.pix.width, IPU_ROTATE_NONE, + dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "CSI_MEM output buffer\n"); + return err; + } + err = ipu_enable_channel(CSI_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_MEM\n"); + return err; + } + + return err; +} + +/*! + * function to update physical buffer address for encorder IDMA channel + * + * @param eba physical buffer address for encorder IDMA channel + * @param buffer_num int buffer 0 or buffer 1 + * + * @return status + */ +static int csi_enc_eba_update(dma_addr_t eba, int *buffer_num) +{ + int err = 0; + + pr_debug("eba %x\n", eba); + err = ipu_update_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + *buffer_num, eba); + if (err != 0) { + ipu_clear_buffer_ready(CSI_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + + err = ipu_update_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + *buffer_num, eba); + if (err != 0) { + pr_err("ERROR: v4l2 capture: fail to update " + "buf%d\n", *buffer_num); + return err; + } + } + + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, *buffer_num); + + *buffer_num = (*buffer_num == 0) ? 1 : 0; + + return 0; +} + +/*! + * Enable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_enabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + CAMERA_TRACE("IPU:In csi_enc_enabling_tasks\n"); + + cam->dummy_frame.vaddress = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->dummy_frame.paddress, + GFP_DMA | GFP_KERNEL); + if (cam->dummy_frame.vaddress == 0) { + pr_err("ERROR: v4l2 capture: Allocate dummy frame " + "failed.\n"); + return -ENOBUFS; + } + cam->dummy_frame.buffer.type = V4L2_BUF_TYPE_PRIVATE; + cam->dummy_frame.buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->dummy_frame.buffer.m.offset = cam->dummy_frame.paddress; + + ipu_clear_irq(IPU_IRQ_CSI0_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_CSI0_OUT_EOF, + csi_enc_callback, 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering rot irq\n"); + return err; + } + + err = csi_enc_setup(cam); + if (err != 0) { + printk(KERN_ERR "csi_enc_setup %d\n", err); + return err; + } + + return err; +} + +/*! + * Disable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +static int csi_enc_disabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + ipu_free_irq(IPU_IRQ_CSI0_OUT_EOF, cam); + + err = ipu_disable_channel(CSI_MEM, true); + + ipu_uninit_channel(CSI_MEM); + + if (cam->dummy_frame.vaddress != 0) { + dma_free_coherent(0, cam->dummy_frame.buffer.length, + cam->dummy_frame.vaddress, + cam->dummy_frame.paddress); + cam->dummy_frame.vaddress = 0; + } + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, false, false); + + return err; +} + +/*! + * Enable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_enable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_enable_csi(cam->csi); +} + +/*! + * Disable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_disable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_disable_csi(cam->csi); +} + +/*! + * function to select CSI ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int csi_enc_select(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = csi_enc_eba_update; + cam->enc_enable = csi_enc_enabling_tasks; + cam->enc_disable = csi_enc_disabling_tasks; + cam->enc_enable_csi = csi_enc_enable_csi; + cam->enc_disable_csi = csi_enc_disable_csi; + } else { + err = -EIO; + } + + return err; +} + +/*! + * function to de-select CSI ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int csi_enc_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + cam->enc_enable_csi = NULL; + cam->enc_disable_csi = NULL; + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int csi_enc_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit csi_enc_exit(void) +{ +} + +module_init(csi_enc_init); +module_exit(csi_enc_exit); + +EXPORT_SYMBOL(csi_enc_select); +EXPORT_SYMBOL(csi_enc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CSI ENC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_enc.c b/drivers/media/video/mxc/capture/ipu_prp_enc.c new file mode 100644 index 000000000000..67637dd150fc --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_enc.c @@ -0,0 +1,530 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_enc.c + * + * @brief IPU Use case for PRP-ENC + * + * @ingroup IPU + */ + +#include +#include +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#ifdef CAMERA_DBG + #define CAMERA_TRACE(x) (printk)x +#else + #define CAMERA_TRACE(x) +#endif + +static ipu_rotate_mode_t grotation = IPU_ROTATE_NONE; + +/* + * Function definitions + */ + +/*! + * IPU ENC callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_enc_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + if (cam->enc_callback == NULL) + return IRQ_HANDLED; + + cam->enc_callback(irq, dev_id); + + return IRQ_HANDLED; +} + +/*! + * PrpENC enable channel setup function + * + * @param cam struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_setup(cam_data *cam) +{ + ipu_channel_params_t enc; + int err = 0; + dma_addr_t dummy = 0xdeadbeaf; + + CAMERA_TRACE("In prp_enc_setup\n"); + if (!cam) { + printk(KERN_ERR "cam private is NULL\n"); + return -ENXIO; + } + memset(&enc, 0, sizeof(ipu_channel_params_t)); + + ipu_csi_get_window_size(&enc.csi_prp_enc_mem.in_width, + &enc.csi_prp_enc_mem.in_height, cam->csi); + + enc.csi_prp_enc_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.width; + enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.height; + enc.csi_prp_enc_mem.csi = cam->csi; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.height; + enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.width; + } + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUV420P; + pr_info("YUV420\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUV422P; + pr_info("YUV422P\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUYV; + pr_info("YUYV\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_UYVY; + pr_info("UYVY\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_NV12; + pr_info("NV12\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_BGR24; + pr_info("BGR24\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB24; + pr_info("RGB24\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB565; + pr_info("RGB565\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_BGR32; + pr_info("BGR32\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB32; + pr_info("RGB32\n"); + } else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + err = ipu_init_channel(CSI_PRP_ENC_MEM, &enc); + if (err != 0) { + printk(KERN_ERR "ipu_init_channel %d\n", err); + return err; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, true, true); + + grotation = cam->rotation; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + if (cam->rot_enc_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + } + if (cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[1], + cam->rot_enc_bufs_vaddr[1], + cam->rot_enc_bufs[1]); + } + cam->rot_enc_buf_size[0] = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->rot_enc_bufs_vaddr[0] = + (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[0], + &cam->rot_enc_bufs[0], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_enc_bufs_vaddr[0]) { + printk(KERN_ERR "alloc enc_bufs0\n"); + return -ENOMEM; + } + cam->rot_enc_buf_size[1] = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->rot_enc_bufs_vaddr[1] = + (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[1], + &cam->rot_enc_bufs[1], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + cam->rot_enc_bufs_vaddr[0] = NULL; + cam->rot_enc_bufs[0] = 0; + printk(KERN_ERR "alloc enc_bufs1\n"); + return -ENOMEM; + } + + err = ipu_init_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + IPU_ROTATE_NONE, + cam->rot_enc_bufs[0], + cam->rot_enc_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "CSI_PRP_ENC_MEM err\n"); + return err; + } + + err = ipu_init_channel(MEM_ROT_ENC_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM channel err\n"); + return err; + } + + err = ipu_init_channel_buffer(MEM_ROT_ENC_MEM, IPU_INPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + cam->rotation, + cam->rot_enc_bufs[0], + cam->rot_enc_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM input buffer\n"); + return err; + } + + err = + ipu_init_channel_buffer(MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + cam->v2f.fmt.pix.bytesperline / + bytes_per_pixel(enc.csi_prp_enc_mem. + out_pixel_fmt), + IPU_ROTATE_NONE, dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM output buffer\n"); + return err; + } + + err = ipu_link_channels(CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); + if (err < 0) { + printk(KERN_ERR + "link CSI_PRP_ENC_MEM-MEM_ROT_ENC_MEM\n"); + return err; + } + + err = ipu_enable_channel(CSI_PRP_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); + return err; + } + err = ipu_enable_channel(MEM_ROT_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel MEM_ROT_ENC_MEM\n"); + return err; + } + + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + err = + ipu_init_channel_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + cam->v2f.fmt.pix.bytesperline / + bytes_per_pixel(enc.csi_prp_enc_mem. + out_pixel_fmt), + cam->rotation, dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "CSI_PRP_ENC_MEM output buffer\n"); + return err; + } + err = ipu_enable_channel(CSI_PRP_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); + return err; + } + } + + return err; +} + +/*! + * function to update physical buffer address for encorder IDMA channel + * + * @param eba physical buffer address for encorder IDMA channel + * @param buffer_num int buffer 0 or buffer 1 + * + * @return status + */ +static int prp_enc_eba_update(dma_addr_t eba, int *buffer_num) +{ + int err = 0; + + pr_debug("eba %x\n", eba); + if (grotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_update_channel_buffer(MEM_ROT_ENC_MEM, + IPU_OUTPUT_BUFFER, *buffer_num, + eba); + } else { + err = ipu_update_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, *buffer_num, + eba); + } + if (err != 0) { + if (grotation >= IPU_ROTATE_90_RIGHT) { + ipu_clear_buffer_ready(MEM_ROT_ENC_MEM, + IPU_OUTPUT_BUFFER, + *buffer_num); + err = ipu_update_channel_buffer(MEM_ROT_ENC_MEM, + IPU_OUTPUT_BUFFER, + *buffer_num, + eba); + } else { + ipu_clear_buffer_ready(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, + *buffer_num); + err = ipu_update_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, + *buffer_num, + eba); + } + + if (err != 0) { + pr_err("ERROR: v4l2 capture: fail to update " + "buf%d\n", *buffer_num); + return err; + } + } + + if (grotation >= IPU_ROTATE_90_RIGHT) { + ipu_select_buffer(MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + } else { + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + } + + *buffer_num = (*buffer_num == 0) ? 1 : 0; + return 0; +} + +/*! + * Enable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_enabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + CAMERA_TRACE("IPU:In prp_enc_enabling_tasks\n"); + + cam->dummy_frame.vaddress = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->dummy_frame.paddress, + GFP_DMA | GFP_KERNEL); + if (cam->dummy_frame.vaddress == 0) { + pr_err("ERROR: v4l2 capture: Allocate dummy frame " + "failed.\n"); + return -ENOBUFS; + } + cam->dummy_frame.buffer.type = V4L2_BUF_TYPE_PRIVATE; + cam->dummy_frame.buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->dummy_frame.buffer.m.offset = cam->dummy_frame.paddress; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_request_irq(IPU_IRQ_PRP_ENC_ROT_OUT_EOF, + prp_enc_callback, 0, "Mxc Camera", cam); + } else { + err = ipu_request_irq(IPU_IRQ_PRP_ENC_OUT_EOF, + prp_enc_callback, 0, "Mxc Camera", cam); + } + if (err != 0) { + printk(KERN_ERR "Error registering rot irq\n"); + return err; + } + + err = prp_enc_setup(cam); + if (err != 0) { + printk(KERN_ERR "prp_enc_setup %d\n", err); + return err; + } + + return err; +} + +/*! + * Disable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +static int prp_enc_disabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_free_irq(IPU_IRQ_PRP_ENC_ROT_OUT_EOF, cam); + } else { + ipu_free_irq(IPU_IRQ_PRP_ENC_OUT_EOF, cam); + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); + } + + err = ipu_disable_channel(CSI_PRP_ENC_MEM, true); + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err |= ipu_disable_channel(MEM_ROT_ENC_MEM, true); + } + + ipu_uninit_channel(CSI_PRP_ENC_MEM); + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_uninit_channel(MEM_ROT_ENC_MEM); + } + + if (cam->dummy_frame.vaddress != 0) { + dma_free_coherent(0, cam->dummy_frame.buffer.length, + cam->dummy_frame.vaddress, + cam->dummy_frame.paddress); + cam->dummy_frame.vaddress = 0; + } + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, false, false); + + return err; +} + +/*! + * Enable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_enable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_enable_csi(cam->csi); +} + +/*! + * Disable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_disable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_disable_csi(cam->csi); +} + +/*! + * function to select PRP-ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int prp_enc_select(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = prp_enc_eba_update; + cam->enc_enable = prp_enc_enabling_tasks; + cam->enc_disable = prp_enc_disabling_tasks; + cam->enc_enable_csi = prp_enc_enable_csi; + cam->enc_disable_csi = prp_enc_disable_csi; + } else { + err = -EIO; + } + + return err; +} + +/*! + * function to de-select PRP-ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int prp_enc_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + cam->enc_enable_csi = NULL; + cam->enc_disable_csi = NULL; + if (cam->rot_enc_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + cam->rot_enc_bufs_vaddr[0] = NULL; + cam->rot_enc_bufs[0] = 0; + } + if (cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[1], + cam->rot_enc_bufs_vaddr[1], + cam->rot_enc_bufs[1]); + cam->rot_enc_bufs_vaddr[1] = NULL; + cam->rot_enc_bufs[1] = 0; + } + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int prp_enc_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit prp_enc_exit(void) +{ +} + +module_init(prp_enc_init); +module_exit(prp_enc_exit); + +EXPORT_SYMBOL(prp_enc_select); +EXPORT_SYMBOL(prp_enc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP ENC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_sw.h b/drivers/media/video/mxc/capture/ipu_prp_sw.h new file mode 100644 index 000000000000..85dd102471f8 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_sw.h @@ -0,0 +1,36 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_sw.h + * + * @brief This file contains the IPU PRP use case driver header. + * + * @ingroup IPU + */ + +#ifndef _INCLUDE_IPU__PRP_SW_H_ +#define _INCLUDE_IPU__PRP_SW_H_ + +int csi_enc_select(void *private); +int csi_enc_deselect(void *private); +int prp_enc_select(void *private); +int prp_enc_deselect(void *private); +int prp_vf_sdc_select(void *private); +int prp_vf_sdc_select_bg(void *private); +int prp_vf_sdc_deselect(void *private); +int prp_vf_sdc_deselect_bg(void *private); +int prp_still_select(void *private); +int prp_still_deselect(void *private); + +#endif diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c new file mode 100644 index 000000000000..95b6ee6a8fb6 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c @@ -0,0 +1,482 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_sdc.c + * + * @brief IPU Use case for PRP-VF + * + * @ingroup IPU + */ + +#include +#include +#include +#include +#include +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#define OVERLAY_FB_SUPPORT_NONSTD (cpu_is_mx5()) + +/* + * Function definitions + */ + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = NULL; + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + u32 vf_out_format = 0; + u32 size = 2, temp = 0; + int err = 0, i = 0; + short *tmp, color; + + if (!cam) { + printk(KERN_ERR "private is NULL\n"); + return -EIO; + } + + if (cam->overlay_active == true) { + pr_debug("already started.\n"); + return 0; + } + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, "DISP3 FG") == 0) { + fbi = registered_fb[i]; + break; + } + } + + if (fbi == NULL) { + printk(KERN_ERR "DISP3 FG fb not found\n"); + return -EPERM; + } + + fbvar = fbi->var; + + /* Store the overlay frame buffer's original std */ + cam->fb_origin_std = fbvar.nonstd; + + if (OVERLAY_FB_SUPPORT_NONSTD) { + /* Use DP to do CSC so that we can get better performance */ + vf_out_format = IPU_PIX_FMT_UYVY; + fbvar.nonstd = vf_out_format; + color = 0x80; + } else { + vf_out_format = IPU_PIX_FMT_RGB565; + fbvar.nonstd = 0; + color = 0x0; + } + + fbvar.bits_per_pixel = 16; + fbvar.xres = fbvar.xres_virtual = cam->win.w.width; + fbvar.yres = cam->win.w.height; + fbvar.yres_virtual = cam->win.w.height * 2; + fbvar.yoffset = 0; + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + ipu_disp_set_window_pos(MEM_FG_SYNC, cam->win.w.left, + cam->win.w.top); + + /* Fill black color for framebuffer */ + tmp = (short *) fbi->screen_base; + for (i = 0; i < (fbi->fix.line_length * fbi->var.yres)/2; + i++, tmp++) + *tmp = color; + + console_lock(); + fb_blank(fbi, FB_BLANK_UNBLANK); + console_unlock(); + + /* correct display ch buffer address */ + ipu_update_channel_buffer(MEM_FG_SYNC, IPU_INPUT_BUFFER, + 0, fbi->fix.smem_start + + (fbi->fix.line_length * fbvar.yres)); + ipu_update_channel_buffer(MEM_FG_SYNC, IPU_INPUT_BUFFER, + 1, fbi->fix.smem_start); + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_mem.in_width, + &vf.csi_prp_vf_mem.in_height, cam->csi); + vf.csi_prp_vf_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_mem.out_width = cam->win.w.width; + vf.csi_prp_vf_mem.out_height = cam->win.w.height; + vf.csi_prp_vf_mem.csi = cam->csi; + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_mem.out_width = cam->win.w.height; + vf.csi_prp_vf_mem.out_height = cam->win.w.width; + } + vf.csi_prp_vf_mem.out_pixel_fmt = vf_out_format; + size = cam->win.w.width * cam->win.w.height * size; + + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + goto out_5; + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[0], + (dma_addr_t *) & + cam->vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_4; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[1], + (dma_addr_t *) & + cam->vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + pr_debug("vf_bufs %x %x\n", cam->vf_bufs[0], cam->vf_bufs[1]); + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + vf_out_format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + goto out_3; + } + + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + vf_out_format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + if (cam->vf_rotation < IPU_ROTATE_90_RIGHT) { + temp = vf.csi_prp_vf_mem.out_width; + vf.csi_prp_vf_mem.out_width = + vf.csi_prp_vf_mem.out_height; + vf.csi_prp_vf_mem.out_height = temp; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + vf_out_format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + fbi->fix.smem_start + + (fbi->fix.line_length * + fbi->var.yres), + fbi->fix.smem_start, 0, 0); + + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + if (err < 0) { + printk(KERN_ERR + "Error link CSI_PRP_VF_MEM-MEM_ROT_VF_MEM\n"); + goto out_2; + } + + err = ipu_link_channels(MEM_ROT_VF_MEM, MEM_FG_SYNC); + if (err < 0) { + printk(KERN_ERR + "Error link MEM_ROT_VF_MEM-MEM_FG_SYNC\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + vf_out_format, cam->win.w.width, + cam->win.w.height, + cam->win.w.width, + cam->vf_rotation, + fbi->fix.smem_start + + (fbi->fix.line_length * + fbi->var.yres), + fbi->fix.smem_start, 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing CSI_PRP_VF_MEM\n"); + goto out_4; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_FG_SYNC); + if (err < 0) { + printk(KERN_ERR "Error linking ipu channels\n"); + goto out_4; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } + + cam->overlay_active = true; + return err; + +out_1: + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); +out_2: + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_uninit_channel(MEM_ROT_VF_MEM); + } +out_3: + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } +out_4: + ipu_uninit_channel(CSI_PRP_VF_MEM); +out_5: + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0, i = 0; + struct fb_info *fbi = NULL; + struct fb_var_screeninfo fbvar; + + if (cam->overlay_active == false) + return 0; + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, "DISP3 FG") == 0) { + fbi = registered_fb[i]; + break; + } + } + + if (fbi == NULL) { + printk(KERN_ERR "DISP3 FG fb not found\n"); + return -EPERM; + } + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + ipu_unlink_channels(MEM_ROT_VF_MEM, MEM_FG_SYNC); + } else { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_FG_SYNC); + } + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + ipu_uninit_channel(CSI_PRP_VF_MEM); + + console_lock(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + console_unlock(); + + /* Set the overlay frame buffer std to what it is used to be */ + fbvar = fbi->var; + fbvar.nonstd = cam->fb_origin_std; + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + + cam->overlay_active = false; + return err; +} + +/*! + * Enable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_vf_enable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_enable_csi(cam->csi); +} + +/*! + * Disable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_vf_disable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_disable_csi(cam->csi); +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_select(void *private) +{ + cam_data *cam; + int err = 0; + if (private) { + cam = (cam_data *) private; + cam->vf_start_sdc = prpvf_start; + cam->vf_stop_sdc = prpvf_stop; + cam->vf_enable_csi = prp_vf_enable_csi; + cam->vf_disable_csi = prp_vf_disable_csi; + cam->overlay_active = false; + } else + err = -EIO; + + return err; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return int + */ +int prp_vf_sdc_deselect(void *private) +{ + cam_data *cam; + int err = 0; + err = prpvf_stop(private); + + if (private) { + cam = (cam_data *) private; + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + cam->vf_enable_csi = NULL; + cam->vf_disable_csi = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_sdc_init(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_sdc_exit(void) +{ +} + +module_init(prp_vf_sdc_init); +module_exit(prp_vf_sdc_exit); + +EXPORT_SYMBOL(prp_vf_sdc_select); +EXPORT_SYMBOL(prp_vf_sdc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF SDC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c new file mode 100644 index 000000000000..042d07e0d758 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c @@ -0,0 +1,443 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_sdc_bg.c + * + * @brief IPU Use case for PRP-VF back-ground + * + * @ingroup IPU + */ +#include +#include +#include +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int buffer_num; +static int buffer_ready; + +/* + * Function definitions + */ + +/*! + * SDC V-Sync callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prpvf_sdc_vsync_callback(int irq, void *dev_id) +{ + pr_debug("buffer_ready %d buffer_num %d\n", buffer_ready, buffer_num); + if (buffer_ready > 0) { + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + buffer_ready--; + } + + return IRQ_HANDLED; +} + +/*! + * VF EOF callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prpvf_vf_eof_callback(int irq, void *dev_id) +{ + pr_debug("buffer_ready %d buffer_num %d\n", buffer_ready, buffer_num); + + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, buffer_num); + + buffer_num = (buffer_num == 0) ? 1 : 0; + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, buffer_num); + buffer_ready++; + return IRQ_HANDLED; +} + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + u32 format; + u32 offset; + u32 bpp, size = 3; + int err = 0; + + if (!cam) { + printk(KERN_ERR "private is NULL\n"); + return -EIO; + } + + if (cam->overlay_active == true) { + pr_debug("already start.\n"); + return 0; + } + + format = cam->v4l2_fb.fmt.pixelformat; + if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_BGR24) { + bpp = 3, size = 3; + pr_info("BGR24\n"); + } else if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_RGB565) { + bpp = 2, size = 2; + pr_info("RGB565\n"); + } else if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_BGR32) { + bpp = 4, size = 4; + pr_info("BGR32\n"); + } else { + printk(KERN_ERR + "unsupported fix format from the framebuffer.\n"); + return -EINVAL; + } + + offset = cam->v4l2_fb.fmt.bytesperline * cam->win.w.top + + size * cam->win.w.left; + + if (cam->v4l2_fb.base == 0) { + printk(KERN_ERR "invalid frame buffer address.\n"); + } else { + offset += (u32) cam->v4l2_fb.base; + } + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_mem.in_width, + &vf.csi_prp_vf_mem.in_height, cam->csi); + vf.csi_prp_vf_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_mem.out_width = cam->win.w.width; + vf.csi_prp_vf_mem.out_height = cam->win.w.height; + vf.csi_prp_vf_mem.csi = cam->csi; + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_mem.out_width = cam->win.w.height; + vf.csi_prp_vf_mem.out_height = cam->win.w.width; + } + vf.csi_prp_vf_mem.out_pixel_fmt = format; + size = cam->win.w.width * cam->win.w.height * size; + + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + goto out_4; + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[0], + &cam->vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[1], + &cam->vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing CSI_PRP_VF_MEM\n"); + goto out_3; + } + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->overlay_fb->var.xres * bpp, + IPU_ROTATE_NONE, offset, 0, 0, 0); + + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + } else { + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + cam->overlay_fb->var.xres * bpp, + IPU_ROTATE_NONE, offset, 0, 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + } + + ipu_clear_irq(IPU_IRQ_PRP_VF_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_PRP_VF_OUT_EOF, prpvf_vf_eof_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR + "Error registering IPU_IRQ_PRP_VF_OUT_EOF irq.\n"); + goto out_2; + } + + ipu_clear_irq(IPU_IRQ_BG_SF_END); + err = ipu_request_irq(IPU_IRQ_BG_SF_END, prpvf_sdc_vsync_callback, + 0, "Mxc Camera", NULL); + if (err != 0) { + printk(KERN_ERR "Error registering IPU_IRQ_BG_SF_END irq.\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + + buffer_num = 0; + buffer_ready = 0; + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + + cam->overlay_active = true; + return err; + + out_1: + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, NULL); + out_2: + ipu_uninit_channel(MEM_ROT_VF_MEM); + out_3: + ipu_uninit_channel(CSI_PRP_VF_MEM); + out_4: + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam->overlay_active == false) + return 0; + + ipu_free_irq(IPU_IRQ_BG_SF_END, NULL); + + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, cam); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(MEM_ROT_VF_MEM); + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + + buffer_num = 0; + buffer_ready = 0; + cam->overlay_active = false; + return 0; +} + +/*! + * Enable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_vf_enable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_enable_csi(cam->csi); +} + +/*! + * Disable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_vf_disable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_disable_csi(cam->csi); +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_select_bg(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->vf_start_sdc = prpvf_start; + cam->vf_stop_sdc = prpvf_stop; + cam->vf_enable_csi = prp_vf_enable_csi; + cam->vf_disable_csi = prp_vf_disable_csi; + cam->overlay_active = false; + } + + return 0; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_deselect_bg(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + err = prpvf_stop(private); + + if (cam) { + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + cam->vf_enable_csi = NULL; + cam->vf_disable_csi = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_sdc_init_bg(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_sdc_exit_bg(void) +{ +} + +module_init(prp_vf_sdc_init_bg); +module_exit(prp_vf_sdc_exit_bg); + +EXPORT_SYMBOL(prp_vf_sdc_select_bg); +EXPORT_SYMBOL(prp_vf_sdc_deselect_bg); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF SDC Backgroud Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_still.c b/drivers/media/video/mxc/capture/ipu_still.c new file mode 100644 index 000000000000..b81891a0c964 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_still.c @@ -0,0 +1,268 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_still.c + * + * @brief IPU Use case for still image capture + * + * @ingroup IPU + */ + +#include +#include +#include +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int callback_eof_flag; +#ifndef CONFIG_MXC_IPU_V1 +static int buffer_num; +#endif + +#ifdef CONFIG_MXC_IPU_V1 +static int callback_flag; +/* + * Function definitions + */ +/*! + * CSI EOF callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_csi_eof_callback(int irq, void *dev_id) +{ + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + callback_flag%2 ? 1 : 0); + if (callback_flag == 0) + ipu_enable_channel(CSI_MEM); + + callback_flag++; + return IRQ_HANDLED; +} +#endif + +/*! + * CSI callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_still_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + callback_eof_flag++; + if (callback_eof_flag < 5) { +#ifndef CONFIG_MXC_IPU_V1 + buffer_num = (buffer_num == 0) ? 1 : 0; + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, buffer_num); +#endif + } else { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + + return IRQ_HANDLED; +} + +/*! + * start csi->mem task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_still_start(void *private) +{ + cam_data *cam = (cam_data *) private; + u32 pixel_fmt; + int err; + ipu_channel_params_t params; + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + pixel_fmt = IPU_PIX_FMT_YUV420P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) + pixel_fmt = IPU_PIX_FMT_NV12; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + pixel_fmt = IPU_PIX_FMT_YUV422P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + pixel_fmt = IPU_PIX_FMT_UYVY; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + pixel_fmt = IPU_PIX_FMT_YUYV; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) + pixel_fmt = IPU_PIX_FMT_BGR24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) + pixel_fmt = IPU_PIX_FMT_RGB24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) + pixel_fmt = IPU_PIX_FMT_RGB565; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) + pixel_fmt = IPU_PIX_FMT_BGR32; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) + pixel_fmt = IPU_PIX_FMT_RGB32; + else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_RAW, cam->csi, true, true); + + memset(¶ms, 0, sizeof(params)); + err = ipu_init_channel(CSI_MEM, ¶ms); + if (err != 0) + return err; + + err = ipu_init_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + pixel_fmt, cam->v2f.fmt.pix.width, + cam->v2f.fmt.pix.height, + cam->v2f.fmt.pix.width, IPU_ROTATE_NONE, + cam->still_buf[0], cam->still_buf[1], + 0, 0); + if (err != 0) + return err; + +#ifdef CONFIG_MXC_IPU_V1 + ipu_clear_irq(IPU_IRQ_SENSOR_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_SENSOR_OUT_EOF, prp_still_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering irq.\n"); + return err; + } + callback_flag = 0; + callback_eof_flag = 0; + ipu_clear_irq(IPU_IRQ_SENSOR_EOF); + err = ipu_request_irq(IPU_IRQ_SENSOR_EOF, prp_csi_eof_callback, + 0, "Mxc Camera", NULL); + if (err != 0) { + printk(KERN_ERR "Error IPU_IRQ_SENSOR_EOF \n"); + return err; + } +#else + callback_eof_flag = 0; + buffer_num = 0; + + ipu_clear_irq(IPU_IRQ_CSI0_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_CSI0_OUT_EOF, prp_still_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering irq.\n"); + return err; + } + + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_enable_channel(CSI_MEM); + ipu_enable_csi(cam->csi); +#endif + + return err; +} + +/*! + * stop csi->mem encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_still_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + +#ifdef CONFIG_MXC_IPU_V1 + ipu_free_irq(IPU_IRQ_SENSOR_EOF, NULL); + ipu_free_irq(IPU_IRQ_SENSOR_OUT_EOF, cam); +#else + ipu_free_irq(IPU_IRQ_CSI0_OUT_EOF, cam); +#endif + + ipu_disable_csi(cam->csi); + ipu_disable_channel(CSI_MEM, true); + ipu_uninit_channel(CSI_MEM); + ipu_csi_enable_mclk_if(CSI_MCLK_RAW, cam->csi, false, false); + + return err; +} + +/*! + * function to select CSI_MEM as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +int prp_still_select(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->csi_start = prp_still_start; + cam->csi_stop = prp_still_stop; + } + + return 0; +} + +/*! + * function to de-select CSI_MEM as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +int prp_still_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + err = prp_still_stop(cam); + + if (cam) { + cam->csi_start = NULL; + cam->csi_stop = NULL; + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int prp_still_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit prp_still_exit(void) +{ +} + +module_init(prp_still_init); +module_exit(prp_still_exit); + +EXPORT_SYMBOL(prp_still_select); +EXPORT_SYMBOL(prp_still_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP STILL IMAGE Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mt9v111.c b/drivers/media/video/mxc/capture/mt9v111.c new file mode 100644 index 000000000000..4305c56c82d9 --- /dev/null +++ b/drivers/media/video/mxc/capture/mt9v111.c @@ -0,0 +1,1076 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mt9v111.c + * + * @brief mt9v111 camera driver functions + * + * @ingroup Camera + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxc_v4l2_capture.h" +#include "mt9v111.h" + +#ifdef MT9V111_DEBUG +static u16 testpattern; +#endif + +static mt9v111_conf mt9v111_device; + +/*! + * Holds the current frame rate. + */ +static int reset_frame_rate = MT9V111_FRAME_RATE; + +struct sensor { + const struct mt9v111_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + +} mt9v111_data; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +static int mt9v111_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int mt9v111_remove(struct i2c_client *client); + +static const struct i2c_device_id mt9v111_id[] = { + {"mt9v111", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mt9v111_id); + +static struct i2c_driver mt9v111_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mt9v111", + }, + .probe = mt9v111_probe, + .remove = mt9v111_remove, + .id_table = mt9v111_id, +/* To add power management add .suspend and .resume functions */ +}; + +/* + * Function definitions + */ + +#ifdef MT9V111_DEBUG +static inline int mt9v111_read_reg(u8 reg) +{ + int val = i2c_smbus_read_word_data(mt9v111_data.i2c_client, reg); + if (val != -1) + val = cpu_to_be16(val); + return val; +} +#endif + +/*! + * Writes to the register via I2C. + */ +static inline int mt9v111_write_reg(u8 reg, u16 val) +{ + pr_debug("In mt9v111_write_reg (0x%x, 0x%x)\n", reg, val); + pr_debug(" write reg %x val %x.\n", reg, val); + + return i2c_smbus_write_word_data(mt9v111_data.i2c_client, + reg, cpu_to_be16(val)); +} + +/*! + * Initialize mt9v111_sensor_lib + * Libarary for Sensor configuration through I2C + * + * @param coreReg Core Registers + * @param ifpReg IFP Register + * + * @return status + */ +static u8 mt9v111_sensor_lib(mt9v111_coreReg *coreReg, mt9v111_IFPReg *ifpReg) +{ + u8 reg; + u16 data; + u8 error = 0; + + pr_debug("In mt9v111_sensor_lib\n"); + + /* + * setup to IFP registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_MODE_CONTROL; + data = ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + /* Output format */ + reg = MT9V111I_FORMAT_CONTROL; + data = ifpReg->formatControl; /* Set bit 12 */ + mt9v111_write_reg(reg, data); + + /* AE limit 4 */ + reg = MT9V111I_SHUTTER_WIDTH_LIMIT_AE; + data = ifpReg->gainLimitAE; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_OUTPUT_FORMAT_CTRL2; + data = ifpReg->outputFormatCtrl2; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_AE_SPEED; + data = ifpReg->AESpeed; + mt9v111_write_reg(reg, data); + + /* output image size */ + reg = MT9V111i_H_PAN; + data = 0x8000 | ifpReg->HPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_ZOOM; + data = 0x8000 | ifpReg->HZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_SIZE; + data = 0x8000 | ifpReg->HSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_PAN; + data = 0x8000 | ifpReg->VPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_ZOOM; + data = 0x8000 | ifpReg->VZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_SIZE; + data = 0x8000 | ifpReg->VSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_PAN; + data = ~0x8000 & ifpReg->HPan; + mt9v111_write_reg(reg, data); +#if 0 + reg = MT9V111I_UPPER_SHUTTER_DELAY_LIM; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SHUTTER_60; + data = ifpReg->shutter_width_60; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SEARCH_FLICK_60; + data = ifpReg->search_flicker_60; + mt9v111_write_reg(reg, data); +#endif + + /* + * setup to sensor core registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = coreReg->addressSelect; + mt9v111_write_reg(reg, data); + + /* enable changes and put the Sync bit on */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_SYNC | MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + /* min PIXCLK - Default */ + reg = MT9V111S_PIXEL_CLOCK_SPEED; + data = coreReg->pixelClockSpeed; + mt9v111_write_reg(reg, data); + + /* Setup image flipping / Dark rows / row/column skip */ + reg = MT9V111S_READ_MODE; + data = coreReg->readMode; + mt9v111_write_reg(reg, data); + + /* zoom 0 */ + reg = MT9V111S_DIGITAL_ZOOM; + data = coreReg->digitalZoom; + mt9v111_write_reg(reg, data); + + /* min H-blank */ + reg = MT9V111S_HOR_BLANKING; + data = coreReg->horizontalBlanking; + mt9v111_write_reg(reg, data); + + /* min V-blank */ + reg = MT9V111S_VER_BLANKING; + data = coreReg->verticalBlanking; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_WIDTH; + data = coreReg->shutterWidth; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_DELAY; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + /* changes become effective */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + return error; +} + +/*! + * MT9V111 frame rate calculate + * + * @param frame_rate int * + * @param mclk int + * @return None + */ +static void mt9v111_rate_cal(int *frame_rate, int mclk) +{ + int num_clock_per_row; + int max_rate = 0; + + pr_debug("In mt9v111_rate_cal\n"); + + num_clock_per_row = (MT9V111_MAX_WIDTH + 114 + MT9V111_HORZBLANK_MIN) + * 2; + max_rate = mclk / (num_clock_per_row * + (MT9V111_MAX_HEIGHT + MT9V111_VERTBLANK_DEFAULT)); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + mt9v111_device.coreReg->verticalBlanking + = mclk / (*frame_rate * num_clock_per_row) - MT9V111_MAX_HEIGHT; + + reset_frame_rate = *frame_rate; +} + +/*! + * MT9V111 sensor configuration + */ +void mt9v111_config(void) +{ + pr_debug("In mt9v111_config\n"); + + mt9v111_device.coreReg->addressSelect = MT9V111I_SEL_SCA; + mt9v111_device.ifpReg->addrSpaceSel = MT9V111I_SEL_IFP; + + mt9v111_device.coreReg->windowHeight = MT9V111_WINHEIGHT; + mt9v111_device.coreReg->windowWidth = MT9V111_WINWIDTH; + mt9v111_device.coreReg->zoomColStart = 0; + mt9v111_device.coreReg->zomRowStart = 0; + mt9v111_device.coreReg->digitalZoom = 0x0; + + mt9v111_device.coreReg->verticalBlanking = MT9V111_VERTBLANK_DEFAULT; + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + mt9v111_device.coreReg->pixelClockSpeed = 0; + mt9v111_device.coreReg->readMode = 0xd0a1; + + mt9v111_device.ifpReg->outputFormatCtrl2 = 0; + mt9v111_device.ifpReg->gainLimitAE = 0x300; + mt9v111_device.ifpReg->AESpeed = 0x80; + + /* here is the default value */ + mt9v111_device.ifpReg->formatControl = 0xc800; + mt9v111_device.ifpReg->modeControl = 0x708e; + mt9v111_device.ifpReg->awbSpeed = 0x4514; + mt9v111_device.coreReg->shutterWidth = 0xf8; + + /* output size */ + mt9v111_device.ifpReg->HPan = 0; + mt9v111_device.ifpReg->HZoom = MT9V111_MAX_WIDTH; + mt9v111_device.ifpReg->HSize = MT9V111_MAX_WIDTH; + mt9v111_device.ifpReg->VPan = 0; + mt9v111_device.ifpReg->VZoom = MT9V111_MAX_HEIGHT; + mt9v111_device.ifpReg->VSize = MT9V111_MAX_HEIGHT; +} + +/*! + * mt9v111 sensor set saturtionn + * + * @param saturation int + + * @return Error code of 0. + */ +static int mt9v111_set_saturation(int saturation) +{ + u8 reg; + u16 data; + pr_debug("In mt9v111_set_saturation(%d)\n", + saturation); + + switch (saturation) { + case 150: + mt9v111_device.ifpReg->awbSpeed = 0x6D14; + break; + case 100: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + case 75: + mt9v111_device.ifpReg->awbSpeed = 0x4D14; + break; + case 50: + mt9v111_device.ifpReg->awbSpeed = 0x5514; + break; + case 37: + mt9v111_device.ifpReg->awbSpeed = 0x5D14; + break; + case 25: + mt9v111_device.ifpReg->awbSpeed = 0x6514; + break; + default: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + } + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_AWB_SPEED; + data = mt9v111_device.ifpReg->awbSpeed; + mt9v111_write_reg(reg, data); + + return 0; +} + +/*! + * mt9v111 sensor set Auto Exposure measurement window mode configuration + * + * @param ae_mode int + * @return Error code of 0 (no Error) + */ +static int mt9v111_set_ae_mode(int ae_mode) +{ + u8 reg; + u16 data; + + pr_debug("In mt9v111_set_ae_mode(%d)\n", + ae_mode); + + /* Currently this driver only supports auto and manual exposure + * modes. */ + if ((ae_mode > 1) || (ae_mode << 0)) + return -EPERM; + + /* + * The auto exposure is set in bit 14. + * Other values are set for: + * -on the fly defect correction is on (bit 13). + * -aperature correction knee enabled (bit 12). + * -ITU_R BT656 synchronization codes are embedded in the image (bit 7) + * -AE measurement window is weighted sum of large and center windows + * (bits 2-3). + * -auto white balance is on (bit 1). + * -normal color processing (bit 4 = 0). + */ + /* V4L2_EXPOSURE_AUTO = 0; needs register setting of 0x708E */ + /* V4L2_EXPOSURE_MANUAL = 1 needs register setting of 0x308E */ + mt9v111_device.ifpReg->modeControl &= 0x3fff; + mt9v111_device.ifpReg->modeControl |= (ae_mode & 0x03) << 14; + mt9v111_data.ae_mode = ae_mode; + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_MODE_CONTROL; + data = mt9v111_device.ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + return 0; +} + +/*! + * mt9v111 sensor get AE measurement window mode configuration + * + * @param ae_mode int * + * @return None + */ +static void mt9v111_get_ae_mode(int *ae_mode) +{ + pr_debug("In mt9v111_get_ae_mode(%d)\n", *ae_mode); + + if (ae_mode != NULL) { + *ae_mode = (mt9v111_device.ifpReg->modeControl & 0xc) >> 2; + } +} + +#ifdef MT9V111_DEBUG +/*! + * Set sensor to test mode, which will generate test pattern. + * + * @return none + */ +static void mt9v111_test_pattern(bool flag) +{ + u16 data; + + /* switch to sensor registers */ + mt9v111_write_reg(MT9V111I_ADDR_SPACE_SEL, MT9V111I_SEL_SCA); + + if (flag == true) { + testpattern = MT9V111S_OUTCTRL_TEST_MODE; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) & 0xBF; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + mt9v111_write_reg(MT9V111S_TEST_DATA, 0); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } else { + testpattern = 0; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) | 0x40; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } +} +#endif + + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Given the image capture format in pix, the nominal frame period in + * timeperframe, calculate the required xclk frequency. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + pr_debug("In mt9v111:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = MT9V111_MCLK; + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = MT9V111_CLK_MIN; + p->u.bt656.clock_max = MT9V111_CLK_MAX; + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + pr_debug("In mt9v111:ioctl_s_power\n"); + + sensor->on = on; + + if (on) + gpio_sensor_active(); + else + gpio_sensor_inactive(); + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + int ret = 0; + struct v4l2_captureparm *cparm = &a->parm.capture; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_g_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = mt9v111_data.streamcap.capability; + cparm->timeperframe = + mt9v111_data.streamcap.timeperframe; + cparm->capturemode = mt9v111_data.streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + int ret = 0; + struct v4l2_captureparm *cparm = &a->parm.capture; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_s_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + + /* Check that the new frame rate is allowed. + * Changing the frame rate is not allowed on this + *camera. */ + if (cparm->timeperframe.denominator != + mt9v111_data.streamcap.timeperframe.denominator) { + pr_err("ERROR: mt9v111: ioctl_s_parm: " \ + "This camera does not allow frame rate " + "changes.\n"); + ret = -EINVAL; + } else { + mt9v111_data.streamcap.timeperframe = + cparm->timeperframe; + /* Call any camera functions to match settings. */ + } + + /* Check that new capture mode is supported. */ + if ((cparm->capturemode != 0) && + !(cparm->capturemode & V4L2_MODE_HIGHQUALITY)) { + pr_err("ERROR: mt9v111: ioctl_s_parm: " \ + "unsupported capture mode\n"); + ret = -EINVAL; + } else { + mt9v111_data.streamcap.capturemode = + cparm->capturemode; + /* Call any camera functions to match settings. */ + /* Right now this camera only supports 1 mode. */ + } + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return 0; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_g_fmt_cap.\n"); + pr_debug(" Returning size of %dx%d\n", + sensor->pix.width, sensor->pix.height); + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_queryctrl - V4L2 sensor interface handler for VIDIOC_QUERYCTRL ioctl + * @s: pointer to standard V4L2 device structure + * @qc: standard V4L2 VIDIOC_QUERYCTRL ioctl structure + * + * If the requested control is supported, returns the control information + * from the video_control[] array. Otherwise, returns -EINVAL if the + * control is not supported. + */ +static int ioctl_queryctrl(struct v4l2_int_device *s, struct v4l2_queryctrl *qc) +{ + pr_debug("In mt9v111:ioctl_queryctrl\n"); + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + pr_debug("In mt9v111:ioctl_g_ctrl\n"); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + vc->value = mt9v111_data.brightness; + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + vc->value = mt9v111_data.contrast; + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + vc->value = mt9v111_data.saturation; + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + vc->value = mt9v111_data.hue; + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + vc->value = 0; + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + vc->value = 0; + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + vc->value = mt9v111_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + vc->value = mt9v111_data.blue; + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + vc->value = 0; + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + vc->value = mt9v111_data.ae_mode; + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + vc->value = 0; + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + vc->value = 0; + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + vc->value = 0; + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + vc->value = 0; + break; + default: + pr_debug(" Default case\n"); + return -EPERM; + break; + } + + return 0; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In mt9v111:ioctl_s_ctrl %d\n", + vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + retval = mt9v111_set_saturation(vc->value); + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + retval = mt9v111_set_ae_mode(vc->value); + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + break; + default: + pr_debug(" Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + pr_debug("In mt9v111:ioctl_init\n"); + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + uint32_t clock_rate = MT9V111_MCLK; + + pr_debug("In mt9v111:ioctl_dev_init\n"); + + gpio_sensor_active(); + + set_mclk_rate(&clock_rate); + mt9v111_rate_cal(&reset_frame_rate, clock_rate); + mt9v111_sensor_lib(mt9v111_device.coreReg, mt9v111_device.ifpReg); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc mt9v111_ioctl_desc[] = { + + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + + /*! + * Delinitialise the dev. at slave detach. + * The complement of ioctl_dev_init. + */ +/* {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func *) ioctl_dev_exit}, */ + + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *) ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *) ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *) ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *) ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *) ioctl_init}, + + /*! + * VIDIOC_ENUM_FMT ioctl for the CAPTURE buffer type. + */ +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_enum_fmt_cap}, */ + + /*! + * VIDIOC_TRY_FMT ioctl for the CAPTURE buffer type. + * This ioctl is used to negotiate the image capture size and + * pixel format without actually making it take effect. + */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_try_fmt_cap}, */ + + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *) ioctl_g_fmt_cap}, + + /*! + * If the requested format is supported, configures the HW to use that + * format, returns error code if format not supported or HW can't be + * correctly configured. + */ +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *) ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *) ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *) ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *) ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *) ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave mt9v111_slave = { + .ioctls = mt9v111_ioctl_desc, + .num_ioctls = ARRAY_SIZE(mt9v111_ioctl_desc), +}; + +static struct v4l2_int_device mt9v111_int_device = { + .module = THIS_MODULE, + .name = "mt9v111", + .type = v4l2_int_type_slave, + .u = { + .slave = &mt9v111_slave, + }, +}; + +/*! + * mt9v111 I2C probe function + * Function set in i2c_driver struct. + * Called by insmod mt9v111_camera.ko. + * + * @return Error code indicating success or failure + */ +static int mt9v111_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + + pr_debug("In mt9v111_probe device id is %s\n", id->name); + + /* Set initial values for the sensor struct. */ + memset(&mt9v111_data, 0, sizeof(mt9v111_data)); + mt9v111_data.i2c_client = client; + pr_debug(" client name is %s\n", client->name); + mt9v111_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + mt9v111_data.pix.width = MT9V111_MAX_WIDTH; + mt9v111_data.pix.height = MT9V111_MAX_HEIGHT; + mt9v111_data.streamcap.capability = 0; /* No higher resolution or frame + * frame rate changes supported. + */ + mt9v111_data.streamcap.timeperframe.denominator = MT9V111_FRAME_RATE; + mt9v111_data.streamcap.timeperframe.numerator = 1; + + mt9v111_int_device.priv = &mt9v111_data; + + pr_debug(" type is %d (expect %d)\n", + mt9v111_int_device.type, v4l2_int_type_slave); + pr_debug(" num ioctls is %d\n", + mt9v111_int_device.u.slave->num_ioctls); + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the mt9v111_data structure here.*/ + retval = v4l2_int_device_register(&mt9v111_int_device); + + return retval; +} + +/*! + * Function set in i2c_driver struct. + * Called on rmmod mt9v111_camera.ko + */ +static int mt9v111_remove(struct i2c_client *client) +{ + pr_debug("In mt9v111_remove\n"); + + v4l2_int_device_unregister(&mt9v111_int_device); + return 0; +} + +/*! + * MT9V111 init function. + * Called by insmod mt9v111_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int mt9v111_init(void) +{ + u8 err; + + pr_debug("In mt9v111_init\n"); + + /* Allocate memory for state structures. */ + mt9v111_device.coreReg = (mt9v111_coreReg *) + kmalloc(sizeof(mt9v111_coreReg), GFP_KERNEL); + if (!mt9v111_device.coreReg) + return -1; + memset(mt9v111_device.coreReg, 0, sizeof(mt9v111_coreReg)); + + mt9v111_device.ifpReg = (mt9v111_IFPReg *) + kmalloc(sizeof(mt9v111_IFPReg), GFP_KERNEL); + if (!mt9v111_device.ifpReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + return -1; + } + memset(mt9v111_device.ifpReg, 0, sizeof(mt9v111_IFPReg)); + + /* Set contents of the just created structures. */ + mt9v111_config(); + + /* Tells the i2c driver what functions to call for this driver. */ + err = i2c_add_driver(&mt9v111_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * MT9V111 cleanup function. + * Called on rmmod mt9v111_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit mt9v111_clean(void) +{ + pr_debug("In mt9v111_clean()\n"); + + i2c_del_driver(&mt9v111_i2c_driver); + gpio_sensor_inactive(); + + if (mt9v111_device.coreReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + } + + if (mt9v111_device.ifpReg) { + kfree(mt9v111_device.ifpReg); + mt9v111_device.ifpReg = NULL; + } +} + +module_init(mt9v111_init); +module_exit(mt9v111_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Mt9v111 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mt9v111.h b/drivers/media/video/mxc/capture/mt9v111.h new file mode 100644 index 000000000000..993520927f65 --- /dev/null +++ b/drivers/media/video/mxc/capture/mt9v111.h @@ -0,0 +1,431 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Camera Sensor Drivers + */ + +/*! + * @file mt9v111.h + * + * @brief MT9V111 Camera Header file + * + * This header file contains defines and structures for the iMagic mi8012 + * aka the Micron mt9v111 camera. + * + * @ingroup Camera + */ + +#ifndef MT9V111_H_ +#define MT9V111_H_ + +/*! + * Basic camera values + */ +#define MT9V111_FRAME_RATE 30 +#define MT9V111_MCLK 27000000 /* Desired clock rate */ +#define MT9V111_CLK_MIN 12000000 /* This clock rate yields 15 fps */ +#define MT9V111_CLK_MAX 27000000 +#define MT9V111_MAX_WIDTH 640 /* Max width for this camera */ +#define MT9V111_MAX_HEIGHT 480 /* Max height for this camera */ + +/*! + * mt9v111 IFP REGISTER BANK MAP + */ +#define MT9V111I_ADDR_SPACE_SEL 0x1 +#define MT9V111I_BASE_MAXTRIX_SIGN 0x2 +#define MT9V111I_BASE_MAXTRIX_SCALE15 0x3 +#define MT9V111I_BASE_MAXTRIX_SCALE69 0x4 +#define MT9V111I_APERTURE_GAIN 0x5 +#define MT9V111I_MODE_CONTROL 0x6 +#define MT9V111I_SOFT_RESET 0x7 +#define MT9V111I_FORMAT_CONTROL 0x8 +#define MT9V111I_BASE_MATRIX_CFK1 0x9 +#define MT9V111I_BASE_MATRIX_CFK2 0xa +#define MT9V111I_BASE_MATRIX_CFK3 0xb +#define MT9V111I_BASE_MATRIX_CFK4 0xc +#define MT9V111I_BASE_MATRIX_CFK5 0xd +#define MT9V111I_BASE_MATRIX_CFK6 0xe +#define MT9V111I_BASE_MATRIX_CFK7 0xf +#define MT9V111I_BASE_MATRIX_CFK8 0x10 +#define MT9V111I_BASE_MATRIX_CFK9 0x11 +#define MT9V111I_AWB_POSITION 0x12 +#define MT9V111I_AWB_RED_GAIN 0x13 +#define MT9V111I_AWB_BLUE_GAIN 0x14 +#define MT9V111I_DELTA_MATRIX_CF_SIGN 0x15 +#define MT9V111I_DELTA_MATRIX_CF_D1 0x16 +#define MT9V111I_DELTA_MATRIX_CF_D2 0x17 +#define MT9V111I_DELTA_MATRIX_CF_D3 0x18 +#define MT9V111I_DELTA_MATRIX_CF_D4 0x19 +#define MT9V111I_DELTA_MATRIX_CF_D5 0x1a +#define MT9V111I_DELTA_MATRIX_CF_D6 0x1b +#define MT9V111I_DELTA_MATRIX_CF_D7 0x1c +#define MT9V111I_DELTA_MATRIX_CF_D8 0x1d +#define MT9V111I_DELTA_MATRIX_CF_D9 0x1e +#define MT9V111I_LUMINANCE_LIMIT_WB 0x20 +#define MT9V111I_RBG_MANUUAL_WB 0x21 +#define MT9V111I_AWB_RED_LIMIT 0x22 +#define MT9V111I_AWB_BLUE_LIMIT 0x23 +#define MT9V111I_MATRIX_ADJUST_LIMIT 0x24 +#define MT9V111I_AWB_SPEED 0x25 +#define MT9V111I_H_BOUND_AE 0x26 +#define MT9V111I_V_BOUND_AE 0x27 +#define MT9V111I_H_BOUND_AE_CEN_WIN 0x2b +#define MT9V111I_V_BOUND_AE_CEN_WIN 0x2c +#define MT9V111I_BOUND_AWB_WIN 0x2d +#define MT9V111I_AE_PRECISION_TARGET 0x2e +#define MT9V111I_AE_SPEED 0x2f +#define MT9V111I_RED_AWB_MEASURE 0x30 +#define MT9V111I_LUMA_AWB_MEASURE 0x31 +#define MT9V111I_BLUE_AWB_MEASURE 0x32 +#define MT9V111I_LIMIT_SHARP_SATU_CTRL 0x33 +#define MT9V111I_LUMA_OFFSET 0x34 +#define MT9V111I_CLIP_LIMIT_OUTPUT_LUMI 0x35 +#define MT9V111I_GAIN_LIMIT_AE 0x36 +#define MT9V111I_SHUTTER_WIDTH_LIMIT_AE 0x37 +#define MT9V111I_UPPER_SHUTTER_DELAY_LIM 0x39 +#define MT9V111I_OUTPUT_FORMAT_CTRL2 0x3a +#define MT9V111I_IPF_BLACK_LEVEL_SUB 0x3b +#define MT9V111I_IPF_BLACK_LEVEL_ADD 0x3c +#define MT9V111I_ADC_LIMIT_AE_ADJ 0x3d +#define MT9V111I_GAIN_THRE_CCAM_ADJ 0x3e +#define MT9V111I_LINEAR_AE 0x3f +#define MT9V111I_THRESHOLD_EDGE_DEFECT 0x47 +#define MT9V111I_LUMA_SUM_MEASURE 0x4c +#define MT9V111I_TIME_ADV_SUM_LUMA 0x4d +#define MT9V111I_MOTION 0x52 +#define MT9V111I_GAMMA_KNEE_Y12 0x53 +#define MT9V111I_GAMMA_KNEE_Y34 0x54 +#define MT9V111I_GAMMA_KNEE_Y56 0x55 +#define MT9V111I_GAMMA_KNEE_Y78 0x56 +#define MT9V111I_GAMMA_KNEE_Y90 0x57 +#define MT9V111I_GAMMA_VALUE_Y0 0x58 +#define MT9V111I_SHUTTER_60 0x59 +#define MT9V111I_SEARCH_FLICK_60 0x5c +#define MT9V111I_RATIO_IMAGE_GAIN_BASE 0x5e +#define MT9V111I_RATIO_IMAGE_GAIN_DELTA 0x5f +#define MT9V111I_SIGN_VALUE_REG5F 0x60 +#define MT9V111I_AE_GAIN 0x62 +#define MT9V111I_MAX_GAIN_AE 0x67 +#define MT9V111I_LENS_CORRECT_CTRL 0x80 +#define MT9V111I_SHADING_PARAMETER1 0x81 +#define MT9V111I_SHADING_PARAMETER2 0x82 +#define MT9V111I_SHADING_PARAMETER3 0x83 +#define MT9V111I_SHADING_PARAMETER4 0x84 +#define MT9V111I_SHADING_PARAMETER5 0x85 +#define MT9V111I_SHADING_PARAMETER6 0x86 +#define MT9V111I_SHADING_PARAMETER7 0x87 +#define MT9V111I_SHADING_PARAMETER8 0x88 +#define MT9V111I_SHADING_PARAMETER9 0x89 +#define MT9V111I_SHADING_PARAMETER10 0x8A +#define MT9V111I_SHADING_PARAMETER11 0x8B +#define MT9V111I_SHADING_PARAMETER12 0x8C +#define MT9V111I_SHADING_PARAMETER13 0x8D +#define MT9V111I_SHADING_PARAMETER14 0x8E +#define MT9V111I_SHADING_PARAMETER15 0x8F +#define MT9V111I_SHADING_PARAMETER16 0x90 +#define MT9V111I_SHADING_PARAMETER17 0x91 +#define MT9V111I_SHADING_PARAMETER18 0x92 +#define MT9V111I_SHADING_PARAMETER19 0x93 +#define MT9V111I_SHADING_PARAMETER20 0x94 +#define MT9V111I_SHADING_PARAMETER21 0x95 +#define MT9V111i_FLASH_CTRL 0x98 +#define MT9V111i_LINE_COUNTER 0x99 +#define MT9V111i_FRAME_COUNTER 0x9A +#define MT9V111i_H_PAN 0xA5 +#define MT9V111i_H_ZOOM 0xA6 +#define MT9V111i_H_SIZE 0xA7 +#define MT9V111i_V_PAN 0xA8 +#define MT9V111i_V_ZOOM 0xA9 +#define MT9V111i_V_SIZE 0xAA + +#define MT9V111I_SEL_IFP 0x1 +#define MT9V111I_SEL_SCA 0x4 +#define MT9V111I_FC_RGB_OR_YUV 0x1000 + +/*! + * Mt9v111 SENSOR CORE REGISTER BANK MAP + */ +#define MT9V111S_ADDR_SPACE_SEL 0x1 +#define MT9V111S_COLUMN_START 0x2 +#define MT9V111S_WIN_HEIGHT 0x3 +#define MT9V111S_WIN_WIDTH 0x4 +#define MT9V111S_HOR_BLANKING 0x5 +#define MT9V111S_VER_BLANKING 0x6 +#define MT9V111S_OUTPUT_CTRL 0x7 +#define MT9V111S_ROW_START 0x8 +#define MT9V111S_SHUTTER_WIDTH 0x9 +#define MT9V111S_PIXEL_CLOCK_SPEED 0xa +#define MT9V111S_RESTART 0xb +#define MT9V111S_SHUTTER_DELAY 0xc +#define MT9V111S_RESET 0xd +#define MT9V111S_COLUMN_START_IN_ZOOM 0x12 +#define MT9V111S_ROW_START_IN_ZOOM 0x13 +#define MT9V111S_DIGITAL_ZOOM 0x1e +#define MT9V111S_READ_MODE 0x20 +#define MT9V111S_DAC_CTRL 0x27 +#define MT9V111S_GREEN1_GAIN 0x2b +#define MT9V111S_BLUE_GAIN 0x2c +#define MT9V111S_READ_GAIN 0x2d +#define MT9V111S_GREEN2_GAIN 0x2e +#define MT9V111S_ROW_NOISE_CTRL 0x30 +#define MT9V111S_DARK_TARGET_W 0x31 +#define MT9V111S_TEST_DATA 0x32 +#define MT9V111S_GLOBAL_GAIN 0x35 +#define MT9V111S_SENSOR_CORE_VERSION 0x36 +#define MT9V111S_DARK_TARGET_WO 0x37 +#define MT9V111S_VERF_DAC 0x41 +#define MT9V111S_VCM_VCL 0x42 +#define MT9V111S_DISABLE_BYPASS 0x58 +#define MT9V111S_CALIB_MEAN_TEST 0x59 +#define MT9V111S_DARK_G1_AVE 0x5B +#define MT9V111S_DARK_G2_AVE 0x5C +#define MT9V111S_DARK_R_AVE 0x5D +#define MT9V111S_DARK_B_AVE 0x5E +#define MT9V111S_CAL_THRESHOLD 0x5f +#define MT9V111S_CAL_G1 0x60 +#define MT9V111S_CAL_G2 0x61 +#define MT9V111S_CAL_CTRL 0x62 +#define MT9V111S_CAL_R 0x63 +#define MT9V111S_CAL_B 0x64 +#define MT9V111S_CHIP_ENABLE 0xF1 +#define MT9V111S_CHIP_VERSION 0xFF + +/* OUTPUT_CTRL */ +#define MT9V111S_OUTCTRL_SYNC 0x1 +#define MT9V111S_OUTCTRL_CHIP_ENABLE 0x2 +#define MT9V111S_OUTCTRL_TEST_MODE 0x40 + +/* READ_MODE */ +#define MT9V111S_RM_NOBADFRAME 0x1 +#define MT9V111S_RM_NODESTRUCT 0x2 +#define MT9V111S_RM_COLUMNSKIP 0x4 +#define MT9V111S_RM_ROWSKIP 0x8 +#define MT9V111S_RM_BOOSTEDRESET 0x1000 +#define MT9V111S_RM_COLUMN_LATE 0x10 +#define MT9V111S_RM_ROW_LATE 0x80 +#define MT9V111S_RM_RIGTH_TO_LEFT 0x4000 +#define MT9V111S_RM_BOTTOM_TO_TOP 0x8000 + +/*! I2C Slave Address */ +#define MT9V111_I2C_ADDRESS 0x48 + +/*! + * The image resolution enum for the mt9v111 sensor + */ +typedef enum { + MT9V111_OutputResolution_VGA = 0, /*!< VGA size */ + MT9V111_OutputResolution_QVGA, /*!< QVGA size */ + MT9V111_OutputResolution_CIF, /*!< CIF size */ + MT9V111_OutputResolution_QCIF, /*!< QCIF size */ + MT9V111_OutputResolution_QQVGA, /*!< QQVGA size */ + MT9V111_OutputResolution_SXGA /*!< SXGA size */ +} MT9V111_OutputResolution; + +enum { + MT9V111_WINWIDTH = 0x287, + MT9V111_WINWIDTH_DEFAULT = 0x287, + MT9V111_WINWIDTH_MIN = 0x9, + + MT9V111_WINHEIGHT = 0x1E7, + MT9V111_WINHEIGHT_DEFAULT = 0x1E7, + + MT9V111_HORZBLANK_DEFAULT = 0x26, + MT9V111_HORZBLANK_MIN = 0x9, + MT9V111_HORZBLANK_MAX = 0x3FF, + + MT9V111_VERTBLANK_DEFAULT = 0x4, + MT9V111_VERTBLANK_MIN = 0x3, + MT9V111_VERTBLANK_MAX = 0xFFF, +}; + +/*! + * Mt9v111 Core Register structure. + */ +typedef struct { + u32 addressSelect; /*!< select address bank for Core Register 0x4 */ + u32 columnStart; /*!< Starting Column */ + u32 windowHeight; /*!< Window Height */ + u32 windowWidth; /*!< Window Width */ + u32 horizontalBlanking; /*!< Horizontal Blank time, in pixels */ + u32 verticalBlanking; /*!< Vertical Blank time, in pixels */ + u32 outputControl; /*!< Register to control sensor output */ + u32 rowStart; /*!< Starting Row */ + u32 shutterWidth; + u32 pixelClockSpeed; /*!< pixel date rate */ + u32 restart; /*!< Abandon the readout of current frame */ + u32 shutterDelay; + u32 reset; /*!< reset the sensor to the default mode */ + u32 zoomColStart; /*!< Column start in the Zoom mode */ + u32 zomRowStart; /*!< Row start in the Zoom mode */ + u32 digitalZoom; /*!< 1 means zoom by 2 */ + u32 readMode; /*!< Readmode: aspects of the readout of the sensor */ + u32 dACStandbyControl; + u32 green1Gain; /*!< Gain Settings */ + u32 blueGain; + u32 redGain; + u32 green2Gain; + u32 rowNoiseControl; + u32 darkTargetwNC; + u32 testData; /*!< test mode */ + u32 globalGain; + u32 chipVersion; + u32 darkTargetwoNC; + u32 vREFDACs; + u32 vCMandVCL; + u32 disableBypass; + u32 calibMeanTest; + u32 darkG1average; + u32 darkG2average; + u32 darkRaverage; + u32 darkBaverage; + u32 calibThreshold; + u32 calibGreen1; + u32 calibGreen2; + u32 calibControl; + u32 calibRed; + u32 calibBlue; + u32 chipEnable; /*!< Image core Registers written by image flow processor */ +} mt9v111_coreReg; + +/*! + * Mt9v111 IFP Register structure. + */ +typedef struct { + u32 addrSpaceSel; /*!< select address bank for Core Register 0x1 */ + u32 baseMaxtrixSign; /*!< sign of coefficient for base color correction matrix */ + u32 baseMaxtrixScale15; /*!< scaling of color correction coefficient K1-5 */ + u32 baseMaxtrixScale69; /*!< scaling of color correction coefficient K6-9 */ + u32 apertureGain; /*!< sharpening */ + u32 modeControl; /*!< bit 7 CCIR656 sync codes are embedded in the image */ + u32 softReset; /*!< Image processing mode: 1 reset mode, 0 operational mode */ + u32 formatControl; /*!< bit12 1 for RGB565, 0 for YcrCb */ + u32 baseMatrixCfk1; /*!< K1 Color correction coefficient */ + u32 baseMatrixCfk2; /*!< K2 Color correction coefficient */ + u32 baseMatrixCfk3; /*!< K3 Color correction coefficient */ + u32 baseMatrixCfk4; /*!< K4 Color correction coefficient */ + u32 baseMatrixCfk5; /*!< K5 Color correction coefficient */ + u32 baseMatrixCfk6; /*!< K6 Color correction coefficient */ + u32 baseMatrixCfk7; /*!< K7 Color correction coefficient */ + u32 baseMatrixCfk8; /*!< K8 Color correction coefficient */ + u32 baseMatrixCfk9; /*!< K9 Color correction coefficient */ + u32 awbPosition; /*!< Current position of AWB color correction matrix */ + u32 awbRedGain; /*!< Current value of AWB red channel gain */ + u32 awbBlueGain; /*!< Current value of AWB blue channel gain */ + u32 deltaMatrixCFSign; /*!< Sign of coefficients of delta color correction matrix register */ + u32 deltaMatrixCFD1; /*!< D1 Delta coefficient */ + u32 deltaMatrixCFD2; /*!< D2 Delta coefficient */ + u32 deltaMatrixCFD3; /*!< D3 Delta coefficient */ + u32 deltaMatrixCFD4; /*!< D4 Delta coefficient */ + u32 deltaMatrixCFD5; /*!< D5 Delta coefficient */ + u32 deltaMatrixCFD6; /*!< D6 Delta coefficient */ + u32 deltaMatrixCFD7; /*!< D7 Delta coefficient */ + u32 deltaMatrixCFD8; /*!< D8 Delta coefficient */ + u32 deltaMatrixCFD9; /*!< D9 Delta coefficient */ + u32 lumLimitWB; /*!< Luminance range of pixels considered in WB statistics */ + u32 RBGManualWB; /*!< Red and Blue color channel gains for manual white balance */ + u32 awbRedLimit; /*!< Limits on Red channel gain adjustment through AWB */ + u32 awbBlueLimit; /*!< Limits on Blue channel gain adjustment through AWB */ + u32 matrixAdjLimit; /*!< Limits on color correction matrix adjustment through AWB */ + u32 awbSpeed; /*!< AWB speed and color saturation control */ + u32 HBoundAE; /*!< Horizontal boundaries of AWB measurement window */ + u32 VBoundAE; /*!< Vertical boundaries of AWB measurement window */ + u32 HBoundAECenWin; /*!< Horizontal boundaries of AE measurement window for backlight compensation */ + u32 VBoundAECenWin; /*!< Vertical boundaries of AE measurement window for backlight compensation */ + u32 boundAwbWin; /*!< Boundaries of AWB measurement window */ + u32 AEPrecisionTarget; /*!< Auto exposure target and precision control */ + u32 AESpeed; /*!< AE speed and sensitivity control register */ + u32 redAWBMeasure; /*!< Measure of the red channel value used by AWB */ + u32 lumaAWBMeasure; /*!< Measure of the luminance channel value used by AWB */ + u32 blueAWBMeasure; /*!< Measure of the blue channel value used by AWB */ + u32 limitSharpSatuCtrl; /*!< Automatic control of sharpness and color saturation */ + u32 lumaOffset; /*!< Luminance offset control (brightness control) */ + u32 clipLimitOutputLumi; /*!< Clipping limits for output luminance */ + u32 gainLimitAE; /*!< Imager gain limits for AE adjustment */ + u32 shutterWidthLimitAE; /*!< Shutter width (exposure time) limits for AE adjustment */ + u32 upperShutterDelayLi; /*!< Upper Shutter Delay Limit */ + u32 outputFormatCtrl2; /*!< Output Format Control 2 + 00 = 16-bit RGB565. + 01 = 15-bit RGB555. + 10 = 12-bit RGB444x. + 11 = 12-bit RGBx444. */ + u32 ipfBlackLevelSub; /*!< IFP black level subtraction */ + u32 ipfBlackLevelAdd; /*!< IFP black level addition */ + u32 adcLimitAEAdj; /*!< ADC limits for AE adjustment */ + u32 agimnThreCamAdj; /*!< Gain threshold for CCM adjustment */ + u32 linearAE; + u32 thresholdEdgeDefect; /*!< Edge threshold for interpolation and defect correction */ + u32 lumaSumMeasure; /*!< Luma measured by AE engine */ + u32 timeAdvSumLuma; /*!< Time-averaged luminance value tracked by auto exposure */ + u32 motion; /*!< 1 when motion is detected */ + u32 gammaKneeY12; /*!< Gamma knee points Y1 and Y2 */ + u32 gammaKneeY34; /*!< Gamma knee points Y3 and Y4 */ + u32 gammaKneeY56; /*!< Gamma knee points Y5 and Y6 */ + u32 gammaKneeY78; /*!< Gamma knee points Y7 and Y8 */ + u32 gammaKneeY90; /*!< Gamma knee points Y9 and Y10 */ + u32 gammaKneeY0; /*!< Gamma knee point Y0 */ + u32 shutter_width_60; + u32 search_flicker_60; + u32 ratioImageGainBase; + u32 ratioImageGainDelta; + u32 signValueReg5F; + u32 aeGain; + u32 maxGainAE; + u32 lensCorrectCtrl; + u32 shadingParameter1; /*!< Shade Parameters */ + u32 shadingParameter2; + u32 shadingParameter3; + u32 shadingParameter4; + u32 shadingParameter5; + u32 shadingParameter6; + u32 shadingParameter7; + u32 shadingParameter8; + u32 shadingParameter9; + u32 shadingParameter10; + u32 shadingParameter11; + u32 shadingParameter12; + u32 shadingParameter13; + u32 shadingParameter14; + u32 shadingParameter15; + u32 shadingParameter16; + u32 shadingParameter17; + u32 shadingParameter18; + u32 shadingParameter19; + u32 shadingParameter20; + u32 shadingParameter21; + u32 flashCtrl; /*!< Flash control */ + u32 lineCounter; /*!< Line counter */ + u32 frameCounter; /*!< Frame counter */ + u32 HPan; /*!< Horizontal pan in decimation */ + u32 HZoom; /*!< Horizontal zoom in decimation */ + u32 HSize; /*!< Horizontal output size iIn decimation */ + u32 VPan; /*!< Vertical pan in decimation */ + u32 VZoom; /*!< Vertical zoom in decimation */ + u32 VSize; /*!< Vertical output size in decimation */ +} mt9v111_IFPReg; + +/*! + * mt9v111 Config structure + */ +typedef struct { + mt9v111_coreReg *coreReg; /*!< Sensor Core Register Bank */ + mt9v111_IFPReg *ifpReg; /*!< IFP Register Bank */ +} mt9v111_conf; + +typedef struct { + u8 index; + u16 width; + u16 height; +} mt9v111_image_format; + +#endif /* MT9V111_H_ */ diff --git a/drivers/media/video/mxc/capture/mxc_v4l2_capture.c b/drivers/media/video/mxc/capture/mxc_v4l2_capture.c new file mode 100644 index 000000000000..68da313783a2 --- /dev/null +++ b/drivers/media/video/mxc/capture/mxc_v4l2_capture.c @@ -0,0 +1,2814 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/capture/mxc_v4l2_capture.c + * + * @brief Mxc Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#define init_MUTEX(sem) sema_init(sem, 1) + +static int video_nr = -1, local_buf_num; +static cam_data *g_cam; + +/*! This data is used for the output to the display. */ +#define MXC_V4L2_CAPTURE_NUM_OUTPUTS 3 +#define MXC_V4L2_CAPTURE_NUM_INPUTS 2 +static struct v4l2_output mxc_capture_outputs[MXC_V4L2_CAPTURE_NUM_OUTPUTS] = { + { + .index = 0, + .name = "DISP3 BG", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, + { + .index = 1, + .name = "DISP3 BG - DI1", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, + { + .index = 2, + .name = "DISP3 FG", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, +}; + +static struct v4l2_input mxc_capture_inputs[MXC_V4L2_CAPTURE_NUM_INPUTS] = { + { + .index = 0, + .name = "CSI IC MEM", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 0, + .tuner = 0, + .std = V4L2_STD_UNKNOWN, + .status = 0, + }, + { + .index = 1, + .name = "CSI MEM", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 0, + .tuner = 0, + .std = V4L2_STD_UNKNOWN, + .status = V4L2_IN_ST_NO_POWER, + }, +}; + +/*! List of TV input video formats supported. The video formats is corresponding + * to the v4l2_id in video_fmt_t. + * Currently, only PAL and NTSC is supported. Needs to be expanded in the + * future. + */ +typedef enum { + TV_NTSC = 0, /*!< Locked on (M) NTSC video signal. */ + TV_PAL, /*!< (B, G, H, I, N)PAL video signal. */ + TV_NOT_LOCKED, /*!< Not locked on a signal. */ +} video_fmt_idx; + +/*! Number of video standards supported (including 'not locked' signal). */ +#define TV_STD_MAX (TV_NOT_LOCKED + 1) + +/*! Video format structure. */ +typedef struct { + int v4l2_id; /*!< Video for linux ID. */ + char name[16]; /*!< Name (e.g., "NTSC", "PAL", etc.) */ + u16 raw_width; /*!< Raw width. */ + u16 raw_height; /*!< Raw height. */ + u16 active_width; /*!< Active width. */ + u16 active_height; /*!< Active height. */ + u16 active_top; /*!< Active top. */ + u16 active_left; /*!< Active left. */ +} video_fmt_t; + +/*! + * Description of video formats supported. + * + * PAL: raw=720x625, active=720x576. + * NTSC: raw=720x525, active=720x480. + */ +static video_fmt_t video_fmts[] = { + { /*! NTSC */ + .v4l2_id = V4L2_STD_NTSC, + .name = "NTSC", + .raw_width = 720, /* SENS_FRM_WIDTH */ + .raw_height = 525, /* SENS_FRM_HEIGHT */ + .active_width = 720, /* ACT_FRM_WIDTH */ + .active_height = 480, /* ACT_FRM_HEIGHT */ + .active_top = 13, + .active_left = 0, + }, + { /*! (B, G, H, I, N) PAL */ + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 576, + .active_top = 0, + .active_left = 0, + }, + { /*! Unlocked standard */ + .v4l2_id = V4L2_STD_ALL, + .name = "Autodetect", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 576, + .active_top = 0, + .active_left = 0, + }, +}; + +/*!* Standard index of TV. */ +static video_fmt_idx video_index = TV_NOT_LOCKED; + +static int mxc_v4l2_master_attach(struct v4l2_int_device *slave); +static void mxc_v4l2_master_detach(struct v4l2_int_device *slave); +static int start_preview(cam_data *cam); +static int stop_preview(cam_data *cam); + +/*! Information about this driver. */ +static struct v4l2_int_master mxc_v4l2_master = { + .attach = mxc_v4l2_master_attach, + .detach = mxc_v4l2_master_detach, +}; + +static struct v4l2_int_device mxc_v4l2_int_device = { + .module = THIS_MODULE, + .name = "mxc_v4l2_cap", + .type = v4l2_int_type_master, + .u = { + .master = &mxc_v4l2_master, + }, +}; + +/*************************************************************************** + * Functions for handling Frame buffers. + **************************************************************************/ + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int mxc_free_frame_buf(cam_data *cam) +{ + int i; + + pr_debug("MVC: In mxc_free_frame_buf\n"); + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data* + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_frame_buf(cam_data *cam, int count) +{ + int i; + + pr_debug("In MVC:mxc_allocate_frame_buf - size=%d\n", + cam->v2f.fmt.pix.sizeimage); + + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = + dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->frame[i].paddress, + GFP_DMA | GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + pr_err("ERROR: v4l2 capture: " + "mxc_allocate_frame_buf failed.\n"); + mxc_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void mxc_free_frames(cam_data *cam) +{ + int i; + + pr_debug("In MVC:mxc_free_frames\n"); + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + } + + cam->enc_counter = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int mxc_v4l2_buffer_status(cam_data *cam, struct v4l2_buffer *buf) +{ + pr_debug("In MVC:mxc_v4l2_buffer_status\n"); + + if (buf->index < 0 || buf->index >= FRAME_NUM) { + pr_err("ERROR: v4l2 capture: mxc_v4l2_buffer_status buffers " + "not allocated\n"); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + return 0; +} + +/*************************************************************************** + * Functions for handling the video stream. + **************************************************************************/ + +/*! + * Indicates whether the palette is supported. + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_UYVY) || + (palette == V4L2_PIX_FMT_YUYV) || + (palette == V4L2_PIX_FMT_YUV420) || + (palette == V4L2_PIX_FMT_NV12)); +} + +/*! + * Start the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamon(cam_data *cam) +{ + struct mxc_v4l_frame *frame; + int err = 0; + + pr_debug("In MVC:mxc_streamon\n"); + + if (NULL == cam) { + pr_err("ERROR! cam parameter is NULL\n"); + return -1; + } + + if (cam->capture_on) { + pr_err("ERROR: v4l2 capture: Capture stream has been turned " + " on\n"); + return -1; + } + + if (list_empty(&cam->ready_q)) { + pr_err("ERROR: v4l2 capture: mxc_streamon buffer has not been " + "queued yet\n"); + return -EINVAL; + } + + cam->capture_pid = current->pid; + + if (cam->overlay_on == true) + stop_preview(cam); + + if (cam->enc_enable) { + err = cam->enc_enable(cam); + if (err != 0) { + return err; + } + } + + cam->ping_pong_csi = 0; + local_buf_num = 0; + if (cam->enc_update_eba) { + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + frame->ipu_buf_num = cam->ping_pong_csi; + err = cam->enc_update_eba(frame->buffer.m.offset, + &cam->ping_pong_csi); + + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + frame->ipu_buf_num = cam->ping_pong_csi; + err |= cam->enc_update_eba(frame->buffer.m.offset, + &cam->ping_pong_csi); + } else { + return -EINVAL; + } + + if (cam->overlay_on == true) + start_preview(cam); + + if (cam->enc_enable_csi) { + err = cam->enc_enable_csi(cam); + if (err != 0) + return err; + } + + cam->capture_on = true; + + return err; +} + +/*! + * Shut down the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamoff(cam_data *cam) +{ + int err = 0; + + pr_debug("In MVC:mxc_streamoff\n"); + + if (cam->capture_on == false) + return 0; + + if (cam->enc_disable_csi) { + err = cam->enc_disable_csi(cam); + if (err != 0) + return err; + } + if (cam->enc_disable) + err = cam->enc_disable(cam); + + mxc_free_frames(cam); + mxc_capture_inputs[cam->current_input].status |= V4L2_IN_ST_NO_POWER; + cam->capture_on = false; + return err; +} + +/*! + * Valid and adjust the overlay window size, position + * + * @param cam structure cam_data * + * @param win struct v4l2_window * + * + * @return 0 + */ +static int verify_preview(cam_data *cam, struct v4l2_window *win) +{ + int i = 0, width_bound = 0, height_bound = 0; + int *width, *height; + unsigned int ipu_ch = CHAN_NONE; + struct fb_info *bg_fbi = NULL, *fbi = NULL; + bool foregound_fb; + mm_segment_t old_fs; + + pr_debug("In MVC: verify_preview\n"); + + do { + fbi = (struct fb_info *)registered_fb[i]; + if (fbi == NULL) { + pr_err("ERROR: verify_preview frame buffer NULL.\n"); + return -1; + } + + /* Which DI supports 2 layers? */ + if (strncmp(fbi->fix.id, "DISP3 BG", 8) == 0) { + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_IPU_CHAN, + (unsigned long)&ipu_ch); + set_fs(old_fs); + } + if (ipu_ch == MEM_BG_SYNC) { + bg_fbi = fbi; + pr_debug("Found background frame buffer.\n"); + } + } + + /* Found the frame buffer to preview on. */ + if (strcmp(fbi->fix.id, + mxc_capture_outputs[cam->output].name) == 0) { + if (strcmp(fbi->fix.id, "DISP3 FG") == 0) + foregound_fb = true; + + cam->overlay_fb = fbi; + break; + } + } while (++i < FB_MAX); + + if (foregound_fb) { + width_bound = bg_fbi->var.xres; + height_bound = bg_fbi->var.yres; + + if (win->w.width + win->w.left > bg_fbi->var.xres || + win->w.height + win->w.top > bg_fbi->var.yres) { + pr_err("ERROR: FG window position exceeds.\n"); + return -1; + } + } else { + /* 4 bytes alignment for BG */ + width_bound = cam->overlay_fb->var.xres; + height_bound = cam->overlay_fb->var.yres; + + if (cam->overlay_fb->var.bits_per_pixel == 24) { + win->w.left -= win->w.left % 4; + } else if (cam->overlay_fb->var.bits_per_pixel == 16) { + win->w.left -= win->w.left % 2; + } + + if (win->w.width + win->w.left > cam->overlay_fb->var.xres) + win->w.width = cam->overlay_fb->var.xres - win->w.left; + if (win->w.height + win->w.top > cam->overlay_fb->var.yres) + win->w.height = cam->overlay_fb->var.yres - win->w.top; + } + + /* stride line limitation */ + win->w.height -= win->w.height % 8; + win->w.width -= win->w.width % 8; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &win->w.width; + width = &win->w.height; + } else { + width = &win->w.width; + height = &win->w.height; + } + + if (*width == 0 || *height == 0) { + pr_err("ERROR: v4l2 capture: width or height" + " too small.\n"); + return -EINVAL; + } + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + if (*width + win->w.left > width_bound) { + pr_err("ERROR: v4l2 capture: width exceeds " + "resize limit.\n"); + return -1; + } + pr_err("ERROR: v4l2 capture: width exceeds limit. " + "Resize to %d.\n", + *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + if (*height + win->w.top > height_bound) { + pr_err("ERROR: v4l2 capture: height exceeds " + "resize limit.\n"); + return -1; + } + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", + *height); + } + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data *cam) +{ + int err = 0; + + pr_debug("MVC: start_preview\n"); + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) + err = prp_vf_sdc_select(cam); + else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_PRIMARY) + err = prp_vf_sdc_select_bg(cam); + if (err != 0) + return err; + + err = cam->vf_start_sdc(cam); + if (err != 0) + return err; + + if (cam->vf_enable_csi) + err = cam->vf_enable_csi(cam); + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return err; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data *cam) +{ + int err = 0; + + pr_debug("MVC: stop preview\n"); + + if (cam->vf_disable_csi) { + err = cam->vf_disable_csi(cam); + if (err != 0) + return err; + } + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) + err = prp_vf_sdc_deselect(cam); + else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_PRIMARY) + err = prp_vf_sdc_deselect_bg(cam); + + return err; +} + +/*************************************************************************** + * VIDIOC Functions. + **************************************************************************/ + +/*! + * V4L2 - mxc_v4l2_g_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + + pr_debug("In MVC: mxc_v4l2_g_fmt type=%d\n", f->type); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + f->fmt.pix = cam->v2f.fmt.pix; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + f->fmt.win = cam->win; + break; + default: + pr_debug(" type is invalid\n"); + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return retval; +} + +/*! + * V4L2 - mxc_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + int *width, *height; + + pr_debug("In MVC: mxc_v4l2_s_fmt\n"); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_err("ERROR: v4l2 capture: mxc_v4l2_s_fmt: format " + "not supported\n"); + return -EINVAL; + } + + /* + * Force the capture window resolution to be crop bounds + * for CSI MEM input mode. + */ + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { + f->fmt.pix.width = cam->crop_current.width; + f->fmt.pix.height = cam->crop_current.height; + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &f->fmt.pix.width; + width = &f->fmt.pix.height; + } else { + width = &f->fmt.pix.width; + height = &f->fmt.pix.height; + } + + /* stride line limitation */ + *width -= *width % 8; + *height -= *height % 8; + + if (*width == 0 || *height == 0) { + pr_err("ERROR: v4l2 capture: width or height" + " too small.\n"); + return -EINVAL; + } + + if ((cam->crop_current.width / *width > 8) || + ((cam->crop_current.width / *width == 8) && + (cam->crop_current.width % *width))) { + *width = cam->crop_current.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + pr_err("ERROR: v4l2 capture: width exceeds limit " + "resize to %d.\n", + *width); + } + + if ((cam->crop_current.height / *height > 8) || + ((cam->crop_current.height / *height == 8) && + (cam->crop_current.height % *height))) { + *height = cam->crop_current.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", + *height); + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_RGB565: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_BGR24: + size = f->fmt.pix.width * f->fmt.pix.height * 3; + bytesperline = f->fmt.pix.width * 3; + break; + case V4L2_PIX_FMT_RGB24: + size = f->fmt.pix.width * f->fmt.pix.height * 3; + bytesperline = f->fmt.pix.width * 3; + break; + case V4L2_PIX_FMT_BGR32: + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + break; + case V4L2_PIX_FMT_RGB32: + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + break; + case V4L2_PIX_FMT_YUV422P: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_NV12: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width; + break; + default: + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + if (f->fmt.pix.sizeimage < size) { + f->fmt.pix.sizeimage = size; + } else { + size = f->fmt.pix.sizeimage; + } + + cam->v2f.fmt.pix = f->fmt.pix; + + if (cam->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&cam->offset, + (void *)cam->v2f.fmt.pix.priv, + sizeof(cam->offset))) { + retval = -EFAULT; + break; + } + } + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + retval = verify_preview(cam, &f->fmt.win); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return retval; +} + +/*! + * get control param + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_ctrl(cam_data *cam, struct v4l2_control *c) +{ + int status = 0; + + pr_debug("In MVC:mxc_v4l2_g_ctrl\n"); + + /* probably don't need to store the values that can be retrieved, + * locally, but they are for now. */ + switch (c->id) { + case V4L2_CID_HFLIP: + /* This is handled in the ipu. */ + if (cam->rotation == IPU_ROTATE_HORIZ_FLIP) + c->value = 1; + break; + case V4L2_CID_VFLIP: + /* This is handled in the ipu. */ + if (cam->rotation == IPU_ROTATE_VERT_FLIP) + c->value = 1; + break; + case V4L2_CID_MXC_ROT: + /* This is handled in the ipu. */ + c->value = cam->rotation; + break; + case V4L2_CID_BRIGHTNESS: + if (cam->sensor) { + c->value = cam->bright; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->bright = c->value; + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + status = -ENODEV; + } + break; + case V4L2_CID_HUE: + if (cam->sensor) { + c->value = cam->hue; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->hue = c->value; + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + status = -ENODEV; + } + break; + case V4L2_CID_CONTRAST: + if (cam->sensor) { + c->value = cam->contrast; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->contrast = c->value; + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + status = -ENODEV; + } + break; + case V4L2_CID_SATURATION: + if (cam->sensor) { + c->value = cam->saturation; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->saturation = c->value; + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + status = -ENODEV; + } + break; + case V4L2_CID_RED_BALANCE: + if (cam->sensor) { + c->value = cam->red; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->red = c->value; + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + status = -ENODEV; + } + break; + case V4L2_CID_BLUE_BALANCE: + if (cam->sensor) { + c->value = cam->blue; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->blue = c->value; + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + status = -ENODEV; + } + break; + case V4L2_CID_BLACK_LEVEL: + if (cam->sensor) { + c->value = cam->ae_mode; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->ae_mode = c->value; + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + status = -ENODEV; + } + break; + default: + pr_err("ERROR: v4l2 capture: unsupported ioctrl!\n"); + } + + return status; +} + +/*! + * V4L2 - set_control function + * V4L2_CID_PRIVATE_BASE is the extention for IPU preprocessing. + * 0 for normal operation + * 1 for vertical flip + * 2 for horizontal flip + * 3 for horizontal and vertical flip + * 4 for 90 degree rotation + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_ctrl(cam_data *cam, struct v4l2_control *c) +{ + int ret = 0; + int tmp_rotation = IPU_ROTATE_NONE; + + pr_debug("In MVC:mxc_v4l2_s_ctrl\n"); + + switch (c->id) { + case V4L2_CID_HFLIP: + /* This is done by the IPU */ + if (c->value == 1) { + if ((cam->rotation != IPU_ROTATE_VERT_FLIP) && + (cam->rotation != IPU_ROTATE_180)) + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + else + cam->rotation = IPU_ROTATE_180; + } else { + if (cam->rotation == IPU_ROTATE_HORIZ_FLIP) + cam->rotation = IPU_ROTATE_NONE; + if (cam->rotation == IPU_ROTATE_180) + cam->rotation = IPU_ROTATE_VERT_FLIP; + } + break; + case V4L2_CID_VFLIP: + /* This is done by the IPU */ + if (c->value == 1) { + if ((cam->rotation != IPU_ROTATE_HORIZ_FLIP) && + (cam->rotation != IPU_ROTATE_180)) + cam->rotation = IPU_ROTATE_VERT_FLIP; + else + cam->rotation = IPU_ROTATE_180; + } else { + if (cam->rotation == IPU_ROTATE_VERT_FLIP) + cam->rotation = IPU_ROTATE_NONE; + if (cam->rotation == IPU_ROTATE_180) + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + } + break; + case V4L2_CID_MXC_ROT: + case V4L2_CID_MXC_VF_ROT: + /* This is done by the IPU */ + switch (c->value) { + case V4L2_MXC_ROTATE_NONE: + tmp_rotation = IPU_ROTATE_NONE; + break; + case V4L2_MXC_ROTATE_VERT_FLIP: + tmp_rotation = IPU_ROTATE_VERT_FLIP; + break; + case V4L2_MXC_ROTATE_HORIZ_FLIP: + tmp_rotation = IPU_ROTATE_HORIZ_FLIP; + break; + case V4L2_MXC_ROTATE_180: + tmp_rotation = IPU_ROTATE_180; + break; + case V4L2_MXC_ROTATE_90_RIGHT: + tmp_rotation = IPU_ROTATE_90_RIGHT; + break; + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + tmp_rotation = IPU_ROTATE_90_RIGHT_VFLIP; + break; + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + tmp_rotation = IPU_ROTATE_90_RIGHT_HFLIP; + break; + case V4L2_MXC_ROTATE_90_LEFT: + tmp_rotation = IPU_ROTATE_90_LEFT; + break; + default: + ret = -EINVAL; + } + + if (c->id == V4L2_CID_MXC_VF_ROT) + cam->vf_rotation = tmp_rotation; + else + cam->rotation = tmp_rotation; + + break; + case V4L2_CID_HUE: + if (cam->sensor) { + cam->hue = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + ret = -ENODEV; + } + break; + case V4L2_CID_CONTRAST: + if (cam->sensor) { + cam->contrast = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + ret = -ENODEV; + } + break; + case V4L2_CID_BRIGHTNESS: + if (cam->sensor) { + cam->bright = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + ret = -ENODEV; + } + break; + case V4L2_CID_SATURATION: + if (cam->sensor) { + cam->saturation = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + ret = -ENODEV; + } + break; + case V4L2_CID_RED_BALANCE: + if (cam->sensor) { + cam->red = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + ret = -ENODEV; + } + break; + case V4L2_CID_BLUE_BALANCE: + if (cam->sensor) { + cam->blue = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + ret = -ENODEV; + } + break; + case V4L2_CID_EXPOSURE: + if (cam->sensor) { + cam->ae_mode = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); + } else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + ret = -ENODEV; + } + break; + case V4L2_CID_MXC_FLASH: +#ifdef CONFIG_MXC_IPU_V1 + ipu_csi_flash_strobe(true); +#endif + break; + default: + pr_debug(" default case\n"); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * V4L2 - mxc_v4l2_s_param function + * Allows setting of capturemode and frame rate. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct v4l2_streamparm currentparm; + ipu_csi_signal_cfg_t csi_param; + int err = 0; + + pr_debug("In mxc_v4l2_s_param\n"); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err(KERN_ERR "mxc_v4l2_s_param invalid type\n"); + return -EINVAL; + } + + /* Stop the viewfinder */ + if (cam->overlay_on == true) { + stop_preview(cam); + } + + currentparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* First check that this device can support the changes requested. */ + err = vidioc_int_g_parm(cam->sensor, ¤tparm); + if (err) { + pr_err("%s: vidioc_int_g_parm returned an error %d\n", + __func__, err); + goto exit; + } + + pr_debug(" Current capabilities are %x\n", + currentparm.parm.capture.capability); + pr_debug(" Current capturemode is %d change to %d\n", + currentparm.parm.capture.capturemode, + parm->parm.capture.capturemode); + pr_debug(" Current framerate is %d change to %d\n", + currentparm.parm.capture.timeperframe.denominator, + parm->parm.capture.timeperframe.denominator); + + /* This will change any camera settings needed. */ + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + err = vidioc_int_s_parm(cam->sensor, parm); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + if (err) { + pr_err("%s: vidioc_int_s_parm returned an error %d\n", + __func__, err); + goto exit; + } + + /* If resolution changed, need to re-program the CSI */ + /* Get new values. */ + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + csi_param.data_width = 0; + csi_param.clk_mode = 0; + csi_param.ext_vsync = 0; + csi_param.Vsync_pol = 0; + csi_param.Hsync_pol = 0; + csi_param.pixclk_pol = 0; + csi_param.data_pol = 0; + csi_param.sens_clksrc = 0; + csi_param.pack_tight = 0; + csi_param.force_eof = 0; + csi_param.data_en_pol = 0; + csi_param.data_fmt = 0; + csi_param.csi = 0; + csi_param.mclk = 0; + + /* This may not work on other platforms. Check when adding a new one.*/ + pr_debug(" clock_curr=mclk=%d\n", ifparm.u.bt656.clock_curr); + if (ifparm.u.bt656.clock_curr == 0) { + csi_param.clk_mode = IPU_CSI_CLK_MODE_CCIR656_INTERLACED; + } else { + csi_param.clk_mode = IPU_CSI_CLK_MODE_GATED_CLK; + } + + csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv; + + if (ifparm.u.bt656.mode == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) { + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + } else if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) { + csi_param.data_width = IPU_CSI_DATA_WIDTH_10; + } else { + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + } + + csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv; + csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv; + csi_param.ext_vsync = ifparm.u.bt656.bt_sync_correct; + + /* if the capturemode changed, the size bounds will have changed. */ + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + pr_debug(" g_fmt_cap returns widthxheight of input as %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + + csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat; + + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* + * Set the default current cropped resolution to be the same with + * the cropping boundary(except for tvin module). + */ + if (cam->device_type != 1) { + cam->crop_current.width = cam->crop_bounds.width; + cam->crop_current.height = cam->crop_bounds.height; + } + + /* This essentially loses the data at the left and bottom of the image + * giving a digital zoom image, if crop_current is less than the full + * size of the image. */ + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + ipu_csi_init_interface(cam->crop_bounds.width, + cam->crop_bounds.height, + cam_fmt.fmt.pix.pixelformat, csi_param); + + +exit: + if (cam->overlay_on == true) + start_preview(cam); + + return err; +} + +/*! + * V4L2 - mxc_v4l2_s_std function + * + * Sets the TV standard to be used. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_std(cam_data *cam, v4l2_std_id e) +{ + pr_debug("In mxc_v4l2_s_std %Lx\n", e); + if (e == V4L2_STD_PAL) { + pr_debug(" Setting standard to PAL %Lx\n", V4L2_STD_PAL); + cam->standard.id = V4L2_STD_PAL; + video_index = TV_PAL; + } else if (e == V4L2_STD_NTSC) { + pr_debug(" Setting standard to NTSC %Lx\n", + V4L2_STD_NTSC); + /* Get rid of the white dot line in NTSC signal input */ + cam->standard.id = V4L2_STD_NTSC; + video_index = TV_NTSC; + } else { + cam->standard.id = V4L2_STD_ALL; + video_index = TV_NOT_LOCKED; + pr_err("ERROR: unrecognized std! %Lx (PAL=%Lx, NTSC=%Lx\n", + e, V4L2_STD_PAL, V4L2_STD_NTSC); + } + + cam->standard.index = video_index; + strcpy(cam->standard.name, video_fmts[video_index].name); + cam->crop_bounds.width = video_fmts[video_index].raw_width; + cam->crop_bounds.height = video_fmts[video_index].raw_height; + cam->crop_current.width = video_fmts[video_index].active_width; + cam->crop_current.height = video_fmts[video_index].active_height; + cam->crop_current.top = video_fmts[video_index].active_top; + cam->crop_current.left = video_fmts[video_index].active_left; + + return 0; +} + +/*! + * V4L2 - mxc_v4l2_g_std function + * + * Gets the TV standard from the TV input device. + * + * @param cam structure cam_data * + * + * @param e structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_std(cam_data *cam, v4l2_std_id *e) +{ + struct v4l2_format tv_fmt; + + pr_debug("In mxc_v4l2_g_std\n"); + + if (cam->device_type == 1) { + /* Use this function to get what the TV-In device detects the + * format to be. pixelformat is used to return the std value + * since the interface has no vidioc_g_std.*/ + tv_fmt.type = V4L2_BUF_TYPE_PRIVATE; + vidioc_int_g_fmt_cap(cam->sensor, &tv_fmt); + + /* If the TV-in automatically detects the standard, then if it + * changes, the settings need to change. */ + if (cam->standard_autodetect) { + if (cam->standard.id != tv_fmt.fmt.pix.pixelformat) { + pr_debug("MVC: mxc_v4l2_g_std: " + "Changing standard\n"); + mxc_v4l2_s_std(cam, tv_fmt.fmt.pix.pixelformat); + } + } + + *e = tv_fmt.fmt.pix.pixelformat; + } + + return 0; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number, + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_dqueue(cam_data *cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + unsigned long lock_flags; + + pr_debug("In MVC:mxc_v4l_dqueue\n"); + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue timeout " + "enc_counter %x\n", + cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue() " + "interrupt received\n"); + return -ERESTARTSYS; + } + + spin_lock_irqsave(&cam->dqueue_int_lock, lock_flags); + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not queued.\n"); + retval = -EINVAL; + } + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + buf->m = cam->frame[frame->index].buffer.m; + buf->timestamp = cam->frame[frame->index].buffer.timestamp; + + spin_unlock_irqrestore(&cam->dqueue_int_lock, lock_flags); + return retval; +} + +/*! + * V4L interface - open function + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_open(struct file *file) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + ipu_csi_signal_cfg_t csi_param; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int err = 0; + + pr_debug("\nIn MVC: mxc_v4l_open\n"); + pr_debug(" device name is %s\n", dev->name); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + if (cam->sensor == NULL || + cam->sensor->type != v4l2_int_type_slave) { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + return -EAGAIN; + } + + down(&cam->busy_lock); + err = 0; + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + err = csi_enc_select(cam); +#endif + } else if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + err = prp_enc_select(cam); +#endif + } + + cam->enc_counter = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + csi_param.sens_clksrc = 0; + + csi_param.clk_mode = 0; + csi_param.data_pol = 0; + csi_param.ext_vsync = 0; + + csi_param.pack_tight = 0; + csi_param.force_eof = 0; + csi_param.data_en_pol = 0; + csi_param.mclk = ifparm.u.bt656.clock_curr; + + csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv; + + /* Once we handle multiple inputs this will need to change. */ + csi_param.csi = 0; + + if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + else if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) + csi_param.data_width = IPU_CSI_DATA_WIDTH_10; + else + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + + + csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv; + csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv; + + csi_param.csi = cam->csi; + + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + + /* Reset the sizes. Needed to prevent carryover of last + * operation.*/ + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* This also is the max crop size for this device. */ + cam->crop_defrect.top = cam->crop_defrect.left = 0; + cam->crop_defrect.width = cam_fmt.fmt.pix.width; + cam->crop_defrect.height = cam_fmt.fmt.pix.height; + + /* At this point, this is also the current image size. */ + cam->crop_current.top = cam->crop_current.left = 0; + cam->crop_current.width = cam_fmt.fmt.pix.width; + cam->crop_current.height = cam_fmt.fmt.pix.height; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat; + pr_debug("On Open: Input to ipu size is %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.width, + cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + ipu_csi_init_interface(cam->crop_bounds.width, + cam->crop_bounds.height, + cam_fmt.fmt.pix.pixelformat, + csi_param); + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + vidioc_int_init(cam->sensor); + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); +} + + file->private_data = dev; + + oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l_close(struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:mxc_v4l_close\n"); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + if (cam->capture_pid == current->pid) { + err |= mxc_streamoff(cam); + wake_up_interruptible(&cam->enc_queue); + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + pr_info("mxc_v4l_close: release resource\n"); + + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + err |= csi_enc_deselect(cam); +#endif + } else if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + err |= prp_enc_deselect(cam); +#endif + } + + mxc_free_frame_buf(cam); + file->private_data = NULL; + + /* capture off */ + wake_up_interruptible(&cam->enc_queue); + mxc_free_frames(cam); + cam->enc_counter++; + } + + return err; +} + +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC) || \ + defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) || \ + defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) +/* + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t mxc_v4l_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int err = 0; + u8 *v_address[2]; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + v_address[0] = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf[0], + GFP_DMA | GFP_KERNEL); + + v_address[1] = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf[1], + GFP_DMA | GFP_KERNEL); + + if (!v_address[0] || !v_address[1]) { + err = -ENOBUFS; + goto exit0; + } + + err = prp_still_select(cam); + if (err != 0) { + err = -EIO; + goto exit0; + } + + cam->still_counter = 0; + err = cam->csi_start(cam); + if (err != 0) { + err = -EIO; + goto exit1; + } + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit1; + } + err = copy_to_user(buf, v_address[1], cam->v2f.fmt.pix.sizeimage); + + exit1: + prp_still_deselect(cam); + + exit0: + if (v_address[0] != 0) + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address[0], + cam->still_buf[0]); + if (v_address[1] != 0) + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address[1], + cam->still_buf[1]); + + cam->still_buf[0] = cam->still_buf[1] = 0; + + if (cam->overlay_on == true) { + start_preview(cam); + } + + up(&cam->busy_lock); + if (err < 0) + return err; + + return cam->v2f.fmt.pix.sizeimage - err; +} +#endif + +/*! + * V4L interface - ioctl function + * + * @param file struct file* + * + * @param ioctlnr unsigned int + * + * @param arg void* + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static long mxc_v4l_do_ioctl(struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retval = 0; + unsigned long lock_flags; + + pr_debug("In MVC: mxc_v4l_do_ioctl %x\n", ioctlnr); + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_QUERYCAP ioctl + */ + case VIDIOC_QUERYCAP: { + struct v4l2_capability *cap = arg; + pr_debug(" case VIDIOC_QUERYCAP\n"); + strcpy(cap->driver, "mxc_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + break; + } + + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT: { + struct v4l2_format *gf = arg; + pr_debug(" case VIDIOC_G_FMT\n"); + retval = mxc_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT: { + struct v4l2_format *sf = arg; + pr_debug(" case VIDIOC_S_FMT\n"); + retval = mxc_v4l2_s_fmt(cam, sf); + break; + } + + /*! + * V4l2 VIDIOC_REQBUFS ioctl + */ + case VIDIOC_REQBUFS: { + struct v4l2_requestbuffers *req = arg; + pr_debug(" case VIDIOC_REQBUFS\n"); + + if (req->count > FRAME_NUM) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "not enough buffers\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "wrong buffer type\n"); + retval = -EINVAL; + break; + } + + mxc_streamoff(cam); + mxc_free_frame_buf(cam); + cam->enc_counter = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + retval = mxc_allocate_frame_buf(cam, req->count); + break; + } + + /*! + * V4l2 VIDIOC_QUERYBUF ioctl + */ + case VIDIOC_QUERYBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QUERYBUF\n"); + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err("ERROR: v4l2 capture: " + "VIDIOC_QUERYBUFS: " + "wrong buffer type\n"); + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + + down(&cam->param_lock); + retval = mxc_v4l2_buffer_status(cam, buf); + up(&cam->param_lock); + break; + } + + /*! + * V4l2 VIDIOC_QBUF ioctl + */ + case VIDIOC_QBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QBUF\n"); + + spin_lock_irqsave(&cam->queue_int_lock, lock_flags); + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "buffer already queued\n"); + retval = -EINVAL; + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } + + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->queue_int_lock, lock_flags); + break; + } + + /*! + * V4l2 VIDIOC_DQBUF ioctl + */ + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = arg; + pr_debug(" case VIDIOC_DQBUF\n"); + + if ((cam->enc_counter == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + retval = mxc_v4l_dqueue(cam, buf); + + break; + } + + /*! + * V4l2 VIDIOC_STREAMON ioctl + */ + case VIDIOC_STREAMON: { + pr_debug(" case VIDIOC_STREAMON\n"); + retval = mxc_streamon(cam); + break; + } + + /*! + * V4l2 VIDIOC_STREAMOFF ioctl + */ + case VIDIOC_STREAMOFF: { + pr_debug(" case VIDIOC_STREAMOFF\n"); + retval = mxc_streamoff(cam); + break; + } + + /*! + * V4l2 VIDIOC_G_CTRL ioctl + */ + case VIDIOC_G_CTRL: { + pr_debug(" case VIDIOC_G_CTRL\n"); + retval = mxc_v4l2_g_ctrl(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_S_CTRL ioctl + */ + case VIDIOC_S_CTRL: { + pr_debug(" case VIDIOC_S_CTRL\n"); + retval = mxc_v4l2_s_ctrl(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_CROPCAP ioctl + */ + case VIDIOC_CROPCAP: { + struct v4l2_cropcap *cap = arg; + pr_debug(" case VIDIOC_CROPCAP\n"); + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + cap->bounds = cam->crop_bounds; + cap->defrect = cam->crop_defrect; + break; + } + + /*! + * V4l2 VIDIOC_G_CROP ioctl + */ + case VIDIOC_G_CROP: { + struct v4l2_crop *crop = arg; + pr_debug(" case VIDIOC_G_CROP\n"); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + crop->c = cam->crop_current; + break; + } + + /*! + * V4l2 VIDIOC_S_CROP ioctl + */ + case VIDIOC_S_CROP: { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = &cam->crop_bounds; + pr_debug(" case VIDIOC_S_CROP\n"); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + + crop->c.top = (crop->c.top < b->top) ? b->top + : crop->c.top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top + b->height - crop->c.top) + crop->c.height = + b->top + b->height - crop->c.top; + + crop->c.left = (crop->c.left < b->left) ? b->left + : crop->c.left; + if (crop->c.left > b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + crop->c.width -= crop->c.width % 8; + crop->c.left -= crop->c.left % 4; + cam->crop_current = crop->c; + + pr_debug(" Cropping Input to ipu size %d x %d\n", + cam->crop_current.width, + cam->crop_current.height); + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, + cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY: { + int *on = arg; + pr_debug(" VIDIOC_OVERLAY on=%d\n", *on); + if (*on) { + cam->overlay_on = true; + cam->overlay_pid = current->pid; + retval = start_preview(cam); + } + if (!*on) { + retval = stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF: { + struct v4l2_framebuffer *fb = arg; + pr_debug(" case VIDIOC_G_FBUF\n"); + *fb = cam->v4l2_fb; + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF: { + struct v4l2_framebuffer *fb = arg; + pr_debug(" case VIDIOC_S_FBUF\n"); + cam->v4l2_fb = *fb; + break; + } + + case VIDIOC_G_PARM: { + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_G_PARM\n"); + if (cam->sensor) + retval = vidioc_int_g_parm(cam->sensor, parm); + else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + retval = -ENODEV; + } + break; + } + + case VIDIOC_S_PARM: { + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_S_PARM\n"); + if (cam->sensor) + retval = mxc_v4l2_s_param(cam, parm); + else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + retval = -ENODEV; + } + break; + } + + /* linux v4l2 bug, kernel c0485619 user c0405619 */ + case VIDIOC_ENUMSTD: { + struct v4l2_standard *e = arg; + pr_debug(" case VIDIOC_ENUMSTD\n"); + *e = cam->standard; + break; + } + + case VIDIOC_G_STD: { + v4l2_std_id *e = arg; + pr_debug(" case VIDIOC_G_STD\n"); + if (cam->sensor) + retval = mxc_v4l2_g_std(cam, e); + else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + retval = -ENODEV; + } + break; + } + + case VIDIOC_S_STD: { + v4l2_std_id *e = arg; + pr_debug(" case VIDIOC_S_STD\n"); + retval = mxc_v4l2_s_std(cam, *e); + + break; + } + + case VIDIOC_ENUMOUTPUT: { + struct v4l2_output *output = arg; + pr_debug(" case VIDIOC_ENUMOUTPUT\n"); + if (output->index >= MXC_V4L2_CAPTURE_NUM_OUTPUTS) { + retval = -EINVAL; + break; + } + *output = mxc_capture_outputs[output->index]; + + break; + } + case VIDIOC_G_OUTPUT: { + int *p_output_num = arg; + pr_debug(" case VIDIOC_G_OUTPUT\n"); + *p_output_num = cam->output; + break; + } + + case VIDIOC_S_OUTPUT: { + int *p_output_num = arg; + pr_debug(" case VIDIOC_S_OUTPUT\n"); + if (*p_output_num >= MXC_V4L2_CAPTURE_NUM_OUTPUTS) { + retval = -EINVAL; + break; + } + cam->output = *p_output_num; + break; + } + + case VIDIOC_ENUMINPUT: { + struct v4l2_input *input = arg; + pr_debug(" case VIDIOC_ENUMINPUT\n"); + if (input->index >= MXC_V4L2_CAPTURE_NUM_INPUTS) { + retval = -EINVAL; + break; + } + *input = mxc_capture_inputs[input->index]; + break; + } + + case VIDIOC_G_INPUT: { + int *index = arg; + pr_debug(" case VIDIOC_G_INPUT\n"); + *index = cam->current_input; + break; + } + + case VIDIOC_S_INPUT: { + int *index = arg; + pr_debug(" case VIDIOC_S_INPUT\n"); + if (*index >= MXC_V4L2_CAPTURE_NUM_INPUTS) { + retval = -EINVAL; + break; + } + + if (*index == cam->current_input) + break; + + if ((mxc_capture_inputs[cam->current_input].status & + V4L2_IN_ST_NO_POWER) == 0) { + retval = mxc_streamoff(cam); + if (retval) + break; + mxc_capture_inputs[cam->current_input].status |= + V4L2_IN_ST_NO_POWER; + } + + if (strcmp(mxc_capture_inputs[*index].name, "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + retval = csi_enc_select(cam); + if (retval) + break; +#endif + } else if (strcmp(mxc_capture_inputs[*index].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + retval = prp_enc_select(cam); + if (retval) + break; +#endif + } + + mxc_capture_inputs[*index].status &= ~V4L2_IN_ST_NO_POWER; + cam->current_input = *index; + break; + } + case VIDIOC_ENUM_FMT: { + struct v4l2_fmtdesc *f = arg; + if (cam->sensor) + retval = vidioc_int_enum_fmt_cap(cam->sensor, f); + else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + retval = -ENODEV; + } + break; + } + case VIDIOC_ENUM_FRAMESIZES: { + struct v4l2_frmsizeenum *fsize = arg; + if (cam->sensor) + retval = vidioc_int_enum_framesizes(cam->sensor, fsize); + else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + retval = -ENODEV; + } + break; + } + case VIDIOC_DBG_G_CHIP_IDENT: { + struct v4l2_dbg_chip_ident *p = arg; + p->ident = V4L2_IDENT_NONE; + p->revision = 0; + if (cam->sensor) + retval = vidioc_int_g_chip_ident(cam->sensor, (int *)p); + else { + pr_err("ERROR: v4l2 capture: slave not found!\n"); + retval = -ENODEV; + } + break; + } + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + pr_debug(" case default or not supported\n"); + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static long mxc_v4l_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + pr_debug("In MVC:mxc_v4l_ioctl\n"); + return video_usercopy(file, cmd, arg, mxc_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:mxc_mmap\n"); + pr_debug(" pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_err("ERROR: v4l2 capture: mxc_mmap: " + "remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * V4L interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table_struct * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + pr_debug("In MVC:mxc_poll\n"); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + queue = &cam->enc_queue; + poll_wait(file, queue, wait); + + up(&cam->busy_lock); + + return res; +} + +/*! + * This structure defines the functions to be called in this driver. + */ +static struct v4l2_file_operations mxc_v4l_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l_open, + .release = mxc_v4l_close, + .read = mxc_v4l_read, + .ioctl = mxc_v4l_ioctl, + .mmap = mxc_mmap, + .poll = mxc_poll, +}; + +static struct video_device mxc_v4l_template = { + .name = "Mxc Camera", + .fops = &mxc_v4l_fops, + .release = video_device_release, +}; + +/*! + * This function can be used to release any platform data on closing. + */ +static void camera_platform_release(struct device *device) +{ +} + +/*! + * Camera V4l2 callback function. + * + * @param mask u32 + * + * @param dev void device structure + * + * @return status + */ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + struct timeval cur_time; + + cam_data *cam = (cam_data *) dev; + if (cam == NULL) + return; + + pr_debug("In MVC:camera_callback\n"); + + if (!list_empty(&cam->working_q)) { + do_gettimeofday(&cur_time); + + done_frame = list_entry(cam->working_q.next, + struct mxc_v4l_frame, + queue); + + if (done_frame->ipu_buf_num != local_buf_num) + goto next; + + /* + * Set the current time to done frame buffer's + * timestamp. Users can use this information to judge + * the frame's usage. + */ + done_frame->buffer.timestamp = cur_time; + + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + + /* Wake up the queue */ + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else + pr_err("ERROR: v4l2 capture: camera_callback: " + "buffer not queued\n"); + } + +next: + if (!list_empty(&cam->ready_q)) { + ready_frame = list_entry(cam->ready_q.next, + struct mxc_v4l_frame, + queue); + if (cam->enc_update_eba) + if (cam->enc_update_eba(ready_frame->buffer.m.offset, + &cam->ping_pong_csi) == 0) { + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, + &cam->working_q); + ready_frame->ipu_buf_num = local_buf_num; + } + } else { + if (cam->enc_update_eba) + cam->enc_update_eba( + cam->dummy_frame.buffer.m.offset, + &cam->ping_pong_csi); + } + + local_buf_num = (local_buf_num == 0) ? 1 : 0; + + return; +} + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data *cam, struct platform_device *pdev) +{ + pr_debug("In MVC: init_camera_struct\n"); + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = mxc_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&pdev->dev, (void *)cam); + cam->video_dev->minor = -1; + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + /* setup cropping */ + cam->crop_bounds.left = 0; + cam->crop_bounds.width = 640; + cam->crop_bounds.top = 0; + cam->crop_bounds.height = 480; + cam->crop_current = cam->crop_defrect = cam->crop_bounds; + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, cam->csi); + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->standard_autodetect = true; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_OVERLAY; + + cam->v2f.fmt.pix.sizeimage = 352 * 288 * 3 / 2; + cam->v2f.fmt.pix.bytesperline = 288 * 3 / 2; + cam->v2f.fmt.pix.width = 288; + cam->v2f.fmt.pix.height = 352; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + + cam->csi = 0; /* Need to determine how to set this correctly with + * multiple video input devices. */ + + cam->enc_callback = camera_callback; + init_waitqueue_head(&cam->power_queue); + spin_lock_init(&cam->queue_int_lock); + spin_lock_init(&cam->dqueue_int_lock); +} + +static ssize_t show_streaming(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *video_dev = container_of(dev, + struct video_device, dev); + cam_data *g_cam = video_get_drvdata(video_dev); + + if (g_cam->capture_on) + return sprintf(buf, "stream on\n"); + else + return sprintf(buf, "stream off\n"); +} +static DEVICE_ATTR(fsl_v4l2_capture_property, S_IRUGO, show_streaming, NULL); + +static ssize_t show_overlay(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *video_dev = container_of(dev, + struct video_device, dev); + cam_data *g_cam = video_get_drvdata(video_dev); + + if (g_cam->overlay_on) + return sprintf(buf, "overlay on\n"); + else + return sprintf(buf, "overlay off\n"); +} +static DEVICE_ATTR(fsl_v4l2_overlay_property, S_IRUGO, show_overlay, NULL); + +/*! + * This function is called to probe the devices if registered. + * + * @param pdev the device structure used to give information on which device + * to probe + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_probe(struct platform_device *pdev) +{ + /* Create g_cam and initialize it. */ + g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL); + if (g_cam == NULL) { + pr_err("ERROR: v4l2 capture: failed to register camera\n"); + return -1; + } + init_camera_struct(g_cam, pdev); + pdev->dev.release = camera_platform_release; + + /* Set up the v4l2 device and register it*/ + mxc_v4l2_int_device.priv = g_cam; + /* This function contains a bug that won't let this be rmmod'd. */ + v4l2_int_device_register(&mxc_v4l2_int_device); + + /* register v4l video device */ + if (video_register_device(g_cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + kfree(g_cam); + g_cam = NULL; + pr_err("ERROR: v4l2 capture: video_register_device failed\n"); + return -1; + } + pr_debug(" Video device registered: %s #%d\n", + g_cam->video_dev->name, g_cam->video_dev->minor); + + if (device_create_file(&g_cam->video_dev->dev, + &dev_attr_fsl_v4l2_capture_property)) + dev_err(&pdev->dev, "Error on creating sysfs file" + " for capture\n"); + + if (device_create_file(&g_cam->video_dev->dev, + &dev_attr_fsl_v4l2_overlay_property)) + dev_err(&pdev->dev, "Error on creating sysfs file" + " for overlay\n"); + + return 0; +} + +/*! + * This function is called to remove the devices when device unregistered. + * + * @param pdev the device structure used to give information on which device + * to remove + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_remove(struct platform_device *pdev) +{ + + if (g_cam->open_count) { + pr_err("ERROR: v4l2 capture:camera open " + "-- setting ops to NULL\n"); + return -EBUSY; + } else { + device_remove_file(&g_cam->video_dev->dev, + &dev_attr_fsl_v4l2_capture_property); + device_remove_file(&g_cam->video_dev->dev, + &dev_attr_fsl_v4l2_overlay_property); + + pr_info("V4L2 freeing image input device\n"); + v4l2_int_device_unregister(&mxc_v4l2_int_device); + video_unregister_device(g_cam->video_dev); + + mxc_free_frame_buf(g_cam); + kfree(g_cam); + g_cam = NULL; + } + + pr_info("V4L2 unregistering video\n"); + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC:mxc_v4l2_suspend\n"); + + if (cam == NULL) { + return -1; + } + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + if ((cam->capture_on == true) && cam->enc_disable) { + cam->enc_disable(cam); + } + + if (cam->sensor) + vidioc_int_s_power(cam->sensor, 0); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxc_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC:mxc_v4l2_resume\n"); + + if (cam == NULL) { + return -1; + } + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + + if (cam->sensor) + vidioc_int_s_power(cam->sensor, 1); + + if (cam->overlay_on == true) + start_preview(cam); + if (cam->capture_on == true) + mxc_streamon(cam); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2_driver = { + .driver = { + .name = "mxc_v4l2_capture", + }, + .probe = mxc_v4l2_probe, + .remove = mxc_v4l2_remove, + .suspend = mxc_v4l2_suspend, + .resume = mxc_v4l2_resume, + .shutdown = NULL, +}; + +/*! + * Initializes the camera driver. + */ +static int mxc_v4l2_master_attach(struct v4l2_int_device *slave) +{ + cam_data *cam = slave->u.slave->master->priv; + struct v4l2_format cam_fmt; + + pr_debug("In MVC: mxc_v4l2_master_attach\n"); + pr_debug(" slave.name = %s\n", slave->name); + pr_debug(" master.name = %s\n", slave->u.slave->master->name); + + cam->sensor = slave; + if (slave == NULL) { + pr_err("ERROR: v4l2 capture: slave parameter not valid.\n"); + return -1; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + vidioc_int_s_power(cam->sensor, 1); + vidioc_int_dev_init(slave); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + + /* Used to detect TV in (type 1) vs. camera (type 0)*/ + cam->device_type = cam_fmt.fmt.pix.priv; + + /* Set the input size to the ipu for this device */ + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* This also is the max crop size for this device. */ + cam->crop_defrect.top = cam->crop_defrect.left = 0; + cam->crop_defrect.width = cam_fmt.fmt.pix.width; + cam->crop_defrect.height = cam_fmt.fmt.pix.height; + + /* At this point, this is also the current image size. */ + cam->crop_current.top = cam->crop_current.left = 0; + cam->crop_current.width = cam_fmt.fmt.pix.width; + cam->crop_current.height = cam_fmt.fmt.pix.height; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return 0; +} + +/*! + * Disconnects the camera driver. + */ +static void mxc_v4l2_master_detach(struct v4l2_int_device *slave) +{ + pr_debug("In MVC:mxc_v4l2_master_detach\n"); + vidioc_int_dev_exit(slave); +} + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + + pr_debug("In MVC:camera_init\n"); + + /* Register the device driver structure. */ + err = platform_driver_register(&mxc_v4l2_driver); + if (err != 0) { + pr_err("ERROR: v4l2 capture:camera_init: " + "platform_driver_register failed.\n"); + return err; + } + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + */ +static void __exit camera_exit(void) +{ + pr_debug("In MVC: camera_exit\n"); + + platform_driver_unregister(&mxc_v4l2_driver); +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mxc based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/mxc_v4l2_capture.h b/drivers/media/video/mxc/capture/mxc_v4l2_capture.h new file mode 100644 index 000000000000..3f53d6029146 --- /dev/null +++ b/drivers/media/video/mxc/capture/mxc_v4l2_capture.h @@ -0,0 +1,207 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_V4L2_CAPTURE MXC V4L2 Video Capture Driver + */ +/*! + * @file mxc_v4l2_capture.h + * + * @brief mxc V4L2 capture device API Header file + * + * It include all the defines for frame operations, also three structure defines + * use case ops structure, common v4l2 driver structure and frame structure. + * + * @ingroup MXC_V4L2_CAPTURE + */ +#ifndef __MXC_V4L2_CAPTURE_H__ +#define __MXC_V4L2_CAPTURE_H__ + +#include +#include +#include +#include +#include + +#include + +#define FRAME_NUM 3 + +/*! + * v4l2 frame structure. + */ +struct mxc_v4l_frame { + u32 paddress; + void *vaddress; + int count; + int width; + int height; + + struct v4l2_buffer buffer; + struct list_head queue; + int index; + int ipu_buf_num; +}; + +/* Only for old version. Will go away soon. */ +typedef struct { + u8 clk_mode; + u8 ext_vsync; + u8 Vsync_pol; + u8 Hsync_pol; + u8 pixclk_pol; + u8 data_pol; + u8 data_width; + u8 pack_tight; + u8 force_eof; + u8 data_en_pol; + u16 width; + u16 height; + u32 pixel_fmt; + u32 mclk; + u16 active_width; + u16 active_height; +} sensor_interface; + +/* Sensor control function */ +/* Only for old version. Will go away soon. */ +struct camera_sensor { + void (*set_color) (int bright, int saturation, int red, int green, + int blue); + void (*get_color) (int *bright, int *saturation, int *red, int *green, + int *blue); + void (*set_ae_mode) (int ae_mode); + void (*get_ae_mode) (int *ae_mode); + sensor_interface *(*config) (int *frame_rate, int high_quality); + sensor_interface *(*reset) (void); + void (*get_std) (v4l2_std_id *std); + void (*set_std) (v4l2_std_id std); + unsigned int csi; +}; + +/*! + * common v4l2 driver structure. + */ +typedef struct _cam_data { + struct video_device *video_dev; + int device_type; + + /* semaphore guard against SMP multithreading */ + struct semaphore busy_lock; + + int open_count; + + /* params lock for this camera */ + struct semaphore param_lock; + + /* Encoder */ + struct list_head ready_q; + struct list_head done_q; + struct list_head working_q; + int ping_pong_csi; + spinlock_t queue_int_lock; + spinlock_t dqueue_int_lock; + struct mxc_v4l_frame frame[FRAME_NUM]; + struct mxc_v4l_frame dummy_frame; + int skip_frame; + wait_queue_head_t enc_queue; + int enc_counter; + dma_addr_t rot_enc_bufs[2]; + void *rot_enc_bufs_vaddr[2]; + int rot_enc_buf_size[2]; + enum v4l2_buf_type type; + + /* still image capture */ + wait_queue_head_t still_queue; + int still_counter; + dma_addr_t still_buf[2]; + void *still_buf_vaddr; + + /* overlay */ + struct v4l2_window win; + struct v4l2_framebuffer v4l2_fb; + dma_addr_t vf_bufs[2]; + void *vf_bufs_vaddr[2]; + int vf_bufs_size[2]; + dma_addr_t rot_vf_bufs[2]; + void *rot_vf_bufs_vaddr[2]; + int rot_vf_buf_size[2]; + bool overlay_active; + int output; + struct fb_info *overlay_fb; + int fb_origin_std; + + /* v4l2 format */ + struct v4l2_format v2f; + int rotation; /* for IPUv1 and IPUv3, this means encoder rotation */ + int vf_rotation; /* viewfinder rotation only for IPUv1 and IPUv3 */ + struct v4l2_mxc_offset offset; + + /* V4l2 control bit */ + int bright; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + /* standard */ + struct v4l2_streamparm streamparm; + struct v4l2_standard standard; + bool standard_autodetect; + + /* crop */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop_current; + + int (*enc_update_eba) (dma_addr_t eba, int *bufferNum); + int (*enc_enable) (void *private); + int (*enc_disable) (void *private); + int (*enc_enable_csi) (void *private); + int (*enc_disable_csi) (void *private); + void (*enc_callback) (u32 mask, void *dev); + int (*vf_start_adc) (void *private); + int (*vf_stop_adc) (void *private); + int (*vf_start_sdc) (void *private); + int (*vf_stop_sdc) (void *private); + int (*vf_enable_csi) (void *private); + int (*vf_disable_csi) (void *private); + int (*csi_start) (void *private); + int (*csi_stop) (void *private); + + /* misc status flag */ + bool overlay_on; + bool capture_on; + int overlay_pid; + int capture_pid; + bool low_power; + wait_queue_head_t power_queue; + unsigned int csi; + int current_input; + + /* camera sensor interface */ + struct camera_sensor *cam_sensor; /* old version */ + struct v4l2_int_device *sensor; +} cam_data; + +#if defined(CONFIG_MXC_IPU_V1) || defined(CONFIG_VIDEO_MXC_EMMA_CAMERA) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA_MODULE) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA) +void set_mclk_rate(uint32_t *p_mclk_freq); +#else +void set_mclk_rate(uint32_t *p_mclk_freq, uint32_t csi); +#endif +#endif /* __MXC_V4L2_CAPTURE_H__ */ diff --git a/drivers/media/video/mxc/capture/ov2640.c b/drivers/media/video/mxc/capture/ov2640.c new file mode 100644 index 000000000000..c968655fa71c --- /dev/null +++ b/drivers/media/video/mxc/capture/ov2640.c @@ -0,0 +1,1081 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ov2640.c + * + * @brief ov2640 camera driver functions + * + * @ingroup Camera + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "mxc_v4l2_capture.h" + +#define MIN_FPS 5 +#define MAX_FPS 30 +#define DEFAULT_FPS 30 + +#define OV2640_XCLK_MIN 6000000 +#define OV2640_XCLK_MAX 27000000 + +/* +enum ov2640_mode { + ov2640_mode_1600_1120, + ov2640_mode_800_600 +}; +*/ + +struct reg_value { + u8 reg; + u8 value; + int delay_ms; +}; + +static struct reg_value ov2640_setting_1600_1120[] = { +#ifdef CONFIG_MACH_MX25_3DS + {0xff, 0x01, 0}, {0x12, 0x80, 5}, {0xff, 0x00, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x01, 0}, {0x3c, 0x32, 0}, {0x11, 0x00, 0}, + {0x09, 0x02, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x02, 0}, + {0x35, 0x58, 0}, {0x22, 0x0a, 0}, {0x37, 0x40, 0}, {0x23, 0x00, 0}, + {0x34, 0xa0, 0}, {0x36, 0x1a, 0}, {0x06, 0x02, 0}, {0x07, 0xc0, 0}, + {0x0d, 0xb7, 0}, {0x0e, 0x01, 0}, {0x4c, 0x00, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x3f, 0}, {0x61, 0x70, 0}, + {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, {0x28, 0x30, 0}, + {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 0x00, 0}, {0x70, 0x02, 0}, + {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x3d, 0x34, 0}, {0x5a, 0x57, 0}, + {0x4f, 0xbb, 0}, {0x50, 0x9c, 0}, {0xff, 0x00, 0}, {0xe5, 0x7f, 0}, + {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0xe0, 0x14, 0}, {0x76, 0xff, 0}, + {0x33, 0xa0, 0}, {0x42, 0x20, 0}, {0x43, 0x18, 0}, {0x4c, 0x00, 0}, + {0x87, 0xd0, 0}, {0x88, 0x3f, 0}, {0xd7, 0x01, 0}, {0xd9, 0x10, 0}, + {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, {0x7c, 0x00, 0}, + {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, {0x7d, 0x48, 0}, + {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, {0x7d, 0x0e, 0}, + {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, {0x91, 0x31, 0}, + {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, {0x91, 0x7e, 0}, + {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, {0x91, 0xa3, 0}, + {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, {0x91, 0xe8, 0}, + {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, {0x93, 0xe3, 0}, + {0x93, 0x05, 0}, {0x93, 0x05, 0}, {0x93, 0x00, 0}, {0x93, 0x04, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x96, 0x00, 0}, + {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, {0x97, 0x0c, 0}, + {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, {0x97, 0x26, 0}, + {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, {0x97, 0x00, 0}, + {0x97, 0x00, 0}, {0xc3, 0xed, 0}, {0xa4, 0x00, 0}, {0xa8, 0x00, 0}, + {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, {0xc7, 0x10, 0}, + {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, {0xb9, 0x7c, 0}, + {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, {0xb0, 0xc5, 0}, + {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, {0xc0, 0xc8, 0}, + {0xc1, 0x96, 0}, {0x86, 0x1d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, + {0x52, 0x2c, 0}, {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, + {0x57, 0x00, 0}, {0x5a, 0x90, 0}, {0x5b, 0x2c, 0}, {0x5c, 0x05, 0}, + {0xc3, 0xed, 0}, {0x7f, 0x00, 0}, {0xda, 0x00, 0}, {0xe5, 0x1f, 0}, + {0xe1, 0x77, 0}, {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0}, + {0xff, 0x00, 0}, {0xe0, 0x04, 0}, {0xc0, 0xc8, 0}, {0xc1, 0x96, 0}, + {0x86, 0x3d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, {0x52, 0x2c, 0}, + {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, {0x57, 0x00, 0}, + {0x5a, 0x40, 0}, {0x5b, 0xf0, 0}, {0x5c, 0x01, 0}, {0xd3, 0x82, 0}, + {0xe0, 0x00, 1000} +#else + {0xff, 0x1, 0}, {0x12, 0x80, 1}, {0xff, 0, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, {0x11, 0x01, 0}, + {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x82, 0}, + {0x35, 0x88, 0}, {0x22, 0x0a, 0}, {0x37, 0x40, 0}, {0x23, 0x00, 0}, + {0x34, 0xa0, 0}, {0x36, 0x1a, 0}, {0x06, 0x02, 0}, {0x07, 0xc0, 0}, + {0x0d, 0xb7, 0}, {0x0e, 0x01, 0}, {0x4c, 0x00, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x3f, 0}, {0x0c, 0x3c, 0}, + {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, {0x60, 0x55, 0}, + {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, + {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 00, 0}, + {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x3d, 0x34, 0}, + {0x5a, 0x57, 0}, {0x4f, 0xbb, 0}, {0x50, 0x9c, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0xd7, 0x03, 0}, + {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, + {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, + {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, + {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, + {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, + {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, + {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, + {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, + {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, {0x93, 0x00, 0}, + {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, + {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, + {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, + {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, {0xa8, 0x00, 0}, + {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, {0xc7, 0x10, 0}, + {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, {0xb9, 0x7c, 0}, + {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, {0xb0, 0xc5, 0}, + {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, {0xa6, 0x00, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, + {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0xc8, 0}, {0xc1, 0x96, 0}, + {0x86, 0x3d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, {0x52, 0x18, 0}, + {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, {0x57, 0x00, 0}, + {0x5a, 0x90, 0}, {0x5b, 0x18, 0}, {0x5c, 0x05, 0}, {0xc3, 0xef, 0}, + {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, {0xe1, 0x67, 0}, + {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +#endif +}; + +static struct reg_value ov2640_setting_800_600[] = { +#ifdef CONFIG_MACH_MX25_3DS + {0xff, 0x01, 0}, {0x12, 0x80, 5}, {0xff, 0x00, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x01, 0}, {0x3c, 0x32, 0}, {0x11, 0x00, 0}, + {0x09, 0x02, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x92, 0}, + {0x35, 0xda, 0}, {0x22, 0x1a, 0}, {0x37, 0xc3, 0}, {0x23, 0x00, 0}, + {0x34, 0xc0, 0}, {0x36, 0x1a, 0}, {0x06, 0x88, 0}, {0x07, 0xc0, 0}, + {0x0d, 0x87, 0}, {0x0e, 0x41, 0}, {0x4c, 0x00, 0}, + {0x48, 0x00, 0}, {0x5b, 0x00, 0}, {0x42, 0x03, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x22, 0}, {0x0c, 0x3c, 0}, + {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, + {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 0x00, 0}, + {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x12, 0x40, 0}, + {0x17, 0x11, 0}, {0x18, 0x43, 0}, {0x19, 0x00, 0}, {0x1a, 0x4b, 0}, + {0x32, 0x09, 0}, {0x37, 0xc0, 0}, {0x4f, 0xca, 0}, {0x50, 0xa8, 0}, + {0x5a, 0x23, 0}, {0x6d, 0x00, 0}, {0x3d, 0x38, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0xe0, 0x14, 0}, + {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, {0x43, 0x18, 0}, + {0x4c, 0x00, 0}, {0x87, 0xd5, 0}, {0x88, 0x3f, 0}, {0xd7, 0x01, 0}, + {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, + {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, + {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, + {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, + {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, + {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, + {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, + {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, + {0x93, 0xe3, 0}, {0x93, 0x05, 0}, {0x93, 0x05, 0}, {0x93, 0x00, 0}, + {0x93, 0x04, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, + {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, + {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, + {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xc3, 0xed, 0}, {0xa4, 0x00, 0}, + {0xa8, 0x00, 0}, {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, + {0xc7, 0x10, 0}, {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, + {0xb9, 0x7c, 0}, {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, + {0xb0, 0xc5, 0}, {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, + {0xc0, 0x64, 0}, {0xc1, 0x4b, 0}, {0x8c, 0x00, 0}, {0x86, 0x3d, 0}, + {0x50, 0x00, 0}, {0x51, 0xc8, 0}, {0x52, 0x96, 0}, {0x53, 0x00, 0}, + {0x54, 0x00, 0}, {0x55, 0x00, 0}, {0x5a, 0xc8, 0}, {0x5b, 0x96, 0}, + {0x5c, 0x00, 0}, {0xd3, 0x82, 0}, {0xc3, 0xed, 0}, {0x7f, 0x00, 0}, + {0xda, 0x00, 0}, {0xe5, 0x1f, 0}, {0xe1, 0x67, 0}, {0xe0, 0x00, 0}, + {0xdd, 0x7f, 0}, {0x05, 0x00, 0}, {0xff, 0x00, 0}, {0xe0, 0x04, 0}, + {0xc0, 0x64, 0}, {0xc1, 0x4b, 0}, {0x8c, 0x00, 0}, {0x86, 0x3d, 0}, + {0x50, 0x00, 0}, {0x51, 0xc8, 0}, {0x52, 0x96, 0}, {0x53, 0x00, 0}, + {0x54, 0x00, 0}, {0x55, 0x00, 0}, {0x5a, 0xa0, 0}, {0x5b, 0x78, 0}, + {0x5c, 0x00, 0}, {0xd3, 0x82, 0}, {0xe0, 0x00, 1000} +#else + {0xff, 0, 0}, {0xff, 1, 0}, {0x12, 0x80, 1}, {0xff, 00, 0}, + {0x2c, 0xff, 0}, {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, + {0x11, 0x01, 0}, {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, + {0x14, 0x48, 0}, {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, + {0x3b, 0xfb, 0}, {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, + {0x39, 0x92, 0}, {0x35, 0xda, 0}, {0x22, 0x1a, 0}, {0x37, 0xc3, 0}, + {0x23, 0x00, 0}, {0x34, 0xc0, 0}, {0x36, 0x1a, 0}, {0x06, 0x88, 0}, + {0x07, 0xc0, 0}, {0x0d, 0x87, 0}, {0x0e, 0x41, 0}, {0x4c, 0x00, 0}, + {0x4a, 0x81, 0}, {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, + {0x26, 0x82, 0}, {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x22, 0}, + {0x0c, 0x3c, 0}, {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, + {0x60, 0x55, 0}, {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, + {0x20, 0x80, 0}, {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, + {0x6e, 00, 0}, {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, + {0x12, 0x40, 0}, {0x17, 0x11, 0}, {0x18, 0x43, 0}, {0x19, 0x00, 0}, + {0x1a, 0x4b, 0}, {0x32, 0x09, 0}, {0x37, 0xc0, 0}, {0x4f, 0xca, 0}, + {0x50, 0xa8, 0}, {0x6d, 0x00, 0}, {0x3d, 0x38, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0x88, 0x3f, 0}, + {0xd7, 0x03, 0}, {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, + {0xc9, 0x80, 0}, {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, + {0x7d, 0x48, 0}, {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, + {0x7d, 0x10, 0}, {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, + {0x91, 0x1a, 0}, {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, + {0x91, 0x75, 0}, {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, + {0x91, 0x96, 0}, {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, + {0x91, 0xd7, 0}, {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, + {0x93, 0x06, 0}, {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, + {0x93, 0x00, 0}, {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, + {0x97, 0x02, 0}, {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, + {0x97, 0x28, 0}, {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, + {0x97, 0x80, 0}, {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, + {0xa8, 0x00, 0}, {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, + {0xc7, 0x10, 0}, {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, + {0xb9, 0x7c, 0}, {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, + {0xb0, 0xc5, 0}, {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, + {0xa6, 0x00, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, + {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, + {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0x64, 0}, + {0xc1, 0x4b, 0}, {0x86, 0x1d, 0}, {0x50, 0x00, 0}, {0x51, 0xc8, 0}, + {0x52, 0x96, 0}, {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x00, 0}, + {0x57, 0x00, 0}, {0x5a, 0xc8, 0}, {0x5b, 0x96, 0}, {0x5c, 0x00, 0}, + {0xc3, 0xef, 0}, {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, + {0xe1, 0x67, 0}, {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +#endif +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + const struct ov2640_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + u32 csi; + u32 mclk; + +} ov2640_data; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/* list of image formats supported by this sensor */ +/* +const static struct v4l2_fmtdesc ov2640_formats[] = { + { + .description = "YUYV (YUV 4:2:2), packed", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, +}; + */ + +static int ov2640_init_mode(struct sensor *s) +{ + int ret = -1; + struct reg_value *setting; + int i, num; + + pr_debug("In ov2640:ov2640_init_mode capturemode is %d\n", + s->streamcap.capturemode); + + if (s->streamcap.capturemode & V4L2_MODE_HIGHQUALITY) { + s->pix.width = 1600; + s->pix.height = 1120; + setting = ov2640_setting_1600_1120; + num = ARRAY_SIZE(ov2640_setting_1600_1120); + } else { + s->pix.width = 800; + s->pix.height = 600; + setting = ov2640_setting_800_600; + num = ARRAY_SIZE(ov2640_setting_800_600); + } + + for (i = 0; i < num; i++) { + ret = i2c_smbus_write_byte_data(s->i2c_client, + setting[i].reg, + setting[i].value); + if (ret < 0) { + pr_err("write reg error: reg=%x, val=%x\n", + setting[i].reg, setting[i].value); + return ret; + } + if (setting[i].delay_ms > 0) + msleep(setting[i].delay_ms); + } + + return ret; +} + +/* At present only support change to 15fps(only for SVGA mode) */ +static int ov2640_set_fps(struct sensor *s, int fps) +{ + int ret = 0; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xff, 0x01) < 0) { + pr_err("in %s,change to sensor addr failed\n", __func__); + ret = -EPERM; + } + + /* change the camera framerate to 15fps(only for SVGA mode) */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0x11, 0x01) < 0) { + pr_err("change camera to 15fps failed\n"); + ret = -EPERM; + } + + return ret; +} + +static int ov2640_set_format(struct sensor *s, int format) +{ + int ret = 0; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xff, 0x00) < 0) + ret = -EPERM; + + if (format == V4L2_PIX_FMT_RGB565) { + /* set RGB565 format */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0xda, 0x08) < 0) + ret = -EPERM; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xd7, 0x03) < 0) + ret = -EPERM; + } else if (format == V4L2_PIX_FMT_YUV420) { + /* set YUV420 format */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0xda, 0x00) < 0) + ret = -EPERM; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xd7, 0x1b) < 0) + ret = -EPERM; + } else { + pr_debug("format not supported\n"); + } + + return ret; +} + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Given the image capture format in pix, the nominal frame period in + * timeperframe, calculate the required xclk frequency. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + pr_debug("In ov2640:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = ov2640_data.mclk; + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = OV2640_XCLK_MIN; + p->u.bt656.clock_max = OV2640_XCLK_MAX; + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on open, close, suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + pr_debug("In ov2640:ioctl_s_power\n"); + + if (on && !sensor->on) { + gpio_sensor_active(); + if (io_regulator) + if (regulator_enable(io_regulator) != 0) + return -EIO; + if (core_regulator) + if (regulator_enable(core_regulator) != 0) + return -EIO; + if (gpo_regulator) + if (regulator_enable(gpo_regulator) != 0) + return -EIO; + if (analog_regulator) + if (regulator_enable(analog_regulator) != 0) + return -EIO; + } else if (!on && sensor->on) { + if (analog_regulator) + regulator_disable(analog_regulator); + if (core_regulator) + regulator_disable(core_regulator); + if (io_regulator) + regulator_disable(io_regulator); + if (gpo_regulator) + regulator_disable(gpo_regulator); + gpio_sensor_inactive(); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + int ret = 0; + + pr_debug("In ov2640:ioctl_g_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + int ret = 0; + + pr_debug("In ov2640:ioctl_s_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + + /* Check that the new frame rate is allowed. */ + if ((timeperframe->numerator == 0) + || (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + tgt_fps = timeperframe->denominator + / timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + sensor->streamcap.timeperframe = *timeperframe; + sensor->streamcap.capturemode = + (u32)a->parm.capture.capturemode; + + ret = ov2640_init_mode(sensor); + if (tgt_fps == 15) + ov2640_set_fps(sensor, tgt_fps); + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_fmt_cap - V4L2 sensor interface handler for ioctl_s_fmt_cap + * set camera output format and resolution format + * + * @s: pointer to standard V4L2 device structure + * @arg: pointer to parameter, according this to set camera + * + * Returns 0 if set succeed, else return -1 + */ +static int ioctl_s_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + u32 format = f->fmt.pix.pixelformat; + int size = 0, ret = 0; + + size = f->fmt.pix.width * f->fmt.pix.height; + switch (format) { + case V4L2_PIX_FMT_RGB565: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + + ret = ov2640_set_format(sensor, V4L2_PIX_FMT_RGB565); + break; + case V4L2_PIX_FMT_UYVY: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + break; + case V4L2_PIX_FMT_YUV420: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + + /* YUYV: width * 2, YY: width */ + ret = ov2640_set_format(sensor, V4L2_PIX_FMT_YUV420); + break; + default: + pr_debug("case not supported\n"); + break; + } + + return ret; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + pr_debug("In ov2640:ioctl_g_fmt_cap.\n"); + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + pr_debug("In ov2640:ioctl_g_ctrl\n"); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + vc->value = ov2640_data.brightness; + break; + case V4L2_CID_HUE: + vc->value = ov2640_data.hue; + break; + case V4L2_CID_CONTRAST: + vc->value = ov2640_data.contrast; + break; + case V4L2_CID_SATURATION: + vc->value = ov2640_data.saturation; + break; + case V4L2_CID_RED_BALANCE: + vc->value = ov2640_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + vc->value = ov2640_data.blue; + break; + case V4L2_CID_EXPOSURE: + vc->value = ov2640_data.ae_mode; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In ov2640:ioctl_s_ctrl %d\n", vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + break; + default: + pr_debug(" Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + pr_debug("In ov2640:ioctl_init\n"); + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct sensor *sensor = s->priv; + u32 tgt_xclk; /* target xclk */ + + pr_debug("In ov2640:ioctl_dev_init\n"); + + gpio_sensor_active(); + ov2640_data.on = true; + + tgt_xclk = ov2640_data.mclk; + tgt_xclk = min(tgt_xclk, (u32)OV2640_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)OV2640_XCLK_MIN); + ov2640_data.mclk = tgt_xclk; + + pr_debug(" Setting mclk to %d MHz\n", + tgt_xclk / 1000000); + set_mclk_rate(&ov2640_data.mclk); + + return ov2640_init_mode(sensor); +} + +/*! + * ioctl_dev_exit - V4L2 sensor interface handler for vidioc_int_dev_exit_num + * @s: pointer to standard V4L2 device structure + * + * Delinitialise the device when slave detaches to the master. + */ +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + pr_debug("In ov2640:ioctl_dev_exit\n"); + + gpio_sensor_inactive(); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc ov2640_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func *)ioctl_dev_exit}, + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, + {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave ov2640_slave = { + .ioctls = ov2640_ioctl_desc, + .num_ioctls = ARRAY_SIZE(ov2640_ioctl_desc), +}; + +static struct v4l2_int_device ov2640_int_device = { + .module = THIS_MODULE, + .name = "ov2640", + .type = v4l2_int_type_slave, + .u = { + .slave = &ov2640_slave, + }, +}; + +/*! + * ov2640 I2C attach function + * Function set in i2c_driver struct. + * Called by insmod ov2640_camera.ko. + * + * @param client struct i2c_client* + * @return Error code indicating success or failure + */ +static int ov2640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + pr_debug("In ov2640_probe (RH_BT565)\n"); + + /* Set initial values for the sensor struct. */ + memset(&ov2640_data, 0, sizeof(ov2640_data)); + ov2640_data.i2c_client = client; + ov2640_data.mclk = 24000000; + ov2640_data.mclk = plat_data->mclk; + ov2640_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + ov2640_data.pix.width = 800; + ov2640_data.pix.height = 600; + ov2640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY + | V4L2_CAP_TIMEPERFRAME; + ov2640_data.streamcap.capturemode = 0; + ov2640_data.streamcap.timeperframe.denominator = DEFAULT_FPS; + ov2640_data.streamcap.timeperframe.numerator = 1; + + if (plat_data->io_regulator) { + io_regulator = + regulator_get(&client->dev, plat_data->io_regulator); + if (!IS_ERR(io_regulator)) { + regulator_set_voltage(io_regulator, 2800000, 2800000); + if (regulator_enable(io_regulator) != 0) { + pr_err("%s:io set voltage error\n", __func__); + goto err1; + } else { + dev_dbg(&client->dev, + "%s:io set voltage ok\n", __func__); + } + } else + io_regulator = NULL; + } + + if (plat_data->core_regulator) { + core_regulator = + regulator_get(&client->dev, plat_data->core_regulator); + if (!IS_ERR(core_regulator)) { + regulator_set_voltage(core_regulator, + 1300000, 1300000); + if (regulator_enable(core_regulator) != 0) { + pr_err("%s:core set voltage error\n", __func__); + goto err2; + } else { + dev_dbg(&client->dev, + "%s:core set voltage ok\n", __func__); + } + } else + core_regulator = NULL; + } + + if (plat_data->analog_regulator) { + analog_regulator = + regulator_get(&client->dev, plat_data->analog_regulator); + if (!IS_ERR(analog_regulator)) { + regulator_set_voltage(analog_regulator, 2000000, 2000000); + if (regulator_enable(analog_regulator) != 0) { + pr_err("%s:analog set voltage error\n", + __func__); + goto err3; + } else { + dev_dbg(&client->dev, + "%s:analog set voltage ok\n", __func__); + } + } else + analog_regulator = NULL; + } + + if (plat_data->gpo_regulator) { + gpo_regulator = + regulator_get(&client->dev, plat_data->gpo_regulator); + if (!IS_ERR(gpo_regulator)) { + if (regulator_enable(gpo_regulator) != 0) { + pr_err("%s:gpo3 set voltage error\n", __func__); + goto err4; + } else { + dev_dbg(&client->dev, + "%s:gpo3 set voltage ok\n", __func__); + } + } else + gpo_regulator = NULL; + } + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the ov2640_data structure here.*/ + ov2640_int_device.priv = &ov2640_data; + retval = v4l2_int_device_register(&ov2640_int_device); + + return retval; + +err4: + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } +err3: + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } +err2: + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } +err1: + return -1; +} + +/*! + * ov2640 I2C detach function + * Called on rmmod ov2640_camera.ko + * + * @param client struct i2c_client* + * @return Error code indicating success or failure + */ +static int ov2640_remove(struct i2c_client *client) +{ + pr_debug("In ov2640_remove\n"); + + v4l2_int_device_unregister(&ov2640_int_device); + + if (gpo_regulator) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + return 0; +} + +static const struct i2c_device_id ov2640_id[] = { + {"ov2640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov2640_id); + +static struct i2c_driver ov2640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov2640", + }, + .probe = ov2640_probe, + .remove = ov2640_remove, + .id_table = ov2640_id, +/* To add power management add .suspend and .resume functions */ +}; + +/*! + * ov2640 init function + * Called by insmod ov2640_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int ov2640_init(void) +{ + u8 err; + + pr_debug("In ov2640_init\n"); + + err = i2c_add_driver(&ov2640_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * OV2640 cleanup function + * Called on rmmod ov2640_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit ov2640_clean(void) +{ + pr_debug("In ov2640_clean\n"); + i2c_del_driver(&ov2640_i2c_driver); +} + +module_init(ov2640_init); +module_exit(ov2640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV2640 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ov3640.c b/drivers/media/video/mxc/capture/ov3640.c new file mode 100644 index 000000000000..dc93b7234cea --- /dev/null +++ b/drivers/media/video/mxc/capture/ov3640.c @@ -0,0 +1,1510 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxc_v4l2_capture.h" + +#define OV3640_VOLTAGE_ANALOG 2800000 +#define OV3640_VOLTAGE_DIGITAL_CORE 1500000 +#define OV3640_VOLTAGE_DIGITAL_IO 1800000 +#define OV3640_VOLTAGE_DIGITAL_GPO 2800000 + +#define MIN_FPS 15 +#define MAX_FPS 30 +#define DEFAULT_FPS 30 + +#define OV3640_XCLK_MIN 6000000 +#define OV3640_XCLK_MAX 24000000 + +enum ov3640_mode { + ov3640_mode_MIN = 0, + ov3640_mode_VGA_640_480 = 0, + ov3640_mode_QVGA_320_240 = 1, + ov3640_mode_XGA_1024_768 = 2, + ov3640_mode_QXGA_2048_1536 = 3, + ov3640_mode_NTSC_720_480 = 4, + ov3640_mode_PAL_720_576 = 5, + ov3640_mode_MAX = 5 +}; + +enum ov3640_frame_rate { + ov3640_15_fps, + ov3640_30_fps +}; + +struct reg_value { + u16 u16RegAddr; + u8 u8Val; + u8 u8Mask; + u32 u32Delay_ms; +}; + +struct ov3640_mode_info { + enum ov3640_mode mode; + u32 width; + u32 height; + struct reg_value *init_data_ptr; + u32 init_data_size; +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + const struct ov3640_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + u32 mclk; + int csi; +} ov3640_data; + +static struct reg_value ov3640_setting_15fps_QXGA_2048_1536[] = { +#if 0 + /* The true 15fps QXGA setting. */ + {0x3012, 0x80, 0, 0}, {0x304d, 0x41, 0, 0}, {0x3087, 0x16, 0, 0}, + {0x30aa, 0x45, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x13, 0, 0}, {0x30d7, 0x10, 0, 0}, {0x309e, 0x00, 0, 0}, + {0x3602, 0x26, 0, 0}, {0x3603, 0x4D, 0, 0}, {0x364c, 0x04, 0, 0}, + {0x360c, 0x12, 0, 0}, {0x361e, 0x00, 0, 0}, {0x361f, 0x11, 0, 0}, + {0x3633, 0x03, 0, 0}, {0x3629, 0x3c, 0, 0}, {0x300e, 0x33, 0, 0}, + {0x300f, 0x21, 0, 0}, {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, + {0x304c, 0x81, 0, 0}, {0x3029, 0x47, 0, 0}, {0x3070, 0x00, 0, 0}, + {0x3071, 0xEC, 0, 0}, {0x301C, 0x06, 0, 0}, {0x3072, 0x00, 0, 0}, + {0x3073, 0xC5, 0, 0}, {0x301D, 0x07, 0, 0}, {0x3018, 0x38, 0, 0}, + {0x3019, 0x30, 0, 0}, {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, + {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, {0x303c, 0x08, 0, 0}, + {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, {0x303F, 0x0c, 0, 0}, + {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, + {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, + {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, {0x3015, 0x12, 0, 0}, + {0x3014, 0x04, 0, 0}, {0x3013, 0xf7, 0, 0}, {0x3104, 0x02, 0, 0}, + {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, + {0x3308, 0xa5, 0, 0}, {0x3316, 0xff, 0, 0}, {0x3317, 0x00, 0, 0}, + {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, {0x3300, 0x13, 0, 0}, + {0x3301, 0xd6, 0, 0}, {0x3302, 0xef, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3304, 0x00, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x02, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x18, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x0c, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x18, 0, 0}, {0x3361, 0x0c, 0, 0}, + {0x3362, 0x68, 0, 0}, {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, + {0x3403, 0x42, 0, 0}, {0x3088, 0x08, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, {0x3507, 0x06, 0, 0}, + {0x350a, 0x4f, 0, 0}, {0x3600, 0xc4, 0, 0}, +#endif + /* + * Only support 7.5fps for QXGA to workaround screen tearing issue + * for 15fps when capturing still image. + */ + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x02, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3088, 0x08, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, + {0x3086, 0x00, 0, 0}, {0x3011, 0x01, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_XGA_1024_768[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x01, 0, 0}, {0x3404, 0x1d, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x34, 0, 0}, {0x3363, 0x00, 0, 0}, {0x3364, 0x00, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x03, 0, 0}, {0x308b, 0x00, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x01, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_XGA_1024_768[] = { + {0x0, 0x0, 0} +}; + +static struct reg_value ov3640_setting_15fps_VGA_640_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x42, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x12, 0, 0}, {0x3363, 0x80, 0, 0}, {0x3364, 0xe0, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, + {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x00, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_VGA_640_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x0c, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, + {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0x10, 0x30, 0}, {0x3304, 0x00, 0x03, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x02, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3012, 0x10, 0, 0}, + {0x3023, 0x06, 0, 0}, {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, + {0x302a, 0x03, 0, 0}, {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, + {0x300d, 0x01, 0, 0}, {0x30d7, 0x80, 0x80, 0}, {0x3069, 0x00, 0x40, 0}, + {0x303e, 0x00, 0, 0}, {0x303f, 0xc0, 0, 0}, {0x3302, 0x20, 0x20, 0}, + {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, + {0x3362, 0x12, 0, 0}, {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, + {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, + {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, {0x3362, 0x12, 0, 0}, + {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, {0x308a, 0x01, 0, 0}, + {0x308b, 0xe0, 0, 0}, {0x300e, 0x37, 0, 0}, {0x300f, 0xe1, 0, 0}, + {0x3010, 0x22, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x84, 0, 0}, + {0x3014, 0x04, 0, 0}, {0x3015, 0x02, 0, 0}, {0x302e, 0x00, 0, 0}, + {0x302d, 0x00, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_QVGA_320_240[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x42, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x01, 0, 0}, {0x3363, 0x40, 0, 0}, {0x3364, 0xf0, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x01, 0, 0}, {0x3089, 0x40, 0, 0}, + {0x308a, 0x00, 0, 0}, {0x308b, 0xf0, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x01, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_QVGA_320_240[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x0c, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, + {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0x10, 0x30, 0}, {0x3304, 0x00, 0x03, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x02, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3012, 0x10, 0, 0}, + {0x3023, 0x06, 0, 0}, {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, + {0x302a, 0x03, 0, 0}, {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, + {0x300d, 0x01, 0, 0}, {0x30d7, 0x80, 0x80, 0}, {0x3069, 0x00, 0x40, 0}, + {0x303e, 0x00, 0, 0}, {0x303f, 0xc0, 0, 0}, {0x3302, 0x20, 0x20, 0}, + {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, + {0x3362, 0x34, 0, 0}, {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, + {0x3403, 0x42, 0, 0}, {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x03, 0, 0}, {0x308b, 0x00, 0, 0}, {0x3362, 0x12, 0, 0}, + {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, {0x308a, 0x01, 0, 0}, + {0x308b, 0xe0, 0, 0}, {0x300e, 0x37, 0, 0}, {0x300f, 0xe1, 0, 0}, + {0x3010, 0x22, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x84, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_NTSC_720_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309C, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x304c, 0x81, 0, 0}, {0x30d7, 0x10, 0, 0}, + {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, {0x3016, 0x82, 0, 0}, + {0x3018, 0x48, 0, 0}, {0x3019, 0x40, 0, 0}, {0x301a, 0x82, 0, 0}, + {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, + {0x3015, 0x12, 0, 0}, {0x3014, 0x84, 0, 0}, {0x3013, 0xf7, 0, 0}, + {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, + {0x303F, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, + {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, + {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, + {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, + {0x3107, 0xff, 0, 0}, {0x3300, 0x13, 0, 0}, {0x3301, 0xde, 0, 0}, + {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, {0x3314, 0x42, 0, 0}, + {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, {0x3310, 0xd0, 0, 0}, + {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, + {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, {0x330b, 0x1c, 0, 0}, + {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, {0x336a, 0x52, 0, 0}, + {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x3088, 0x08, 0, 0}, + {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, + {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, + {0x30a9, 0xb5, 0, 0}, {0x3317, 0x04, 0, 0}, {0x3316, 0xf8, 0, 0}, + {0x3312, 0x17, 0, 0}, {0x3314, 0x30, 0, 0}, {0x3313, 0x23, 0, 0}, + {0x3315, 0x3e, 0, 0}, {0x3311, 0x9e, 0, 0}, {0x3310, 0xc0, 0, 0}, + {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, {0x330e, 0x5e, 0, 0}, + {0x330f, 0x6c, 0, 0}, {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, + {0x3307, 0x11, 0, 0}, {0x3308, 0x25, 0, 0}, {0x3340, 0x20, 0, 0}, + {0x3341, 0x50, 0, 0}, {0x3342, 0x18, 0, 0}, {0x3343, 0x23, 0, 0}, + {0x3344, 0xad, 0, 0}, {0x3345, 0xd0, 0, 0}, {0x3346, 0xb8, 0, 0}, + {0x3347, 0xb4, 0, 0}, {0x3348, 0x04, 0, 0}, {0x3349, 0x98, 0, 0}, + {0x3355, 0x02, 0, 0}, {0x3358, 0x44, 0, 0}, {0x3359, 0x44, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x3367, 0x23, 0, 0}, {0x3368, 0xBB, 0, 0}, + {0x3369, 0xD6, 0, 0}, {0x336A, 0x2A, 0, 0}, {0x336B, 0x07, 0, 0}, + {0x336C, 0x00, 0, 0}, {0x336D, 0x23, 0, 0}, {0x336E, 0xC3, 0, 0}, + {0x336F, 0xDE, 0, 0}, {0x3370, 0x2b, 0, 0}, {0x3371, 0x07, 0, 0}, + {0x3372, 0x00, 0, 0}, {0x3373, 0x23, 0, 0}, {0x3374, 0x9e, 0, 0}, + {0x3375, 0xD6, 0, 0}, {0x3376, 0x29, 0, 0}, {0x3377, 0x07, 0, 0}, + {0x3378, 0x00, 0, 0}, {0x332a, 0x1d, 0, 0}, {0x331b, 0x08, 0, 0}, + {0x331c, 0x16, 0, 0}, {0x331d, 0x2d, 0, 0}, {0x331e, 0x54, 0, 0}, + {0x331f, 0x66, 0, 0}, {0x3320, 0x73, 0, 0}, {0x3321, 0x80, 0, 0}, + {0x3322, 0x8c, 0, 0}, {0x3323, 0x95, 0, 0}, {0x3324, 0x9d, 0, 0}, + {0x3325, 0xac, 0, 0}, {0x3326, 0xb8, 0, 0}, {0x3327, 0xcc, 0, 0}, + {0x3328, 0xdd, 0, 0}, {0x3329, 0xee, 0, 0}, {0x332e, 0x04, 0, 0}, + {0x332f, 0x04, 0, 0}, {0x3331, 0x02, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3610, 0x40, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x307C, 0x10, 0, 0}, {0x3012, 0x10, 0, 0}, {0x3023, 0x06, 0, 0}, + {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, {0x302a, 0x03, 0, 0}, + {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, {0x300d, 0x01, 0, 0}, + {0x30d7, 0x90, 0, 0}, {0x3069, 0x04, 0, 0}, {0x303e, 0x00, 0, 0}, + {0x303f, 0xc0, 0, 0}, {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, + {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, {0x3362, 0x34, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x03, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, + {0x3361, 0x04, 0, 0}, {0x3362, 0x23, 0, 0}, {0x3363, 0x28, 0, 0}, + {0x3364, 0x5c, 0, 0}, {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, + {0x3089, 0xD0, 0, 0}, {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, + {0x304c, 0x83, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3011, 0x00, 0, 0}, {0x3010, 0x81, 0, 0}, {0x302e, 0x00, 0, 0}, + {0x302d, 0x00, 0, 0}, {0x3071, 0xeb, 0, 0}, {0x301C, 0x02, 0, 0}, + {0x3404, 0x02, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_PAL_720_576[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309C, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x304c, 0x81, 0, 0}, {0x30d7, 0x10, 0, 0}, + {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, {0x3016, 0x82, 0, 0}, + {0x3018, 0x48, 0, 0}, {0x3019, 0x40, 0, 0}, {0x301a, 0x82, 0, 0}, + {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, + {0x3015, 0x12, 0, 0}, {0x3014, 0x84, 0, 0}, {0x3013, 0xf7, 0, 0}, + {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, + {0x303F, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, + {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, + {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, + {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, + {0x3107, 0xff, 0, 0}, {0x3300, 0x13, 0, 0}, {0x3301, 0xde, 0, 0}, + {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, {0x3314, 0x42, 0, 0}, + {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, {0x3310, 0xd0, 0, 0}, + {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, + {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, {0x330b, 0x1c, 0, 0}, + {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, {0x336a, 0x52, 0, 0}, + {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x3088, 0x08, 0, 0}, + {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, + {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, + {0x30a9, 0xb5, 0, 0}, {0x3317, 0x04, 0, 0}, {0x3316, 0xf8, 0, 0}, + {0x3312, 0x17, 0, 0}, {0x3314, 0x30, 0, 0}, {0x3313, 0x23, 0, 0}, + {0x3315, 0x3e, 0, 0}, {0x3311, 0x9e, 0, 0}, {0x3310, 0xc0, 0, 0}, + {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, {0x330e, 0x5e, 0, 0}, + {0x330f, 0x6c, 0, 0}, {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, + {0x3307, 0x11, 0, 0}, {0x3308, 0x25, 0, 0}, {0x3340, 0x20, 0, 0}, + {0x3341, 0x50, 0, 0}, {0x3342, 0x18, 0, 0}, {0x3343, 0x23, 0, 0}, + {0x3344, 0xad, 0, 0}, {0x3345, 0xd0, 0, 0}, {0x3346, 0xb8, 0, 0}, + {0x3347, 0xb4, 0, 0}, {0x3348, 0x04, 0, 0}, {0x3349, 0x98, 0, 0}, + {0x3355, 0x02, 0, 0}, {0x3358, 0x44, 0, 0}, {0x3359, 0x44, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x3367, 0x23, 0, 0}, {0x3368, 0xBB, 0, 0}, + {0x3369, 0xD6, 0, 0}, {0x336A, 0x2A, 0, 0}, {0x336B, 0x07, 0, 0}, + {0x336C, 0x00, 0, 0}, {0x336D, 0x23, 0, 0}, {0x336E, 0xC3, 0, 0}, + {0x336F, 0xDE, 0, 0}, {0x3370, 0x2b, 0, 0}, {0x3371, 0x07, 0, 0}, + {0x3372, 0x00, 0, 0}, {0x3373, 0x23, 0, 0}, {0x3374, 0x9e, 0, 0}, + {0x3375, 0xD6, 0, 0}, {0x3376, 0x29, 0, 0}, {0x3377, 0x07, 0, 0}, + {0x3378, 0x00, 0, 0}, {0x332a, 0x1d, 0, 0}, {0x331b, 0x08, 0, 0}, + {0x331c, 0x16, 0, 0}, {0x331d, 0x2d, 0, 0}, {0x331e, 0x54, 0, 0}, + {0x331f, 0x66, 0, 0}, {0x3320, 0x73, 0, 0}, {0x3321, 0x80, 0, 0}, + {0x3322, 0x8c, 0, 0}, {0x3323, 0x95, 0, 0}, {0x3324, 0x9d, 0, 0}, + {0x3325, 0xac, 0, 0}, {0x3326, 0xb8, 0, 0}, {0x3327, 0xcc, 0, 0}, + {0x3328, 0xdd, 0, 0}, {0x3329, 0xee, 0, 0}, {0x332e, 0x04, 0, 0}, + {0x332f, 0x04, 0, 0}, {0x3331, 0x02, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3610, 0x40, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x307C, 0x10, 0, 0}, {0x3012, 0x10, 0, 0}, {0x3023, 0x06, 0, 0}, + {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, {0x302a, 0x03, 0, 0}, + {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, {0x300d, 0x01, 0, 0}, + {0x30d7, 0x90, 0, 0}, {0x3069, 0x04, 0, 0}, {0x303e, 0x00, 0, 0}, + {0x303f, 0xc0, 0, 0}, {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, + {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, {0x3362, 0x34, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x03, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, + {0x3361, 0x04, 0, 0}, {0x3362, 0x23, 0, 0}, {0x3363, 0x28, 0, 0}, + {0x3364, 0x5c, 0, 0}, {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, + {0x3089, 0xD0, 0, 0}, {0x308a, 0x02, 0, 0}, {0x308b, 0x40, 0, 0}, + {0x304c, 0x83, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3011, 0x00, 0, 0}, {0x3010, 0x81, 0, 0}, {0x302e, 0x00, 0, 0}, + {0x302d, 0x00, 0, 0}, {0x3071, 0xeb, 0, 0}, {0x301C, 0x02, 0, 0}, + {0x3404, 0x02, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_NTSC_720_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309C, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x304c, 0x81, 0, 0}, {0x30d7, 0x10, 0, 0}, + {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, {0x3016, 0x82, 0, 0}, + {0x3018, 0x48, 0, 0}, {0x3019, 0x40, 0, 0}, {0x301a, 0x82, 0, 0}, + {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, + {0x3015, 0x12, 0, 0}, {0x3014, 0x84, 0, 0}, {0x3013, 0xf7, 0, 0}, + {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, + {0x303F, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, + {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, + {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, + {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, + {0x3107, 0xff, 0, 0}, {0x3300, 0x13, 0, 0}, {0x3301, 0xde, 0, 0}, + {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, {0x3314, 0x42, 0, 0}, + {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, {0x3310, 0xd0, 0, 0}, + {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, + {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, {0x330b, 0x1c, 0, 0}, + {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, {0x336a, 0x52, 0, 0}, + {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x3088, 0x08, 0, 0}, + {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, + {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, + {0x30a9, 0xb5, 0, 0}, {0x3317, 0x04, 0, 0}, {0x3316, 0xf8, 0, 0}, + {0x3312, 0x17, 0, 0}, {0x3314, 0x30, 0, 0}, {0x3313, 0x23, 0, 0}, + {0x3315, 0x3e, 0, 0}, {0x3311, 0x9e, 0, 0}, {0x3310, 0xc0, 0, 0}, + {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, {0x330e, 0x5e, 0, 0}, + {0x330f, 0x6c, 0, 0}, {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, + {0x3307, 0x11, 0, 0}, {0x3308, 0x25, 0, 0}, {0x3340, 0x20, 0, 0}, + {0x3341, 0x50, 0, 0}, {0x3342, 0x18, 0, 0}, {0x3343, 0x23, 0, 0}, + {0x3344, 0xad, 0, 0}, {0x3345, 0xd0, 0, 0}, {0x3346, 0xb8, 0, 0}, + {0x3347, 0xb4, 0, 0}, {0x3348, 0x04, 0, 0}, {0x3349, 0x98, 0, 0}, + {0x3355, 0x02, 0, 0}, {0x3358, 0x44, 0, 0}, {0x3359, 0x44, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x3367, 0x23, 0, 0}, {0x3368, 0xBB, 0, 0}, + {0x3369, 0xD6, 0, 0}, {0x336A, 0x2A, 0, 0}, {0x336B, 0x07, 0, 0}, + {0x336C, 0x00, 0, 0}, {0x336D, 0x23, 0, 0}, {0x336E, 0xC3, 0, 0}, + {0x336F, 0xDE, 0, 0}, {0x3370, 0x2b, 0, 0}, {0x3371, 0x07, 0, 0}, + {0x3372, 0x00, 0, 0}, {0x3373, 0x23, 0, 0}, {0x3374, 0x9e, 0, 0}, + {0x3375, 0xD6, 0, 0}, {0x3376, 0x29, 0, 0}, {0x3377, 0x07, 0, 0}, + {0x3378, 0x00, 0, 0}, {0x332a, 0x1d, 0, 0}, {0x331b, 0x08, 0, 0}, + {0x331c, 0x16, 0, 0}, {0x331d, 0x2d, 0, 0}, {0x331e, 0x54, 0, 0}, + {0x331f, 0x66, 0, 0}, {0x3320, 0x73, 0, 0}, {0x3321, 0x80, 0, 0}, + {0x3322, 0x8c, 0, 0}, {0x3323, 0x95, 0, 0}, {0x3324, 0x9d, 0, 0}, + {0x3325, 0xac, 0, 0}, {0x3326, 0xb8, 0, 0}, {0x3327, 0xcc, 0, 0}, + {0x3328, 0xdd, 0, 0}, {0x3329, 0xee, 0, 0}, {0x332e, 0x04, 0, 0}, + {0x332f, 0x04, 0, 0}, {0x3331, 0x02, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3610, 0x40, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x307C, 0x10, 0, 0}, {0x3012, 0x10, 0, 0}, {0x3023, 0x06, 0, 0}, + {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, {0x302a, 0x03, 0, 0}, + {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, {0x300d, 0x01, 0, 0}, + {0x30d7, 0x90, 0, 0}, {0x3069, 0x04, 0, 0}, {0x303e, 0x00, 0, 0}, + {0x303f, 0xc0, 0, 0}, {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, + {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, {0x3362, 0x34, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x03, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, + {0x3361, 0x04, 0, 0}, {0x3362, 0x23, 0, 0}, {0x3363, 0x28, 0, 0}, + {0x3364, 0x5c, 0, 0}, {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, + {0x3089, 0xD0, 0, 0}, {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, + {0x304c, 0x83, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0xA1, 0, 0}, + {0x3011, 0x00, 0, 0}, {0x3010, 0x81, 0, 0}, {0x3014, 0x84, 0, 0}, + {0x302e, 0x00, 0, 0}, {0x302d, 0x00, 0, 0}, {0x3071, 0xeb, 0, 0}, + {0x301C, 0x02, 0, 0}, {0x3404, 0x02, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_PAL_720_576[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3086, 0x16, 0, 0}, {0x309C, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x304c, 0x81, 0, 0}, {0x30d7, 0x10, 0, 0}, + {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, {0x3016, 0x82, 0, 0}, + {0x3018, 0x48, 0, 0}, {0x3019, 0x40, 0, 0}, {0x301a, 0x82, 0, 0}, + {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, + {0x3015, 0x12, 0, 0}, {0x3014, 0x84, 0, 0}, {0x3013, 0xf7, 0, 0}, + {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, + {0x303F, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, + {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, + {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, + {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, + {0x3107, 0xff, 0, 0}, {0x3300, 0x13, 0, 0}, {0x3301, 0xde, 0, 0}, + {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, {0x3314, 0x42, 0, 0}, + {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, {0x3310, 0xd0, 0, 0}, + {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, + {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, {0x330b, 0x1c, 0, 0}, + {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, {0x336a, 0x52, 0, 0}, + {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x3088, 0x08, 0, 0}, + {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, + {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, + {0x30a9, 0xb5, 0, 0}, {0x3317, 0x04, 0, 0}, {0x3316, 0xf8, 0, 0}, + {0x3312, 0x17, 0, 0}, {0x3314, 0x30, 0, 0}, {0x3313, 0x23, 0, 0}, + {0x3315, 0x3e, 0, 0}, {0x3311, 0x9e, 0, 0}, {0x3310, 0xc0, 0, 0}, + {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, {0x330e, 0x5e, 0, 0}, + {0x330f, 0x6c, 0, 0}, {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, + {0x3307, 0x11, 0, 0}, {0x3308, 0x25, 0, 0}, {0x3340, 0x20, 0, 0}, + {0x3341, 0x50, 0, 0}, {0x3342, 0x18, 0, 0}, {0x3343, 0x23, 0, 0}, + {0x3344, 0xad, 0, 0}, {0x3345, 0xd0, 0, 0}, {0x3346, 0xb8, 0, 0}, + {0x3347, 0xb4, 0, 0}, {0x3348, 0x04, 0, 0}, {0x3349, 0x98, 0, 0}, + {0x3355, 0x02, 0, 0}, {0x3358, 0x44, 0, 0}, {0x3359, 0x44, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x3367, 0x23, 0, 0}, {0x3368, 0xBB, 0, 0}, + {0x3369, 0xD6, 0, 0}, {0x336A, 0x2A, 0, 0}, {0x336B, 0x07, 0, 0}, + {0x336C, 0x00, 0, 0}, {0x336D, 0x23, 0, 0}, {0x336E, 0xC3, 0, 0}, + {0x336F, 0xDE, 0, 0}, {0x3370, 0x2b, 0, 0}, {0x3371, 0x07, 0, 0}, + {0x3372, 0x00, 0, 0}, {0x3373, 0x23, 0, 0}, {0x3374, 0x9e, 0, 0}, + {0x3375, 0xD6, 0, 0}, {0x3376, 0x29, 0, 0}, {0x3377, 0x07, 0, 0}, + {0x3378, 0x00, 0, 0}, {0x332a, 0x1d, 0, 0}, {0x331b, 0x08, 0, 0}, + {0x331c, 0x16, 0, 0}, {0x331d, 0x2d, 0, 0}, {0x331e, 0x54, 0, 0}, + {0x331f, 0x66, 0, 0}, {0x3320, 0x73, 0, 0}, {0x3321, 0x80, 0, 0}, + {0x3322, 0x8c, 0, 0}, {0x3323, 0x95, 0, 0}, {0x3324, 0x9d, 0, 0}, + {0x3325, 0xac, 0, 0}, {0x3326, 0xb8, 0, 0}, {0x3327, 0xcc, 0, 0}, + {0x3328, 0xdd, 0, 0}, {0x3329, 0xee, 0, 0}, {0x332e, 0x04, 0, 0}, + {0x332f, 0x04, 0, 0}, {0x3331, 0x02, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3610, 0x40, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x307C, 0x10, 0, 0}, {0x3012, 0x10, 0, 0}, {0x3023, 0x06, 0, 0}, + {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, {0x302a, 0x03, 0, 0}, + {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, {0x300d, 0x01, 0, 0}, + {0x30d7, 0x90, 0, 0}, {0x3069, 0x04, 0, 0}, {0x303e, 0x00, 0, 0}, + {0x303f, 0xc0, 0, 0}, {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, + {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, {0x3362, 0x34, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x03, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, + {0x3361, 0x04, 0, 0}, {0x3362, 0x23, 0, 0}, {0x3363, 0x28, 0, 0}, + {0x3364, 0x5c, 0, 0}, {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, + {0x3089, 0xD0, 0, 0}, {0x308a, 0x02, 0, 0}, {0x308b, 0x40, 0, 0}, + {0x304c, 0x83, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0xA1, 0, 0}, + {0x3011, 0x00, 0, 0}, {0x3010, 0x81, 0, 0}, {0x3014, 0x84, 0, 0}, + {0x302e, 0x00, 0, 0}, {0x302d, 0x00, 0, 0}, {0x3071, 0xeb, 0, 0}, + {0x301C, 0x02, 0, 0}, {0x3404, 0x02, 0, 0}, +}; + +static struct ov3640_mode_info ov3640_mode_info_data[2][ov3640_mode_MAX + 1] = { + { + {ov3640_mode_VGA_640_480, 640, 480, + ov3640_setting_15fps_VGA_640_480, + ARRAY_SIZE(ov3640_setting_15fps_VGA_640_480)}, + {ov3640_mode_QVGA_320_240, 320, 240, + ov3640_setting_15fps_QVGA_320_240, + ARRAY_SIZE(ov3640_setting_15fps_QVGA_320_240)}, + {ov3640_mode_XGA_1024_768, 1024, 768, + ov3640_setting_15fps_XGA_1024_768, + ARRAY_SIZE(ov3640_setting_15fps_XGA_1024_768)}, + {ov3640_mode_QXGA_2048_1536, 2048, 1536, + ov3640_setting_15fps_QXGA_2048_1536, + ARRAY_SIZE(ov3640_setting_15fps_QXGA_2048_1536)}, + {ov3640_mode_NTSC_720_480, 720, 480, + ov3640_setting_15fps_NTSC_720_480, + ARRAY_SIZE(ov3640_setting_15fps_NTSC_720_480)}, + {ov3640_mode_PAL_720_576, 720, 576, + ov3640_setting_15fps_PAL_720_576, + ARRAY_SIZE(ov3640_setting_15fps_PAL_720_576)}, + }, + { + {ov3640_mode_VGA_640_480, 640, 480, + ov3640_setting_30fps_VGA_640_480, + ARRAY_SIZE(ov3640_setting_30fps_VGA_640_480)}, + {ov3640_mode_QVGA_320_240, 320, 240, + ov3640_setting_30fps_QVGA_320_240, + ARRAY_SIZE(ov3640_setting_30fps_QVGA_320_240)}, + {ov3640_mode_XGA_1024_768, 1024, 768, + ov3640_setting_30fps_XGA_1024_768, + ARRAY_SIZE(ov3640_setting_30fps_XGA_1024_768)}, + {ov3640_mode_QXGA_2048_1536, 0, 0, NULL, 0}, + {ov3640_mode_NTSC_720_480, 720, 480, + ov3640_setting_30fps_NTSC_720_480, + ARRAY_SIZE(ov3640_setting_30fps_NTSC_720_480)}, + {ov3640_mode_PAL_720_576, 720, 576, + ov3640_setting_30fps_PAL_720_576, + ARRAY_SIZE(ov3640_setting_30fps_PAL_720_576)}, + }, +}; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; +static struct mxc_camera_platform_data *camera_plat; + +static int ov3640_probe(struct i2c_client *adapter, + const struct i2c_device_id *device_id); +static int ov3640_remove(struct i2c_client *client); + +static s32 ov3640_read_reg(u16 reg, u8 *val); +static s32 ov3640_write_reg(u16 reg, u8 val); + +static const struct i2c_device_id ov3640_id[] = { + {"ov3640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov3640_id); + +static struct i2c_driver ov3640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov3640", + }, + .probe = ov3640_probe, + .remove = ov3640_remove, + .id_table = ov3640_id, +}; + +#if 0 +extern void gpio_sensor_active(unsigned int csi_index); +extern void gpio_sensor_inactive(unsigned int csi); +#else +void gpio_sensor_active(unsigned int csi_index) +{ + return; +} +void gpio_sensor_inactive(unsigned int csi) +{ + return; +} +#endif + +static s32 ov3640_write_reg(u16 reg, u8 val) +{ + u8 au8Buf[3] = {0}; + + au8Buf[0] = reg >> 8; + au8Buf[1] = reg & 0xff; + au8Buf[2] = val; + + if (i2c_master_send(ov3640_data.i2c_client, au8Buf, 3) < 0) { + pr_err("%s:write reg error:reg=%x,val=%x\n", + __func__, reg, val); + return -1; + } + + return 0; +} + +static s32 ov3640_read_reg(u16 reg, u8 *val) +{ + u8 au8RegBuf[2] = {0}; + u8 u8RdVal = 0; + + au8RegBuf[0] = reg >> 8; + au8RegBuf[1] = reg & 0xff; + + if (2 != i2c_master_send(ov3640_data.i2c_client, au8RegBuf, 2)) { + pr_err("%s:write reg error:reg=%x\n", + __func__, reg); + return -1; + } + + if (1 != i2c_master_recv(ov3640_data.i2c_client, &u8RdVal, 1)) { + pr_err("%s:read reg error:reg=%x,val=%x\n", + __func__, reg, u8RdVal); + return -1; + } + + *val = u8RdVal; + + return u8RdVal; +} + +static int ov3640_init_mode(enum ov3640_frame_rate frame_rate, + enum ov3640_mode mode) +{ + struct reg_value *pModeSetting = NULL; + s32 i = 0; + s32 iModeSettingArySize = 0; + register u32 Delay_ms = 0; + register u16 RegAddr = 0; + register u8 Mask = 0; + register u8 Val = 0; + u8 RegVal = 0; + int retval = 0; + + if (mode > ov3640_mode_MAX || mode < ov3640_mode_MIN) { + pr_err("Wrong ov3640 mode detected!\n"); + return -1; + } + + pModeSetting = ov3640_mode_info_data[frame_rate][mode].init_data_ptr; + iModeSettingArySize = + ov3640_mode_info_data[frame_rate][mode].init_data_size; + + ov3640_data.pix.width = ov3640_mode_info_data[frame_rate][mode].width; + ov3640_data.pix.height = ov3640_mode_info_data[frame_rate][mode].height; + + if (ov3640_data.pix.width == 0 || ov3640_data.pix.height == 0 || + pModeSetting == NULL || iModeSettingArySize == 0) + return -EINVAL; + + for (i = 0; i < iModeSettingArySize; ++i, ++pModeSetting) { + Delay_ms = pModeSetting->u32Delay_ms; + RegAddr = pModeSetting->u16RegAddr; + Val = pModeSetting->u8Val; + Mask = pModeSetting->u8Mask; + + if (Mask) { + retval = ov3640_read_reg(RegAddr, &RegVal); + if (retval < 0) + goto err; + + RegVal &= ~(u8)Mask; + Val &= Mask; + Val |= RegVal; + } + + retval = ov3640_write_reg(RegAddr, Val); + if (retval < 0) + goto err; + + if (Delay_ms) + msleep(Delay_ms); + } +err: + return retval; +} + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = ov3640_data.mclk; + pr_debug(" clock_curr=mclk=%d\n", ov3640_data.mclk); + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = OV3640_XCLK_MIN; + p->u.bt656.clock_max = OV3640_XCLK_MAX; + p->u.bt656.bt_sync_correct = 1; /* Indicate external vsync */ + + return 0; +} + +/*! + * ioctl_s_power - V4L2 sensor interface handler for VIDIOC_S_POWER ioctl + * @s: pointer to standard V4L2 device structure + * @on: indicates power mode (on or off) + * + * Turns the power on or off, depending on the value of on and returns the + * appropriate error code. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + if (on && !sensor->on) { + gpio_sensor_active(ov3640_data.csi); + if (io_regulator) + if (regulator_enable(io_regulator) != 0) + return -EIO; + if (core_regulator) + if (regulator_enable(core_regulator) != 0) + return -EIO; + if (gpo_regulator) + if (regulator_enable(gpo_regulator) != 0) + return -EIO; + if (analog_regulator) + if (regulator_enable(analog_regulator) != 0) + return -EIO; + /* Make sure power on */ + if (camera_plat->pwdn) + camera_plat->pwdn(0); + + } else if (!on && sensor->on) { + if (analog_regulator) + regulator_disable(analog_regulator); + if (core_regulator) + regulator_disable(core_regulator); + if (io_regulator) + regulator_disable(io_regulator); + if (gpo_regulator) + regulator_disable(gpo_regulator); + gpio_sensor_inactive(ov3640_data.csi); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + int ret = 0; + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + enum ov3640_frame_rate frame_rate; + int ret = 0; + + /* Make sure power on */ + if (camera_plat->pwdn) + camera_plat->pwdn(0); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + /* Check that the new frame rate is allowed. */ + if ((timeperframe->numerator == 0) || + (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + + /* Actual frame rate we use */ + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps == 15) + frame_rate = ov3640_15_fps; + else if (tgt_fps == 30) + frame_rate = ov3640_30_fps; + else { + pr_err(" The camera frame rate is not supported!\n"); + return -EINVAL; + } + + sensor->streamcap.timeperframe = *timeperframe; + sensor->streamcap.capturemode = + (u32)a->parm.capture.capturemode; + + ret = ov3640_init_mode(frame_rate, + sensor->streamcap.capturemode); + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_debug(" type is not " \ + "V4L2_BUF_TYPE_VIDEO_CAPTURE but %d\n", + a->type); + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + vc->value = ov3640_data.brightness; + break; + case V4L2_CID_HUE: + vc->value = ov3640_data.hue; + break; + case V4L2_CID_CONTRAST: + vc->value = ov3640_data.contrast; + break; + case V4L2_CID_SATURATION: + vc->value = ov3640_data.saturation; + break; + case V4L2_CID_RED_BALANCE: + vc->value = ov3640_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + vc->value = ov3640_data.blue; + break; + case V4L2_CID_EXPOSURE: + vc->value = ov3640_data.ae_mode; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In ov3640:ioctl_s_ctrl %d\n", + vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + break; + case V4L2_CID_CONTRAST: + break; + case V4L2_CID_SATURATION: + break; + case V4L2_CID_HUE: + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + break; + case V4L2_CID_DO_WHITE_BALANCE: + break; + case V4L2_CID_RED_BALANCE: + break; + case V4L2_CID_BLUE_BALANCE: + break; + case V4L2_CID_GAMMA: + break; + case V4L2_CID_EXPOSURE: + break; + case V4L2_CID_AUTOGAIN: + break; + case V4L2_CID_GAIN: + break; + case V4L2_CID_HFLIP: + break; + case V4L2_CID_VFLIP: + break; + default: + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_enum_framesizes - V4L2 sensor interface handler for + * VIDIOC_ENUM_FRAMESIZES ioctl + * @s: pointer to standard V4L2 device structure + * @fsize: standard V4L2 VIDIOC_ENUM_FRAMESIZES ioctl structure + * + * Return 0 if successful, otherwise -EINVAL. + */ +static int ioctl_enum_framesizes(struct v4l2_int_device *s, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > ov3640_mode_MAX) + return -EINVAL; + + fsize->pixel_format = ov3640_data.pix.pixelformat; + fsize->discrete.width = + max(ov3640_mode_info_data[0][fsize->index].width, + ov3640_mode_info_data[1][fsize->index].width); + fsize->discrete.height = + max(ov3640_mode_info_data[0][fsize->index].height, + ov3640_mode_info_data[1][fsize->index].height); + return 0; +} + +/*! + * ioctl_g_chip_ident - V4L2 sensor interface handler for + * VIDIOC_DBG_G_CHIP_IDENT ioctl + * @s: pointer to standard V4L2 device structure + * @id: pointer to int + * + * Return 0. + */ +static int ioctl_g_chip_ident(struct v4l2_int_device *s, int *id) +{ + ((struct v4l2_dbg_chip_ident *)id)->match.type = + V4L2_CHIP_MATCH_I2C_DRIVER; + strcpy(((struct v4l2_dbg_chip_ident *)id)->match.name, "ov3640_camera"); + + return 0; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + + return 0; +} + +/*! + * ioctl_enum_fmt_cap - V4L2 sensor interface handler for VIDIOC_ENUM_FMT + * @s: pointer to standard V4L2 device structure + * @fmt: pointer to standard V4L2 fmt description structure + * + * Return 0. + */ +static int ioctl_enum_fmt_cap(struct v4l2_int_device *s, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->index > ov3640_mode_MAX) + return -EINVAL; + + fmt->pixelformat = ov3640_data.pix.pixelformat; + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct sensor *sensor = s->priv; + u32 tgt_xclk; /* target xclk */ + u32 tgt_fps; /* target frames per secound */ + enum ov3640_frame_rate frame_rate; + + gpio_sensor_active(ov3640_data.csi); + ov3640_data.on = true; + + /* mclk */ + tgt_xclk = ov3640_data.mclk; + tgt_xclk = min(tgt_xclk, (u32)OV3640_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)OV3640_XCLK_MIN); + ov3640_data.mclk = tgt_xclk; + + pr_debug(" Setting mclk to %d MHz\n", tgt_xclk / 1000000); + set_mclk_rate(&ov3640_data.mclk, ov3640_data.csi); + + /* Default camera frame rate is set in probe */ + tgt_fps = sensor->streamcap.timeperframe.denominator / + sensor->streamcap.timeperframe.numerator; + + if (tgt_fps == 15) + frame_rate = ov3640_15_fps; + else if (tgt_fps == 30) + frame_rate = ov3640_30_fps; + else + return -EINVAL; /* Only support 15fps or 30fps now. */ + + return ov3640_init_mode(frame_rate, + sensor->streamcap.capturemode); +} + +/*! + * ioctl_dev_exit - V4L2 sensor interface handler for vidioc_int_dev_exit_num + * @s: pointer to standard V4L2 device structure + * + * Delinitialise the device when slave detaches to the master. + */ +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + gpio_sensor_inactive(ov3640_data.csi); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc ov3640_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + {vidioc_int_dev_exit_num, ioctl_dev_exit}, + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, + {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, + {vidioc_int_enum_framesizes_num, + (v4l2_int_ioctl_func *)ioctl_enum_framesizes}, + {vidioc_int_g_chip_ident_num, + (v4l2_int_ioctl_func *)ioctl_g_chip_ident}, +}; + +static struct v4l2_int_slave ov3640_slave = { + .ioctls = ov3640_ioctl_desc, + .num_ioctls = ARRAY_SIZE(ov3640_ioctl_desc), +}; + +static struct v4l2_int_device ov3640_int_device = { + .module = THIS_MODULE, + .name = "ov3640", + .type = v4l2_int_type_slave, + .u = { + .slave = &ov3640_slave, + }, +}; + +/*! + * ov3640 I2C probe function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int ov3640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + /* Set initial values for the sensor struct. */ + memset(&ov3640_data, 0, sizeof(ov3640_data)); + ov3640_data.mclk = 24000000; /* 6 - 54 MHz, typical 24MHz */ + ov3640_data.mclk = plat_data->mclk; + ov3640_data.csi = plat_data->csi; + + ov3640_data.i2c_client = client; + ov3640_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + ov3640_data.pix.width = 640; + ov3640_data.pix.height = 480; + ov3640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY | + V4L2_CAP_TIMEPERFRAME; + ov3640_data.streamcap.capturemode = 0; + ov3640_data.streamcap.timeperframe.denominator = DEFAULT_FPS; + ov3640_data.streamcap.timeperframe.numerator = 1; + + if (plat_data->io_regulator) { + io_regulator = regulator_get(&client->dev, + plat_data->io_regulator); + if (!IS_ERR(io_regulator)) { + regulator_set_voltage(io_regulator, + OV3640_VOLTAGE_DIGITAL_IO, + OV3640_VOLTAGE_DIGITAL_IO); + if (regulator_enable(io_regulator) != 0) { + pr_err("%s:io set voltage error\n", __func__); + goto err1; + } else { + dev_dbg(&client->dev, + "%s:io set voltage ok\n", __func__); + } + } else + io_regulator = NULL; + } + + if (plat_data->core_regulator) { + core_regulator = regulator_get(&client->dev, + plat_data->core_regulator); + if (!IS_ERR(core_regulator)) { + regulator_set_voltage(core_regulator, + OV3640_VOLTAGE_DIGITAL_CORE, + OV3640_VOLTAGE_DIGITAL_CORE); + if (regulator_enable(core_regulator) != 0) { + pr_err("%s:core set voltage error\n", __func__); + goto err2; + } else { + dev_dbg(&client->dev, + "%s:core set voltage ok\n", __func__); + } + } else + core_regulator = NULL; + } + + if (plat_data->analog_regulator) { + analog_regulator = regulator_get(&client->dev, + plat_data->analog_regulator); + if (!IS_ERR(analog_regulator)) { + regulator_set_voltage(analog_regulator, + OV3640_VOLTAGE_ANALOG, + OV3640_VOLTAGE_ANALOG); + if (regulator_enable(analog_regulator) != 0) { + pr_err("%s:analog set voltage error\n", + __func__); + goto err3; + } else { + dev_dbg(&client->dev, + "%s:analog set voltage ok\n", __func__); + } + } else + analog_regulator = NULL; + } + + if (plat_data->gpo_regulator) { + gpo_regulator = regulator_get(&client->dev, + plat_data->gpo_regulator); + if (!IS_ERR(gpo_regulator)) { + regulator_set_voltage(gpo_regulator, + OV3640_VOLTAGE_DIGITAL_GPO, + OV3640_VOLTAGE_DIGITAL_GPO); + if (regulator_enable(gpo_regulator) != 0) { + pr_err("%s:gpo enable error\n", __func__); + goto err4; + } else { + dev_dbg(&client->dev, + "%s:gpo enable ok\n", __func__); + } + } else + gpo_regulator = NULL; + } + + if (plat_data->pwdn) + plat_data->pwdn(0); + + camera_plat = plat_data; + + ov3640_int_device.priv = &ov3640_data; + retval = v4l2_int_device_register(&ov3640_int_device); + + return retval; + +err4: + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } +err3: + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } +err2: + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } +err1: + return -1; +} + +/*! + * ov3640 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int ov3640_remove(struct i2c_client *client) +{ + v4l2_int_device_unregister(&ov3640_int_device); + + if (gpo_regulator) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + return 0; +} + +/*! + * ov3640 init function + * Called by insmod ov3640_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int ov3640_init(void) +{ + u8 err; + + err = i2c_add_driver(&ov3640_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * OV3640 cleanup function + * Called on rmmod ov3640_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit ov3640_clean(void) +{ + i2c_del_driver(&ov3640_i2c_driver); +} + +module_init(ov3640_init); +module_exit(ov3640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV3640 Camera Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("CSI"); diff --git a/drivers/media/video/mxc/capture/ov5640.c b/drivers/media/video/mxc/capture/ov5640.c new file mode 100644 index 000000000000..cd4b61b460c9 --- /dev/null +++ b/drivers/media/video/mxc/capture/ov5640.c @@ -0,0 +1,1527 @@ +/* + * Copyright (C) 2011 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxc_v4l2_capture.h" + +#define OV5640_VOLTAGE_ANALOG 2800000 +#define OV5640_VOLTAGE_DIGITAL_CORE 1500000 +#define OV5640_VOLTAGE_DIGITAL_IO 1800000 + +#define MIN_FPS 15 +#define MAX_FPS 30 +#define DEFAULT_FPS 30 + +#define OV5640_XCLK_MIN 6000000 +#define OV5640_XCLK_MAX 24000000 + +enum ov5640_mode { + ov5640_mode_MIN = 0, + ov5640_mode_VGA_640_480 = 0, + ov5640_mode_QVGA_320_240 = 1, + ov5640_mode_NTSC_720_480 = 2, + ov5640_mode_PAL_720_576 = 3, + ov5640_mode_720P_1280_720 = 4, + ov5640_mode_1080P_1920_1080 = 5, + ov5640_mode_QSXGA_2592_1944 = 6, + ov5640_mode_MAX = 6 +}; + +enum ov5640_frame_rate { + ov5640_15_fps, + ov5640_30_fps +}; + +struct reg_value { + u16 u16RegAddr; + u8 u8Val; + u8 u8Mask; + u32 u32Delay_ms; +}; + +struct ov5640_mode_info { + enum ov5640_mode mode; + u32 width; + u32 height; + struct reg_value *init_data_ptr; + u32 init_data_size; +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + const struct ov5640_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + u32 mclk; + int csi; +} ov5640_data; + +static struct reg_value ov5640_setting_15fps_QSXGA_2592_1944[] = { + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3017, 0xff, 0, 0}, {0x3018, 0xff, 0, 0}, + {0x3034, 0x1a, 0, 0}, {0x3035, 0x21, 0, 0}, {0x3036, 0x69, 0, 0}, + {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3630, 0x2e, 0, 0}, + {0x3632, 0xe2, 0, 0}, {0x3633, 0x23, 0, 0}, {0x3621, 0xe0, 0, 0}, + {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, {0x3715, 0x78, 0, 0}, + {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, {0x3705, 0x1a, 0, 0}, + {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, {0x3901, 0x0a, 0, 0}, + {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, {0x3601, 0x33, 0, 0}, + {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, {0x371b, 0x20, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, + {0x3635, 0x1c, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0x34, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x40, 0, 0}, {0x3821, 0x06, 0, 0}, {0x3814, 0x11, 0, 0}, + {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0}, + {0x3808, 0x0a, 0, 0}, {0x3809, 0x20, 0, 0}, {0x380a, 0x07, 0, 0}, + {0x380b, 0x98, 0, 0}, {0x380c, 0x0b, 0, 0}, {0x380d, 0x1c, 0, 0}, + {0x380e, 0x07, 0, 0}, {0x380f, 0xb0, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x04, 0, 0}, {0x3612, 0x2b, 0, 0}, {0x3708, 0x21, 0, 0}, + {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x07, 0, 0}, + {0x3a03, 0xb0, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x06, 0, 0}, + {0x3a0d, 0x08, 0, 0}, {0x3a14, 0x07, 0, 0}, {0x3a15, 0xb0, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0}, + {0x300e, 0x58, 0, 0}, {0x302e, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x5000, 0xa7, 0, 0}, {0x5001, 0x83, 0, 0}, + {0x5180, 0xff, 0, 0}, {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, + {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, + {0x5186, 0x09, 0, 0}, {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, + {0x5189, 0x75, 0, 0}, {0x518a, 0x54, 0, 0}, {0x518b, 0xe0, 0, 0}, + {0x518c, 0xb2, 0, 0}, {0x518d, 0x42, 0, 0}, {0x518e, 0x3d, 0, 0}, + {0x518f, 0x56, 0, 0}, {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, + {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, + {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, + {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0}, + {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0}, + {0x519e, 0x38, 0, 0}, {0x5381, 0x1c, 0, 0}, {0x5382, 0x5a, 0, 0}, + {0x5383, 0x06, 0, 0}, {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, + {0x5386, 0x88, 0, 0}, {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, + {0x5389, 0x10, 0, 0}, {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, + {0x5300, 0x08, 0, 0}, {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, + {0x5303, 0x00, 0, 0}, {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, + {0x5306, 0x08, 0, 0}, {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, + {0x530a, 0x30, 0, 0}, {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, + {0x5480, 0x01, 0, 0}, {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, + {0x5483, 0x28, 0, 0}, {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, + {0x5486, 0x71, 0, 0}, {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, + {0x5489, 0x91, 0, 0}, {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, + {0x548c, 0xb8, 0, 0}, {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, + {0x548f, 0xea, 0, 0}, {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, + {0x5583, 0x40, 0, 0}, {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, + {0x558a, 0x00, 0, 0}, {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, + {0x5801, 0x15, 0, 0}, {0x5802, 0x10, 0, 0}, {0x5803, 0x10, 0, 0}, + {0x5804, 0x15, 0, 0}, {0x5805, 0x23, 0, 0}, {0x5806, 0x0c, 0, 0}, + {0x5807, 0x08, 0, 0}, {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, + {0x580a, 0x08, 0, 0}, {0x580b, 0x0c, 0, 0}, {0x580c, 0x07, 0, 0}, + {0x580d, 0x03, 0, 0}, {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, + {0x5810, 0x03, 0, 0}, {0x5811, 0x07, 0, 0}, {0x5812, 0x07, 0, 0}, + {0x5813, 0x03, 0, 0}, {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, + {0x5816, 0x03, 0, 0}, {0x5817, 0x07, 0, 0}, {0x5818, 0x0b, 0, 0}, + {0x5819, 0x08, 0, 0}, {0x581a, 0x05, 0, 0}, {0x581b, 0x05, 0, 0}, + {0x581c, 0x07, 0, 0}, {0x581d, 0x0b, 0, 0}, {0x581e, 0x2a, 0, 0}, + {0x581f, 0x16, 0, 0}, {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, + {0x5822, 0x15, 0, 0}, {0x5823, 0x29, 0, 0}, {0x5824, 0xbf, 0, 0}, + {0x5825, 0xaf, 0, 0}, {0x5826, 0x9f, 0, 0}, {0x5827, 0xaf, 0, 0}, + {0x5828, 0xdf, 0, 0}, {0x5829, 0x6f, 0, 0}, {0x582a, 0x8e, 0, 0}, + {0x582b, 0xab, 0, 0}, {0x582c, 0x9e, 0, 0}, {0x582d, 0x7f, 0, 0}, + {0x582e, 0x4f, 0, 0}, {0x582f, 0x89, 0, 0}, {0x5830, 0x86, 0, 0}, + {0x5831, 0x98, 0, 0}, {0x5832, 0x6f, 0, 0}, {0x5833, 0x4f, 0, 0}, + {0x5834, 0x6e, 0, 0}, {0x5835, 0x7b, 0, 0}, {0x5836, 0x7e, 0, 0}, + {0x5837, 0x6f, 0, 0}, {0x5838, 0xde, 0, 0}, {0x5839, 0xbf, 0, 0}, + {0x583a, 0x9f, 0, 0}, {0x583b, 0xbf, 0, 0}, {0x583c, 0xec, 0, 0}, + {0x5025, 0x00, 0, 0}, {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, + {0x3a1b, 0x30, 0, 0}, {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, + {0x3a1f, 0x14, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3035, 0x11, 0, 0}, + {0x3036, 0x46, 0, 0}, +}; + +static struct reg_value ov5640_setting_30fps_VGA_640_480[] = { + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3017, 0xff, 0, 0}, {0x3018, 0xff, 0, 0}, + {0x3034, 0x1a, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x46, 0, 0}, + {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3630, 0x2e, 0, 0}, + {0x3632, 0xe2, 0, 0}, {0x3633, 0x23, 0, 0}, {0x3621, 0xe0, 0, 0}, + {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, {0x3715, 0x78, 0, 0}, + {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, {0x3705, 0x1a, 0, 0}, + {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, {0x3901, 0x0a, 0, 0}, + {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, {0x3601, 0x33, 0, 0}, + {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, {0x371b, 0x20, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, + {0x3635, 0x1c, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0x34, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x08, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x62, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0}, + {0x300e, 0x58, 0, 0}, {0x302e, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, {0x3824, 0x02, 0, 0}, + {0x5000, 0xa7, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x5180, 0xff, 0, 0}, + {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, + {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, + {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x75, 0, 0}, + {0x518a, 0x54, 0, 0}, {0x518b, 0xe0, 0, 0}, {0x518c, 0xb2, 0, 0}, + {0x518d, 0x42, 0, 0}, {0x518e, 0x3d, 0, 0}, {0x518f, 0x56, 0, 0}, + {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, + {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, + {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, + {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0}, {0x519e, 0x38, 0, 0}, + {0x5381, 0x1c, 0, 0}, {0x5382, 0x5a, 0, 0}, {0x5383, 0x06, 0, 0}, + {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, + {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, + {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, + {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, + {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, + {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, + {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, + {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, + {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, + {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, + {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, + {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, + {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, + {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x15, 0, 0}, + {0x5802, 0x10, 0, 0}, {0x5803, 0x10, 0, 0}, {0x5804, 0x15, 0, 0}, + {0x5805, 0x23, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, + {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, + {0x580b, 0x0c, 0, 0}, {0x580c, 0x07, 0, 0}, {0x580d, 0x03, 0, 0}, + {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, + {0x5811, 0x07, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, + {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x03, 0, 0}, + {0x5817, 0x07, 0, 0}, {0x5818, 0x0b, 0, 0}, {0x5819, 0x08, 0, 0}, + {0x581a, 0x05, 0, 0}, {0x581b, 0x05, 0, 0}, {0x581c, 0x07, 0, 0}, + {0x581d, 0x0b, 0, 0}, {0x581e, 0x2a, 0, 0}, {0x581f, 0x16, 0, 0}, + {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, + {0x5823, 0x29, 0, 0}, {0x5824, 0xbf, 0, 0}, {0x5825, 0xaf, 0, 0}, + {0x5826, 0x9f, 0, 0}, {0x5827, 0xaf, 0, 0}, {0x5828, 0xdf, 0, 0}, + {0x5829, 0x6f, 0, 0}, {0x582a, 0x8e, 0, 0}, {0x582b, 0xab, 0, 0}, + {0x582c, 0x9e, 0, 0}, {0x582d, 0x7f, 0, 0}, {0x582e, 0x4f, 0, 0}, + {0x582f, 0x89, 0, 0}, {0x5830, 0x86, 0, 0}, {0x5831, 0x98, 0, 0}, + {0x5832, 0x6f, 0, 0}, {0x5833, 0x4f, 0, 0}, {0x5834, 0x6e, 0, 0}, + {0x5835, 0x7b, 0, 0}, {0x5836, 0x7e, 0, 0}, {0x5837, 0x6f, 0, 0}, + {0x5838, 0xde, 0, 0}, {0x5839, 0xbf, 0, 0}, {0x583a, 0x9f, 0, 0}, + {0x583b, 0xbf, 0, 0}, {0x583c, 0xec, 0, 0}, {0x5025, 0x00, 0, 0}, + {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, {0x3a1b, 0x30, 0, 0}, + {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x14, 0, 0}, + {0x3008, 0x02, 0, 0}, {0x3035, 0x11, 0, 0}, +}; + +static struct reg_value ov5640_setting_30fps_QVGA_320_240[] = { + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3017, 0xff, 0, 0}, {0x3018, 0xff, 0, 0}, + {0x3034, 0x1a, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x46, 0, 0}, + {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3630, 0x2e, 0, 0}, + {0x3632, 0xe2, 0, 0}, {0x3633, 0x23, 0, 0}, {0x3621, 0xe0, 0, 0}, + {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, {0x3715, 0x78, 0, 0}, + {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, {0x3705, 0x1a, 0, 0}, + {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, {0x3901, 0x0a, 0, 0}, + {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, {0x3601, 0x33, 0, 0}, + {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, {0x371b, 0x20, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, + {0x3635, 0x1c, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0x34, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x08, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x62, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0}, + {0x300e, 0x58, 0, 0}, {0x302e, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, {0x3824, 0x02, 0, 0}, + {0x5000, 0xa7, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x5180, 0xff, 0, 0}, + {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, + {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, + {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x75, 0, 0}, + {0x518a, 0x54, 0, 0}, {0x518b, 0xe0, 0, 0}, {0x518c, 0xb2, 0, 0}, + {0x518d, 0x42, 0, 0}, {0x518e, 0x3d, 0, 0}, {0x518f, 0x56, 0, 0}, + {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, + {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, + {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, + {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0}, {0x519e, 0x38, 0, 0}, + {0x5381, 0x1c, 0, 0}, {0x5382, 0x5a, 0, 0}, {0x5383, 0x06, 0, 0}, + {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, + {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, + {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, + {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, + {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, + {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, + {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, + {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, + {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, + {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, + {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, + {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, + {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, + {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x15, 0, 0}, + {0x5802, 0x10, 0, 0}, {0x5803, 0x10, 0, 0}, {0x5804, 0x15, 0, 0}, + {0x5805, 0x23, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, + {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, + {0x580b, 0x0c, 0, 0}, {0x580c, 0x07, 0, 0}, {0x580d, 0x03, 0, 0}, + {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, + {0x5811, 0x07, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, + {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x03, 0, 0}, + {0x5817, 0x07, 0, 0}, {0x5818, 0x0b, 0, 0}, {0x5819, 0x08, 0, 0}, + {0x581a, 0x05, 0, 0}, {0x581b, 0x05, 0, 0}, {0x581c, 0x07, 0, 0}, + {0x581d, 0x0b, 0, 0}, {0x581e, 0x2a, 0, 0}, {0x581f, 0x16, 0, 0}, + {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, + {0x5823, 0x29, 0, 0}, {0x5824, 0xbf, 0, 0}, {0x5825, 0xaf, 0, 0}, + {0x5826, 0x9f, 0, 0}, {0x5827, 0xaf, 0, 0}, {0x5828, 0xdf, 0, 0}, + {0x5829, 0x6f, 0, 0}, {0x582a, 0x8e, 0, 0}, {0x582b, 0xab, 0, 0}, + {0x582c, 0x9e, 0, 0}, {0x582d, 0x7f, 0, 0}, {0x582e, 0x4f, 0, 0}, + {0x582f, 0x89, 0, 0}, {0x5830, 0x86, 0, 0}, {0x5831, 0x98, 0, 0}, + {0x5832, 0x6f, 0, 0}, {0x5833, 0x4f, 0, 0}, {0x5834, 0x6e, 0, 0}, + {0x5835, 0x7b, 0, 0}, {0x5836, 0x7e, 0, 0}, {0x5837, 0x6f, 0, 0}, + {0x5838, 0xde, 0, 0}, {0x5839, 0xbf, 0, 0}, {0x583a, 0x9f, 0, 0}, + {0x583b, 0xbf, 0, 0}, {0x583c, 0xec, 0, 0}, {0x5025, 0x00, 0, 0}, + {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, {0x3a1b, 0x30, 0, 0}, + {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x14, 0, 0}, + {0x3008, 0x02, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3808, 0x01, 0, 0}, + {0x3809, 0x40, 0, 0}, {0x380a, 0x00, 0, 0}, {0x380b, 0xf0, 0, 0}, +}; + +static struct reg_value ov5640_setting_30fps_NTSC_720_480[] = { + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3017, 0xff, 0, 0}, {0x3018, 0xff, 0, 0}, + {0x3034, 0x1a, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x46, 0, 0}, + {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3630, 0x2e, 0, 0}, + {0x3632, 0xe2, 0, 0}, {0x3633, 0x23, 0, 0}, {0x3621, 0xe0, 0, 0}, + {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, {0x3715, 0x78, 0, 0}, + {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, {0x3705, 0x1a, 0, 0}, + {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, {0x3901, 0x0a, 0, 0}, + {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, {0x3601, 0x33, 0, 0}, + {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, {0x371b, 0x20, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, + {0x3635, 0x1c, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0x34, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x08, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0xd0, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x60, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x62, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0}, + {0x300e, 0x58, 0, 0}, {0x302e, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, {0x3824, 0x02, 0, 0}, + {0x5000, 0xa7, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x5180, 0xff, 0, 0}, + {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, + {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, + {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x75, 0, 0}, + {0x518a, 0x54, 0, 0}, {0x518b, 0xe0, 0, 0}, {0x518c, 0xb2, 0, 0}, + {0x518d, 0x42, 0, 0}, {0x518e, 0x3d, 0, 0}, {0x518f, 0x56, 0, 0}, + {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, + {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, + {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, + {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0}, {0x519e, 0x38, 0, 0}, + {0x5381, 0x1c, 0, 0}, {0x5382, 0x5a, 0, 0}, {0x5383, 0x06, 0, 0}, + {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, + {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, + {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, + {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, + {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, + {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, + {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, + {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, + {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, + {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, + {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, + {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, + {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, + {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x15, 0, 0}, + {0x5802, 0x10, 0, 0}, {0x5803, 0x10, 0, 0}, {0x5804, 0x15, 0, 0}, + {0x5805, 0x23, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, + {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, + {0x580b, 0x0c, 0, 0}, {0x580c, 0x07, 0, 0}, {0x580d, 0x03, 0, 0}, + {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, + {0x5811, 0x07, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, + {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x03, 0, 0}, + {0x5817, 0x07, 0, 0}, {0x5818, 0x0b, 0, 0}, {0x5819, 0x08, 0, 0}, + {0x581a, 0x05, 0, 0}, {0x581b, 0x05, 0, 0}, {0x581c, 0x07, 0, 0}, + {0x581d, 0x0b, 0, 0}, {0x581e, 0x2a, 0, 0}, {0x581f, 0x16, 0, 0}, + {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, + {0x5823, 0x29, 0, 0}, {0x5824, 0xbf, 0, 0}, {0x5825, 0xaf, 0, 0}, + {0x5826, 0x9f, 0, 0}, {0x5827, 0xaf, 0, 0}, {0x5828, 0xdf, 0, 0}, + {0x5829, 0x6f, 0, 0}, {0x582a, 0x8e, 0, 0}, {0x582b, 0xab, 0, 0}, + {0x582c, 0x9e, 0, 0}, {0x582d, 0x7f, 0, 0}, {0x582e, 0x4f, 0, 0}, + {0x582f, 0x89, 0, 0}, {0x5830, 0x86, 0, 0}, {0x5831, 0x98, 0, 0}, + {0x5832, 0x6f, 0, 0}, {0x5833, 0x4f, 0, 0}, {0x5834, 0x6e, 0, 0}, + {0x5835, 0x7b, 0, 0}, {0x5836, 0x7e, 0, 0}, {0x5837, 0x6f, 0, 0}, + {0x5838, 0xde, 0, 0}, {0x5839, 0xbf, 0, 0}, {0x583a, 0x9f, 0, 0}, + {0x583b, 0xbf, 0, 0}, {0x583c, 0xec, 0, 0}, {0x5025, 0x00, 0, 0}, + {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, {0x3a1b, 0x30, 0, 0}, + {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x14, 0, 0}, + {0x3008, 0x02, 0, 0}, {0x3035, 0x11, 0, 0}, +}; + +static struct reg_value ov5640_setting_30fps_PAL_720_576[] = { + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3017, 0xff, 0, 0}, {0x3018, 0xff, 0, 0}, + {0x3034, 0x1a, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x46, 0, 0}, + {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3630, 0x2e, 0, 0}, + {0x3632, 0xe2, 0, 0}, {0x3633, 0x23, 0, 0}, {0x3621, 0xe0, 0, 0}, + {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, {0x3715, 0x78, 0, 0}, + {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, {0x3705, 0x1a, 0, 0}, + {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, {0x3901, 0x0a, 0, 0}, + {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, {0x3601, 0x33, 0, 0}, + {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, {0x371b, 0x20, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, + {0x3635, 0x1c, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0x34, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x08, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0xd0, 0, 0}, {0x380a, 0x02, 0, 0}, + {0x380b, 0x40, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x62, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x62, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0}, + {0x300e, 0x58, 0, 0}, {0x302e, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, {0x3824, 0x02, 0, 0}, + {0x5000, 0xa7, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x5180, 0xff, 0, 0}, + {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, + {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, + {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x75, 0, 0}, + {0x518a, 0x54, 0, 0}, {0x518b, 0xe0, 0, 0}, {0x518c, 0xb2, 0, 0}, + {0x518d, 0x42, 0, 0}, {0x518e, 0x3d, 0, 0}, {0x518f, 0x56, 0, 0}, + {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, + {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, + {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, + {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0}, {0x519e, 0x38, 0, 0}, + {0x5381, 0x1c, 0, 0}, {0x5382, 0x5a, 0, 0}, {0x5383, 0x06, 0, 0}, + {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, + {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, + {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, + {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, + {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, + {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, + {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, + {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, + {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, + {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, + {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, + {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, + {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, + {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x15, 0, 0}, + {0x5802, 0x10, 0, 0}, {0x5803, 0x10, 0, 0}, {0x5804, 0x15, 0, 0}, + {0x5805, 0x23, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, + {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, + {0x580b, 0x0c, 0, 0}, {0x580c, 0x07, 0, 0}, {0x580d, 0x03, 0, 0}, + {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, + {0x5811, 0x07, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, + {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x03, 0, 0}, + {0x5817, 0x07, 0, 0}, {0x5818, 0x0b, 0, 0}, {0x5819, 0x08, 0, 0}, + {0x581a, 0x05, 0, 0}, {0x581b, 0x05, 0, 0}, {0x581c, 0x07, 0, 0}, + {0x581d, 0x0b, 0, 0}, {0x581e, 0x2a, 0, 0}, {0x581f, 0x16, 0, 0}, + {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, + {0x5823, 0x29, 0, 0}, {0x5824, 0xbf, 0, 0}, {0x5825, 0xaf, 0, 0}, + {0x5826, 0x9f, 0, 0}, {0x5827, 0xaf, 0, 0}, {0x5828, 0xdf, 0, 0}, + {0x5829, 0x6f, 0, 0}, {0x582a, 0x8e, 0, 0}, {0x582b, 0xab, 0, 0}, + {0x582c, 0x9e, 0, 0}, {0x582d, 0x7f, 0, 0}, {0x582e, 0x4f, 0, 0}, + {0x582f, 0x89, 0, 0}, {0x5830, 0x86, 0, 0}, {0x5831, 0x98, 0, 0}, + {0x5832, 0x6f, 0, 0}, {0x5833, 0x4f, 0, 0}, {0x5834, 0x6e, 0, 0}, + {0x5835, 0x7b, 0, 0}, {0x5836, 0x7e, 0, 0}, {0x5837, 0x6f, 0, 0}, + {0x5838, 0xde, 0, 0}, {0x5839, 0xbf, 0, 0}, {0x583a, 0x9f, 0, 0}, + {0x583b, 0xbf, 0, 0}, {0x583c, 0xec, 0, 0}, {0x5025, 0x00, 0, 0}, + {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, {0x3a1b, 0x30, 0, 0}, + {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x14, 0, 0}, + {0x3008, 0x02, 0, 0}, {0x3035, 0x11, 0, 0}, +}; + +static struct reg_value ov5640_setting_30fps_720P_1280_720[] = { + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3017, 0xff, 0, 0}, {0x3018, 0xff, 0, 0}, + {0x3034, 0x1a, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x69, 0, 0}, + {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3630, 0x2e, 0, 0}, + {0x3632, 0xe2, 0, 0}, {0x3633, 0x23, 0, 0}, {0x3621, 0xe0, 0, 0}, + {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, {0x3715, 0x78, 0, 0}, + {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, {0x3705, 0x1a, 0, 0}, + {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, {0x3901, 0x0a, 0, 0}, + {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, {0x3601, 0x33, 0, 0}, + {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, {0x371b, 0x20, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, + {0x3635, 0x1c, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0x34, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x27, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0xfa, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x06, 0, 0}, {0x3807, 0xa9, 0, 0}, + {0x3808, 0x05, 0, 0}, {0x3809, 0x00, 0, 0}, {0x380a, 0x02, 0, 0}, + {0x380b, 0xd0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x64, 0, 0}, + {0x380e, 0x02, 0, 0}, {0x380f, 0xe4, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x62, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x02, 0, 0}, + {0x3a03, 0xe4, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xbc, 0, 0}, + {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0}, + {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x00, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xff, 0, 0}, + {0x300e, 0x58, 0, 0}, {0x302e, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x4713, 0x02, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, {0x3824, 0x04, 0, 0}, + {0x5000, 0xa7, 0, 0}, {0x5001, 0x83, 0, 0}, {0x5180, 0xff, 0, 0}, + {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, + {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, + {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x75, 0, 0}, + {0x518a, 0x54, 0, 0}, {0x518b, 0xe0, 0, 0}, {0x518c, 0xb2, 0, 0}, + {0x518d, 0x42, 0, 0}, {0x518e, 0x3d, 0, 0}, {0x518f, 0x56, 0, 0}, + {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, + {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, + {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, + {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0}, {0x519e, 0x38, 0, 0}, + {0x5381, 0x1c, 0, 0}, {0x5382, 0x5a, 0, 0}, {0x5383, 0x06, 0, 0}, + {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, + {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, + {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, + {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, + {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, + {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, + {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, + {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, + {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, + {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, + {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, + {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, + {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, + {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x15, 0, 0}, + {0x5802, 0x10, 0, 0}, {0x5803, 0x10, 0, 0}, {0x5804, 0x15, 0, 0}, + {0x5805, 0x23, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, + {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, + {0x580b, 0x0c, 0, 0}, {0x580c, 0x07, 0, 0}, {0x580d, 0x03, 0, 0}, + {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, + {0x5811, 0x07, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, + {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x03, 0, 0}, + {0x5817, 0x07, 0, 0}, {0x5818, 0x0b, 0, 0}, {0x5819, 0x08, 0, 0}, + {0x581a, 0x05, 0, 0}, {0x581b, 0x05, 0, 0}, {0x581c, 0x07, 0, 0}, + {0x581d, 0x0b, 0, 0}, {0x581e, 0x2a, 0, 0}, {0x581f, 0x16, 0, 0}, + {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, + {0x5823, 0x29, 0, 0}, {0x5824, 0xbf, 0, 0}, {0x5825, 0xaf, 0, 0}, + {0x5826, 0x9f, 0, 0}, {0x5827, 0xaf, 0, 0}, {0x5828, 0xdf, 0, 0}, + {0x5829, 0x6f, 0, 0}, {0x582a, 0x8e, 0, 0}, {0x582b, 0xab, 0, 0}, + {0x582c, 0x9e, 0, 0}, {0x582d, 0x7f, 0, 0}, {0x582e, 0x4f, 0, 0}, + {0x582f, 0x89, 0, 0}, {0x5830, 0x86, 0, 0}, {0x5831, 0x98, 0, 0}, + {0x5832, 0x6f, 0, 0}, {0x5833, 0x4f, 0, 0}, {0x5834, 0x6e, 0, 0}, + {0x5835, 0x7b, 0, 0}, {0x5836, 0x7e, 0, 0}, {0x5837, 0x6f, 0, 0}, + {0x5838, 0xde, 0, 0}, {0x5839, 0xbf, 0, 0}, {0x583a, 0x9f, 0, 0}, + {0x583b, 0xbf, 0, 0}, {0x583c, 0xec, 0, 0}, {0x5025, 0x00, 0, 0}, + {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, {0x3a1b, 0x30, 0, 0}, + {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x14, 0, 0}, + {0x3008, 0x02, 0, 0}, {0x3035, 0x21, 0, 0}, {0x3002, 0x1c, 0, 0}, + {0x3006, 0xc3, 0, 0}, {0x3821, 0x07, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0}, + {0x460b, 0x37, 0, 0}, +}; + +static struct reg_value ov5640_setting_30fps_1080P_1920_1080[] = { + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3017, 0xff, 0, 0}, {0x3018, 0xff, 0, 0}, + {0x3034, 0x1a, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x69, 0, 0}, + {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3630, 0x2e, 0, 0}, + {0x3632, 0xe2, 0, 0}, {0x3633, 0x23, 0, 0}, {0x3621, 0xe0, 0, 0}, + {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, {0x3715, 0x78, 0, 0}, + {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, {0x3705, 0x1a, 0, 0}, + {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, {0x3901, 0x0a, 0, 0}, + {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, {0x3601, 0x33, 0, 0}, + {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, {0x371b, 0x20, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, + {0x3635, 0x1c, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0x34, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x26, 0, 0}, {0x3814, 0x11, 0, 0}, + {0x3815, 0x11, 0, 0}, {0x3800, 0x01, 0, 0}, {0x3801, 0x50, 0, 0}, + {0x3802, 0x01, 0, 0}, {0x3803, 0xb2, 0, 0}, {0x3804, 0x08, 0, 0}, + {0x3805, 0xef, 0, 0}, {0x3806, 0x05, 0, 0}, {0x3807, 0xf2, 0, 0}, + {0x3808, 0x07, 0, 0}, {0x3809, 0x80, 0, 0}, {0x380a, 0x04, 0, 0}, + {0x380b, 0x38, 0, 0}, {0x380c, 0x09, 0, 0}, {0x380d, 0xc4, 0, 0}, + {0x380e, 0x04, 0, 0}, {0x380f, 0x60, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x04, 0, 0}, {0x3612, 0x2b, 0, 0}, {0x3708, 0x62, 0, 0}, + {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x04, 0, 0}, + {0x3a03, 0x60, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x50, 0, 0}, + {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0}, {0x3a15, 0x60, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x00, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xff, 0, 0}, + {0x300e, 0x58, 0, 0}, {0x302e, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x4713, 0x02, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, {0x3824, 0x04, 0, 0}, + {0x5000, 0xa7, 0, 0}, {0x5001, 0x83, 0, 0}, {0x5180, 0xff, 0, 0}, + {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, + {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, + {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x75, 0, 0}, + {0x518a, 0x54, 0, 0}, {0x518b, 0xe0, 0, 0}, {0x518c, 0xb2, 0, 0}, + {0x518d, 0x42, 0, 0}, {0x518e, 0x3d, 0, 0}, {0x518f, 0x56, 0, 0}, + {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, + {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, + {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, + {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0}, {0x519e, 0x38, 0, 0}, + {0x5381, 0x1c, 0, 0}, {0x5382, 0x5a, 0, 0}, {0x5383, 0x06, 0, 0}, + {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, + {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, + {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, + {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, + {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, + {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, + {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, + {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, + {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, + {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, + {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, + {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, + {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, + {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x15, 0, 0}, + {0x5802, 0x10, 0, 0}, {0x5803, 0x10, 0, 0}, {0x5804, 0x15, 0, 0}, + {0x5805, 0x23, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, + {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, + {0x580b, 0x0c, 0, 0}, {0x580c, 0x07, 0, 0}, {0x580d, 0x03, 0, 0}, + {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, + {0x5811, 0x07, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, + {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x03, 0, 0}, + {0x5817, 0x07, 0, 0}, {0x5818, 0x0b, 0, 0}, {0x5819, 0x08, 0, 0}, + {0x581a, 0x05, 0, 0}, {0x581b, 0x05, 0, 0}, {0x581c, 0x07, 0, 0}, + {0x581d, 0x0b, 0, 0}, {0x581e, 0x2a, 0, 0}, {0x581f, 0x16, 0, 0}, + {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, + {0x5823, 0x29, 0, 0}, {0x5824, 0xbf, 0, 0}, {0x5825, 0xaf, 0, 0}, + {0x5826, 0x9f, 0, 0}, {0x5827, 0xaf, 0, 0}, {0x5828, 0xdf, 0, 0}, + {0x5829, 0x6f, 0, 0}, {0x582a, 0x8e, 0, 0}, {0x582b, 0xab, 0, 0}, + {0x582c, 0x9e, 0, 0}, {0x582d, 0x7f, 0, 0}, {0x582e, 0x4f, 0, 0}, + {0x582f, 0x89, 0, 0}, {0x5830, 0x86, 0, 0}, {0x5831, 0x98, 0, 0}, + {0x5832, 0x6f, 0, 0}, {0x5833, 0x4f, 0, 0}, {0x5834, 0x6e, 0, 0}, + {0x5835, 0x7b, 0, 0}, {0x5836, 0x7e, 0, 0}, {0x5837, 0x6f, 0, 0}, + {0x5838, 0xde, 0, 0}, {0x5839, 0xbf, 0, 0}, {0x583a, 0x9f, 0, 0}, + {0x583b, 0xbf, 0, 0}, {0x583c, 0xec, 0, 0}, {0x5025, 0x00, 0, 0}, + {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, {0x3a1b, 0x30, 0, 0}, + {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x14, 0, 0}, + {0x3008, 0x02, 0, 0}, {0x3035, 0x21, 0, 0}, {0x3002, 0x1c, 0, 0}, + {0x3006, 0xc3, 0, 0}, {0x3821, 0x06, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0}, + {0x460b, 0x37, 0, 0}, +}; + +static struct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX + 1] = { + { + {ov5640_mode_VGA_640_480, 0, 0, NULL, 0}, + {ov5640_mode_QVGA_320_240, 0, 0, NULL, 0}, + {ov5640_mode_NTSC_720_480, 0, 0, NULL, 0}, + {ov5640_mode_PAL_720_576, 0, 0, NULL, 0}, + {ov5640_mode_720P_1280_720, 0, 0, NULL, 0}, + {ov5640_mode_1080P_1920_1080, 0, 0, NULL, 0}, + {ov5640_mode_QSXGA_2592_1944, 2592, 1944, + ov5640_setting_15fps_QSXGA_2592_1944, + ARRAY_SIZE(ov5640_setting_15fps_QSXGA_2592_1944)}, + }, + { + {ov5640_mode_VGA_640_480, 640, 480, + ov5640_setting_30fps_VGA_640_480, + ARRAY_SIZE(ov5640_setting_30fps_VGA_640_480)}, + {ov5640_mode_QVGA_320_240, 320, 240, + ov5640_setting_30fps_QVGA_320_240, + ARRAY_SIZE(ov5640_setting_30fps_QVGA_320_240)}, + {ov5640_mode_NTSC_720_480, 720, 480, + ov5640_setting_30fps_NTSC_720_480, + ARRAY_SIZE(ov5640_setting_30fps_NTSC_720_480)}, + {ov5640_mode_PAL_720_576, 720, 576, + ov5640_setting_30fps_PAL_720_576, + ARRAY_SIZE(ov5640_setting_30fps_PAL_720_576)}, + {ov5640_mode_720P_1280_720, 1280, 720, + ov5640_setting_30fps_720P_1280_720, + ARRAY_SIZE(ov5640_setting_30fps_720P_1280_720)}, + {ov5640_mode_1080P_1920_1080, 1920, 1080, + ov5640_setting_30fps_1080P_1920_1080, + ARRAY_SIZE(ov5640_setting_30fps_1080P_1920_1080)}, + {ov5640_mode_QSXGA_2592_1944, 0, 0, NULL, 0}, + }, +}; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; +static struct mxc_camera_platform_data *camera_plat; + +static int ov5640_probe(struct i2c_client *adapter, + const struct i2c_device_id *device_id); +static int ov5640_remove(struct i2c_client *client); + +static s32 ov5640_read_reg(u16 reg, u8 *val); +static s32 ov5640_write_reg(u16 reg, u8 val); + +static const struct i2c_device_id ov5640_id[] = { + {"ov5640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov5640_id); + +static struct i2c_driver ov5640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov5640", + }, + .probe = ov5640_probe, + .remove = ov5640_remove, + .id_table = ov5640_id, +}; + +extern void gpio_sensor_active(unsigned int csi_index); +extern void gpio_sensor_inactive(unsigned int csi); + +static s32 ov5640_write_reg(u16 reg, u8 val) +{ + u8 au8Buf[3] = {0}; + + au8Buf[0] = reg >> 8; + au8Buf[1] = reg & 0xff; + au8Buf[2] = val; + + if (i2c_master_send(ov5640_data.i2c_client, au8Buf, 3) < 0) { + pr_err("%s:write reg error:reg=%x,val=%x\n", + __func__, reg, val); + return -1; + } + + return 0; +} + +static s32 ov5640_read_reg(u16 reg, u8 *val) +{ + u8 au8RegBuf[2] = {0}; + u8 u8RdVal = 0; + + au8RegBuf[0] = reg >> 8; + au8RegBuf[1] = reg & 0xff; + + if (2 != i2c_master_send(ov5640_data.i2c_client, au8RegBuf, 2)) { + pr_err("%s:write reg error:reg=%x\n", + __func__, reg); + return -1; + } + + if (1 != i2c_master_recv(ov5640_data.i2c_client, &u8RdVal, 1)) { + pr_err("%s:read reg error:reg=%x,val=%x\n", + __func__, reg, u8RdVal); + return -1; + } + + *val = u8RdVal; + + return u8RdVal; +} + +static int ov5640_init_mode(enum ov5640_frame_rate frame_rate, + enum ov5640_mode mode) +{ + struct reg_value *pModeSetting = NULL; + s32 i = 0; + s32 iModeSettingArySize = 0; + register u32 Delay_ms = 0; + register u16 RegAddr = 0; + register u8 Mask = 0; + register u8 Val = 0; + u8 RegVal = 0; + int retval = 0; + + if (mode > ov5640_mode_MAX || mode < ov5640_mode_MIN) { + pr_err("Wrong ov5640 mode detected!\n"); + return -1; + } + + pModeSetting = ov5640_mode_info_data[frame_rate][mode].init_data_ptr; + iModeSettingArySize = + ov5640_mode_info_data[frame_rate][mode].init_data_size; + + ov5640_data.pix.width = ov5640_mode_info_data[frame_rate][mode].width; + ov5640_data.pix.height = ov5640_mode_info_data[frame_rate][mode].height; + + if (ov5640_data.pix.width == 0 || ov5640_data.pix.height == 0 || + pModeSetting == NULL || iModeSettingArySize == 0) + return -EINVAL; + + for (i = 0; i < iModeSettingArySize; ++i, ++pModeSetting) { + Delay_ms = pModeSetting->u32Delay_ms; + RegAddr = pModeSetting->u16RegAddr; + Val = pModeSetting->u8Val; + Mask = pModeSetting->u8Mask; + + if (Mask) { + retval = ov5640_read_reg(RegAddr, &RegVal); + if (retval < 0) + goto err; + + RegVal &= ~(u8)Mask; + Val &= Mask; + Val |= RegVal; + } + + retval = ov5640_write_reg(RegAddr, Val); + if (retval < 0) + goto err; + + if (Delay_ms) + msleep(Delay_ms); + } +err: + return retval; +} + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = ov5640_data.mclk; + pr_debug(" clock_curr=mclk=%d\n", ov5640_data.mclk); + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = OV5640_XCLK_MIN; + p->u.bt656.clock_max = OV5640_XCLK_MAX; + p->u.bt656.bt_sync_correct = 1; /* Indicate external vsync */ + + return 0; +} + +/*! + * ioctl_s_power - V4L2 sensor interface handler for VIDIOC_S_POWER ioctl + * @s: pointer to standard V4L2 device structure + * @on: indicates power mode (on or off) + * + * Turns the power on or off, depending on the value of on and returns the + * appropriate error code. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + if (on && !sensor->on) { + gpio_sensor_active(ov5640_data.csi); + if (io_regulator) + if (regulator_enable(io_regulator) != 0) + return -EIO; + if (core_regulator) + if (regulator_enable(core_regulator) != 0) + return -EIO; + if (gpo_regulator) + if (regulator_enable(gpo_regulator) != 0) + return -EIO; + if (analog_regulator) + if (regulator_enable(analog_regulator) != 0) + return -EIO; + /* Make sure power on */ + if (camera_plat->pwdn) + camera_plat->pwdn(0); + + } else if (!on && sensor->on) { + if (analog_regulator) + regulator_disable(analog_regulator); + if (core_regulator) + regulator_disable(core_regulator); + if (io_regulator) + regulator_disable(io_regulator); + if (gpo_regulator) + regulator_disable(gpo_regulator); + gpio_sensor_inactive(ov5640_data.csi); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + int ret = 0; + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + enum ov5640_frame_rate frame_rate; + int ret = 0; + + /* Make sure power on */ + if (camera_plat->pwdn) + camera_plat->pwdn(0); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + /* Check that the new frame rate is allowed. */ + if ((timeperframe->numerator == 0) || + (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + + /* Actual frame rate we use */ + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps == 15) + frame_rate = ov5640_15_fps; + else if (tgt_fps == 30) + frame_rate = ov5640_30_fps; + else { + pr_err(" The camera frame rate is not supported!\n"); + return -EINVAL; + } + + sensor->streamcap.timeperframe = *timeperframe; + sensor->streamcap.capturemode = + (u32)a->parm.capture.capturemode; + + ret = ov5640_init_mode(frame_rate, + sensor->streamcap.capturemode); + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_debug(" type is not " \ + "V4L2_BUF_TYPE_VIDEO_CAPTURE but %d\n", + a->type); + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + vc->value = ov5640_data.brightness; + break; + case V4L2_CID_HUE: + vc->value = ov5640_data.hue; + break; + case V4L2_CID_CONTRAST: + vc->value = ov5640_data.contrast; + break; + case V4L2_CID_SATURATION: + vc->value = ov5640_data.saturation; + break; + case V4L2_CID_RED_BALANCE: + vc->value = ov5640_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + vc->value = ov5640_data.blue; + break; + case V4L2_CID_EXPOSURE: + vc->value = ov5640_data.ae_mode; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In ov5640:ioctl_s_ctrl %d\n", + vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + break; + case V4L2_CID_CONTRAST: + break; + case V4L2_CID_SATURATION: + break; + case V4L2_CID_HUE: + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + break; + case V4L2_CID_DO_WHITE_BALANCE: + break; + case V4L2_CID_RED_BALANCE: + break; + case V4L2_CID_BLUE_BALANCE: + break; + case V4L2_CID_GAMMA: + break; + case V4L2_CID_EXPOSURE: + break; + case V4L2_CID_AUTOGAIN: + break; + case V4L2_CID_GAIN: + break; + case V4L2_CID_HFLIP: + break; + case V4L2_CID_VFLIP: + break; + default: + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_enum_framesizes - V4L2 sensor interface handler for + * VIDIOC_ENUM_FRAMESIZES ioctl + * @s: pointer to standard V4L2 device structure + * @fsize: standard V4L2 VIDIOC_ENUM_FRAMESIZES ioctl structure + * + * Return 0 if successful, otherwise -EINVAL. + */ +static int ioctl_enum_framesizes(struct v4l2_int_device *s, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > ov5640_mode_MAX) + return -EINVAL; + + fsize->pixel_format = ov5640_data.pix.pixelformat; + fsize->discrete.width = + max(ov5640_mode_info_data[0][fsize->index].width, + ov5640_mode_info_data[1][fsize->index].width); + fsize->discrete.height = + max(ov5640_mode_info_data[0][fsize->index].height, + ov5640_mode_info_data[1][fsize->index].height); + return 0; +} + +/*! + * ioctl_g_chip_ident - V4L2 sensor interface handler for + * VIDIOC_DBG_G_CHIP_IDENT ioctl + * @s: pointer to standard V4L2 device structure + * @id: pointer to int + * + * Return 0. + */ +static int ioctl_g_chip_ident(struct v4l2_int_device *s, int *id) +{ + ((struct v4l2_dbg_chip_ident *)id)->match.type = + V4L2_CHIP_MATCH_I2C_DRIVER; + strcpy(((struct v4l2_dbg_chip_ident *)id)->match.name, "ov5640_camera"); + + return 0; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + + return 0; +} + +/*! + * ioctl_enum_fmt_cap - V4L2 sensor interface handler for VIDIOC_ENUM_FMT + * @s: pointer to standard V4L2 device structure + * @fmt: pointer to standard V4L2 fmt description structure + * + * Return 0. + */ +static int ioctl_enum_fmt_cap(struct v4l2_int_device *s, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->index > ov5640_mode_MAX) + return -EINVAL; + + fmt->pixelformat = ov5640_data.pix.pixelformat; + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct sensor *sensor = s->priv; + u32 tgt_xclk; /* target xclk */ + u32 tgt_fps; /* target frames per secound */ + enum ov5640_frame_rate frame_rate; + + gpio_sensor_active(ov5640_data.csi); + ov5640_data.on = true; + + /* mclk */ + tgt_xclk = ov5640_data.mclk; + tgt_xclk = min(tgt_xclk, (u32)OV5640_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)OV5640_XCLK_MIN); + ov5640_data.mclk = tgt_xclk; + + pr_debug(" Setting mclk to %d MHz\n", tgt_xclk / 1000000); + set_mclk_rate(&ov5640_data.mclk, ov5640_data.csi); + + /* Default camera frame rate is set in probe */ + tgt_fps = sensor->streamcap.timeperframe.denominator / + sensor->streamcap.timeperframe.numerator; + + if (tgt_fps == 15) + frame_rate = ov5640_15_fps; + else if (tgt_fps == 30) + frame_rate = ov5640_30_fps; + else + return -EINVAL; /* Only support 15fps or 30fps now. */ + + return ov5640_init_mode(frame_rate, + sensor->streamcap.capturemode); +} + +/*! + * ioctl_dev_exit - V4L2 sensor interface handler for vidioc_int_dev_exit_num + * @s: pointer to standard V4L2 device structure + * + * Delinitialise the device when slave detaches to the master. + */ +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + gpio_sensor_inactive(ov5640_data.csi); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc ov5640_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + {vidioc_int_dev_exit_num, ioctl_dev_exit}, + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, + {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, + {vidioc_int_enum_framesizes_num, + (v4l2_int_ioctl_func *)ioctl_enum_framesizes}, + {vidioc_int_g_chip_ident_num, + (v4l2_int_ioctl_func *)ioctl_g_chip_ident}, +}; + +static struct v4l2_int_slave ov5640_slave = { + .ioctls = ov5640_ioctl_desc, + .num_ioctls = ARRAY_SIZE(ov5640_ioctl_desc), +}; + +static struct v4l2_int_device ov5640_int_device = { + .module = THIS_MODULE, + .name = "ov5640", + .type = v4l2_int_type_slave, + .u = { + .slave = &ov5640_slave, + }, +}; + +/*! + * ov5640 I2C probe function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int ov5640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + /* Set initial values for the sensor struct. */ + memset(&ov5640_data, 0, sizeof(ov5640_data)); + ov5640_data.mclk = 24000000; /* 6 - 54 MHz, typical 24MHz */ + ov5640_data.mclk = plat_data->mclk; + ov5640_data.csi = plat_data->csi; + + ov5640_data.i2c_client = client; + ov5640_data.pix.pixelformat = V4L2_PIX_FMT_YUYV; + ov5640_data.pix.width = 640; + ov5640_data.pix.height = 480; + ov5640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY | + V4L2_CAP_TIMEPERFRAME; + ov5640_data.streamcap.capturemode = 0; + ov5640_data.streamcap.timeperframe.denominator = DEFAULT_FPS; + ov5640_data.streamcap.timeperframe.numerator = 1; + + if (plat_data->io_regulator) { + io_regulator = regulator_get(&client->dev, + plat_data->io_regulator); + if (!IS_ERR(io_regulator)) { + regulator_set_voltage(io_regulator, + OV5640_VOLTAGE_DIGITAL_IO, + OV5640_VOLTAGE_DIGITAL_IO); + if (regulator_enable(io_regulator) != 0) { + pr_err("%s:io set voltage error\n", __func__); + goto err1; + } else { + dev_dbg(&client->dev, + "%s:io set voltage ok\n", __func__); + } + } else + io_regulator = NULL; + } + + if (plat_data->core_regulator) { + core_regulator = regulator_get(&client->dev, + plat_data->core_regulator); + if (!IS_ERR(core_regulator)) { + regulator_set_voltage(core_regulator, + OV5640_VOLTAGE_DIGITAL_CORE, + OV5640_VOLTAGE_DIGITAL_CORE); + if (regulator_enable(core_regulator) != 0) { + pr_err("%s:core set voltage error\n", __func__); + goto err2; + } else { + dev_dbg(&client->dev, + "%s:core set voltage ok\n", __func__); + } + } else + core_regulator = NULL; + } + + if (plat_data->analog_regulator) { + analog_regulator = regulator_get(&client->dev, + plat_data->analog_regulator); + if (!IS_ERR(analog_regulator)) { + regulator_set_voltage(analog_regulator, + OV5640_VOLTAGE_ANALOG, + OV5640_VOLTAGE_ANALOG); + if (regulator_enable(analog_regulator) != 0) { + pr_err("%s:analog set voltage error\n", + __func__); + goto err3; + } else { + dev_dbg(&client->dev, + "%s:analog set voltage ok\n", __func__); + } + } else + analog_regulator = NULL; + } + + if (plat_data->pwdn) + plat_data->pwdn(0); + + camera_plat = plat_data; + + ov5640_int_device.priv = &ov5640_data; + retval = v4l2_int_device_register(&ov5640_int_device); + + return retval; + +err3: + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } +err2: + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } +err1: + return -1; +} + +/*! + * ov5640 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int ov5640_remove(struct i2c_client *client) +{ + v4l2_int_device_unregister(&ov5640_int_device); + + if (gpo_regulator) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + return 0; +} + +/*! + * ov5640 init function + * Called by insmod ov5640_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int ov5640_init(void) +{ + u8 err; + + err = i2c_add_driver(&ov5640_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * OV5640 cleanup function + * Called on rmmod ov5640_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit ov5640_clean(void) +{ + i2c_del_driver(&ov5640_i2c_driver); +} + +module_init(ov5640_init); +module_exit(ov5640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV5640 Camera Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("CSI"); diff --git a/drivers/media/video/mxc/capture/ov5642.c b/drivers/media/video/mxc/capture/ov5642.c new file mode 100644 index 000000000000..63d85c6a4036 --- /dev/null +++ b/drivers/media/video/mxc/capture/ov5642.c @@ -0,0 +1,2105 @@ +/* + * Copyright (C) 2011 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxc_v4l2_capture.h" + +#define OV5642_VOLTAGE_ANALOG 2800000 +#define OV5642_VOLTAGE_DIGITAL_CORE 1500000 +#define OV5642_VOLTAGE_DIGITAL_IO 1800000 + +#define MIN_FPS 15 +#define MAX_FPS 30 +#define DEFAULT_FPS 30 + +#define OV5642_XCLK_MIN 6000000 +#define OV5642_XCLK_MAX 24000000 + +enum ov5642_mode { + ov5642_mode_MIN = 0, + ov5642_mode_VGA_640_480 = 0, + ov5642_mode_QVGA_320_240 = 1, + ov5642_mode_NTSC_720_480 = 2, + ov5642_mode_PAL_720_576 = 3, + ov5642_mode_720P_1280_720 = 4, + ov5642_mode_1080P_1920_1080 = 5, + ov5642_mode_QSXGA_2592_1944 = 6, + ov5642_mode_MAX = 6 +}; + +enum ov5642_frame_rate { + ov5642_15_fps, + ov5642_30_fps +}; + +struct reg_value { + u16 u16RegAddr; + u8 u8Val; + u8 u8Mask; + u32 u32Delay_ms; +}; + +struct ov5642_mode_info { + enum ov5642_mode mode; + u32 width; + u32 height; + struct reg_value *init_data_ptr; + u32 init_data_size; +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + const struct ov5642_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + u32 mclk; + int csi; +} ov5642_data; + +static struct reg_value ov5642_setting_15fps_QSXGA_2592_1944[] = { + {0x3103, 0x93, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3017, 0x7f, 0, 0}, + {0x3018, 0xfc, 0, 0}, {0x3810, 0xc2, 0, 0}, {0x3615, 0xf0, 0, 0}, + {0x3000, 0x00, 0, 0}, {0x3001, 0x00, 0, 0}, {0x3002, 0x00, 0, 0}, + {0x3003, 0x00, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3030, 0x2b, 0, 0}, + {0x3011, 0x08, 0, 0}, {0x3010, 0x00, 0, 0}, {0x3604, 0x60, 0, 0}, + {0x3622, 0x60, 0, 0}, {0x3621, 0x09, 0, 0}, {0x3709, 0x00, 0, 0}, + {0x4000, 0x21, 0, 0}, {0x401d, 0x22, 0, 0}, {0x3600, 0x54, 0, 0}, + {0x3605, 0x04, 0, 0}, {0x3606, 0x3f, 0, 0}, {0x3c01, 0x80, 0, 0}, + {0x300d, 0x22, 0, 0}, {0x3623, 0x22, 0, 0}, {0x5000, 0x4f, 0, 0}, + {0x5020, 0x04, 0, 0}, {0x5181, 0x79, 0, 0}, {0x5182, 0x00, 0, 0}, + {0x5185, 0x22, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5500, 0x0a, 0, 0}, + {0x5504, 0x00, 0, 0}, {0x5505, 0x7f, 0, 0}, {0x5080, 0x08, 0, 0}, + {0x300e, 0x18, 0, 0}, {0x4610, 0x00, 0, 0}, {0x471d, 0x05, 0, 0}, + {0x4708, 0x06, 0, 0}, {0x370c, 0xa0, 0, 0}, {0x3808, 0x0a, 0, 0}, + {0x3809, 0x20, 0, 0}, {0x380a, 0x07, 0, 0}, {0x380b, 0x98, 0, 0}, + {0x380c, 0x0c, 0, 0}, {0x380d, 0x80, 0, 0}, {0x380e, 0x07, 0, 0}, + {0x380f, 0xd0, 0, 0}, {0x5687, 0x94, 0, 0}, {0x501f, 0x00, 0, 0}, + {0x5000, 0x4f, 0, 0}, {0x5001, 0xcf, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x4300, 0x30, 0, 0}, {0x460b, 0x35, 0, 0}, {0x471d, 0x00, 0, 0}, + {0x3002, 0x0c, 0, 0}, {0x3002, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x4721, 0x02, 0, 0}, {0x4402, 0x90, 0, 0}, + {0x460c, 0x22, 0, 0}, {0x3815, 0x44, 0, 0}, {0x3503, 0x07, 0, 0}, + {0x3501, 0x73, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3818, 0xc8, 0, 0}, {0x3801, 0x88, 0, 0}, {0x3824, 0x11, 0, 0}, + {0x3a00, 0x78, 0, 0}, {0x3a1a, 0x04, 0, 0}, {0x3a13, 0x30, 0, 0}, + {0x3a18, 0x00, 0, 0}, {0x3a19, 0x7c, 0, 0}, {0x3a08, 0x12, 0, 0}, + {0x3a09, 0xc0, 0, 0}, {0x3a0a, 0x0f, 0, 0}, {0x3a0b, 0xa0, 0, 0}, + {0x350c, 0x07, 0, 0}, {0x350d, 0xd0, 0, 0}, {0x3a0d, 0x08, 0, 0}, + {0x3a0e, 0x06, 0, 0}, {0x3500, 0x00, 0, 0}, {0x3501, 0x00, 0, 0}, + {0x3502, 0x00, 0, 0}, {0x350a, 0x00, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x00, 0, 0}, {0x3a0f, 0x3c, 0, 0}, {0x3a10, 0x32, 0, 0}, + {0x3a1b, 0x3c, 0, 0}, {0x3a1e, 0x32, 0, 0}, {0x3a11, 0x80, 0, 0}, + {0x3a1f, 0x20, 0, 0}, {0x3030, 0x2b, 0, 0}, {0x3a02, 0x00, 0, 0}, + {0x3a03, 0x7d, 0, 0}, {0x3a04, 0x00, 0, 0}, {0x3a14, 0x00, 0, 0}, + {0x3a15, 0x7d, 0, 0}, {0x3a16, 0x00, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a08, 0x09, 0, 0}, {0x3a09, 0x60, 0, 0}, {0x3a0a, 0x07, 0, 0}, + {0x3a0b, 0xd0, 0, 0}, {0x3a0d, 0x10, 0, 0}, {0x3a0e, 0x0d, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x589b, 0x00, 0, 0}, + {0x589a, 0xc0, 0, 0}, {0x401e, 0x20, 0, 0}, {0x4001, 0x42, 0, 0}, + {0x401c, 0x06, 0, 0}, {0x3825, 0xac, 0, 0}, {0x3827, 0x0c, 0, 0}, + {0x528a, 0x01, 0, 0}, {0x528b, 0x04, 0, 0}, {0x528c, 0x08, 0, 0}, + {0x528d, 0x10, 0, 0}, {0x528e, 0x20, 0, 0}, {0x528f, 0x28, 0, 0}, + {0x5290, 0x30, 0, 0}, {0x5292, 0x00, 0, 0}, {0x5293, 0x01, 0, 0}, + {0x5294, 0x00, 0, 0}, {0x5295, 0x04, 0, 0}, {0x5296, 0x00, 0, 0}, + {0x5297, 0x08, 0, 0}, {0x5298, 0x00, 0, 0}, {0x5299, 0x10, 0, 0}, + {0x529a, 0x00, 0, 0}, {0x529b, 0x20, 0, 0}, {0x529c, 0x00, 0, 0}, + {0x529d, 0x28, 0, 0}, {0x529e, 0x00, 0, 0}, {0x529f, 0x30, 0, 0}, + {0x5282, 0x00, 0, 0}, {0x5300, 0x00, 0, 0}, {0x5301, 0x20, 0, 0}, + {0x5302, 0x00, 0, 0}, {0x5303, 0x7c, 0, 0}, {0x530c, 0x00, 0, 0}, + {0x530d, 0x0c, 0, 0}, {0x530e, 0x20, 0, 0}, {0x530f, 0x80, 0, 0}, + {0x5310, 0x20, 0, 0}, {0x5311, 0x80, 0, 0}, {0x5308, 0x20, 0, 0}, + {0x5309, 0x40, 0, 0}, {0x5304, 0x00, 0, 0}, {0x5305, 0x30, 0, 0}, + {0x5306, 0x00, 0, 0}, {0x5307, 0x80, 0, 0}, {0x5314, 0x08, 0, 0}, + {0x5315, 0x20, 0, 0}, {0x5319, 0x30, 0, 0}, {0x5316, 0x10, 0, 0}, + {0x5317, 0x00, 0, 0}, {0x5318, 0x02, 0, 0}, {0x5380, 0x01, 0, 0}, + {0x5381, 0x00, 0, 0}, {0x5382, 0x00, 0, 0}, {0x5383, 0x4e, 0, 0}, + {0x5384, 0x00, 0, 0}, {0x5385, 0x0f, 0, 0}, {0x5386, 0x00, 0, 0}, + {0x5387, 0x00, 0, 0}, {0x5388, 0x01, 0, 0}, {0x5389, 0x15, 0, 0}, + {0x538a, 0x00, 0, 0}, {0x538b, 0x31, 0, 0}, {0x538c, 0x00, 0, 0}, + {0x538d, 0x00, 0, 0}, {0x538e, 0x00, 0, 0}, {0x538f, 0x0f, 0, 0}, + {0x5390, 0x00, 0, 0}, {0x5391, 0xab, 0, 0}, {0x5392, 0x00, 0, 0}, + {0x5393, 0xa2, 0, 0}, {0x5394, 0x08, 0, 0}, {0x5480, 0x14, 0, 0}, + {0x5481, 0x21, 0, 0}, {0x5482, 0x36, 0, 0}, {0x5483, 0x57, 0, 0}, + {0x5484, 0x65, 0, 0}, {0x5485, 0x71, 0, 0}, {0x5486, 0x7d, 0, 0}, + {0x5487, 0x87, 0, 0}, {0x5488, 0x91, 0, 0}, {0x5489, 0x9a, 0, 0}, + {0x548a, 0xaa, 0, 0}, {0x548b, 0xb8, 0, 0}, {0x548c, 0xcd, 0, 0}, + {0x548d, 0xdd, 0, 0}, {0x548e, 0xea, 0, 0}, {0x548f, 0x1d, 0, 0}, + {0x5490, 0x05, 0, 0}, {0x5491, 0x00, 0, 0}, {0x5492, 0x04, 0, 0}, + {0x5493, 0x20, 0, 0}, {0x5494, 0x03, 0, 0}, {0x5495, 0x60, 0, 0}, + {0x5496, 0x02, 0, 0}, {0x5497, 0xb8, 0, 0}, {0x5498, 0x02, 0, 0}, + {0x5499, 0x86, 0, 0}, {0x549a, 0x02, 0, 0}, {0x549b, 0x5b, 0, 0}, + {0x549c, 0x02, 0, 0}, {0x549d, 0x3b, 0, 0}, {0x549e, 0x02, 0, 0}, + {0x549f, 0x1c, 0, 0}, {0x54a0, 0x02, 0, 0}, {0x54a1, 0x04, 0, 0}, + {0x54a2, 0x01, 0, 0}, {0x54a3, 0xed, 0, 0}, {0x54a4, 0x01, 0, 0}, + {0x54a5, 0xc5, 0, 0}, {0x54a6, 0x01, 0, 0}, {0x54a7, 0xa5, 0, 0}, + {0x54a8, 0x01, 0, 0}, {0x54a9, 0x6c, 0, 0}, {0x54aa, 0x01, 0, 0}, + {0x54ab, 0x41, 0, 0}, {0x54ac, 0x01, 0, 0}, {0x54ad, 0x20, 0, 0}, + {0x54ae, 0x00, 0, 0}, {0x54af, 0x16, 0, 0}, {0x54b0, 0x01, 0, 0}, + {0x54b1, 0x20, 0, 0}, {0x54b2, 0x00, 0, 0}, {0x54b3, 0x10, 0, 0}, + {0x54b4, 0x00, 0, 0}, {0x54b5, 0xf0, 0, 0}, {0x54b6, 0x00, 0, 0}, + {0x54b7, 0xdf, 0, 0}, {0x5402, 0x3f, 0, 0}, {0x5403, 0x00, 0, 0}, + {0x3406, 0x00, 0, 0}, {0x5180, 0xff, 0, 0}, {0x5181, 0x52, 0, 0}, + {0x5182, 0x11, 0, 0}, {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, + {0x5185, 0x24, 0, 0}, {0x5186, 0x06, 0, 0}, {0x5187, 0x08, 0, 0}, + {0x5188, 0x08, 0, 0}, {0x5189, 0x7c, 0, 0}, {0x518a, 0x60, 0, 0}, + {0x518b, 0xb2, 0, 0}, {0x518c, 0xb2, 0, 0}, {0x518d, 0x44, 0, 0}, + {0x518e, 0x3d, 0, 0}, {0x518f, 0x58, 0, 0}, {0x5190, 0x46, 0, 0}, + {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, + {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, + {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, + {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, + {0x519d, 0x82, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5025, 0x80, 0, 0}, + {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0}, {0x3a1b, 0x3a, 0, 0}, + {0x3a1e, 0x2e, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x10, 0, 0}, + {0x5688, 0xa6, 0, 0}, {0x5689, 0x6a, 0, 0}, {0x568a, 0xea, 0, 0}, + {0x568b, 0xae, 0, 0}, {0x568c, 0xa6, 0, 0}, {0x568d, 0x6a, 0, 0}, + {0x568e, 0x62, 0, 0}, {0x568f, 0x26, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x40, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5000, 0xcf, 0, 0}, + {0x5800, 0x27, 0, 0}, {0x5801, 0x19, 0, 0}, {0x5802, 0x12, 0, 0}, + {0x5803, 0x0f, 0, 0}, {0x5804, 0x10, 0, 0}, {0x5805, 0x15, 0, 0}, + {0x5806, 0x1e, 0, 0}, {0x5807, 0x2f, 0, 0}, {0x5808, 0x15, 0, 0}, + {0x5809, 0x0d, 0, 0}, {0x580a, 0x0a, 0, 0}, {0x580b, 0x09, 0, 0}, + {0x580c, 0x0a, 0, 0}, {0x580d, 0x0c, 0, 0}, {0x580e, 0x12, 0, 0}, + {0x580f, 0x19, 0, 0}, {0x5810, 0x0b, 0, 0}, {0x5811, 0x07, 0, 0}, + {0x5812, 0x04, 0, 0}, {0x5813, 0x03, 0, 0}, {0x5814, 0x03, 0, 0}, + {0x5815, 0x06, 0, 0}, {0x5816, 0x0a, 0, 0}, {0x5817, 0x0f, 0, 0}, + {0x5818, 0x0a, 0, 0}, {0x5819, 0x05, 0, 0}, {0x581a, 0x01, 0, 0}, + {0x581b, 0x00, 0, 0}, {0x581c, 0x00, 0, 0}, {0x581d, 0x03, 0, 0}, + {0x581e, 0x08, 0, 0}, {0x581f, 0x0c, 0, 0}, {0x5820, 0x0a, 0, 0}, + {0x5821, 0x05, 0, 0}, {0x5822, 0x01, 0, 0}, {0x5823, 0x00, 0, 0}, + {0x5824, 0x00, 0, 0}, {0x5825, 0x03, 0, 0}, {0x5826, 0x08, 0, 0}, + {0x5827, 0x0c, 0, 0}, {0x5828, 0x0e, 0, 0}, {0x5829, 0x08, 0, 0}, + {0x582a, 0x06, 0, 0}, {0x582b, 0x04, 0, 0}, {0x582c, 0x05, 0, 0}, + {0x582d, 0x07, 0, 0}, {0x582e, 0x0b, 0, 0}, {0x582f, 0x12, 0, 0}, + {0x5830, 0x18, 0, 0}, {0x5831, 0x10, 0, 0}, {0x5832, 0x0c, 0, 0}, + {0x5833, 0x0a, 0, 0}, {0x5834, 0x0b, 0, 0}, {0x5835, 0x0e, 0, 0}, + {0x5836, 0x15, 0, 0}, {0x5837, 0x19, 0, 0}, {0x5838, 0x32, 0, 0}, + {0x5839, 0x1f, 0, 0}, {0x583a, 0x18, 0, 0}, {0x583b, 0x16, 0, 0}, + {0x583c, 0x17, 0, 0}, {0x583d, 0x1e, 0, 0}, {0x583e, 0x26, 0, 0}, + {0x583f, 0x53, 0, 0}, {0x5840, 0x10, 0, 0}, {0x5841, 0x0f, 0, 0}, + {0x5842, 0x0d, 0, 0}, {0x5843, 0x0c, 0, 0}, {0x5844, 0x0e, 0, 0}, + {0x5845, 0x09, 0, 0}, {0x5846, 0x11, 0, 0}, {0x5847, 0x10, 0, 0}, + {0x5848, 0x10, 0, 0}, {0x5849, 0x10, 0, 0}, {0x584a, 0x10, 0, 0}, + {0x584b, 0x0e, 0, 0}, {0x584c, 0x10, 0, 0}, {0x584d, 0x10, 0, 0}, + {0x584e, 0x11, 0, 0}, {0x584f, 0x10, 0, 0}, {0x5850, 0x0f, 0, 0}, + {0x5851, 0x0c, 0, 0}, {0x5852, 0x0f, 0, 0}, {0x5853, 0x10, 0, 0}, + {0x5854, 0x10, 0, 0}, {0x5855, 0x0f, 0, 0}, {0x5856, 0x0e, 0, 0}, + {0x5857, 0x0b, 0, 0}, {0x5858, 0x10, 0, 0}, {0x5859, 0x0d, 0, 0}, + {0x585a, 0x0d, 0, 0}, {0x585b, 0x0c, 0, 0}, {0x585c, 0x0c, 0, 0}, + {0x585d, 0x0c, 0, 0}, {0x585e, 0x0b, 0, 0}, {0x585f, 0x0c, 0, 0}, + {0x5860, 0x0c, 0, 0}, {0x5861, 0x0c, 0, 0}, {0x5862, 0x0d, 0, 0}, + {0x5863, 0x08, 0, 0}, {0x5864, 0x11, 0, 0}, {0x5865, 0x18, 0, 0}, + {0x5866, 0x18, 0, 0}, {0x5867, 0x19, 0, 0}, {0x5868, 0x17, 0, 0}, + {0x5869, 0x19, 0, 0}, {0x586a, 0x16, 0, 0}, {0x586b, 0x13, 0, 0}, + {0x586c, 0x13, 0, 0}, {0x586d, 0x12, 0, 0}, {0x586e, 0x13, 0, 0}, + {0x586f, 0x16, 0, 0}, {0x5870, 0x14, 0, 0}, {0x5871, 0x12, 0, 0}, + {0x5872, 0x10, 0, 0}, {0x5873, 0x11, 0, 0}, {0x5874, 0x11, 0, 0}, + {0x5875, 0x16, 0, 0}, {0x5876, 0x14, 0, 0}, {0x5877, 0x11, 0, 0}, + {0x5878, 0x10, 0, 0}, {0x5879, 0x0f, 0, 0}, {0x587a, 0x10, 0, 0}, + {0x587b, 0x14, 0, 0}, {0x587c, 0x13, 0, 0}, {0x587d, 0x12, 0, 0}, + {0x587e, 0x11, 0, 0}, {0x587f, 0x11, 0, 0}, {0x5880, 0x12, 0, 0}, + {0x5881, 0x15, 0, 0}, {0x5882, 0x14, 0, 0}, {0x5883, 0x15, 0, 0}, + {0x5884, 0x15, 0, 0}, {0x5885, 0x15, 0, 0}, {0x5886, 0x13, 0, 0}, + {0x5887, 0x17, 0, 0}, {0x3710, 0x10, 0, 0}, {0x3632, 0x51, 0, 0}, + {0x3702, 0x10, 0, 0}, {0x3703, 0xb2, 0, 0}, {0x3704, 0x18, 0, 0}, + {0x370b, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x3631, 0x01, 0, 0}, + {0x3632, 0x52, 0, 0}, {0x3606, 0x24, 0, 0}, {0x3620, 0x96, 0, 0}, + {0x5785, 0x07, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3600, 0x52, 0, 0}, + {0x3604, 0x48, 0, 0}, {0x3606, 0x1b, 0, 0}, {0x370d, 0x0b, 0, 0}, + {0x370f, 0xc0, 0, 0}, {0x3709, 0x01, 0, 0}, {0x3823, 0x00, 0, 0}, + {0x5007, 0x00, 0, 0}, {0x5009, 0x00, 0, 0}, {0x5011, 0x00, 0, 0}, + {0x5013, 0x00, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5086, 0x00, 0, 0}, + {0x5087, 0x00, 0, 0}, {0x5088, 0x00, 0, 0}, {0x5089, 0x00, 0, 0}, + {0x302b, 0x00, 0, 0}, {0x460c, 0x20, 0, 0}, {0x460b, 0x37, 0, 0}, + {0x471c, 0xd0, 0, 0}, {0x471d, 0x05, 0, 0}, {0x3815, 0x01, 0, 0}, + {0x3818, 0xc0, 0, 0}, {0x501f, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x3002, 0x1c, 0, 0}, {0x3819, 0x80, 0, 0}, {0x5002, 0xe0, 0, 0}, + {0x3012, 0x01, 0, 0}, +}; + +static struct reg_value ov5642_setting_30fps_VGA_640_480[] = { + {0x3103, 0x93, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3017, 0x7f, 0, 0}, + {0x3018, 0xfc, 0, 0}, {0x3615, 0xf0, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3001, 0x00, 0, 0}, {0x3002, 0x5c, 0, 0}, {0x3003, 0x00, 0, 0}, + {0x3004, 0xff, 0, 0}, {0x3005, 0xff, 0, 0}, {0x3006, 0x43, 0, 0}, + {0x3007, 0x37, 0, 0}, {0x3011, 0x09, 0, 0}, {0x3012, 0x02, 0, 0}, + {0x3010, 0x00, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3815, 0x04, 0, 0}, + {0x370c, 0xa0, 0, 0}, {0x3602, 0xfc, 0, 0}, {0x3612, 0xff, 0, 0}, + {0x3634, 0xc0, 0, 0}, {0x3613, 0x00, 0, 0}, {0x3605, 0x7c, 0, 0}, + {0x3621, 0x09, 0, 0}, {0x3622, 0x60, 0, 0}, {0x3604, 0x40, 0, 0}, + {0x3603, 0xa7, 0, 0}, {0x3603, 0x27, 0, 0}, {0x4000, 0x21, 0, 0}, + {0x401d, 0x22, 0, 0}, {0x3600, 0x54, 0, 0}, {0x3605, 0x04, 0, 0}, + {0x3606, 0x3f, 0, 0}, {0x3c01, 0x80, 0, 0}, {0x5000, 0x4f, 0, 0}, + {0x5020, 0x04, 0, 0}, {0x5181, 0x79, 0, 0}, {0x5182, 0x00, 0, 0}, + {0x5185, 0x22, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5001, 0xff, 0, 0}, + {0x5500, 0x0a, 0, 0}, {0x5504, 0x00, 0, 0}, {0x5505, 0x7f, 0, 0}, + {0x5080, 0x08, 0, 0}, {0x300e, 0x18, 0, 0}, {0x4610, 0x00, 0, 0}, + {0x471d, 0x05, 0, 0}, {0x4708, 0x06, 0, 0}, {0x3808, 0x02, 0, 0}, + {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, {0x380b, 0xe0, 0, 0}, + {0x380e, 0x07, 0, 0}, {0x380f, 0xd0, 0, 0}, {0x501f, 0x00, 0, 0}, + {0x5000, 0x4f, 0, 0}, {0x4300, 0x30, 0, 0}, {0x3503, 0x07, 0, 0}, + {0x3501, 0x73, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x07, 0, 0}, {0x3824, 0x11, 0, 0}, {0x3825, 0xb0, 0, 0}, + {0x3501, 0x1e, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x7f, 0, 0}, + {0x380c, 0x07, 0, 0}, {0x380d, 0x2a, 0, 0}, {0x380e, 0x03, 0, 0}, + {0x380f, 0xe8, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3818, 0xc1, 0, 0}, {0x3705, 0xdb, 0, 0}, {0x370a, 0x81, 0, 0}, + {0x3801, 0x80, 0, 0}, {0x3621, 0xc7, 0, 0}, {0x3801, 0x50, 0, 0}, + {0x3803, 0x08, 0, 0}, {0x3827, 0x08, 0, 0}, {0x3810, 0x80, 0, 0}, + {0x3804, 0x05, 0, 0}, {0x3805, 0x00, 0, 0}, {0x5682, 0x05, 0, 0}, + {0x5683, 0x00, 0, 0}, {0x3806, 0x03, 0, 0}, {0x3807, 0xc0, 0, 0}, + {0x5686, 0x03, 0, 0}, {0x5687, 0xbc, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a1a, 0x05, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3a18, 0x00, 0, 0}, + {0x3a19, 0x7c, 0, 0}, {0x3a08, 0x12, 0, 0}, {0x3a09, 0xc0, 0, 0}, + {0x3a0a, 0x0f, 0, 0}, {0x3a0b, 0xa0, 0, 0}, {0x350c, 0x07, 0, 0}, + {0x350d, 0xd0, 0, 0}, {0x3500, 0x00, 0, 0}, {0x3501, 0x00, 0, 0}, + {0x3502, 0x00, 0, 0}, {0x350a, 0x00, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x00, 0, 0}, {0x528a, 0x02, 0, 0}, {0x528b, 0x04, 0, 0}, + {0x528c, 0x08, 0, 0}, {0x528d, 0x08, 0, 0}, {0x528e, 0x08, 0, 0}, + {0x528f, 0x10, 0, 0}, {0x5290, 0x10, 0, 0}, {0x5292, 0x00, 0, 0}, + {0x5293, 0x02, 0, 0}, {0x5294, 0x00, 0, 0}, {0x5295, 0x02, 0, 0}, + {0x5296, 0x00, 0, 0}, {0x5297, 0x02, 0, 0}, {0x5298, 0x00, 0, 0}, + {0x5299, 0x02, 0, 0}, {0x529a, 0x00, 0, 0}, {0x529b, 0x02, 0, 0}, + {0x529c, 0x00, 0, 0}, {0x529d, 0x02, 0, 0}, {0x529e, 0x00, 0, 0}, + {0x529f, 0x02, 0, 0}, {0x3a0f, 0x3c, 0, 0}, {0x3a10, 0x30, 0, 0}, + {0x3a1b, 0x3c, 0, 0}, {0x3a1e, 0x30, 0, 0}, {0x3a11, 0x70, 0, 0}, + {0x3a1f, 0x10, 0, 0}, {0x3030, 0x2b, 0, 0}, {0x3a02, 0x00, 0, 0}, + {0x3a03, 0x7d, 0, 0}, {0x3a04, 0x00, 0, 0}, {0x3a14, 0x00, 0, 0}, + {0x3a15, 0x7d, 0, 0}, {0x3a16, 0x00, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a08, 0x12, 0, 0}, {0x3a09, 0xc0, 0, 0}, {0x3a0a, 0x0f, 0, 0}, + {0x3a0b, 0xa0, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x589b, 0x04, 0, 0}, {0x589a, 0xc5, 0, 0}, + {0x401e, 0x20, 0, 0}, {0x4001, 0x42, 0, 0}, {0x401c, 0x04, 0, 0}, + {0x528a, 0x01, 0, 0}, {0x528b, 0x04, 0, 0}, {0x528c, 0x08, 0, 0}, + {0x528d, 0x10, 0, 0}, {0x528e, 0x20, 0, 0}, {0x528f, 0x28, 0, 0}, + {0x5290, 0x30, 0, 0}, {0x5292, 0x00, 0, 0}, {0x5293, 0x01, 0, 0}, + {0x5294, 0x00, 0, 0}, {0x5295, 0x04, 0, 0}, {0x5296, 0x00, 0, 0}, + {0x5297, 0x08, 0, 0}, {0x5298, 0x00, 0, 0}, {0x5299, 0x10, 0, 0}, + {0x529a, 0x00, 0, 0}, {0x529b, 0x20, 0, 0}, {0x529c, 0x00, 0, 0}, + {0x529d, 0x28, 0, 0}, {0x529e, 0x00, 0, 0}, {0x529f, 0x30, 0, 0}, + {0x5282, 0x00, 0, 0}, {0x5300, 0x00, 0, 0}, {0x5301, 0x20, 0, 0}, + {0x5302, 0x00, 0, 0}, {0x5303, 0x7c, 0, 0}, {0x530c, 0x00, 0, 0}, + {0x530d, 0x0c, 0, 0}, {0x530e, 0x20, 0, 0}, {0x530f, 0x80, 0, 0}, + {0x5310, 0x20, 0, 0}, {0x5311, 0x80, 0, 0}, {0x5308, 0x20, 0, 0}, + {0x5309, 0x40, 0, 0}, {0x5304, 0x00, 0, 0}, {0x5305, 0x30, 0, 0}, + {0x5306, 0x00, 0, 0}, {0x5307, 0x80, 0, 0}, {0x5314, 0x08, 0, 0}, + {0x5315, 0x20, 0, 0}, {0x5319, 0x30, 0, 0}, {0x5316, 0x10, 0, 0}, + {0x5317, 0x00, 0, 0}, {0x5318, 0x02, 0, 0}, {0x5380, 0x01, 0, 0}, + {0x5381, 0x00, 0, 0}, {0x5382, 0x00, 0, 0}, {0x5383, 0x4e, 0, 0}, + {0x5384, 0x00, 0, 0}, {0x5385, 0x0f, 0, 0}, {0x5386, 0x00, 0, 0}, + {0x5387, 0x00, 0, 0}, {0x5388, 0x01, 0, 0}, {0x5389, 0x15, 0, 0}, + {0x538a, 0x00, 0, 0}, {0x538b, 0x31, 0, 0}, {0x538c, 0x00, 0, 0}, + {0x538d, 0x00, 0, 0}, {0x538e, 0x00, 0, 0}, {0x538f, 0x0f, 0, 0}, + {0x5390, 0x00, 0, 0}, {0x5391, 0xab, 0, 0}, {0x5392, 0x00, 0, 0}, + {0x5393, 0xa2, 0, 0}, {0x5394, 0x08, 0, 0}, {0x5480, 0x14, 0, 0}, + {0x5481, 0x21, 0, 0}, {0x5482, 0x36, 0, 0}, {0x5483, 0x57, 0, 0}, + {0x5484, 0x65, 0, 0}, {0x5485, 0x71, 0, 0}, {0x5486, 0x7d, 0, 0}, + {0x5487, 0x87, 0, 0}, {0x5488, 0x91, 0, 0}, {0x5489, 0x9a, 0, 0}, + {0x548a, 0xaa, 0, 0}, {0x548b, 0xb8, 0, 0}, {0x548c, 0xcd, 0, 0}, + {0x548d, 0xdd, 0, 0}, {0x548e, 0xea, 0, 0}, {0x548f, 0x1d, 0, 0}, + {0x5490, 0x05, 0, 0}, {0x5491, 0x00, 0, 0}, {0x5492, 0x04, 0, 0}, + {0x5493, 0x20, 0, 0}, {0x5494, 0x03, 0, 0}, {0x5495, 0x60, 0, 0}, + {0x5496, 0x02, 0, 0}, {0x5497, 0xb8, 0, 0}, {0x5498, 0x02, 0, 0}, + {0x5499, 0x86, 0, 0}, {0x549a, 0x02, 0, 0}, {0x549b, 0x5b, 0, 0}, + {0x549c, 0x02, 0, 0}, {0x549d, 0x3b, 0, 0}, {0x549e, 0x02, 0, 0}, + {0x549f, 0x1c, 0, 0}, {0x54a0, 0x02, 0, 0}, {0x54a1, 0x04, 0, 0}, + {0x54a2, 0x01, 0, 0}, {0x54a3, 0xed, 0, 0}, {0x54a4, 0x01, 0, 0}, + {0x54a5, 0xc5, 0, 0}, {0x54a6, 0x01, 0, 0}, {0x54a7, 0xa5, 0, 0}, + {0x54a8, 0x01, 0, 0}, {0x54a9, 0x6c, 0, 0}, {0x54aa, 0x01, 0, 0}, + {0x54ab, 0x41, 0, 0}, {0x54ac, 0x01, 0, 0}, {0x54ad, 0x20, 0, 0}, + {0x54ae, 0x00, 0, 0}, {0x54af, 0x16, 0, 0}, {0x54b0, 0x01, 0, 0}, + {0x54b1, 0x20, 0, 0}, {0x54b2, 0x00, 0, 0}, {0x54b3, 0x10, 0, 0}, + {0x54b4, 0x00, 0, 0}, {0x54b5, 0xf0, 0, 0}, {0x54b6, 0x00, 0, 0}, + {0x54b7, 0xdf, 0, 0}, {0x5402, 0x3f, 0, 0}, {0x5403, 0x00, 0, 0}, + {0x3406, 0x00, 0, 0}, {0x5180, 0xff, 0, 0}, {0x5181, 0x52, 0, 0}, + {0x5182, 0x11, 0, 0}, {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, + {0x5185, 0x24, 0, 0}, {0x5186, 0x06, 0, 0}, {0x5187, 0x08, 0, 0}, + {0x5188, 0x08, 0, 0}, {0x5189, 0x7c, 0, 0}, {0x518a, 0x60, 0, 0}, + {0x518b, 0xb2, 0, 0}, {0x518c, 0xb2, 0, 0}, {0x518d, 0x44, 0, 0}, + {0x518e, 0x3d, 0, 0}, {0x518f, 0x58, 0, 0}, {0x5190, 0x46, 0, 0}, + {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, + {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, + {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, + {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, + {0x519d, 0x82, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5025, 0x80, 0, 0}, + {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0}, {0x3a1b, 0x3a, 0, 0}, + {0x3a1e, 0x2e, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x10, 0, 0}, + {0x5688, 0xa6, 0, 0}, {0x5689, 0x6a, 0, 0}, {0x568a, 0xea, 0, 0}, + {0x568b, 0xae, 0, 0}, {0x568c, 0xa6, 0, 0}, {0x568d, 0x6a, 0, 0}, + {0x568e, 0x62, 0, 0}, {0x568f, 0x26, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x40, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5000, 0xcf, 0, 0}, + {0x5800, 0x27, 0, 0}, {0x5801, 0x19, 0, 0}, {0x5802, 0x12, 0, 0}, + {0x5803, 0x0f, 0, 0}, {0x5804, 0x10, 0, 0}, {0x5805, 0x15, 0, 0}, + {0x5806, 0x1e, 0, 0}, {0x5807, 0x2f, 0, 0}, {0x5808, 0x15, 0, 0}, + {0x5809, 0x0d, 0, 0}, {0x580a, 0x0a, 0, 0}, {0x580b, 0x09, 0, 0}, + {0x580c, 0x0a, 0, 0}, {0x580d, 0x0c, 0, 0}, {0x580e, 0x12, 0, 0}, + {0x580f, 0x19, 0, 0}, {0x5810, 0x0b, 0, 0}, {0x5811, 0x07, 0, 0}, + {0x5812, 0x04, 0, 0}, {0x5813, 0x03, 0, 0}, {0x5814, 0x03, 0, 0}, + {0x5815, 0x06, 0, 0}, {0x5816, 0x0a, 0, 0}, {0x5817, 0x0f, 0, 0}, + {0x5818, 0x0a, 0, 0}, {0x5819, 0x05, 0, 0}, {0x581a, 0x01, 0, 0}, + {0x581b, 0x00, 0, 0}, {0x581c, 0x00, 0, 0}, {0x581d, 0x03, 0, 0}, + {0x581e, 0x08, 0, 0}, {0x581f, 0x0c, 0, 0}, {0x5820, 0x0a, 0, 0}, + {0x5821, 0x05, 0, 0}, {0x5822, 0x01, 0, 0}, {0x5823, 0x00, 0, 0}, + {0x5824, 0x00, 0, 0}, {0x5825, 0x03, 0, 0}, {0x5826, 0x08, 0, 0}, + {0x5827, 0x0c, 0, 0}, {0x5828, 0x0e, 0, 0}, {0x5829, 0x08, 0, 0}, + {0x582a, 0x06, 0, 0}, {0x582b, 0x04, 0, 0}, {0x582c, 0x05, 0, 0}, + {0x582d, 0x07, 0, 0}, {0x582e, 0x0b, 0, 0}, {0x582f, 0x12, 0, 0}, + {0x5830, 0x18, 0, 0}, {0x5831, 0x10, 0, 0}, {0x5832, 0x0c, 0, 0}, + {0x5833, 0x0a, 0, 0}, {0x5834, 0x0b, 0, 0}, {0x5835, 0x0e, 0, 0}, + {0x5836, 0x15, 0, 0}, {0x5837, 0x19, 0, 0}, {0x5838, 0x32, 0, 0}, + {0x5839, 0x1f, 0, 0}, {0x583a, 0x18, 0, 0}, {0x583b, 0x16, 0, 0}, + {0x583c, 0x17, 0, 0}, {0x583d, 0x1e, 0, 0}, {0x583e, 0x26, 0, 0}, + {0x583f, 0x53, 0, 0}, {0x5840, 0x10, 0, 0}, {0x5841, 0x0f, 0, 0}, + {0x5842, 0x0d, 0, 0}, {0x5843, 0x0c, 0, 0}, {0x5844, 0x0e, 0, 0}, + {0x5845, 0x09, 0, 0}, {0x5846, 0x11, 0, 0}, {0x5847, 0x10, 0, 0}, + {0x5848, 0x10, 0, 0}, {0x5849, 0x10, 0, 0}, {0x584a, 0x10, 0, 0}, + {0x584b, 0x0e, 0, 0}, {0x584c, 0x10, 0, 0}, {0x584d, 0x10, 0, 0}, + {0x584e, 0x11, 0, 0}, {0x584f, 0x10, 0, 0}, {0x5850, 0x0f, 0, 0}, + {0x5851, 0x0c, 0, 0}, {0x5852, 0x0f, 0, 0}, {0x5853, 0x10, 0, 0}, + {0x5854, 0x10, 0, 0}, {0x5855, 0x0f, 0, 0}, {0x5856, 0x0e, 0, 0}, + {0x5857, 0x0b, 0, 0}, {0x5858, 0x10, 0, 0}, {0x5859, 0x0d, 0, 0}, + {0x585a, 0x0d, 0, 0}, {0x585b, 0x0c, 0, 0}, {0x585c, 0x0c, 0, 0}, + {0x585d, 0x0c, 0, 0}, {0x585e, 0x0b, 0, 0}, {0x585f, 0x0c, 0, 0}, + {0x5860, 0x0c, 0, 0}, {0x5861, 0x0c, 0, 0}, {0x5862, 0x0d, 0, 0}, + {0x5863, 0x08, 0, 0}, {0x5864, 0x11, 0, 0}, {0x5865, 0x18, 0, 0}, + {0x5866, 0x18, 0, 0}, {0x5867, 0x19, 0, 0}, {0x5868, 0x17, 0, 0}, + {0x5869, 0x19, 0, 0}, {0x586a, 0x16, 0, 0}, {0x586b, 0x13, 0, 0}, + {0x586c, 0x13, 0, 0}, {0x586d, 0x12, 0, 0}, {0x586e, 0x13, 0, 0}, + {0x586f, 0x16, 0, 0}, {0x5870, 0x14, 0, 0}, {0x5871, 0x12, 0, 0}, + {0x5872, 0x10, 0, 0}, {0x5873, 0x11, 0, 0}, {0x5874, 0x11, 0, 0}, + {0x5875, 0x16, 0, 0}, {0x5876, 0x14, 0, 0}, {0x5877, 0x11, 0, 0}, + {0x5878, 0x10, 0, 0}, {0x5879, 0x0f, 0, 0}, {0x587a, 0x10, 0, 0}, + {0x587b, 0x14, 0, 0}, {0x587c, 0x13, 0, 0}, {0x587d, 0x12, 0, 0}, + {0x587e, 0x11, 0, 0}, {0x587f, 0x11, 0, 0}, {0x5880, 0x12, 0, 0}, + {0x5881, 0x15, 0, 0}, {0x5882, 0x14, 0, 0}, {0x5883, 0x15, 0, 0}, + {0x5884, 0x15, 0, 0}, {0x5885, 0x15, 0, 0}, {0x5886, 0x13, 0, 0}, + {0x5887, 0x17, 0, 0}, {0x3710, 0x10, 0, 0}, {0x3632, 0x51, 0, 0}, + {0x3702, 0x10, 0, 0}, {0x3703, 0xb2, 0, 0}, {0x3704, 0x18, 0, 0}, + {0x370b, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x3631, 0x01, 0, 0}, + {0x3632, 0x52, 0, 0}, {0x3606, 0x24, 0, 0}, {0x3620, 0x96, 0, 0}, + {0x5785, 0x07, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3600, 0x52, 0, 0}, + {0x3604, 0x48, 0, 0}, {0x3606, 0x1b, 0, 0}, {0x370d, 0x0b, 0, 0}, + {0x370f, 0xc0, 0, 0}, {0x3709, 0x01, 0, 0}, {0x3823, 0x00, 0, 0}, + {0x5007, 0x00, 0, 0}, {0x5009, 0x00, 0, 0}, {0x5011, 0x00, 0, 0}, + {0x5013, 0x00, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5086, 0x00, 0, 0}, + {0x5087, 0x00, 0, 0}, {0x5088, 0x00, 0, 0}, {0x5089, 0x00, 0, 0}, + {0x302b, 0x00, 0, 0}, {0x3621, 0x87, 0, 0}, {0x3a00, 0x78, 0, 0}, +}; + +static struct reg_value ov5642_setting_30fps_QVGA_320_240[] = { + {0x3103, 0x93, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3017, 0x7f, 0, 0}, + {0x3018, 0xfc, 0, 0}, {0x3615, 0xf0, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3001, 0x00, 0, 0}, {0x3002, 0x5c, 0, 0}, {0x3003, 0x00, 0, 0}, + {0x3004, 0xff, 0, 0}, {0x3005, 0xff, 0, 0}, {0x3006, 0x43, 0, 0}, + {0x3007, 0x37, 0, 0}, {0x3011, 0x09, 0, 0}, {0x3012, 0x02, 0, 0}, + {0x3010, 0x00, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3815, 0x04, 0, 0}, + {0x370c, 0xa0, 0, 0}, {0x3602, 0xfc, 0, 0}, {0x3612, 0xff, 0, 0}, + {0x3634, 0xc0, 0, 0}, {0x3613, 0x00, 0, 0}, {0x3605, 0x7c, 0, 0}, + {0x3621, 0x09, 0, 0}, {0x3622, 0x60, 0, 0}, {0x3604, 0x40, 0, 0}, + {0x3603, 0xa7, 0, 0}, {0x3603, 0x27, 0, 0}, {0x4000, 0x21, 0, 0}, + {0x401d, 0x22, 0, 0}, {0x3600, 0x54, 0, 0}, {0x3605, 0x04, 0, 0}, + {0x3606, 0x3f, 0, 0}, {0x3c01, 0x80, 0, 0}, {0x5000, 0x4f, 0, 0}, + {0x5020, 0x04, 0, 0}, {0x5181, 0x79, 0, 0}, {0x5182, 0x00, 0, 0}, + {0x5185, 0x22, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5001, 0xff, 0, 0}, + {0x5500, 0x0a, 0, 0}, {0x5504, 0x00, 0, 0}, {0x5505, 0x7f, 0, 0}, + {0x5080, 0x08, 0, 0}, {0x300e, 0x18, 0, 0}, {0x4610, 0x00, 0, 0}, + {0x471d, 0x05, 0, 0}, {0x4708, 0x06, 0, 0}, {0x3808, 0x02, 0, 0}, + {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, {0x380b, 0xe0, 0, 0}, + {0x380e, 0x07, 0, 0}, {0x380f, 0xd0, 0, 0}, {0x501f, 0x00, 0, 0}, + {0x5000, 0x4f, 0, 0}, {0x4300, 0x30, 0, 0}, {0x3503, 0x07, 0, 0}, + {0x3501, 0x73, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x07, 0, 0}, {0x3824, 0x11, 0, 0}, {0x3825, 0xb0, 0, 0}, + {0x3501, 0x1e, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x7f, 0, 0}, + {0x380c, 0x07, 0, 0}, {0x380d, 0x2a, 0, 0}, {0x380e, 0x03, 0, 0}, + {0x380f, 0xe8, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3818, 0xc1, 0, 0}, {0x3705, 0xdb, 0, 0}, {0x370a, 0x81, 0, 0}, + {0x3801, 0x80, 0, 0}, {0x3621, 0xc7, 0, 0}, {0x3801, 0x50, 0, 0}, + {0x3803, 0x08, 0, 0}, {0x3827, 0x08, 0, 0}, {0x3810, 0x80, 0, 0}, + {0x3804, 0x05, 0, 0}, {0x3805, 0x00, 0, 0}, {0x5682, 0x05, 0, 0}, + {0x5683, 0x00, 0, 0}, {0x3806, 0x03, 0, 0}, {0x3807, 0xc0, 0, 0}, + {0x5686, 0x03, 0, 0}, {0x5687, 0xbc, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a1a, 0x05, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3a18, 0x00, 0, 0}, + {0x3a19, 0x7c, 0, 0}, {0x3a08, 0x12, 0, 0}, {0x3a09, 0xc0, 0, 0}, + {0x3a0a, 0x0f, 0, 0}, {0x3a0b, 0xa0, 0, 0}, {0x350c, 0x07, 0, 0}, + {0x350d, 0xd0, 0, 0}, {0x3500, 0x00, 0, 0}, {0x3501, 0x00, 0, 0}, + {0x3502, 0x00, 0, 0}, {0x350a, 0x00, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x00, 0, 0}, {0x528a, 0x02, 0, 0}, {0x528b, 0x04, 0, 0}, + {0x528c, 0x08, 0, 0}, {0x528d, 0x08, 0, 0}, {0x528e, 0x08, 0, 0}, + {0x528f, 0x10, 0, 0}, {0x5290, 0x10, 0, 0}, {0x5292, 0x00, 0, 0}, + {0x5293, 0x02, 0, 0}, {0x5294, 0x00, 0, 0}, {0x5295, 0x02, 0, 0}, + {0x5296, 0x00, 0, 0}, {0x5297, 0x02, 0, 0}, {0x5298, 0x00, 0, 0}, + {0x5299, 0x02, 0, 0}, {0x529a, 0x00, 0, 0}, {0x529b, 0x02, 0, 0}, + {0x529c, 0x00, 0, 0}, {0x529d, 0x02, 0, 0}, {0x529e, 0x00, 0, 0}, + {0x529f, 0x02, 0, 0}, {0x3a0f, 0x3c, 0, 0}, {0x3a10, 0x30, 0, 0}, + {0x3a1b, 0x3c, 0, 0}, {0x3a1e, 0x30, 0, 0}, {0x3a11, 0x70, 0, 0}, + {0x3a1f, 0x10, 0, 0}, {0x3030, 0x2b, 0, 0}, {0x3a02, 0x00, 0, 0}, + {0x3a03, 0x7d, 0, 0}, {0x3a04, 0x00, 0, 0}, {0x3a14, 0x00, 0, 0}, + {0x3a15, 0x7d, 0, 0}, {0x3a16, 0x00, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a08, 0x12, 0, 0}, {0x3a09, 0xc0, 0, 0}, {0x3a0a, 0x0f, 0, 0}, + {0x3a0b, 0xa0, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x589b, 0x04, 0, 0}, {0x589a, 0xc5, 0, 0}, + {0x401e, 0x20, 0, 0}, {0x4001, 0x42, 0, 0}, {0x401c, 0x04, 0, 0}, + {0x528a, 0x01, 0, 0}, {0x528b, 0x04, 0, 0}, {0x528c, 0x08, 0, 0}, + {0x528d, 0x10, 0, 0}, {0x528e, 0x20, 0, 0}, {0x528f, 0x28, 0, 0}, + {0x5290, 0x30, 0, 0}, {0x5292, 0x00, 0, 0}, {0x5293, 0x01, 0, 0}, + {0x5294, 0x00, 0, 0}, {0x5295, 0x04, 0, 0}, {0x5296, 0x00, 0, 0}, + {0x5297, 0x08, 0, 0}, {0x5298, 0x00, 0, 0}, {0x5299, 0x10, 0, 0}, + {0x529a, 0x00, 0, 0}, {0x529b, 0x20, 0, 0}, {0x529c, 0x00, 0, 0}, + {0x529d, 0x28, 0, 0}, {0x529e, 0x00, 0, 0}, {0x529f, 0x30, 0, 0}, + {0x5282, 0x00, 0, 0}, {0x5300, 0x00, 0, 0}, {0x5301, 0x20, 0, 0}, + {0x5302, 0x00, 0, 0}, {0x5303, 0x7c, 0, 0}, {0x530c, 0x00, 0, 0}, + {0x530d, 0x0c, 0, 0}, {0x530e, 0x20, 0, 0}, {0x530f, 0x80, 0, 0}, + {0x5310, 0x20, 0, 0}, {0x5311, 0x80, 0, 0}, {0x5308, 0x20, 0, 0}, + {0x5309, 0x40, 0, 0}, {0x5304, 0x00, 0, 0}, {0x5305, 0x30, 0, 0}, + {0x5306, 0x00, 0, 0}, {0x5307, 0x80, 0, 0}, {0x5314, 0x08, 0, 0}, + {0x5315, 0x20, 0, 0}, {0x5319, 0x30, 0, 0}, {0x5316, 0x10, 0, 0}, + {0x5317, 0x00, 0, 0}, {0x5318, 0x02, 0, 0}, {0x5380, 0x01, 0, 0}, + {0x5381, 0x00, 0, 0}, {0x5382, 0x00, 0, 0}, {0x5383, 0x4e, 0, 0}, + {0x5384, 0x00, 0, 0}, {0x5385, 0x0f, 0, 0}, {0x5386, 0x00, 0, 0}, + {0x5387, 0x00, 0, 0}, {0x5388, 0x01, 0, 0}, {0x5389, 0x15, 0, 0}, + {0x538a, 0x00, 0, 0}, {0x538b, 0x31, 0, 0}, {0x538c, 0x00, 0, 0}, + {0x538d, 0x00, 0, 0}, {0x538e, 0x00, 0, 0}, {0x538f, 0x0f, 0, 0}, + {0x5390, 0x00, 0, 0}, {0x5391, 0xab, 0, 0}, {0x5392, 0x00, 0, 0}, + {0x5393, 0xa2, 0, 0}, {0x5394, 0x08, 0, 0}, {0x5480, 0x14, 0, 0}, + {0x5481, 0x21, 0, 0}, {0x5482, 0x36, 0, 0}, {0x5483, 0x57, 0, 0}, + {0x5484, 0x65, 0, 0}, {0x5485, 0x71, 0, 0}, {0x5486, 0x7d, 0, 0}, + {0x5487, 0x87, 0, 0}, {0x5488, 0x91, 0, 0}, {0x5489, 0x9a, 0, 0}, + {0x548a, 0xaa, 0, 0}, {0x548b, 0xb8, 0, 0}, {0x548c, 0xcd, 0, 0}, + {0x548d, 0xdd, 0, 0}, {0x548e, 0xea, 0, 0}, {0x548f, 0x1d, 0, 0}, + {0x5490, 0x05, 0, 0}, {0x5491, 0x00, 0, 0}, {0x5492, 0x04, 0, 0}, + {0x5493, 0x20, 0, 0}, {0x5494, 0x03, 0, 0}, {0x5495, 0x60, 0, 0}, + {0x5496, 0x02, 0, 0}, {0x5497, 0xb8, 0, 0}, {0x5498, 0x02, 0, 0}, + {0x5499, 0x86, 0, 0}, {0x549a, 0x02, 0, 0}, {0x549b, 0x5b, 0, 0}, + {0x549c, 0x02, 0, 0}, {0x549d, 0x3b, 0, 0}, {0x549e, 0x02, 0, 0}, + {0x549f, 0x1c, 0, 0}, {0x54a0, 0x02, 0, 0}, {0x54a1, 0x04, 0, 0}, + {0x54a2, 0x01, 0, 0}, {0x54a3, 0xed, 0, 0}, {0x54a4, 0x01, 0, 0}, + {0x54a5, 0xc5, 0, 0}, {0x54a6, 0x01, 0, 0}, {0x54a7, 0xa5, 0, 0}, + {0x54a8, 0x01, 0, 0}, {0x54a9, 0x6c, 0, 0}, {0x54aa, 0x01, 0, 0}, + {0x54ab, 0x41, 0, 0}, {0x54ac, 0x01, 0, 0}, {0x54ad, 0x20, 0, 0}, + {0x54ae, 0x00, 0, 0}, {0x54af, 0x16, 0, 0}, {0x54b0, 0x01, 0, 0}, + {0x54b1, 0x20, 0, 0}, {0x54b2, 0x00, 0, 0}, {0x54b3, 0x10, 0, 0}, + {0x54b4, 0x00, 0, 0}, {0x54b5, 0xf0, 0, 0}, {0x54b6, 0x00, 0, 0}, + {0x54b7, 0xdf, 0, 0}, {0x5402, 0x3f, 0, 0}, {0x5403, 0x00, 0, 0}, + {0x3406, 0x00, 0, 0}, {0x5180, 0xff, 0, 0}, {0x5181, 0x52, 0, 0}, + {0x5182, 0x11, 0, 0}, {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, + {0x5185, 0x24, 0, 0}, {0x5186, 0x06, 0, 0}, {0x5187, 0x08, 0, 0}, + {0x5188, 0x08, 0, 0}, {0x5189, 0x7c, 0, 0}, {0x518a, 0x60, 0, 0}, + {0x518b, 0xb2, 0, 0}, {0x518c, 0xb2, 0, 0}, {0x518d, 0x44, 0, 0}, + {0x518e, 0x3d, 0, 0}, {0x518f, 0x58, 0, 0}, {0x5190, 0x46, 0, 0}, + {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, + {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, + {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, + {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, + {0x519d, 0x82, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5025, 0x80, 0, 0}, + {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0}, {0x3a1b, 0x3a, 0, 0}, + {0x3a1e, 0x2e, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x10, 0, 0}, + {0x5688, 0xa6, 0, 0}, {0x5689, 0x6a, 0, 0}, {0x568a, 0xea, 0, 0}, + {0x568b, 0xae, 0, 0}, {0x568c, 0xa6, 0, 0}, {0x568d, 0x6a, 0, 0}, + {0x568e, 0x62, 0, 0}, {0x568f, 0x26, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x40, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5000, 0xcf, 0, 0}, + {0x5800, 0x27, 0, 0}, {0x5801, 0x19, 0, 0}, {0x5802, 0x12, 0, 0}, + {0x5803, 0x0f, 0, 0}, {0x5804, 0x10, 0, 0}, {0x5805, 0x15, 0, 0}, + {0x5806, 0x1e, 0, 0}, {0x5807, 0x2f, 0, 0}, {0x5808, 0x15, 0, 0}, + {0x5809, 0x0d, 0, 0}, {0x580a, 0x0a, 0, 0}, {0x580b, 0x09, 0, 0}, + {0x580c, 0x0a, 0, 0}, {0x580d, 0x0c, 0, 0}, {0x580e, 0x12, 0, 0}, + {0x580f, 0x19, 0, 0}, {0x5810, 0x0b, 0, 0}, {0x5811, 0x07, 0, 0}, + {0x5812, 0x04, 0, 0}, {0x5813, 0x03, 0, 0}, {0x5814, 0x03, 0, 0}, + {0x5815, 0x06, 0, 0}, {0x5816, 0x0a, 0, 0}, {0x5817, 0x0f, 0, 0}, + {0x5818, 0x0a, 0, 0}, {0x5819, 0x05, 0, 0}, {0x581a, 0x01, 0, 0}, + {0x581b, 0x00, 0, 0}, {0x581c, 0x00, 0, 0}, {0x581d, 0x03, 0, 0}, + {0x581e, 0x08, 0, 0}, {0x581f, 0x0c, 0, 0}, {0x5820, 0x0a, 0, 0}, + {0x5821, 0x05, 0, 0}, {0x5822, 0x01, 0, 0}, {0x5823, 0x00, 0, 0}, + {0x5824, 0x00, 0, 0}, {0x5825, 0x03, 0, 0}, {0x5826, 0x08, 0, 0}, + {0x5827, 0x0c, 0, 0}, {0x5828, 0x0e, 0, 0}, {0x5829, 0x08, 0, 0}, + {0x582a, 0x06, 0, 0}, {0x582b, 0x04, 0, 0}, {0x582c, 0x05, 0, 0}, + {0x582d, 0x07, 0, 0}, {0x582e, 0x0b, 0, 0}, {0x582f, 0x12, 0, 0}, + {0x5830, 0x18, 0, 0}, {0x5831, 0x10, 0, 0}, {0x5832, 0x0c, 0, 0}, + {0x5833, 0x0a, 0, 0}, {0x5834, 0x0b, 0, 0}, {0x5835, 0x0e, 0, 0}, + {0x5836, 0x15, 0, 0}, {0x5837, 0x19, 0, 0}, {0x5838, 0x32, 0, 0}, + {0x5839, 0x1f, 0, 0}, {0x583a, 0x18, 0, 0}, {0x583b, 0x16, 0, 0}, + {0x583c, 0x17, 0, 0}, {0x583d, 0x1e, 0, 0}, {0x583e, 0x26, 0, 0}, + {0x583f, 0x53, 0, 0}, {0x5840, 0x10, 0, 0}, {0x5841, 0x0f, 0, 0}, + {0x5842, 0x0d, 0, 0}, {0x5843, 0x0c, 0, 0}, {0x5844, 0x0e, 0, 0}, + {0x5845, 0x09, 0, 0}, {0x5846, 0x11, 0, 0}, {0x5847, 0x10, 0, 0}, + {0x5848, 0x10, 0, 0}, {0x5849, 0x10, 0, 0}, {0x584a, 0x10, 0, 0}, + {0x584b, 0x0e, 0, 0}, {0x584c, 0x10, 0, 0}, {0x584d, 0x10, 0, 0}, + {0x584e, 0x11, 0, 0}, {0x584f, 0x10, 0, 0}, {0x5850, 0x0f, 0, 0}, + {0x5851, 0x0c, 0, 0}, {0x5852, 0x0f, 0, 0}, {0x5853, 0x10, 0, 0}, + {0x5854, 0x10, 0, 0}, {0x5855, 0x0f, 0, 0}, {0x5856, 0x0e, 0, 0}, + {0x5857, 0x0b, 0, 0}, {0x5858, 0x10, 0, 0}, {0x5859, 0x0d, 0, 0}, + {0x585a, 0x0d, 0, 0}, {0x585b, 0x0c, 0, 0}, {0x585c, 0x0c, 0, 0}, + {0x585d, 0x0c, 0, 0}, {0x585e, 0x0b, 0, 0}, {0x585f, 0x0c, 0, 0}, + {0x5860, 0x0c, 0, 0}, {0x5861, 0x0c, 0, 0}, {0x5862, 0x0d, 0, 0}, + {0x5863, 0x08, 0, 0}, {0x5864, 0x11, 0, 0}, {0x5865, 0x18, 0, 0}, + {0x5866, 0x18, 0, 0}, {0x5867, 0x19, 0, 0}, {0x5868, 0x17, 0, 0}, + {0x5869, 0x19, 0, 0}, {0x586a, 0x16, 0, 0}, {0x586b, 0x13, 0, 0}, + {0x586c, 0x13, 0, 0}, {0x586d, 0x12, 0, 0}, {0x586e, 0x13, 0, 0}, + {0x586f, 0x16, 0, 0}, {0x5870, 0x14, 0, 0}, {0x5871, 0x12, 0, 0}, + {0x5872, 0x10, 0, 0}, {0x5873, 0x11, 0, 0}, {0x5874, 0x11, 0, 0}, + {0x5875, 0x16, 0, 0}, {0x5876, 0x14, 0, 0}, {0x5877, 0x11, 0, 0}, + {0x5878, 0x10, 0, 0}, {0x5879, 0x0f, 0, 0}, {0x587a, 0x10, 0, 0}, + {0x587b, 0x14, 0, 0}, {0x587c, 0x13, 0, 0}, {0x587d, 0x12, 0, 0}, + {0x587e, 0x11, 0, 0}, {0x587f, 0x11, 0, 0}, {0x5880, 0x12, 0, 0}, + {0x5881, 0x15, 0, 0}, {0x5882, 0x14, 0, 0}, {0x5883, 0x15, 0, 0}, + {0x5884, 0x15, 0, 0}, {0x5885, 0x15, 0, 0}, {0x5886, 0x13, 0, 0}, + {0x5887, 0x17, 0, 0}, {0x3710, 0x10, 0, 0}, {0x3632, 0x51, 0, 0}, + {0x3702, 0x10, 0, 0}, {0x3703, 0xb2, 0, 0}, {0x3704, 0x18, 0, 0}, + {0x370b, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x3631, 0x01, 0, 0}, + {0x3632, 0x52, 0, 0}, {0x3606, 0x24, 0, 0}, {0x3620, 0x96, 0, 0}, + {0x5785, 0x07, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3600, 0x52, 0, 0}, + {0x3604, 0x48, 0, 0}, {0x3606, 0x1b, 0, 0}, {0x370d, 0x0b, 0, 0}, + {0x370f, 0xc0, 0, 0}, {0x3709, 0x01, 0, 0}, {0x3823, 0x00, 0, 0}, + {0x5007, 0x00, 0, 0}, {0x5009, 0x00, 0, 0}, {0x5011, 0x00, 0, 0}, + {0x5013, 0x00, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5086, 0x00, 0, 0}, + {0x5087, 0x00, 0, 0}, {0x5088, 0x00, 0, 0}, {0x5089, 0x00, 0, 0}, + {0x302b, 0x00, 0, 0}, {0x3621, 0x87, 0, 0}, {0x3808, 0x01, 0, 0}, + {0x3809, 0x40, 0, 0}, {0x380a, 0x00, 0, 0}, {0x380b, 0xf0, 0, 0}, +}; + +static struct reg_value ov5642_setting_30fps_NTSC_720_480[] = { + {0x3103, 0x93, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3017, 0x7f, 0, 0}, + {0x3018, 0xfc, 0, 0}, {0x3615, 0xf0, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3001, 0x00, 0, 0}, {0x3002, 0x5c, 0, 0}, {0x3003, 0x00, 0, 0}, + {0x3004, 0xff, 0, 0}, {0x3005, 0xff, 0, 0}, {0x3006, 0x43, 0, 0}, + {0x3007, 0x37, 0, 0}, {0x3011, 0x09, 0, 0}, {0x3012, 0x02, 0, 0}, + {0x3010, 0x00, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3815, 0x04, 0, 0}, + {0x370c, 0xa0, 0, 0}, {0x3602, 0xfc, 0, 0}, {0x3612, 0xff, 0, 0}, + {0x3634, 0xc0, 0, 0}, {0x3613, 0x00, 0, 0}, {0x3605, 0x7c, 0, 0}, + {0x3621, 0x09, 0, 0}, {0x3622, 0x60, 0, 0}, {0x3604, 0x40, 0, 0}, + {0x3603, 0xa7, 0, 0}, {0x3603, 0x27, 0, 0}, {0x4000, 0x21, 0, 0}, + {0x401d, 0x22, 0, 0}, {0x3600, 0x54, 0, 0}, {0x3605, 0x04, 0, 0}, + {0x3606, 0x3f, 0, 0}, {0x3c01, 0x80, 0, 0}, {0x5000, 0x4f, 0, 0}, + {0x5020, 0x04, 0, 0}, {0x5181, 0x79, 0, 0}, {0x5182, 0x00, 0, 0}, + {0x5185, 0x22, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5001, 0xff, 0, 0}, + {0x5500, 0x0a, 0, 0}, {0x5504, 0x00, 0, 0}, {0x5505, 0x7f, 0, 0}, + {0x5080, 0x08, 0, 0}, {0x300e, 0x18, 0, 0}, {0x4610, 0x00, 0, 0}, + {0x471d, 0x05, 0, 0}, {0x4708, 0x06, 0, 0}, {0x3808, 0x02, 0, 0}, + {0x3809, 0xd0, 0, 0}, {0x380a, 0x01, 0, 0}, {0x380b, 0xe0, 0, 0}, + {0x380e, 0x07, 0, 0}, {0x380f, 0xd0, 0, 0}, {0x501f, 0x00, 0, 0}, + {0x5000, 0x4f, 0, 0}, {0x4300, 0x30, 0, 0}, {0x3503, 0x07, 0, 0}, + {0x3501, 0x73, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x07, 0, 0}, {0x3824, 0x11, 0, 0}, {0x3825, 0xb0, 0, 0}, + {0x3501, 0x1e, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x7f, 0, 0}, + {0x380c, 0x07, 0, 0}, {0x380d, 0x2a, 0, 0}, {0x380e, 0x03, 0, 0}, + {0x380f, 0xe8, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3818, 0xc1, 0, 0}, {0x3705, 0xdb, 0, 0}, {0x370a, 0x81, 0, 0}, + {0x3801, 0x80, 0, 0}, {0x3621, 0xc7, 0, 0}, {0x3801, 0x50, 0, 0}, + {0x3803, 0x08, 0, 0}, {0x3827, 0x3c, 0, 0}, {0x3810, 0x80, 0, 0}, + {0x3804, 0x05, 0, 0}, {0x3805, 0x00, 0, 0}, {0x5682, 0x05, 0, 0}, + {0x5683, 0x00, 0, 0}, {0x3806, 0x03, 0, 0}, {0x3807, 0x58, 0, 0}, + {0x5686, 0x03, 0, 0}, {0x5687, 0x58, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a1a, 0x05, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3a18, 0x00, 0, 0}, + {0x3a19, 0x7c, 0, 0}, {0x3a08, 0x12, 0, 0}, {0x3a09, 0xc0, 0, 0}, + {0x3a0a, 0x0f, 0, 0}, {0x3a0b, 0xa0, 0, 0}, {0x350c, 0x07, 0, 0}, + {0x350d, 0xd0, 0, 0}, {0x3500, 0x00, 0, 0}, {0x3501, 0x00, 0, 0}, + {0x3502, 0x00, 0, 0}, {0x350a, 0x00, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x00, 0, 0}, {0x528a, 0x02, 0, 0}, {0x528b, 0x04, 0, 0}, + {0x528c, 0x08, 0, 0}, {0x528d, 0x08, 0, 0}, {0x528e, 0x08, 0, 0}, + {0x528f, 0x10, 0, 0}, {0x5290, 0x10, 0, 0}, {0x5292, 0x00, 0, 0}, + {0x5293, 0x02, 0, 0}, {0x5294, 0x00, 0, 0}, {0x5295, 0x02, 0, 0}, + {0x5296, 0x00, 0, 0}, {0x5297, 0x02, 0, 0}, {0x5298, 0x00, 0, 0}, + {0x5299, 0x02, 0, 0}, {0x529a, 0x00, 0, 0}, {0x529b, 0x02, 0, 0}, + {0x529c, 0x00, 0, 0}, {0x529d, 0x02, 0, 0}, {0x529e, 0x00, 0, 0}, + {0x529f, 0x02, 0, 0}, {0x3a0f, 0x3c, 0, 0}, {0x3a10, 0x30, 0, 0}, + {0x3a1b, 0x3c, 0, 0}, {0x3a1e, 0x30, 0, 0}, {0x3a11, 0x70, 0, 0}, + {0x3a1f, 0x10, 0, 0}, {0x3030, 0x2b, 0, 0}, {0x3a02, 0x00, 0, 0}, + {0x3a03, 0x7d, 0, 0}, {0x3a04, 0x00, 0, 0}, {0x3a14, 0x00, 0, 0}, + {0x3a15, 0x7d, 0, 0}, {0x3a16, 0x00, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a08, 0x12, 0, 0}, {0x3a09, 0xc0, 0, 0}, {0x3a0a, 0x0f, 0, 0}, + {0x3a0b, 0xa0, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x589b, 0x04, 0, 0}, {0x589a, 0xc5, 0, 0}, + {0x401e, 0x20, 0, 0}, {0x4001, 0x42, 0, 0}, {0x401c, 0x04, 0, 0}, + {0x528a, 0x01, 0, 0}, {0x528b, 0x04, 0, 0}, {0x528c, 0x08, 0, 0}, + {0x528d, 0x10, 0, 0}, {0x528e, 0x20, 0, 0}, {0x528f, 0x28, 0, 0}, + {0x5290, 0x30, 0, 0}, {0x5292, 0x00, 0, 0}, {0x5293, 0x01, 0, 0}, + {0x5294, 0x00, 0, 0}, {0x5295, 0x04, 0, 0}, {0x5296, 0x00, 0, 0}, + {0x5297, 0x08, 0, 0}, {0x5298, 0x00, 0, 0}, {0x5299, 0x10, 0, 0}, + {0x529a, 0x00, 0, 0}, {0x529b, 0x20, 0, 0}, {0x529c, 0x00, 0, 0}, + {0x529d, 0x28, 0, 0}, {0x529e, 0x00, 0, 0}, {0x529f, 0x30, 0, 0}, + {0x5282, 0x00, 0, 0}, {0x5300, 0x00, 0, 0}, {0x5301, 0x20, 0, 0}, + {0x5302, 0x00, 0, 0}, {0x5303, 0x7c, 0, 0}, {0x530c, 0x00, 0, 0}, + {0x530d, 0x0c, 0, 0}, {0x530e, 0x20, 0, 0}, {0x530f, 0x80, 0, 0}, + {0x5310, 0x20, 0, 0}, {0x5311, 0x80, 0, 0}, {0x5308, 0x20, 0, 0}, + {0x5309, 0x40, 0, 0}, {0x5304, 0x00, 0, 0}, {0x5305, 0x30, 0, 0}, + {0x5306, 0x00, 0, 0}, {0x5307, 0x80, 0, 0}, {0x5314, 0x08, 0, 0}, + {0x5315, 0x20, 0, 0}, {0x5319, 0x30, 0, 0}, {0x5316, 0x10, 0, 0}, + {0x5317, 0x00, 0, 0}, {0x5318, 0x02, 0, 0}, {0x5380, 0x01, 0, 0}, + {0x5381, 0x00, 0, 0}, {0x5382, 0x00, 0, 0}, {0x5383, 0x4e, 0, 0}, + {0x5384, 0x00, 0, 0}, {0x5385, 0x0f, 0, 0}, {0x5386, 0x00, 0, 0}, + {0x5387, 0x00, 0, 0}, {0x5388, 0x01, 0, 0}, {0x5389, 0x15, 0, 0}, + {0x538a, 0x00, 0, 0}, {0x538b, 0x31, 0, 0}, {0x538c, 0x00, 0, 0}, + {0x538d, 0x00, 0, 0}, {0x538e, 0x00, 0, 0}, {0x538f, 0x0f, 0, 0}, + {0x5390, 0x00, 0, 0}, {0x5391, 0xab, 0, 0}, {0x5392, 0x00, 0, 0}, + {0x5393, 0xa2, 0, 0}, {0x5394, 0x08, 0, 0}, {0x5480, 0x14, 0, 0}, + {0x5481, 0x21, 0, 0}, {0x5482, 0x36, 0, 0}, {0x5483, 0x57, 0, 0}, + {0x5484, 0x65, 0, 0}, {0x5485, 0x71, 0, 0}, {0x5486, 0x7d, 0, 0}, + {0x5487, 0x87, 0, 0}, {0x5488, 0x91, 0, 0}, {0x5489, 0x9a, 0, 0}, + {0x548a, 0xaa, 0, 0}, {0x548b, 0xb8, 0, 0}, {0x548c, 0xcd, 0, 0}, + {0x548d, 0xdd, 0, 0}, {0x548e, 0xea, 0, 0}, {0x548f, 0x1d, 0, 0}, + {0x5490, 0x05, 0, 0}, {0x5491, 0x00, 0, 0}, {0x5492, 0x04, 0, 0}, + {0x5493, 0x20, 0, 0}, {0x5494, 0x03, 0, 0}, {0x5495, 0x60, 0, 0}, + {0x5496, 0x02, 0, 0}, {0x5497, 0xb8, 0, 0}, {0x5498, 0x02, 0, 0}, + {0x5499, 0x86, 0, 0}, {0x549a, 0x02, 0, 0}, {0x549b, 0x5b, 0, 0}, + {0x549c, 0x02, 0, 0}, {0x549d, 0x3b, 0, 0}, {0x549e, 0x02, 0, 0}, + {0x549f, 0x1c, 0, 0}, {0x54a0, 0x02, 0, 0}, {0x54a1, 0x04, 0, 0}, + {0x54a2, 0x01, 0, 0}, {0x54a3, 0xed, 0, 0}, {0x54a4, 0x01, 0, 0}, + {0x54a5, 0xc5, 0, 0}, {0x54a6, 0x01, 0, 0}, {0x54a7, 0xa5, 0, 0}, + {0x54a8, 0x01, 0, 0}, {0x54a9, 0x6c, 0, 0}, {0x54aa, 0x01, 0, 0}, + {0x54ab, 0x41, 0, 0}, {0x54ac, 0x01, 0, 0}, {0x54ad, 0x20, 0, 0}, + {0x54ae, 0x00, 0, 0}, {0x54af, 0x16, 0, 0}, {0x54b0, 0x01, 0, 0}, + {0x54b1, 0x20, 0, 0}, {0x54b2, 0x00, 0, 0}, {0x54b3, 0x10, 0, 0}, + {0x54b4, 0x00, 0, 0}, {0x54b5, 0xf0, 0, 0}, {0x54b6, 0x00, 0, 0}, + {0x54b7, 0xdf, 0, 0}, {0x5402, 0x3f, 0, 0}, {0x5403, 0x00, 0, 0}, + {0x3406, 0x00, 0, 0}, {0x5180, 0xff, 0, 0}, {0x5181, 0x52, 0, 0}, + {0x5182, 0x11, 0, 0}, {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, + {0x5185, 0x24, 0, 0}, {0x5186, 0x06, 0, 0}, {0x5187, 0x08, 0, 0}, + {0x5188, 0x08, 0, 0}, {0x5189, 0x7c, 0, 0}, {0x518a, 0x60, 0, 0}, + {0x518b, 0xb2, 0, 0}, {0x518c, 0xb2, 0, 0}, {0x518d, 0x44, 0, 0}, + {0x518e, 0x3d, 0, 0}, {0x518f, 0x58, 0, 0}, {0x5190, 0x46, 0, 0}, + {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, + {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, + {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, + {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, + {0x519d, 0x82, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5025, 0x80, 0, 0}, + {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0}, {0x3a1b, 0x3a, 0, 0}, + {0x3a1e, 0x2e, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x10, 0, 0}, + {0x5688, 0xa6, 0, 0}, {0x5689, 0x6a, 0, 0}, {0x568a, 0xea, 0, 0}, + {0x568b, 0xae, 0, 0}, {0x568c, 0xa6, 0, 0}, {0x568d, 0x6a, 0, 0}, + {0x568e, 0x62, 0, 0}, {0x568f, 0x26, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x40, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5000, 0xcf, 0, 0}, + {0x5800, 0x27, 0, 0}, {0x5801, 0x19, 0, 0}, {0x5802, 0x12, 0, 0}, + {0x5803, 0x0f, 0, 0}, {0x5804, 0x10, 0, 0}, {0x5805, 0x15, 0, 0}, + {0x5806, 0x1e, 0, 0}, {0x5807, 0x2f, 0, 0}, {0x5808, 0x15, 0, 0}, + {0x5809, 0x0d, 0, 0}, {0x580a, 0x0a, 0, 0}, {0x580b, 0x09, 0, 0}, + {0x580c, 0x0a, 0, 0}, {0x580d, 0x0c, 0, 0}, {0x580e, 0x12, 0, 0}, + {0x580f, 0x19, 0, 0}, {0x5810, 0x0b, 0, 0}, {0x5811, 0x07, 0, 0}, + {0x5812, 0x04, 0, 0}, {0x5813, 0x03, 0, 0}, {0x5814, 0x03, 0, 0}, + {0x5815, 0x06, 0, 0}, {0x5816, 0x0a, 0, 0}, {0x5817, 0x0f, 0, 0}, + {0x5818, 0x0a, 0, 0}, {0x5819, 0x05, 0, 0}, {0x581a, 0x01, 0, 0}, + {0x581b, 0x00, 0, 0}, {0x581c, 0x00, 0, 0}, {0x581d, 0x03, 0, 0}, + {0x581e, 0x08, 0, 0}, {0x581f, 0x0c, 0, 0}, {0x5820, 0x0a, 0, 0}, + {0x5821, 0x05, 0, 0}, {0x5822, 0x01, 0, 0}, {0x5823, 0x00, 0, 0}, + {0x5824, 0x00, 0, 0}, {0x5825, 0x03, 0, 0}, {0x5826, 0x08, 0, 0}, + {0x5827, 0x0c, 0, 0}, {0x5828, 0x0e, 0, 0}, {0x5829, 0x08, 0, 0}, + {0x582a, 0x06, 0, 0}, {0x582b, 0x04, 0, 0}, {0x582c, 0x05, 0, 0}, + {0x582d, 0x07, 0, 0}, {0x582e, 0x0b, 0, 0}, {0x582f, 0x12, 0, 0}, + {0x5830, 0x18, 0, 0}, {0x5831, 0x10, 0, 0}, {0x5832, 0x0c, 0, 0}, + {0x5833, 0x0a, 0, 0}, {0x5834, 0x0b, 0, 0}, {0x5835, 0x0e, 0, 0}, + {0x5836, 0x15, 0, 0}, {0x5837, 0x19, 0, 0}, {0x5838, 0x32, 0, 0}, + {0x5839, 0x1f, 0, 0}, {0x583a, 0x18, 0, 0}, {0x583b, 0x16, 0, 0}, + {0x583c, 0x17, 0, 0}, {0x583d, 0x1e, 0, 0}, {0x583e, 0x26, 0, 0}, + {0x583f, 0x53, 0, 0}, {0x5840, 0x10, 0, 0}, {0x5841, 0x0f, 0, 0}, + {0x5842, 0x0d, 0, 0}, {0x5843, 0x0c, 0, 0}, {0x5844, 0x0e, 0, 0}, + {0x5845, 0x09, 0, 0}, {0x5846, 0x11, 0, 0}, {0x5847, 0x10, 0, 0}, + {0x5848, 0x10, 0, 0}, {0x5849, 0x10, 0, 0}, {0x584a, 0x10, 0, 0}, + {0x584b, 0x0e, 0, 0}, {0x584c, 0x10, 0, 0}, {0x584d, 0x10, 0, 0}, + {0x584e, 0x11, 0, 0}, {0x584f, 0x10, 0, 0}, {0x5850, 0x0f, 0, 0}, + {0x5851, 0x0c, 0, 0}, {0x5852, 0x0f, 0, 0}, {0x5853, 0x10, 0, 0}, + {0x5854, 0x10, 0, 0}, {0x5855, 0x0f, 0, 0}, {0x5856, 0x0e, 0, 0}, + {0x5857, 0x0b, 0, 0}, {0x5858, 0x10, 0, 0}, {0x5859, 0x0d, 0, 0}, + {0x585a, 0x0d, 0, 0}, {0x585b, 0x0c, 0, 0}, {0x585c, 0x0c, 0, 0}, + {0x585d, 0x0c, 0, 0}, {0x585e, 0x0b, 0, 0}, {0x585f, 0x0c, 0, 0}, + {0x5860, 0x0c, 0, 0}, {0x5861, 0x0c, 0, 0}, {0x5862, 0x0d, 0, 0}, + {0x5863, 0x08, 0, 0}, {0x5864, 0x11, 0, 0}, {0x5865, 0x18, 0, 0}, + {0x5866, 0x18, 0, 0}, {0x5867, 0x19, 0, 0}, {0x5868, 0x17, 0, 0}, + {0x5869, 0x19, 0, 0}, {0x586a, 0x16, 0, 0}, {0x586b, 0x13, 0, 0}, + {0x586c, 0x13, 0, 0}, {0x586d, 0x12, 0, 0}, {0x586e, 0x13, 0, 0}, + {0x586f, 0x16, 0, 0}, {0x5870, 0x14, 0, 0}, {0x5871, 0x12, 0, 0}, + {0x5872, 0x10, 0, 0}, {0x5873, 0x11, 0, 0}, {0x5874, 0x11, 0, 0}, + {0x5875, 0x16, 0, 0}, {0x5876, 0x14, 0, 0}, {0x5877, 0x11, 0, 0}, + {0x5878, 0x10, 0, 0}, {0x5879, 0x0f, 0, 0}, {0x587a, 0x10, 0, 0}, + {0x587b, 0x14, 0, 0}, {0x587c, 0x13, 0, 0}, {0x587d, 0x12, 0, 0}, + {0x587e, 0x11, 0, 0}, {0x587f, 0x11, 0, 0}, {0x5880, 0x12, 0, 0}, + {0x5881, 0x15, 0, 0}, {0x5882, 0x14, 0, 0}, {0x5883, 0x15, 0, 0}, + {0x5884, 0x15, 0, 0}, {0x5885, 0x15, 0, 0}, {0x5886, 0x13, 0, 0}, + {0x5887, 0x17, 0, 0}, {0x3710, 0x10, 0, 0}, {0x3632, 0x51, 0, 0}, + {0x3702, 0x10, 0, 0}, {0x3703, 0xb2, 0, 0}, {0x3704, 0x18, 0, 0}, + {0x370b, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x3631, 0x01, 0, 0}, + {0x3632, 0x52, 0, 0}, {0x3606, 0x24, 0, 0}, {0x3620, 0x96, 0, 0}, + {0x5785, 0x07, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3600, 0x52, 0, 0}, + {0x3604, 0x48, 0, 0}, {0x3606, 0x1b, 0, 0}, {0x370d, 0x0b, 0, 0}, + {0x370f, 0xc0, 0, 0}, {0x3709, 0x01, 0, 0}, {0x3823, 0x00, 0, 0}, + {0x5007, 0x00, 0, 0}, {0x5009, 0x00, 0, 0}, {0x5011, 0x00, 0, 0}, + {0x5013, 0x00, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5086, 0x00, 0, 0}, + {0x5087, 0x00, 0, 0}, {0x5088, 0x00, 0, 0}, {0x5089, 0x00, 0, 0}, + {0x302b, 0x00, 0, 0}, {0x3621, 0x87, 0, 0}, {0x3a00, 0x78, 0, 0}, +}; + +static struct reg_value ov5642_setting_30fps_PAL_720_576[] = { + {0x3103, 0x93, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3017, 0x7f, 0, 0}, + {0x3018, 0xfc, 0, 0}, {0x3615, 0xf0, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3001, 0x00, 0, 0}, {0x3002, 0x5c, 0, 0}, {0x3003, 0x00, 0, 0}, + {0x3004, 0xff, 0, 0}, {0x3005, 0xff, 0, 0}, {0x3006, 0x43, 0, 0}, + {0x3007, 0x37, 0, 0}, {0x3011, 0x09, 0, 0}, {0x3012, 0x02, 0, 0}, + {0x3010, 0x00, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3815, 0x04, 0, 0}, + {0x370c, 0xa0, 0, 0}, {0x3602, 0xfc, 0, 0}, {0x3612, 0xff, 0, 0}, + {0x3634, 0xc0, 0, 0}, {0x3613, 0x00, 0, 0}, {0x3605, 0x7c, 0, 0}, + {0x3621, 0x09, 0, 0}, {0x3622, 0x60, 0, 0}, {0x3604, 0x40, 0, 0}, + {0x3603, 0xa7, 0, 0}, {0x3603, 0x27, 0, 0}, {0x4000, 0x21, 0, 0}, + {0x401d, 0x22, 0, 0}, {0x3600, 0x54, 0, 0}, {0x3605, 0x04, 0, 0}, + {0x3606, 0x3f, 0, 0}, {0x3c01, 0x80, 0, 0}, {0x5000, 0x4f, 0, 0}, + {0x5020, 0x04, 0, 0}, {0x5181, 0x79, 0, 0}, {0x5182, 0x00, 0, 0}, + {0x5185, 0x22, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5001, 0xff, 0, 0}, + {0x5500, 0x0a, 0, 0}, {0x5504, 0x00, 0, 0}, {0x5505, 0x7f, 0, 0}, + {0x5080, 0x08, 0, 0}, {0x300e, 0x18, 0, 0}, {0x4610, 0x00, 0, 0}, + {0x471d, 0x05, 0, 0}, {0x4708, 0x06, 0, 0}, {0x3808, 0x02, 0, 0}, + {0x3809, 0xd0, 0, 0}, {0x380a, 0x02, 0, 0}, {0x380b, 0x40, 0, 0}, + {0x380e, 0x07, 0, 0}, {0x380f, 0xd0, 0, 0}, {0x501f, 0x00, 0, 0}, + {0x5000, 0x4f, 0, 0}, {0x4300, 0x30, 0, 0}, {0x3503, 0x07, 0, 0}, + {0x3501, 0x73, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x07, 0, 0}, {0x3824, 0x11, 0, 0}, {0x3825, 0xd8, 0, 0}, + {0x3501, 0x1e, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x7f, 0, 0}, + {0x380c, 0x07, 0, 0}, {0x380d, 0x2a, 0, 0}, {0x380e, 0x03, 0, 0}, + {0x380f, 0xe8, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3818, 0xc1, 0, 0}, {0x3705, 0xdb, 0, 0}, {0x370a, 0x81, 0, 0}, + {0x3801, 0x80, 0, 0}, {0x3621, 0xc7, 0, 0}, {0x3801, 0x50, 0, 0}, + {0x3803, 0x08, 0, 0}, {0x3827, 0x3c, 0, 0}, {0x3810, 0x80, 0, 0}, + {0x3804, 0x04, 0, 0}, {0x3805, 0xb0, 0, 0}, {0x5682, 0x05, 0, 0}, + {0x5683, 0x00, 0, 0}, {0x3806, 0x03, 0, 0}, {0x3807, 0x58, 0, 0}, + {0x5686, 0x03, 0, 0}, {0x5687, 0xbc, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a1a, 0x05, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3a18, 0x00, 0, 0}, + {0x3a19, 0x7c, 0, 0}, {0x3a08, 0x12, 0, 0}, {0x3a09, 0xc0, 0, 0}, + {0x3a0a, 0x0f, 0, 0}, {0x3a0b, 0xa0, 0, 0}, {0x350c, 0x07, 0, 0}, + {0x350d, 0xd0, 0, 0}, {0x3500, 0x00, 0, 0}, {0x3501, 0x00, 0, 0}, + {0x3502, 0x00, 0, 0}, {0x350a, 0x00, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x00, 0, 0}, {0x528a, 0x02, 0, 0}, {0x528b, 0x04, 0, 0}, + {0x528c, 0x08, 0, 0}, {0x528d, 0x08, 0, 0}, {0x528e, 0x08, 0, 0}, + {0x528f, 0x10, 0, 0}, {0x5290, 0x10, 0, 0}, {0x5292, 0x00, 0, 0}, + {0x5293, 0x02, 0, 0}, {0x5294, 0x00, 0, 0}, {0x5295, 0x02, 0, 0}, + {0x5296, 0x00, 0, 0}, {0x5297, 0x02, 0, 0}, {0x5298, 0x00, 0, 0}, + {0x5299, 0x02, 0, 0}, {0x529a, 0x00, 0, 0}, {0x529b, 0x02, 0, 0}, + {0x529c, 0x00, 0, 0}, {0x529d, 0x02, 0, 0}, {0x529e, 0x00, 0, 0}, + {0x529f, 0x02, 0, 0}, {0x3a0f, 0x3c, 0, 0}, {0x3a10, 0x30, 0, 0}, + {0x3a1b, 0x3c, 0, 0}, {0x3a1e, 0x30, 0, 0}, {0x3a11, 0x70, 0, 0}, + {0x3a1f, 0x10, 0, 0}, {0x3030, 0x2b, 0, 0}, {0x3a02, 0x00, 0, 0}, + {0x3a03, 0x7d, 0, 0}, {0x3a04, 0x00, 0, 0}, {0x3a14, 0x00, 0, 0}, + {0x3a15, 0x7d, 0, 0}, {0x3a16, 0x00, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a08, 0x12, 0, 0}, {0x3a09, 0xc0, 0, 0}, {0x3a0a, 0x0f, 0, 0}, + {0x3a0b, 0xa0, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x589b, 0x04, 0, 0}, {0x589a, 0xc5, 0, 0}, + {0x401e, 0x20, 0, 0}, {0x4001, 0x42, 0, 0}, {0x401c, 0x04, 0, 0}, + {0x528a, 0x01, 0, 0}, {0x528b, 0x04, 0, 0}, {0x528c, 0x08, 0, 0}, + {0x528d, 0x10, 0, 0}, {0x528e, 0x20, 0, 0}, {0x528f, 0x28, 0, 0}, + {0x5290, 0x30, 0, 0}, {0x5292, 0x00, 0, 0}, {0x5293, 0x01, 0, 0}, + {0x5294, 0x00, 0, 0}, {0x5295, 0x04, 0, 0}, {0x5296, 0x00, 0, 0}, + {0x5297, 0x08, 0, 0}, {0x5298, 0x00, 0, 0}, {0x5299, 0x10, 0, 0}, + {0x529a, 0x00, 0, 0}, {0x529b, 0x20, 0, 0}, {0x529c, 0x00, 0, 0}, + {0x529d, 0x28, 0, 0}, {0x529e, 0x00, 0, 0}, {0x529f, 0x30, 0, 0}, + {0x5282, 0x00, 0, 0}, {0x5300, 0x00, 0, 0}, {0x5301, 0x20, 0, 0}, + {0x5302, 0x00, 0, 0}, {0x5303, 0x7c, 0, 0}, {0x530c, 0x00, 0, 0}, + {0x530d, 0x0c, 0, 0}, {0x530e, 0x20, 0, 0}, {0x530f, 0x80, 0, 0}, + {0x5310, 0x20, 0, 0}, {0x5311, 0x80, 0, 0}, {0x5308, 0x20, 0, 0}, + {0x5309, 0x40, 0, 0}, {0x5304, 0x00, 0, 0}, {0x5305, 0x30, 0, 0}, + {0x5306, 0x00, 0, 0}, {0x5307, 0x80, 0, 0}, {0x5314, 0x08, 0, 0}, + {0x5315, 0x20, 0, 0}, {0x5319, 0x30, 0, 0}, {0x5316, 0x10, 0, 0}, + {0x5317, 0x00, 0, 0}, {0x5318, 0x02, 0, 0}, {0x5380, 0x01, 0, 0}, + {0x5381, 0x00, 0, 0}, {0x5382, 0x00, 0, 0}, {0x5383, 0x4e, 0, 0}, + {0x5384, 0x00, 0, 0}, {0x5385, 0x0f, 0, 0}, {0x5386, 0x00, 0, 0}, + {0x5387, 0x00, 0, 0}, {0x5388, 0x01, 0, 0}, {0x5389, 0x15, 0, 0}, + {0x538a, 0x00, 0, 0}, {0x538b, 0x31, 0, 0}, {0x538c, 0x00, 0, 0}, + {0x538d, 0x00, 0, 0}, {0x538e, 0x00, 0, 0}, {0x538f, 0x0f, 0, 0}, + {0x5390, 0x00, 0, 0}, {0x5391, 0xab, 0, 0}, {0x5392, 0x00, 0, 0}, + {0x5393, 0xa2, 0, 0}, {0x5394, 0x08, 0, 0}, {0x5480, 0x14, 0, 0}, + {0x5481, 0x21, 0, 0}, {0x5482, 0x36, 0, 0}, {0x5483, 0x57, 0, 0}, + {0x5484, 0x65, 0, 0}, {0x5485, 0x71, 0, 0}, {0x5486, 0x7d, 0, 0}, + {0x5487, 0x87, 0, 0}, {0x5488, 0x91, 0, 0}, {0x5489, 0x9a, 0, 0}, + {0x548a, 0xaa, 0, 0}, {0x548b, 0xb8, 0, 0}, {0x548c, 0xcd, 0, 0}, + {0x548d, 0xdd, 0, 0}, {0x548e, 0xea, 0, 0}, {0x548f, 0x1d, 0, 0}, + {0x5490, 0x05, 0, 0}, {0x5491, 0x00, 0, 0}, {0x5492, 0x04, 0, 0}, + {0x5493, 0x20, 0, 0}, {0x5494, 0x03, 0, 0}, {0x5495, 0x60, 0, 0}, + {0x5496, 0x02, 0, 0}, {0x5497, 0xb8, 0, 0}, {0x5498, 0x02, 0, 0}, + {0x5499, 0x86, 0, 0}, {0x549a, 0x02, 0, 0}, {0x549b, 0x5b, 0, 0}, + {0x549c, 0x02, 0, 0}, {0x549d, 0x3b, 0, 0}, {0x549e, 0x02, 0, 0}, + {0x549f, 0x1c, 0, 0}, {0x54a0, 0x02, 0, 0}, {0x54a1, 0x04, 0, 0}, + {0x54a2, 0x01, 0, 0}, {0x54a3, 0xed, 0, 0}, {0x54a4, 0x01, 0, 0}, + {0x54a5, 0xc5, 0, 0}, {0x54a6, 0x01, 0, 0}, {0x54a7, 0xa5, 0, 0}, + {0x54a8, 0x01, 0, 0}, {0x54a9, 0x6c, 0, 0}, {0x54aa, 0x01, 0, 0}, + {0x54ab, 0x41, 0, 0}, {0x54ac, 0x01, 0, 0}, {0x54ad, 0x20, 0, 0}, + {0x54ae, 0x00, 0, 0}, {0x54af, 0x16, 0, 0}, {0x54b0, 0x01, 0, 0}, + {0x54b1, 0x20, 0, 0}, {0x54b2, 0x00, 0, 0}, {0x54b3, 0x10, 0, 0}, + {0x54b4, 0x00, 0, 0}, {0x54b5, 0xf0, 0, 0}, {0x54b6, 0x00, 0, 0}, + {0x54b7, 0xdf, 0, 0}, {0x5402, 0x3f, 0, 0}, {0x5403, 0x00, 0, 0}, + {0x3406, 0x00, 0, 0}, {0x5180, 0xff, 0, 0}, {0x5181, 0x52, 0, 0}, + {0x5182, 0x11, 0, 0}, {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, + {0x5185, 0x24, 0, 0}, {0x5186, 0x06, 0, 0}, {0x5187, 0x08, 0, 0}, + {0x5188, 0x08, 0, 0}, {0x5189, 0x7c, 0, 0}, {0x518a, 0x60, 0, 0}, + {0x518b, 0xb2, 0, 0}, {0x518c, 0xb2, 0, 0}, {0x518d, 0x44, 0, 0}, + {0x518e, 0x3d, 0, 0}, {0x518f, 0x58, 0, 0}, {0x5190, 0x46, 0, 0}, + {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, + {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, + {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, + {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, + {0x519d, 0x82, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5025, 0x80, 0, 0}, + {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0}, {0x3a1b, 0x3a, 0, 0}, + {0x3a1e, 0x2e, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x10, 0, 0}, + {0x5688, 0xa6, 0, 0}, {0x5689, 0x6a, 0, 0}, {0x568a, 0xea, 0, 0}, + {0x568b, 0xae, 0, 0}, {0x568c, 0xa6, 0, 0}, {0x568d, 0x6a, 0, 0}, + {0x568e, 0x62, 0, 0}, {0x568f, 0x26, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x40, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5000, 0xcf, 0, 0}, + {0x5800, 0x27, 0, 0}, {0x5801, 0x19, 0, 0}, {0x5802, 0x12, 0, 0}, + {0x5803, 0x0f, 0, 0}, {0x5804, 0x10, 0, 0}, {0x5805, 0x15, 0, 0}, + {0x5806, 0x1e, 0, 0}, {0x5807, 0x2f, 0, 0}, {0x5808, 0x15, 0, 0}, + {0x5809, 0x0d, 0, 0}, {0x580a, 0x0a, 0, 0}, {0x580b, 0x09, 0, 0}, + {0x580c, 0x0a, 0, 0}, {0x580d, 0x0c, 0, 0}, {0x580e, 0x12, 0, 0}, + {0x580f, 0x19, 0, 0}, {0x5810, 0x0b, 0, 0}, {0x5811, 0x07, 0, 0}, + {0x5812, 0x04, 0, 0}, {0x5813, 0x03, 0, 0}, {0x5814, 0x03, 0, 0}, + {0x5815, 0x06, 0, 0}, {0x5816, 0x0a, 0, 0}, {0x5817, 0x0f, 0, 0}, + {0x5818, 0x0a, 0, 0}, {0x5819, 0x05, 0, 0}, {0x581a, 0x01, 0, 0}, + {0x581b, 0x00, 0, 0}, {0x581c, 0x00, 0, 0}, {0x581d, 0x03, 0, 0}, + {0x581e, 0x08, 0, 0}, {0x581f, 0x0c, 0, 0}, {0x5820, 0x0a, 0, 0}, + {0x5821, 0x05, 0, 0}, {0x5822, 0x01, 0, 0}, {0x5823, 0x00, 0, 0}, + {0x5824, 0x00, 0, 0}, {0x5825, 0x03, 0, 0}, {0x5826, 0x08, 0, 0}, + {0x5827, 0x0c, 0, 0}, {0x5828, 0x0e, 0, 0}, {0x5829, 0x08, 0, 0}, + {0x582a, 0x06, 0, 0}, {0x582b, 0x04, 0, 0}, {0x582c, 0x05, 0, 0}, + {0x582d, 0x07, 0, 0}, {0x582e, 0x0b, 0, 0}, {0x582f, 0x12, 0, 0}, + {0x5830, 0x18, 0, 0}, {0x5831, 0x10, 0, 0}, {0x5832, 0x0c, 0, 0}, + {0x5833, 0x0a, 0, 0}, {0x5834, 0x0b, 0, 0}, {0x5835, 0x0e, 0, 0}, + {0x5836, 0x15, 0, 0}, {0x5837, 0x19, 0, 0}, {0x5838, 0x32, 0, 0}, + {0x5839, 0x1f, 0, 0}, {0x583a, 0x18, 0, 0}, {0x583b, 0x16, 0, 0}, + {0x583c, 0x17, 0, 0}, {0x583d, 0x1e, 0, 0}, {0x583e, 0x26, 0, 0}, + {0x583f, 0x53, 0, 0}, {0x5840, 0x10, 0, 0}, {0x5841, 0x0f, 0, 0}, + {0x5842, 0x0d, 0, 0}, {0x5843, 0x0c, 0, 0}, {0x5844, 0x0e, 0, 0}, + {0x5845, 0x09, 0, 0}, {0x5846, 0x11, 0, 0}, {0x5847, 0x10, 0, 0}, + {0x5848, 0x10, 0, 0}, {0x5849, 0x10, 0, 0}, {0x584a, 0x10, 0, 0}, + {0x584b, 0x0e, 0, 0}, {0x584c, 0x10, 0, 0}, {0x584d, 0x10, 0, 0}, + {0x584e, 0x11, 0, 0}, {0x584f, 0x10, 0, 0}, {0x5850, 0x0f, 0, 0}, + {0x5851, 0x0c, 0, 0}, {0x5852, 0x0f, 0, 0}, {0x5853, 0x10, 0, 0}, + {0x5854, 0x10, 0, 0}, {0x5855, 0x0f, 0, 0}, {0x5856, 0x0e, 0, 0}, + {0x5857, 0x0b, 0, 0}, {0x5858, 0x10, 0, 0}, {0x5859, 0x0d, 0, 0}, + {0x585a, 0x0d, 0, 0}, {0x585b, 0x0c, 0, 0}, {0x585c, 0x0c, 0, 0}, + {0x585d, 0x0c, 0, 0}, {0x585e, 0x0b, 0, 0}, {0x585f, 0x0c, 0, 0}, + {0x5860, 0x0c, 0, 0}, {0x5861, 0x0c, 0, 0}, {0x5862, 0x0d, 0, 0}, + {0x5863, 0x08, 0, 0}, {0x5864, 0x11, 0, 0}, {0x5865, 0x18, 0, 0}, + {0x5866, 0x18, 0, 0}, {0x5867, 0x19, 0, 0}, {0x5868, 0x17, 0, 0}, + {0x5869, 0x19, 0, 0}, {0x586a, 0x16, 0, 0}, {0x586b, 0x13, 0, 0}, + {0x586c, 0x13, 0, 0}, {0x586d, 0x12, 0, 0}, {0x586e, 0x13, 0, 0}, + {0x586f, 0x16, 0, 0}, {0x5870, 0x14, 0, 0}, {0x5871, 0x12, 0, 0}, + {0x5872, 0x10, 0, 0}, {0x5873, 0x11, 0, 0}, {0x5874, 0x11, 0, 0}, + {0x5875, 0x16, 0, 0}, {0x5876, 0x14, 0, 0}, {0x5877, 0x11, 0, 0}, + {0x5878, 0x10, 0, 0}, {0x5879, 0x0f, 0, 0}, {0x587a, 0x10, 0, 0}, + {0x587b, 0x14, 0, 0}, {0x587c, 0x13, 0, 0}, {0x587d, 0x12, 0, 0}, + {0x587e, 0x11, 0, 0}, {0x587f, 0x11, 0, 0}, {0x5880, 0x12, 0, 0}, + {0x5881, 0x15, 0, 0}, {0x5882, 0x14, 0, 0}, {0x5883, 0x15, 0, 0}, + {0x5884, 0x15, 0, 0}, {0x5885, 0x15, 0, 0}, {0x5886, 0x13, 0, 0}, + {0x5887, 0x17, 0, 0}, {0x3710, 0x10, 0, 0}, {0x3632, 0x51, 0, 0}, + {0x3702, 0x10, 0, 0}, {0x3703, 0xb2, 0, 0}, {0x3704, 0x18, 0, 0}, + {0x370b, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x3631, 0x01, 0, 0}, + {0x3632, 0x52, 0, 0}, {0x3606, 0x24, 0, 0}, {0x3620, 0x96, 0, 0}, + {0x5785, 0x07, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3600, 0x52, 0, 0}, + {0x3604, 0x48, 0, 0}, {0x3606, 0x1b, 0, 0}, {0x370d, 0x0b, 0, 0}, + {0x370f, 0xc0, 0, 0}, {0x3709, 0x01, 0, 0}, {0x3823, 0x00, 0, 0}, + {0x5007, 0x00, 0, 0}, {0x5009, 0x00, 0, 0}, {0x5011, 0x00, 0, 0}, + {0x5013, 0x00, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5086, 0x00, 0, 0}, + {0x5087, 0x00, 0, 0}, {0x5088, 0x00, 0, 0}, {0x5089, 0x00, 0, 0}, + {0x302b, 0x00, 0, 0}, {0x3621, 0x87, 0, 0}, {0x3a00, 0x78, 0, 0}, +}; + +static struct reg_value ov5642_setting_30fps_720P_1280_720[] = { + {0x3103, 0x93, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3017, 0x7f, 0, 0}, + {0x3018, 0xfc, 0, 0}, {0x3810, 0xc2, 0, 0}, {0x3615, 0xf0, 0, 0}, + {0x3000, 0x00, 0, 0}, {0x3001, 0x00, 0, 0}, {0x3002, 0x00, 0, 0}, + {0x3003, 0x00, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3030, 0x2b, 0, 0}, + {0x3011, 0x08, 0, 0}, {0x3010, 0x10, 0, 0}, {0x3604, 0x60, 0, 0}, + {0x3622, 0x60, 0, 0}, {0x3621, 0x09, 0, 0}, {0x3709, 0x00, 0, 0}, + {0x4000, 0x21, 0, 0}, {0x401d, 0x22, 0, 0}, {0x3600, 0x54, 0, 0}, + {0x3605, 0x04, 0, 0}, {0x3606, 0x3f, 0, 0}, {0x3c01, 0x80, 0, 0}, + {0x300d, 0x22, 0, 0}, {0x3623, 0x22, 0, 0}, {0x5000, 0x4f, 0, 0}, + {0x5020, 0x04, 0, 0}, {0x5181, 0x79, 0, 0}, {0x5182, 0x00, 0, 0}, + {0x5185, 0x22, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5500, 0x0a, 0, 0}, + {0x5504, 0x00, 0, 0}, {0x5505, 0x7f, 0, 0}, {0x5080, 0x08, 0, 0}, + {0x300e, 0x18, 0, 0}, {0x4610, 0x00, 0, 0}, {0x471d, 0x05, 0, 0}, + {0x4708, 0x06, 0, 0}, {0x370c, 0xa0, 0, 0}, {0x3808, 0x0a, 0, 0}, + {0x3809, 0x20, 0, 0}, {0x380a, 0x07, 0, 0}, {0x380b, 0x98, 0, 0}, + {0x380c, 0x0c, 0, 0}, {0x380d, 0x80, 0, 0}, {0x380e, 0x07, 0, 0}, + {0x380f, 0xd0, 0, 0}, {0x5687, 0x94, 0, 0}, {0x501f, 0x00, 0, 0}, + {0x5000, 0x4f, 0, 0}, {0x5001, 0xcf, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x4300, 0x30, 0, 0}, {0x460b, 0x35, 0, 0}, {0x471d, 0x00, 0, 0}, + {0x3002, 0x0c, 0, 0}, {0x3002, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x4721, 0x02, 0, 0}, {0x4402, 0x90, 0, 0}, + {0x460c, 0x22, 0, 0}, {0x3815, 0x44, 0, 0}, {0x3503, 0x07, 0, 0}, + {0x3501, 0x73, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3818, 0xc8, 0, 0}, {0x3801, 0x88, 0, 0}, {0x3824, 0x11, 0, 0}, + {0x3a00, 0x78, 0, 0}, {0x3a1a, 0x04, 0, 0}, {0x3a13, 0x30, 0, 0}, + {0x3a18, 0x00, 0, 0}, {0x3a19, 0x7c, 0, 0}, {0x3a08, 0x12, 0, 0}, + {0x3a09, 0xc0, 0, 0}, {0x3a0a, 0x0f, 0, 0}, {0x3a0b, 0xa0, 0, 0}, + {0x350c, 0x07, 0, 0}, {0x350d, 0xd0, 0, 0}, {0x3a0d, 0x08, 0, 0}, + {0x3a0e, 0x06, 0, 0}, {0x3500, 0x00, 0, 0}, {0x3501, 0x00, 0, 0}, + {0x3502, 0x00, 0, 0}, {0x350a, 0x00, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x00, 0, 0}, {0x3a0f, 0x3c, 0, 0}, {0x3a10, 0x32, 0, 0}, + {0x3a1b, 0x3c, 0, 0}, {0x3a1e, 0x32, 0, 0}, {0x3a11, 0x80, 0, 0}, + {0x3a1f, 0x20, 0, 0}, {0x3030, 0x2b, 0, 0}, {0x3a02, 0x00, 0, 0}, + {0x3a03, 0x7d, 0, 0}, {0x3a04, 0x00, 0, 0}, {0x3a14, 0x00, 0, 0}, + {0x3a15, 0x7d, 0, 0}, {0x3a16, 0x00, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a08, 0x09, 0, 0}, {0x3a09, 0x60, 0, 0}, {0x3a0a, 0x07, 0, 0}, + {0x3a0b, 0xd0, 0, 0}, {0x3a0d, 0x10, 0, 0}, {0x3a0e, 0x0d, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x589b, 0x00, 0, 0}, + {0x589a, 0xc0, 0, 0}, {0x401e, 0x20, 0, 0}, {0x4001, 0x42, 0, 0}, + {0x401c, 0x06, 0, 0}, {0x3825, 0xac, 0, 0}, {0x3827, 0x0c, 0, 0}, + {0x528a, 0x01, 0, 0}, {0x528b, 0x04, 0, 0}, {0x528c, 0x08, 0, 0}, + {0x528d, 0x10, 0, 0}, {0x528e, 0x20, 0, 0}, {0x528f, 0x28, 0, 0}, + {0x5290, 0x30, 0, 0}, {0x5292, 0x00, 0, 0}, {0x5293, 0x01, 0, 0}, + {0x5294, 0x00, 0, 0}, {0x5295, 0x04, 0, 0}, {0x5296, 0x00, 0, 0}, + {0x5297, 0x08, 0, 0}, {0x5298, 0x00, 0, 0}, {0x5299, 0x10, 0, 0}, + {0x529a, 0x00, 0, 0}, {0x529b, 0x20, 0, 0}, {0x529c, 0x00, 0, 0}, + {0x529d, 0x28, 0, 0}, {0x529e, 0x00, 0, 0}, {0x529f, 0x30, 0, 0}, + {0x5282, 0x00, 0, 0}, {0x5300, 0x00, 0, 0}, {0x5301, 0x20, 0, 0}, + {0x5302, 0x00, 0, 0}, {0x5303, 0x7c, 0, 0}, {0x530c, 0x00, 0, 0}, + {0x530d, 0x0c, 0, 0}, {0x530e, 0x20, 0, 0}, {0x530f, 0x80, 0, 0}, + {0x5310, 0x20, 0, 0}, {0x5311, 0x80, 0, 0}, {0x5308, 0x20, 0, 0}, + {0x5309, 0x40, 0, 0}, {0x5304, 0x00, 0, 0}, {0x5305, 0x30, 0, 0}, + {0x5306, 0x00, 0, 0}, {0x5307, 0x80, 0, 0}, {0x5314, 0x08, 0, 0}, + {0x5315, 0x20, 0, 0}, {0x5319, 0x30, 0, 0}, {0x5316, 0x10, 0, 0}, + {0x5317, 0x00, 0, 0}, {0x5318, 0x02, 0, 0}, {0x5380, 0x01, 0, 0}, + {0x5381, 0x00, 0, 0}, {0x5382, 0x00, 0, 0}, {0x5383, 0x4e, 0, 0}, + {0x5384, 0x00, 0, 0}, {0x5385, 0x0f, 0, 0}, {0x5386, 0x00, 0, 0}, + {0x5387, 0x00, 0, 0}, {0x5388, 0x01, 0, 0}, {0x5389, 0x15, 0, 0}, + {0x538a, 0x00, 0, 0}, {0x538b, 0x31, 0, 0}, {0x538c, 0x00, 0, 0}, + {0x538d, 0x00, 0, 0}, {0x538e, 0x00, 0, 0}, {0x538f, 0x0f, 0, 0}, + {0x5390, 0x00, 0, 0}, {0x5391, 0xab, 0, 0}, {0x5392, 0x00, 0, 0}, + {0x5393, 0xa2, 0, 0}, {0x5394, 0x08, 0, 0}, {0x5480, 0x14, 0, 0}, + {0x5481, 0x21, 0, 0}, {0x5482, 0x36, 0, 0}, {0x5483, 0x57, 0, 0}, + {0x5484, 0x65, 0, 0}, {0x5485, 0x71, 0, 0}, {0x5486, 0x7d, 0, 0}, + {0x5487, 0x87, 0, 0}, {0x5488, 0x91, 0, 0}, {0x5489, 0x9a, 0, 0}, + {0x548a, 0xaa, 0, 0}, {0x548b, 0xb8, 0, 0}, {0x548c, 0xcd, 0, 0}, + {0x548d, 0xdd, 0, 0}, {0x548e, 0xea, 0, 0}, {0x548f, 0x1d, 0, 0}, + {0x5490, 0x05, 0, 0}, {0x5491, 0x00, 0, 0}, {0x5492, 0x04, 0, 0}, + {0x5493, 0x20, 0, 0}, {0x5494, 0x03, 0, 0}, {0x5495, 0x60, 0, 0}, + {0x5496, 0x02, 0, 0}, {0x5497, 0xb8, 0, 0}, {0x5498, 0x02, 0, 0}, + {0x5499, 0x86, 0, 0}, {0x549a, 0x02, 0, 0}, {0x549b, 0x5b, 0, 0}, + {0x549c, 0x02, 0, 0}, {0x549d, 0x3b, 0, 0}, {0x549e, 0x02, 0, 0}, + {0x549f, 0x1c, 0, 0}, {0x54a0, 0x02, 0, 0}, {0x54a1, 0x04, 0, 0}, + {0x54a2, 0x01, 0, 0}, {0x54a3, 0xed, 0, 0}, {0x54a4, 0x01, 0, 0}, + {0x54a5, 0xc5, 0, 0}, {0x54a6, 0x01, 0, 0}, {0x54a7, 0xa5, 0, 0}, + {0x54a8, 0x01, 0, 0}, {0x54a9, 0x6c, 0, 0}, {0x54aa, 0x01, 0, 0}, + {0x54ab, 0x41, 0, 0}, {0x54ac, 0x01, 0, 0}, {0x54ad, 0x20, 0, 0}, + {0x54ae, 0x00, 0, 0}, {0x54af, 0x16, 0, 0}, {0x54b0, 0x01, 0, 0}, + {0x54b1, 0x20, 0, 0}, {0x54b2, 0x00, 0, 0}, {0x54b3, 0x10, 0, 0}, + {0x54b4, 0x00, 0, 0}, {0x54b5, 0xf0, 0, 0}, {0x54b6, 0x00, 0, 0}, + {0x54b7, 0xdf, 0, 0}, {0x5402, 0x3f, 0, 0}, {0x5403, 0x00, 0, 0}, + {0x3406, 0x00, 0, 0}, {0x5180, 0xff, 0, 0}, {0x5181, 0x52, 0, 0}, + {0x5182, 0x11, 0, 0}, {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, + {0x5185, 0x24, 0, 0}, {0x5186, 0x06, 0, 0}, {0x5187, 0x08, 0, 0}, + {0x5188, 0x08, 0, 0}, {0x5189, 0x7c, 0, 0}, {0x518a, 0x60, 0, 0}, + {0x518b, 0xb2, 0, 0}, {0x518c, 0xb2, 0, 0}, {0x518d, 0x44, 0, 0}, + {0x518e, 0x3d, 0, 0}, {0x518f, 0x58, 0, 0}, {0x5190, 0x46, 0, 0}, + {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, + {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, + {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, + {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, + {0x519d, 0x82, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5025, 0x80, 0, 0}, + {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0}, {0x3a1b, 0x3a, 0, 0}, + {0x3a1e, 0x2e, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x10, 0, 0}, + {0x5688, 0xa6, 0, 0}, {0x5689, 0x6a, 0, 0}, {0x568a, 0xea, 0, 0}, + {0x568b, 0xae, 0, 0}, {0x568c, 0xa6, 0, 0}, {0x568d, 0x6a, 0, 0}, + {0x568e, 0x62, 0, 0}, {0x568f, 0x26, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x40, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5000, 0xcf, 0, 0}, + {0x5800, 0x27, 0, 0}, {0x5801, 0x19, 0, 0}, {0x5802, 0x12, 0, 0}, + {0x5803, 0x0f, 0, 0}, {0x5804, 0x10, 0, 0}, {0x5805, 0x15, 0, 0}, + {0x5806, 0x1e, 0, 0}, {0x5807, 0x2f, 0, 0}, {0x5808, 0x15, 0, 0}, + {0x5809, 0x0d, 0, 0}, {0x580a, 0x0a, 0, 0}, {0x580b, 0x09, 0, 0}, + {0x580c, 0x0a, 0, 0}, {0x580d, 0x0c, 0, 0}, {0x580e, 0x12, 0, 0}, + {0x580f, 0x19, 0, 0}, {0x5810, 0x0b, 0, 0}, {0x5811, 0x07, 0, 0}, + {0x5812, 0x04, 0, 0}, {0x5813, 0x03, 0, 0}, {0x5814, 0x03, 0, 0}, + {0x5815, 0x06, 0, 0}, {0x5816, 0x0a, 0, 0}, {0x5817, 0x0f, 0, 0}, + {0x5818, 0x0a, 0, 0}, {0x5819, 0x05, 0, 0}, {0x581a, 0x01, 0, 0}, + {0x581b, 0x00, 0, 0}, {0x581c, 0x00, 0, 0}, {0x581d, 0x03, 0, 0}, + {0x581e, 0x08, 0, 0}, {0x581f, 0x0c, 0, 0}, {0x5820, 0x0a, 0, 0}, + {0x5821, 0x05, 0, 0}, {0x5822, 0x01, 0, 0}, {0x5823, 0x00, 0, 0}, + {0x5824, 0x00, 0, 0}, {0x5825, 0x03, 0, 0}, {0x5826, 0x08, 0, 0}, + {0x5827, 0x0c, 0, 0}, {0x5828, 0x0e, 0, 0}, {0x5829, 0x08, 0, 0}, + {0x582a, 0x06, 0, 0}, {0x582b, 0x04, 0, 0}, {0x582c, 0x05, 0, 0}, + {0x582d, 0x07, 0, 0}, {0x582e, 0x0b, 0, 0}, {0x582f, 0x12, 0, 0}, + {0x5830, 0x18, 0, 0}, {0x5831, 0x10, 0, 0}, {0x5832, 0x0c, 0, 0}, + {0x5833, 0x0a, 0, 0}, {0x5834, 0x0b, 0, 0}, {0x5835, 0x0e, 0, 0}, + {0x5836, 0x15, 0, 0}, {0x5837, 0x19, 0, 0}, {0x5838, 0x32, 0, 0}, + {0x5839, 0x1f, 0, 0}, {0x583a, 0x18, 0, 0}, {0x583b, 0x16, 0, 0}, + {0x583c, 0x17, 0, 0}, {0x583d, 0x1e, 0, 0}, {0x583e, 0x26, 0, 0}, + {0x583f, 0x53, 0, 0}, {0x5840, 0x10, 0, 0}, {0x5841, 0x0f, 0, 0}, + {0x5842, 0x0d, 0, 0}, {0x5843, 0x0c, 0, 0}, {0x5844, 0x0e, 0, 0}, + {0x5845, 0x09, 0, 0}, {0x5846, 0x11, 0, 0}, {0x5847, 0x10, 0, 0}, + {0x5848, 0x10, 0, 0}, {0x5849, 0x10, 0, 0}, {0x584a, 0x10, 0, 0}, + {0x584b, 0x0e, 0, 0}, {0x584c, 0x10, 0, 0}, {0x584d, 0x10, 0, 0}, + {0x584e, 0x11, 0, 0}, {0x584f, 0x10, 0, 0}, {0x5850, 0x0f, 0, 0}, + {0x5851, 0x0c, 0, 0}, {0x5852, 0x0f, 0, 0}, {0x5853, 0x10, 0, 0}, + {0x5854, 0x10, 0, 0}, {0x5855, 0x0f, 0, 0}, {0x5856, 0x0e, 0, 0}, + {0x5857, 0x0b, 0, 0}, {0x5858, 0x10, 0, 0}, {0x5859, 0x0d, 0, 0}, + {0x585a, 0x0d, 0, 0}, {0x585b, 0x0c, 0, 0}, {0x585c, 0x0c, 0, 0}, + {0x585d, 0x0c, 0, 0}, {0x585e, 0x0b, 0, 0}, {0x585f, 0x0c, 0, 0}, + {0x5860, 0x0c, 0, 0}, {0x5861, 0x0c, 0, 0}, {0x5862, 0x0d, 0, 0}, + {0x5863, 0x08, 0, 0}, {0x5864, 0x11, 0, 0}, {0x5865, 0x18, 0, 0}, + {0x5866, 0x18, 0, 0}, {0x5867, 0x19, 0, 0}, {0x5868, 0x17, 0, 0}, + {0x5869, 0x19, 0, 0}, {0x586a, 0x16, 0, 0}, {0x586b, 0x13, 0, 0}, + {0x586c, 0x13, 0, 0}, {0x586d, 0x12, 0, 0}, {0x586e, 0x13, 0, 0}, + {0x586f, 0x16, 0, 0}, {0x5870, 0x14, 0, 0}, {0x5871, 0x12, 0, 0}, + {0x5872, 0x10, 0, 0}, {0x5873, 0x11, 0, 0}, {0x5874, 0x11, 0, 0}, + {0x5875, 0x16, 0, 0}, {0x5876, 0x14, 0, 0}, {0x5877, 0x11, 0, 0}, + {0x5878, 0x10, 0, 0}, {0x5879, 0x0f, 0, 0}, {0x587a, 0x10, 0, 0}, + {0x587b, 0x14, 0, 0}, {0x587c, 0x13, 0, 0}, {0x587d, 0x12, 0, 0}, + {0x587e, 0x11, 0, 0}, {0x587f, 0x11, 0, 0}, {0x5880, 0x12, 0, 0}, + {0x5881, 0x15, 0, 0}, {0x5882, 0x14, 0, 0}, {0x5883, 0x15, 0, 0}, + {0x5884, 0x15, 0, 0}, {0x5885, 0x15, 0, 0}, {0x5886, 0x13, 0, 0}, + {0x5887, 0x17, 0, 0}, {0x3710, 0x10, 0, 0}, {0x3632, 0x51, 0, 0}, + {0x3702, 0x10, 0, 0}, {0x3703, 0xb2, 0, 0}, {0x3704, 0x18, 0, 0}, + {0x370b, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x3631, 0x01, 0, 0}, + {0x3632, 0x52, 0, 0}, {0x3606, 0x24, 0, 0}, {0x3620, 0x96, 0, 0}, + {0x5785, 0x07, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3600, 0x52, 0, 0}, + {0x3604, 0x48, 0, 0}, {0x3606, 0x1b, 0, 0}, {0x370d, 0x0b, 0, 0}, + {0x370f, 0xc0, 0, 0}, {0x3709, 0x01, 0, 0}, {0x3823, 0x00, 0, 0}, + {0x5007, 0x00, 0, 0}, {0x5009, 0x00, 0, 0}, {0x5011, 0x00, 0, 0}, + {0x5013, 0x00, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5086, 0x00, 0, 0}, + {0x5087, 0x00, 0, 0}, {0x5088, 0x00, 0, 0}, {0x5089, 0x00, 0, 0}, + {0x302b, 0x00, 0, 0}, {0x3503, 0x07, 0, 0}, {0x3011, 0x08, 0, 0}, + {0x350c, 0x02, 0, 0}, {0x350d, 0xe4, 0, 0}, {0x3621, 0xc9, 0, 0}, + {0x370a, 0x81, 0, 0}, {0x3803, 0x08, 0, 0}, {0x3804, 0x05, 0, 0}, + {0x3805, 0x00, 0, 0}, {0x3806, 0x02, 0, 0}, {0x3807, 0xd0, 0, 0}, + {0x3808, 0x05, 0, 0}, {0x3809, 0x00, 0, 0}, {0x380a, 0x02, 0, 0}, + {0x380b, 0xd0, 0, 0}, {0x380c, 0x08, 0, 0}, {0x380d, 0x72, 0, 0}, + {0x380e, 0x02, 0, 0}, {0x380f, 0xe4, 0, 0}, {0x3810, 0xc0, 0, 0}, + {0x3818, 0xc9, 0, 0}, {0x381c, 0x10, 0, 0}, {0x381d, 0xa0, 0, 0}, + {0x381e, 0x05, 0, 0}, {0x381f, 0xb0, 0, 0}, {0x3820, 0x00, 0, 0}, + {0x3821, 0x00, 0, 0}, {0x3824, 0x11, 0, 0}, {0x3a08, 0x1b, 0, 0}, + {0x3a09, 0xc0, 0, 0}, {0x3a0a, 0x17, 0, 0}, {0x3a0b, 0x20, 0, 0}, + {0x3a0d, 0x02, 0, 0}, {0x3a0e, 0x01, 0, 0}, {0x401c, 0x04, 0, 0}, + {0x5682, 0x05, 0, 0}, {0x5683, 0x00, 0, 0}, {0x5686, 0x02, 0, 0}, + {0x5687, 0xcc, 0, 0}, {0x5001, 0x7f, 0, 0}, {0x589b, 0x06, 0, 0}, + {0x589a, 0xc5, 0, 0}, {0x3503, 0x00, 0, 0}, {0x3010, 0x10, 0, 0}, + {0x460c, 0x20, 0, 0}, {0x460b, 0x37, 0, 0}, {0x471c, 0xd0, 0, 0}, + {0x471d, 0x05, 0, 0}, {0x3815, 0x01, 0, 0}, {0x3818, 0x00, 0x08, 0}, + {0x501f, 0x00, 0, 0}, {0x4300, 0x30, 0, 0}, {0x3002, 0x1c, 0, 0}, + {0x3819, 0x80, 0, 0}, {0x5002, 0xe0, 0, 0}, +}; + +static struct reg_value ov5642_setting_30fps_1080P_1920_1080[] = { + {0x3103, 0x93, 0, 0}, {0x3008, 0x82, 0, 0}, {0x3017, 0x7f, 0, 0}, + {0x3018, 0xfc, 0, 0}, {0x3810, 0xc2, 0, 0}, {0x3615, 0xf0, 0, 0}, + {0x3000, 0x00, 0, 0}, {0x3001, 0x00, 0, 0}, {0x3002, 0x00, 0, 0}, + {0x3003, 0x00, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3030, 0x2b, 0, 0}, + {0x3011, 0x08, 0, 0}, {0x3010, 0x10, 0, 0}, {0x3604, 0x60, 0, 0}, + {0x3622, 0x60, 0, 0}, {0x3621, 0x09, 0, 0}, {0x3709, 0x00, 0, 0}, + {0x4000, 0x21, 0, 0}, {0x401d, 0x22, 0, 0}, {0x3600, 0x54, 0, 0}, + {0x3605, 0x04, 0, 0}, {0x3606, 0x3f, 0, 0}, {0x3c01, 0x80, 0, 0}, + {0x300d, 0x22, 0, 0}, {0x3623, 0x22, 0, 0}, {0x5000, 0x4f, 0, 0}, + {0x5020, 0x04, 0, 0}, {0x5181, 0x79, 0, 0}, {0x5182, 0x00, 0, 0}, + {0x5185, 0x22, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5500, 0x0a, 0, 0}, + {0x5504, 0x00, 0, 0}, {0x5505, 0x7f, 0, 0}, {0x5080, 0x08, 0, 0}, + {0x300e, 0x18, 0, 0}, {0x4610, 0x00, 0, 0}, {0x471d, 0x05, 0, 0}, + {0x4708, 0x06, 0, 0}, {0x370c, 0xa0, 0, 0}, {0x3808, 0x0a, 0, 0}, + {0x3809, 0x20, 0, 0}, {0x380a, 0x07, 0, 0}, {0x380b, 0x98, 0, 0}, + {0x380c, 0x0c, 0, 0}, {0x380d, 0x80, 0, 0}, {0x380e, 0x07, 0, 0}, + {0x380f, 0xd0, 0, 0}, {0x5687, 0x94, 0, 0}, {0x501f, 0x00, 0, 0}, + {0x5000, 0x4f, 0, 0}, {0x5001, 0xcf, 0, 0}, {0x4300, 0x30, 0, 0}, + {0x4300, 0x30, 0, 0}, {0x460b, 0x35, 0, 0}, {0x471d, 0x00, 0, 0}, + {0x3002, 0x0c, 0, 0}, {0x3002, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x471c, 0x50, 0, 0}, {0x4721, 0x02, 0, 0}, {0x4402, 0x90, 0, 0}, + {0x460c, 0x22, 0, 0}, {0x3815, 0x44, 0, 0}, {0x3503, 0x07, 0, 0}, + {0x3501, 0x73, 0, 0}, {0x3502, 0x80, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3818, 0xc8, 0, 0}, {0x3801, 0x88, 0, 0}, {0x3824, 0x11, 0, 0}, + {0x3a00, 0x78, 0, 0}, {0x3a1a, 0x04, 0, 0}, {0x3a13, 0x30, 0, 0}, + {0x3a18, 0x00, 0, 0}, {0x3a19, 0x7c, 0, 0}, {0x3a08, 0x12, 0, 0}, + {0x3a09, 0xc0, 0, 0}, {0x3a0a, 0x0f, 0, 0}, {0x3a0b, 0xa0, 0, 0}, + {0x350c, 0x07, 0, 0}, {0x350d, 0xd0, 0, 0}, {0x3a0d, 0x08, 0, 0}, + {0x3a0e, 0x06, 0, 0}, {0x3500, 0x00, 0, 0}, {0x3501, 0x00, 0, 0}, + {0x3502, 0x00, 0, 0}, {0x350a, 0x00, 0, 0}, {0x350b, 0x00, 0, 0}, + {0x3503, 0x00, 0, 0}, {0x3a0f, 0x3c, 0, 0}, {0x3a10, 0x32, 0, 0}, + {0x3a1b, 0x3c, 0, 0}, {0x3a1e, 0x32, 0, 0}, {0x3a11, 0x80, 0, 0}, + {0x3a1f, 0x20, 0, 0}, {0x3030, 0x2b, 0, 0}, {0x3a02, 0x00, 0, 0}, + {0x3a03, 0x7d, 0, 0}, {0x3a04, 0x00, 0, 0}, {0x3a14, 0x00, 0, 0}, + {0x3a15, 0x7d, 0, 0}, {0x3a16, 0x00, 0, 0}, {0x3a00, 0x78, 0, 0}, + {0x3a08, 0x09, 0, 0}, {0x3a09, 0x60, 0, 0}, {0x3a0a, 0x07, 0, 0}, + {0x3a0b, 0xd0, 0, 0}, {0x3a0d, 0x10, 0, 0}, {0x3a0e, 0x0d, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x589b, 0x00, 0, 0}, + {0x589a, 0xc0, 0, 0}, {0x401e, 0x20, 0, 0}, {0x4001, 0x42, 0, 0}, + {0x401c, 0x06, 0, 0}, {0x3825, 0xac, 0, 0}, {0x3827, 0x0c, 0, 0}, + {0x528a, 0x01, 0, 0}, {0x528b, 0x04, 0, 0}, {0x528c, 0x08, 0, 0}, + {0x528d, 0x10, 0, 0}, {0x528e, 0x20, 0, 0}, {0x528f, 0x28, 0, 0}, + {0x5290, 0x30, 0, 0}, {0x5292, 0x00, 0, 0}, {0x5293, 0x01, 0, 0}, + {0x5294, 0x00, 0, 0}, {0x5295, 0x04, 0, 0}, {0x5296, 0x00, 0, 0}, + {0x5297, 0x08, 0, 0}, {0x5298, 0x00, 0, 0}, {0x5299, 0x10, 0, 0}, + {0x529a, 0x00, 0, 0}, {0x529b, 0x20, 0, 0}, {0x529c, 0x00, 0, 0}, + {0x529d, 0x28, 0, 0}, {0x529e, 0x00, 0, 0}, {0x529f, 0x30, 0, 0}, + {0x5282, 0x00, 0, 0}, {0x5300, 0x00, 0, 0}, {0x5301, 0x20, 0, 0}, + {0x5302, 0x00, 0, 0}, {0x5303, 0x7c, 0, 0}, {0x530c, 0x00, 0, 0}, + {0x530d, 0x0c, 0, 0}, {0x530e, 0x20, 0, 0}, {0x530f, 0x80, 0, 0}, + {0x5310, 0x20, 0, 0}, {0x5311, 0x80, 0, 0}, {0x5308, 0x20, 0, 0}, + {0x5309, 0x40, 0, 0}, {0x5304, 0x00, 0, 0}, {0x5305, 0x30, 0, 0}, + {0x5306, 0x00, 0, 0}, {0x5307, 0x80, 0, 0}, {0x5314, 0x08, 0, 0}, + {0x5315, 0x20, 0, 0}, {0x5319, 0x30, 0, 0}, {0x5316, 0x10, 0, 0}, + {0x5317, 0x00, 0, 0}, {0x5318, 0x02, 0, 0}, {0x5380, 0x01, 0, 0}, + {0x5381, 0x00, 0, 0}, {0x5382, 0x00, 0, 0}, {0x5383, 0x4e, 0, 0}, + {0x5384, 0x00, 0, 0}, {0x5385, 0x0f, 0, 0}, {0x5386, 0x00, 0, 0}, + {0x5387, 0x00, 0, 0}, {0x5388, 0x01, 0, 0}, {0x5389, 0x15, 0, 0}, + {0x538a, 0x00, 0, 0}, {0x538b, 0x31, 0, 0}, {0x538c, 0x00, 0, 0}, + {0x538d, 0x00, 0, 0}, {0x538e, 0x00, 0, 0}, {0x538f, 0x0f, 0, 0}, + {0x5390, 0x00, 0, 0}, {0x5391, 0xab, 0, 0}, {0x5392, 0x00, 0, 0}, + {0x5393, 0xa2, 0, 0}, {0x5394, 0x08, 0, 0}, {0x5480, 0x14, 0, 0}, + {0x5481, 0x21, 0, 0}, {0x5482, 0x36, 0, 0}, {0x5483, 0x57, 0, 0}, + {0x5484, 0x65, 0, 0}, {0x5485, 0x71, 0, 0}, {0x5486, 0x7d, 0, 0}, + {0x5487, 0x87, 0, 0}, {0x5488, 0x91, 0, 0}, {0x5489, 0x9a, 0, 0}, + {0x548a, 0xaa, 0, 0}, {0x548b, 0xb8, 0, 0}, {0x548c, 0xcd, 0, 0}, + {0x548d, 0xdd, 0, 0}, {0x548e, 0xea, 0, 0}, {0x548f, 0x1d, 0, 0}, + {0x5490, 0x05, 0, 0}, {0x5491, 0x00, 0, 0}, {0x5492, 0x04, 0, 0}, + {0x5493, 0x20, 0, 0}, {0x5494, 0x03, 0, 0}, {0x5495, 0x60, 0, 0}, + {0x5496, 0x02, 0, 0}, {0x5497, 0xb8, 0, 0}, {0x5498, 0x02, 0, 0}, + {0x5499, 0x86, 0, 0}, {0x549a, 0x02, 0, 0}, {0x549b, 0x5b, 0, 0}, + {0x549c, 0x02, 0, 0}, {0x549d, 0x3b, 0, 0}, {0x549e, 0x02, 0, 0}, + {0x549f, 0x1c, 0, 0}, {0x54a0, 0x02, 0, 0}, {0x54a1, 0x04, 0, 0}, + {0x54a2, 0x01, 0, 0}, {0x54a3, 0xed, 0, 0}, {0x54a4, 0x01, 0, 0}, + {0x54a5, 0xc5, 0, 0}, {0x54a6, 0x01, 0, 0}, {0x54a7, 0xa5, 0, 0}, + {0x54a8, 0x01, 0, 0}, {0x54a9, 0x6c, 0, 0}, {0x54aa, 0x01, 0, 0}, + {0x54ab, 0x41, 0, 0}, {0x54ac, 0x01, 0, 0}, {0x54ad, 0x20, 0, 0}, + {0x54ae, 0x00, 0, 0}, {0x54af, 0x16, 0, 0}, {0x54b0, 0x01, 0, 0}, + {0x54b1, 0x20, 0, 0}, {0x54b2, 0x00, 0, 0}, {0x54b3, 0x10, 0, 0}, + {0x54b4, 0x00, 0, 0}, {0x54b5, 0xf0, 0, 0}, {0x54b6, 0x00, 0, 0}, + {0x54b7, 0xdf, 0, 0}, {0x5402, 0x3f, 0, 0}, {0x5403, 0x00, 0, 0}, + {0x3406, 0x00, 0, 0}, {0x5180, 0xff, 0, 0}, {0x5181, 0x52, 0, 0}, + {0x5182, 0x11, 0, 0}, {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, + {0x5185, 0x24, 0, 0}, {0x5186, 0x06, 0, 0}, {0x5187, 0x08, 0, 0}, + {0x5188, 0x08, 0, 0}, {0x5189, 0x7c, 0, 0}, {0x518a, 0x60, 0, 0}, + {0x518b, 0xb2, 0, 0}, {0x518c, 0xb2, 0, 0}, {0x518d, 0x44, 0, 0}, + {0x518e, 0x3d, 0, 0}, {0x518f, 0x58, 0, 0}, {0x5190, 0x46, 0, 0}, + {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, + {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, + {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, + {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, + {0x519d, 0x82, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5025, 0x80, 0, 0}, + {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0}, {0x3a1b, 0x3a, 0, 0}, + {0x3a1e, 0x2e, 0, 0}, {0x3a11, 0x60, 0, 0}, {0x3a1f, 0x10, 0, 0}, + {0x5688, 0xa6, 0, 0}, {0x5689, 0x6a, 0, 0}, {0x568a, 0xea, 0, 0}, + {0x568b, 0xae, 0, 0}, {0x568c, 0xa6, 0, 0}, {0x568d, 0x6a, 0, 0}, + {0x568e, 0x62, 0, 0}, {0x568f, 0x26, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x40, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5000, 0xcf, 0, 0}, + {0x5800, 0x27, 0, 0}, {0x5801, 0x19, 0, 0}, {0x5802, 0x12, 0, 0}, + {0x5803, 0x0f, 0, 0}, {0x5804, 0x10, 0, 0}, {0x5805, 0x15, 0, 0}, + {0x5806, 0x1e, 0, 0}, {0x5807, 0x2f, 0, 0}, {0x5808, 0x15, 0, 0}, + {0x5809, 0x0d, 0, 0}, {0x580a, 0x0a, 0, 0}, {0x580b, 0x09, 0, 0}, + {0x580c, 0x0a, 0, 0}, {0x580d, 0x0c, 0, 0}, {0x580e, 0x12, 0, 0}, + {0x580f, 0x19, 0, 0}, {0x5810, 0x0b, 0, 0}, {0x5811, 0x07, 0, 0}, + {0x5812, 0x04, 0, 0}, {0x5813, 0x03, 0, 0}, {0x5814, 0x03, 0, 0}, + {0x5815, 0x06, 0, 0}, {0x5816, 0x0a, 0, 0}, {0x5817, 0x0f, 0, 0}, + {0x5818, 0x0a, 0, 0}, {0x5819, 0x05, 0, 0}, {0x581a, 0x01, 0, 0}, + {0x581b, 0x00, 0, 0}, {0x581c, 0x00, 0, 0}, {0x581d, 0x03, 0, 0}, + {0x581e, 0x08, 0, 0}, {0x581f, 0x0c, 0, 0}, {0x5820, 0x0a, 0, 0}, + {0x5821, 0x05, 0, 0}, {0x5822, 0x01, 0, 0}, {0x5823, 0x00, 0, 0}, + {0x5824, 0x00, 0, 0}, {0x5825, 0x03, 0, 0}, {0x5826, 0x08, 0, 0}, + {0x5827, 0x0c, 0, 0}, {0x5828, 0x0e, 0, 0}, {0x5829, 0x08, 0, 0}, + {0x582a, 0x06, 0, 0}, {0x582b, 0x04, 0, 0}, {0x582c, 0x05, 0, 0}, + {0x582d, 0x07, 0, 0}, {0x582e, 0x0b, 0, 0}, {0x582f, 0x12, 0, 0}, + {0x5830, 0x18, 0, 0}, {0x5831, 0x10, 0, 0}, {0x5832, 0x0c, 0, 0}, + {0x5833, 0x0a, 0, 0}, {0x5834, 0x0b, 0, 0}, {0x5835, 0x0e, 0, 0}, + {0x5836, 0x15, 0, 0}, {0x5837, 0x19, 0, 0}, {0x5838, 0x32, 0, 0}, + {0x5839, 0x1f, 0, 0}, {0x583a, 0x18, 0, 0}, {0x583b, 0x16, 0, 0}, + {0x583c, 0x17, 0, 0}, {0x583d, 0x1e, 0, 0}, {0x583e, 0x26, 0, 0}, + {0x583f, 0x53, 0, 0}, {0x5840, 0x10, 0, 0}, {0x5841, 0x0f, 0, 0}, + {0x5842, 0x0d, 0, 0}, {0x5843, 0x0c, 0, 0}, {0x5844, 0x0e, 0, 0}, + {0x5845, 0x09, 0, 0}, {0x5846, 0x11, 0, 0}, {0x5847, 0x10, 0, 0}, + {0x5848, 0x10, 0, 0}, {0x5849, 0x10, 0, 0}, {0x584a, 0x10, 0, 0}, + {0x584b, 0x0e, 0, 0}, {0x584c, 0x10, 0, 0}, {0x584d, 0x10, 0, 0}, + {0x584e, 0x11, 0, 0}, {0x584f, 0x10, 0, 0}, {0x5850, 0x0f, 0, 0}, + {0x5851, 0x0c, 0, 0}, {0x5852, 0x0f, 0, 0}, {0x5853, 0x10, 0, 0}, + {0x5854, 0x10, 0, 0}, {0x5855, 0x0f, 0, 0}, {0x5856, 0x0e, 0, 0}, + {0x5857, 0x0b, 0, 0}, {0x5858, 0x10, 0, 0}, {0x5859, 0x0d, 0, 0}, + {0x585a, 0x0d, 0, 0}, {0x585b, 0x0c, 0, 0}, {0x585c, 0x0c, 0, 0}, + {0x585d, 0x0c, 0, 0}, {0x585e, 0x0b, 0, 0}, {0x585f, 0x0c, 0, 0}, + {0x5860, 0x0c, 0, 0}, {0x5861, 0x0c, 0, 0}, {0x5862, 0x0d, 0, 0}, + {0x5863, 0x08, 0, 0}, {0x5864, 0x11, 0, 0}, {0x5865, 0x18, 0, 0}, + {0x5866, 0x18, 0, 0}, {0x5867, 0x19, 0, 0}, {0x5868, 0x17, 0, 0}, + {0x5869, 0x19, 0, 0}, {0x586a, 0x16, 0, 0}, {0x586b, 0x13, 0, 0}, + {0x586c, 0x13, 0, 0}, {0x586d, 0x12, 0, 0}, {0x586e, 0x13, 0, 0}, + {0x586f, 0x16, 0, 0}, {0x5870, 0x14, 0, 0}, {0x5871, 0x12, 0, 0}, + {0x5872, 0x10, 0, 0}, {0x5873, 0x11, 0, 0}, {0x5874, 0x11, 0, 0}, + {0x5875, 0x16, 0, 0}, {0x5876, 0x14, 0, 0}, {0x5877, 0x11, 0, 0}, + {0x5878, 0x10, 0, 0}, {0x5879, 0x0f, 0, 0}, {0x587a, 0x10, 0, 0}, + {0x587b, 0x14, 0, 0}, {0x587c, 0x13, 0, 0}, {0x587d, 0x12, 0, 0}, + {0x587e, 0x11, 0, 0}, {0x587f, 0x11, 0, 0}, {0x5880, 0x12, 0, 0}, + {0x5881, 0x15, 0, 0}, {0x5882, 0x14, 0, 0}, {0x5883, 0x15, 0, 0}, + {0x5884, 0x15, 0, 0}, {0x5885, 0x15, 0, 0}, {0x5886, 0x13, 0, 0}, + {0x5887, 0x17, 0, 0}, {0x3710, 0x10, 0, 0}, {0x3632, 0x51, 0, 0}, + {0x3702, 0x10, 0, 0}, {0x3703, 0xb2, 0, 0}, {0x3704, 0x18, 0, 0}, + {0x370b, 0x40, 0, 0}, {0x370d, 0x03, 0, 0}, {0x3631, 0x01, 0, 0}, + {0x3632, 0x52, 0, 0}, {0x3606, 0x24, 0, 0}, {0x3620, 0x96, 0, 0}, + {0x5785, 0x07, 0, 0}, {0x3a13, 0x30, 0, 0}, {0x3600, 0x52, 0, 0}, + {0x3604, 0x48, 0, 0}, {0x3606, 0x1b, 0, 0}, {0x370d, 0x0b, 0, 0}, + {0x370f, 0xc0, 0, 0}, {0x3709, 0x01, 0, 0}, {0x3823, 0x00, 0, 0}, + {0x5007, 0x00, 0, 0}, {0x5009, 0x00, 0, 0}, {0x5011, 0x00, 0, 0}, + {0x5013, 0x00, 0, 0}, {0x519e, 0x00, 0, 0}, {0x5086, 0x00, 0, 0}, + {0x5087, 0x00, 0, 0}, {0x5088, 0x00, 0, 0}, {0x5089, 0x00, 0, 0}, + {0x302b, 0x00, 0, 0}, {0x3503, 0x07, 0, 0}, {0x3011, 0x07, 0, 0}, + {0x350c, 0x04, 0, 0}, {0x350d, 0x58, 0, 0}, {0x3801, 0x8a, 0, 0}, + {0x3803, 0x0a, 0, 0}, {0x3804, 0x07, 0, 0}, {0x3805, 0x80, 0, 0}, + {0x3806, 0x04, 0, 0}, {0x3807, 0x38, 0, 0}, {0x3808, 0x07, 0, 0}, + {0x3809, 0x80, 0, 0}, {0x380a, 0x04, 0, 0}, {0x380b, 0x38, 0, 0}, + {0x380c, 0x09, 0, 0}, {0x380d, 0xd6, 0, 0}, {0x380e, 0x04, 0, 0}, + {0x380f, 0x58, 0, 0}, {0x381c, 0x11, 0, 0}, {0x381d, 0xba, 0, 0}, + {0x381e, 0x04, 0, 0}, {0x381f, 0x48, 0, 0}, {0x3820, 0x04, 0, 0}, + {0x3821, 0x18, 0, 0}, {0x3a08, 0x14, 0, 0}, {0x3a09, 0xe0, 0, 0}, + {0x3a0a, 0x11, 0, 0}, {0x3a0b, 0x60, 0, 0}, {0x3a0d, 0x04, 0, 0}, + {0x3a0e, 0x03, 0, 0}, {0x5682, 0x07, 0, 0}, {0x5683, 0x60, 0, 0}, + {0x5686, 0x04, 0, 0}, {0x5687, 0x1c, 0, 0}, {0x5001, 0x7f, 0, 0}, + {0x3503, 0x00, 0, 0}, {0x3010, 0x00, 0, 0}, {0x460c, 0x20, 0, 0}, + {0x460b, 0x37, 0, 0}, {0x471c, 0xd0, 0, 0}, {0x471d, 0x05, 0, 0}, + {0x3815, 0x01, 0, 0}, {0x3818, 0x00, 0x08, 0}, {0x501f, 0x00, 0, 0}, + {0x4300, 0x30, 0, 0}, {0x3002, 0x1c, 0, 0}, {0x3819, 0x80, 0, 0}, + {0x5002, 0xe0, 0, 0}, +}; + +static struct ov5642_mode_info ov5642_mode_info_data[2][ov5642_mode_MAX + 1] = { + { + {ov5642_mode_VGA_640_480, 0, 0, NULL, 0}, + {ov5642_mode_QVGA_320_240, 0, 0, NULL, 0}, + {ov5642_mode_NTSC_720_480, 0, 0, NULL, 0}, + {ov5642_mode_PAL_720_576, 0, 0, NULL, 0}, + {ov5642_mode_720P_1280_720, 0, 0, NULL, 0}, + {ov5642_mode_1080P_1920_1080, 0, 0, NULL, 0}, + {ov5642_mode_QSXGA_2592_1944, 2592, 1944, + ov5642_setting_15fps_QSXGA_2592_1944, + ARRAY_SIZE(ov5642_setting_15fps_QSXGA_2592_1944)}, + }, + { + {ov5642_mode_VGA_640_480, 640, 480, + ov5642_setting_30fps_VGA_640_480, + ARRAY_SIZE(ov5642_setting_30fps_VGA_640_480)}, + {ov5642_mode_QVGA_320_240, 320, 240, + ov5642_setting_30fps_QVGA_320_240, + ARRAY_SIZE(ov5642_setting_30fps_QVGA_320_240)}, + {ov5642_mode_NTSC_720_480, 720, 480, + ov5642_setting_30fps_NTSC_720_480, + ARRAY_SIZE(ov5642_setting_30fps_NTSC_720_480)}, + {ov5642_mode_PAL_720_576, 720, 576, + ov5642_setting_30fps_PAL_720_576, + ARRAY_SIZE(ov5642_setting_30fps_PAL_720_576)}, + {ov5642_mode_720P_1280_720, 1280, 720, + ov5642_setting_30fps_720P_1280_720, + ARRAY_SIZE(ov5642_setting_30fps_720P_1280_720)}, + {ov5642_mode_1080P_1920_1080, 1920, 1080, + ov5642_setting_30fps_1080P_1920_1080, + ARRAY_SIZE(ov5642_setting_30fps_1080P_1920_1080)}, + {ov5642_mode_QSXGA_2592_1944, 0, 0, NULL, 0}, + }, +}; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; +static struct mxc_camera_platform_data *camera_plat; + +static int ov5642_probe(struct i2c_client *adapter, + const struct i2c_device_id *device_id); +static int ov5642_remove(struct i2c_client *client); + +static s32 ov5642_read_reg(u16 reg, u8 *val); +static s32 ov5642_write_reg(u16 reg, u8 val); + +static const struct i2c_device_id ov5642_id[] = { + {"ov5642", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov5642_id); + +static struct i2c_driver ov5642_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov5642", + }, + .probe = ov5642_probe, + .remove = ov5642_remove, + .id_table = ov5642_id, +}; + +extern void gpio_sensor_active(unsigned int csi_index); +extern void gpio_sensor_inactive(unsigned int csi); + +static s32 ov5642_write_reg(u16 reg, u8 val) +{ + u8 au8Buf[3] = {0}; + + au8Buf[0] = reg >> 8; + au8Buf[1] = reg & 0xff; + au8Buf[2] = val; + + if (i2c_master_send(ov5642_data.i2c_client, au8Buf, 3) < 0) { + pr_err("%s:write reg error:reg=%x,val=%x\n", + __func__, reg, val); + return -1; + } + + return 0; +} + +static s32 ov5642_read_reg(u16 reg, u8 *val) +{ + u8 au8RegBuf[2] = {0}; + u8 u8RdVal = 0; + + au8RegBuf[0] = reg >> 8; + au8RegBuf[1] = reg & 0xff; + + if (2 != i2c_master_send(ov5642_data.i2c_client, au8RegBuf, 2)) { + pr_err("%s:write reg error:reg=%x\n", + __func__, reg); + return -1; + } + + if (1 != i2c_master_recv(ov5642_data.i2c_client, &u8RdVal, 1)) { + pr_err("%s:read reg error:reg=%x,val=%x\n", + __func__, reg, u8RdVal); + return -1; + } + + *val = u8RdVal; + + return u8RdVal; +} + +static int ov5642_init_mode(enum ov5642_frame_rate frame_rate, + enum ov5642_mode mode) +{ + struct reg_value *pModeSetting = NULL; + s32 i = 0; + s32 iModeSettingArySize = 0; + register u32 Delay_ms = 0; + register u16 RegAddr = 0; + register u8 Mask = 0; + register u8 Val = 0; + u8 RegVal = 0; + int retval = 0; + + if (mode > ov5642_mode_MAX || mode < ov5642_mode_MIN) { + pr_err("Wrong ov5642 mode detected!\n"); + return -1; + } + + pModeSetting = ov5642_mode_info_data[frame_rate][mode].init_data_ptr; + iModeSettingArySize = + ov5642_mode_info_data[frame_rate][mode].init_data_size; + + ov5642_data.pix.width = ov5642_mode_info_data[frame_rate][mode].width; + ov5642_data.pix.height = ov5642_mode_info_data[frame_rate][mode].height; + + if (ov5642_data.pix.width == 0 || ov5642_data.pix.height == 0 || + pModeSetting == NULL || iModeSettingArySize == 0) + return -EINVAL; + + for (i = 0; i < iModeSettingArySize; ++i, ++pModeSetting) { + Delay_ms = pModeSetting->u32Delay_ms; + RegAddr = pModeSetting->u16RegAddr; + Val = pModeSetting->u8Val; + Mask = pModeSetting->u8Mask; + + if (Mask) { + retval = ov5642_read_reg(RegAddr, &RegVal); + if (retval < 0) + goto err; + + RegVal &= ~(u8)Mask; + Val &= Mask; + Val |= RegVal; + } + + retval = ov5642_write_reg(RegAddr, Val); + if (retval < 0) + goto err; + + if (Delay_ms) + msleep(Delay_ms); + } +err: + return retval; +} + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = ov5642_data.mclk; + pr_debug(" clock_curr=mclk=%d\n", ov5642_data.mclk); + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = OV5642_XCLK_MIN; + p->u.bt656.clock_max = OV5642_XCLK_MAX; + p->u.bt656.bt_sync_correct = 1; /* Indicate external vsync */ + + return 0; +} + +/*! + * ioctl_s_power - V4L2 sensor interface handler for VIDIOC_S_POWER ioctl + * @s: pointer to standard V4L2 device structure + * @on: indicates power mode (on or off) + * + * Turns the power on or off, depending on the value of on and returns the + * appropriate error code. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + if (on && !sensor->on) { + gpio_sensor_active(ov5642_data.csi); + if (io_regulator) + if (regulator_enable(io_regulator) != 0) + return -EIO; + if (core_regulator) + if (regulator_enable(core_regulator) != 0) + return -EIO; + if (gpo_regulator) + if (regulator_enable(gpo_regulator) != 0) + return -EIO; + if (analog_regulator) + if (regulator_enable(analog_regulator) != 0) + return -EIO; + /* Make sure power on */ + if (camera_plat->pwdn) + camera_plat->pwdn(0); + + } else if (!on && sensor->on) { + if (analog_regulator) + regulator_disable(analog_regulator); + if (core_regulator) + regulator_disable(core_regulator); + if (io_regulator) + regulator_disable(io_regulator); + if (gpo_regulator) + regulator_disable(gpo_regulator); + gpio_sensor_inactive(ov5642_data.csi); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + int ret = 0; + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + enum ov5642_frame_rate frame_rate; + int ret = 0; + + /* Make sure power on */ + if (camera_plat->pwdn) + camera_plat->pwdn(0); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + /* Check that the new frame rate is allowed. */ + if ((timeperframe->numerator == 0) || + (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + + /* Actual frame rate we use */ + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps == 15) + frame_rate = ov5642_15_fps; + else if (tgt_fps == 30) + frame_rate = ov5642_30_fps; + else { + pr_err(" The camera frame rate is not supported!\n"); + return -EINVAL; + } + + sensor->streamcap.timeperframe = *timeperframe; + sensor->streamcap.capturemode = + (u32)a->parm.capture.capturemode; + + ret = ov5642_init_mode(frame_rate, + sensor->streamcap.capturemode); + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_debug(" type is not " \ + "V4L2_BUF_TYPE_VIDEO_CAPTURE but %d\n", + a->type); + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + vc->value = ov5642_data.brightness; + break; + case V4L2_CID_HUE: + vc->value = ov5642_data.hue; + break; + case V4L2_CID_CONTRAST: + vc->value = ov5642_data.contrast; + break; + case V4L2_CID_SATURATION: + vc->value = ov5642_data.saturation; + break; + case V4L2_CID_RED_BALANCE: + vc->value = ov5642_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + vc->value = ov5642_data.blue; + break; + case V4L2_CID_EXPOSURE: + vc->value = ov5642_data.ae_mode; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In ov5642:ioctl_s_ctrl %d\n", + vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + break; + case V4L2_CID_CONTRAST: + break; + case V4L2_CID_SATURATION: + break; + case V4L2_CID_HUE: + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + break; + case V4L2_CID_DO_WHITE_BALANCE: + break; + case V4L2_CID_RED_BALANCE: + break; + case V4L2_CID_BLUE_BALANCE: + break; + case V4L2_CID_GAMMA: + break; + case V4L2_CID_EXPOSURE: + break; + case V4L2_CID_AUTOGAIN: + break; + case V4L2_CID_GAIN: + break; + case V4L2_CID_HFLIP: + break; + case V4L2_CID_VFLIP: + break; + default: + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_enum_framesizes - V4L2 sensor interface handler for + * VIDIOC_ENUM_FRAMESIZES ioctl + * @s: pointer to standard V4L2 device structure + * @fsize: standard V4L2 VIDIOC_ENUM_FRAMESIZES ioctl structure + * + * Return 0 if successful, otherwise -EINVAL. + */ +static int ioctl_enum_framesizes(struct v4l2_int_device *s, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > ov5642_mode_MAX) + return -EINVAL; + + fsize->pixel_format = ov5642_data.pix.pixelformat; + fsize->discrete.width = + max(ov5642_mode_info_data[0][fsize->index].width, + ov5642_mode_info_data[1][fsize->index].width); + fsize->discrete.height = + max(ov5642_mode_info_data[0][fsize->index].height, + ov5642_mode_info_data[1][fsize->index].height); + return 0; +} + +/*! + * ioctl_g_chip_ident - V4L2 sensor interface handler for + * VIDIOC_DBG_G_CHIP_IDENT ioctl + * @s: pointer to standard V4L2 device structure + * @id: pointer to int + * + * Return 0. + */ +static int ioctl_g_chip_ident(struct v4l2_int_device *s, int *id) +{ + ((struct v4l2_dbg_chip_ident *)id)->match.type = + V4L2_CHIP_MATCH_I2C_DRIVER; + strcpy(((struct v4l2_dbg_chip_ident *)id)->match.name, "ov5642_camera"); + + return 0; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + + return 0; +} + +/*! + * ioctl_enum_fmt_cap - V4L2 sensor interface handler for VIDIOC_ENUM_FMT + * @s: pointer to standard V4L2 device structure + * @fmt: pointer to standard V4L2 fmt description structure + * + * Return 0. + */ +static int ioctl_enum_fmt_cap(struct v4l2_int_device *s, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->index > ov5642_mode_MAX) + return -EINVAL; + + fmt->pixelformat = ov5642_data.pix.pixelformat; + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct sensor *sensor = s->priv; + u32 tgt_xclk; /* target xclk */ + u32 tgt_fps; /* target frames per secound */ + enum ov5642_frame_rate frame_rate; + + gpio_sensor_active(ov5642_data.csi); + ov5642_data.on = true; + + /* mclk */ + tgt_xclk = ov5642_data.mclk; + tgt_xclk = min(tgt_xclk, (u32)OV5642_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)OV5642_XCLK_MIN); + ov5642_data.mclk = tgt_xclk; + + pr_debug(" Setting mclk to %d MHz\n", tgt_xclk / 1000000); + set_mclk_rate(&ov5642_data.mclk, ov5642_data.csi); + + /* Default camera frame rate is set in probe */ + tgt_fps = sensor->streamcap.timeperframe.denominator / + sensor->streamcap.timeperframe.numerator; + + if (tgt_fps == 15) + frame_rate = ov5642_15_fps; + else if (tgt_fps == 30) + frame_rate = ov5642_30_fps; + else + return -EINVAL; /* Only support 15fps or 30fps now. */ + + return ov5642_init_mode(frame_rate, + sensor->streamcap.capturemode); +} + +/*! + * ioctl_dev_exit - V4L2 sensor interface handler for vidioc_int_dev_exit_num + * @s: pointer to standard V4L2 device structure + * + * Delinitialise the device when slave detaches to the master. + */ +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + gpio_sensor_inactive(ov5642_data.csi); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc ov5642_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + {vidioc_int_dev_exit_num, ioctl_dev_exit}, + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, + {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, + {vidioc_int_enum_framesizes_num, + (v4l2_int_ioctl_func *)ioctl_enum_framesizes}, + {vidioc_int_g_chip_ident_num, + (v4l2_int_ioctl_func *)ioctl_g_chip_ident}, +}; + +static struct v4l2_int_slave ov5642_slave = { + .ioctls = ov5642_ioctl_desc, + .num_ioctls = ARRAY_SIZE(ov5642_ioctl_desc), +}; + +static struct v4l2_int_device ov5642_int_device = { + .module = THIS_MODULE, + .name = "ov5642", + .type = v4l2_int_type_slave, + .u = { + .slave = &ov5642_slave, + }, +}; + +/*! + * ov5642 I2C probe function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int ov5642_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + /* Set initial values for the sensor struct. */ + memset(&ov5642_data, 0, sizeof(ov5642_data)); + ov5642_data.mclk = 24000000; /* 6 - 54 MHz, typical 24MHz */ + ov5642_data.mclk = plat_data->mclk; + ov5642_data.csi = plat_data->csi; + + ov5642_data.i2c_client = client; + ov5642_data.pix.pixelformat = V4L2_PIX_FMT_YUYV; + ov5642_data.pix.width = 640; + ov5642_data.pix.height = 480; + ov5642_data.streamcap.capability = V4L2_MODE_HIGHQUALITY | + V4L2_CAP_TIMEPERFRAME; + ov5642_data.streamcap.capturemode = 0; + ov5642_data.streamcap.timeperframe.denominator = DEFAULT_FPS; + ov5642_data.streamcap.timeperframe.numerator = 1; + + if (plat_data->io_regulator) { + io_regulator = regulator_get(&client->dev, + plat_data->io_regulator); + if (!IS_ERR(io_regulator)) { + regulator_set_voltage(io_regulator, + OV5642_VOLTAGE_DIGITAL_IO, + OV5642_VOLTAGE_DIGITAL_IO); + if (regulator_enable(io_regulator) != 0) { + pr_err("%s:io set voltage error\n", __func__); + goto err1; + } else { + dev_dbg(&client->dev, + "%s:io set voltage ok\n", __func__); + } + } else + io_regulator = NULL; + } + + if (plat_data->core_regulator) { + core_regulator = regulator_get(&client->dev, + plat_data->core_regulator); + if (!IS_ERR(core_regulator)) { + regulator_set_voltage(core_regulator, + OV5642_VOLTAGE_DIGITAL_CORE, + OV5642_VOLTAGE_DIGITAL_CORE); + if (regulator_enable(core_regulator) != 0) { + pr_err("%s:core set voltage error\n", __func__); + goto err2; + } else { + dev_dbg(&client->dev, + "%s:core set voltage ok\n", __func__); + } + } else + core_regulator = NULL; + } + + if (plat_data->analog_regulator) { + analog_regulator = regulator_get(&client->dev, + plat_data->analog_regulator); + if (!IS_ERR(analog_regulator)) { + regulator_set_voltage(analog_regulator, + OV5642_VOLTAGE_ANALOG, + OV5642_VOLTAGE_ANALOG); + if (regulator_enable(analog_regulator) != 0) { + pr_err("%s:analog set voltage error\n", + __func__); + goto err3; + } else { + dev_dbg(&client->dev, + "%s:analog set voltage ok\n", __func__); + } + } else + analog_regulator = NULL; + } + + if (plat_data->pwdn) + plat_data->pwdn(0); + + camera_plat = plat_data; + + ov5642_int_device.priv = &ov5642_data; + retval = v4l2_int_device_register(&ov5642_int_device); + + return retval; + +err3: + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } +err2: + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } +err1: + return -1; +} + +/*! + * ov5642 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int ov5642_remove(struct i2c_client *client) +{ + v4l2_int_device_unregister(&ov5642_int_device); + + if (gpo_regulator) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + return 0; +} + +/*! + * ov5642 init function + * Called by insmod ov5642_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int ov5642_init(void) +{ + u8 err; + + err = i2c_add_driver(&ov5642_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * OV5642 cleanup function + * Called on rmmod ov5642_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit ov5642_clean(void) +{ + i2c_del_driver(&ov5642_i2c_driver); +} + +module_init(ov5642_init); +module_exit(ov5642_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV5642 Camera Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("CSI"); diff --git a/drivers/media/video/mxc/capture/sensor_clock.c b/drivers/media/video/mxc/capture/sensor_clock.c new file mode 100644 index 000000000000..31560b575575 --- /dev/null +++ b/drivers/media/video/mxc/capture/sensor_clock.c @@ -0,0 +1,97 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sensor_clock.c + * + * @brief camera clock function + * + * @ingroup Camera + */ +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_MXC_IPU_V1) || defined(CONFIG_VIDEO_MXC_EMMA_CAMERA) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA_MODULE) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA) +/* + * set_mclk_rate + * + * @param p_mclk_freq mclk frequence + * + */ +void set_mclk_rate(uint32_t *p_mclk_freq) +{ + struct clk *clk; + uint32_t freq = 0; + + clk = clk_get(NULL, "csi_clk"); + + freq = clk_round_rate(clk, *p_mclk_freq); + clk_set_rate(clk, freq); + + *p_mclk_freq = freq; + + clk_put(clk); + pr_debug("mclk frequency = %d\n", *p_mclk_freq); +} +#else +/* + * set_mclk_rate + * + * @param p_mclk_freq mclk frequence + * @param csi csi 0 or csi 1 + * + */ +void set_mclk_rate(uint32_t *p_mclk_freq, uint32_t csi) +{ + struct clk *clk; + uint32_t freq = 0; + char *mclk; + + if (cpu_is_mx53()) { + if (csi == 0) + mclk = "ssi_ext1_clk"; + else { + pr_err("invalid csi num %d\n", csi); + return; + } + } else { + if (csi == 0) { + mclk = "csi_mclk1"; + } else if (csi == 1) { + mclk = "csi_mclk2"; + } else { + pr_err("invalid csi num %d\n", csi); + return; + } + } + + clk = clk_get(NULL, mclk); + + freq = clk_round_rate(clk, *p_mclk_freq); + clk_set_rate(clk, freq); + + *p_mclk_freq = freq; + + clk_put(clk); + pr_debug("%s frequency = %d\n", mclk, *p_mclk_freq); +} +#endif + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(set_mclk_rate); diff --git a/drivers/media/video/mxc/opl/Makefile b/drivers/media/video/mxc/opl/Makefile new file mode 100644 index 000000000000..092a62c5ac4a --- /dev/null +++ b/drivers/media/video/mxc/opl/Makefile @@ -0,0 +1,5 @@ +opl-objs := opl_mod.o rotate90_u16.o rotate270_u16.o \ + rotate90_u16_qcif.o rotate270_u16_qcif.o \ + vmirror_u16.o hmirror_rotate180_u16.o + +obj-$(CONFIG_VIDEO_MXC_OPL) += opl.o diff --git a/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c b/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c new file mode 100644 index 000000000000..b03d7d99be59 --- /dev/null +++ b/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c @@ -0,0 +1,259 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include "opl.h" + +static inline u32 rot_left_u16(u16 x, unsigned int n) +{ + return (x << n) | (x >> (16 - n)); +} + +static inline u32 rot_left_u32(u32 x, unsigned int n) +{ + return (x << n) | (x >> (32 - n)); +} + +static inline u32 byte_swap_u32(u32 x) +{ + u32 t1, t2, t3; + + t1 = x ^ ((x << 16) | x >> 16); + t2 = t1 & 0xff00ffff; + t3 = (x >> 8) | (x << 24); + return t3 ^ (t2 >> 8); +} + +static int opl_hmirror_u16_by1(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by2(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by4(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by8(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror); + +int opl_hmirror_u16(const u8 *src, int src_line_stride, int width, int height, + u8 *dst, int dst_line_stride) +{ + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + if (width % 8 == 0) + return opl_hmirror_u16_by8(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else if (width % 4 == 0) + return opl_hmirror_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else if (width % 2 == 0) + return opl_hmirror_u16_by2(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else /* (width % 1) */ + return opl_hmirror_u16_by1(src, src_line_stride, width, height, + dst, dst_line_stride, 0); +} + +int opl_rotate180_u16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride) +{ + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + if (width % 8 == 0) + return opl_hmirror_u16_by8(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else if (width % 4 == 0) + return opl_hmirror_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else if (width % 2 == 0) + return opl_hmirror_u16_by2(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else /* (width % 1) */ + return opl_hmirror_u16_by1(src, src_line_stride, width, height, + dst, dst_line_stride, 1); +} + +static int opl_hmirror_u16_by1(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + u16 pixel; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 1) * BYTES_PER_PIXEL + - (BYTES_PER_PIXEL - BYTES_PER_PIXEL); + for (j = 0; j < width; j++) { + pixel = *(u16 *) psrc; + *(u16 *) pdst = pixel; + psrc += BYTES_PER_PIXEL; + pdst -= BYTES_PER_PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by2(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + u32 pixelsin, pixelsout; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 2) * BYTES_PER_PIXEL; + for (j = 0; j < (width >> 1); j++) { + pixelsin = *(u32 *) psrc; + pixelsout = rot_left_u32(pixelsin, 16); + *(u32 *) pdst = pixelsout; + psrc += BYTES_PER_2PIXEL; + pdst -= BYTES_PER_2PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by4(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + + union doubleword { + u64 dw; + u32 w[2]; + }; + + union doubleword inbuf; + union doubleword outbuf; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 4) * BYTES_PER_PIXEL; + for (j = 0; j < (width >> 2); j++) { + inbuf.dw = *(u64 *) psrc; + outbuf.w[0] = rot_left_u32(inbuf.w[1], 16); + outbuf.w[1] = rot_left_u32(inbuf.w[0], 16); + *(u64 *) pdst = outbuf.dw; + psrc += BYTES_PER_4PIXEL; + pdst -= BYTES_PER_4PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by8(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 1) * BYTES_PER_PIXEL - 2; + for (j = (width >> 3); j > 0; j--) { + __asm__ volatile ( + "ldmia %0!,{r2-r5}\n\t" + "mov r6, r2\n\t" + "mov r7, r3\n\t" + "mov r2, r5, ROR #16\n\t" + "mov r3, r4, ROR #16\n\t" + "mov r4, r7, ROR #16\n\t" + "mov r5, r6, ROR #16\n\t" + "stmda %1!,{r2-r5}\n\t" + + : "+r"(psrc), "+r"(pdst) + : "0"(psrc), "1"(pdst) + : "r2", "r3", "r4", "r5", "r6", "r7", + "memory" + ); + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_hmirror_u16); +EXPORT_SYMBOL(opl_rotate180_u16); diff --git a/drivers/media/video/mxc/opl/opl.h b/drivers/media/video/mxc/opl/opl.h new file mode 100644 index 000000000000..f1158a83712c --- /dev/null +++ b/drivers/media/video/mxc/opl/opl.h @@ -0,0 +1,162 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup OPLIP OPL Image Processing + */ +/*! + * @file opl.h + * + * @brief The OPL (Open Primitives Library) Image Processing library defines + * efficient functions for rotation and mirroring. + * + * It includes ARM9-optimized rotation and mirroring functions. It is derived + * from the original OPL project which is found at sourceforge.freescale.net. + * + * @ingroup OPLIP + */ +#ifndef __OPL_H__ +#define __OPL_H__ + +#include + +#define BYTES_PER_PIXEL 2 +#define CACHE_LINE_WORDS 8 +#define BYTES_PER_WORD 4 + +#define BYTES_PER_2PIXEL (BYTES_PER_PIXEL * 2) +#define BYTES_PER_4PIXEL (BYTES_PER_PIXEL * 4) +#define BYTES_PER_8PIXEL (BYTES_PER_PIXEL * 8) + +#define QCIF_Y_WIDTH 176 +#define QCIF_Y_HEIGHT 144 + +/*! Enumerations of opl error code */ +enum opl_error { + OPLERR_SUCCESS = 0, + OPLERR_NULL_PTR, + OPLERR_BAD_ARG, + OPLERR_DIV_BY_ZERO, + OPLERR_OVER_FLOW, + OPLERR_UNDER_FLOW, + OPLERR_MISALIGNED, +}; + +/*! + * @brief Rotate a 16bbp buffer 90 degrees clockwise. + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate90_u16(const u8 *src, int src_line_stride, int width, int height, + u8 *dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 180 degrees clockwise. + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate180_u16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 270 degrees clockwise + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate270_u16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride); + +/*! + * @brief Mirror a 16bpp buffer horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_hmirror_u16(const u8 *src, int src_line_stride, int width, int height, + u8 *dst, int dst_line_stride); + +/*! + * @brief Mirror a 16bpp buffer vertically + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_vmirror_u16(const u8 *src, int src_line_stride, int width, int height, + u8 *dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 90 degrees clockwise and mirror vertically + * It is equivalent to rotate 270 degree and mirror horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate90_vmirror_u16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 270 degrees clockwise and mirror vertically + * It is equivalent to rotate 90 degree and mirror horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate270_vmirror_u16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride); + +#endif /* __OPL_H__ */ diff --git a/drivers/media/video/mxc/opl/opl_mod.c b/drivers/media/video/mxc/opl/opl_mod.c new file mode 100644 index 000000000000..7df5063b4aa0 --- /dev/null +++ b/drivers/media/video/mxc/opl/opl_mod.c @@ -0,0 +1,30 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include + +static __init int opl_init(void) +{ + return 0; +} + +static void __exit opl_exit(void) +{ +} + +module_init(opl_init); +module_exit(opl_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OPL Software Rotation/Mirroring"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/opl/rotate270_u16.c b/drivers/media/video/mxc/opl/rotate270_u16.c new file mode 100644 index 000000000000..9a5c7e0a5c79 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate270_u16.c @@ -0,0 +1,285 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include "opl.h" + +static int opl_rotate270_u16_by16(const u8 *src, int src_line_stride, + int width, int height, u8 *dst, + int dst_line_stride, int vmirror); +static int opl_rotate270_u16_by4(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror); +static int opl_rotate270_vmirror_u16_both(const u8 *src, int src_line_stride, + int width, int height, u8 *dst, + int dst_line_stride, int vmirror); +int opl_rotate270_u16_qcif(const u8 *src, u8 *dst); + +int opl_rotate270_u16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride) +{ + return opl_rotate270_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 0); +} + +int opl_rotate270_vmirror_u16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride) +{ + return opl_rotate270_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 1); +} + +static int opl_rotate270_vmirror_u16_both(const u8 *src, int src_line_stride, + int width, int height, u8 *dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_PIXELS_BY4 = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + /* The QCIF algorithm doesn't support vertical mirroring */ + if (vmirror == 0 && width == QCIF_Y_WIDTH && height == QCIF_Y_HEIGHT + && src_line_stride == QCIF_Y_WIDTH * 2 + && src_line_stride == QCIF_Y_HEIGHT * 2) + return opl_rotate270_u16_qcif(src, dst); + else if (width % BLOCK_SIZE_PIXELS == 0 + && height % BLOCK_SIZE_PIXELS == 0) + return opl_rotate270_u16_by16(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else if (width % BLOCK_SIZE_PIXELS_BY4 == 0 + && height % BLOCK_SIZE_PIXELS_BY4 == 0) + return opl_rotate270_u16_by4(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else + return OPLERR_BAD_ARG; +} + +/* + * Rotate Counter Clockwise, divide RGB component into 16 row strips, read + * non sequentially and write sequentially. This is done in 16 line strips + * so that the cache is used better. Cachelines are 8 words = 32 bytes. Pixels + * are 2 bytes. The 16 reads will be cache misses, but the next 240 should + * be from cache. The writes to the output buffer will be sequential for 16 + * writes. + * + * Example: + * Input data matrix: output matrix + * + * 0 | 1 | 2 | 3 | 4 | 4 | 0 | 0 | 3 | + * 4 | 3 | 2 | 1 | 0 | 3 | 1 | 9 | 6 | + * 6 | 7 | 8 | 9 | 0 | 2 | 2 | 8 | 2 | + * 5 | 3 | 2 | 6 | 3 | 1 | 3 | 7 | 3 | + * ^ 0 | 4 | 6 | 5 | < Write the input data sequentially + * Read first column + * Start at the bottom + * Move to next column and repeat + * + * Loop over k decreasing (blocks) + * in_block_ptr = src + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_PIXELS) * (RGB_WIDTH_BYTES) + * out_block_ptr = dst + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_BYTES) + (RGB_WIDTH_PIXELS - 1) + * * RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + * + * Loop over i decreasing (width) + * Each pix: + * in_block_ptr += RGB_WIDTH_BYTES + * out_block_ptr += 4 + * + * Each row of block: + * in_block_ptr -= RGB_WIDTH_BYTES * BLOCK_SIZE_PIXELS - 2 + * out_block_ptr -= RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + 2 * BLOCK_SIZE_PIXELS; + * + * It may perform vertical mirroring too depending on the vmirror flag. + */ +static int opl_rotate270_u16_by16(const u8 *src, int src_line_stride, + int width, int height, u8 *dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + - BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + : dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS) * src_line_stride; + out_block_ptr = dst + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS * BYTES_PER_PIXEL) + + (width - 1) * dst_line_stride; + + /* + * For vertical mirroring the writing starts from the + * first line + */ + if (vmirror) + out_block_ptr -= dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + : "+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + : "0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + : "r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr -= IN_INDEX; + out_block_ptr -= OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +/* + * Rotate Counter Clockwise, divide RGB component into 4 row strips, read + * non sequentially and write sequentially. This is done in 4 line strips + * so that the cache is used better. Cachelines are 8 words = 32 bytes. Pixels + * are 2 bytes. The 4 reads will be cache misses, but the next 60 should + * be from cache. The writes to the output buffer will be sequential for 4 + * writes. + * + * Example: + * Input data matrix: output matrix + * + * 0 | 1 | 2 | 3 | 4 | 4 | 0 | 0 | 3 | + * 4 | 3 | 2 | 1 | 0 | 3 | 1 | 9 | 6 | + * 6 | 7 | 8 | 9 | 0 | 2 | 2 | 8 | 2 | + * 5 | 3 | 2 | 6 | 3 | 1 | 3 | 7 | 3 | + * ^ 0 | 4 | 6 | 5 | < Write the input data sequentially + * Read first column + * Start at the bottom + * Move to next column and repeat + * + * Loop over k decreasing (blocks) + * in_block_ptr = src + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_PIXELS) * (RGB_WIDTH_BYTES) + * out_block_ptr = dst + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_BYTES) + (RGB_WIDTH_PIXELS - 1) + * * RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + * + * Loop over i decreasing (width) + * Each pix: + * in_block_ptr += RGB_WIDTH_BYTES + * out_block_ptr += 4 + * + * Each row of block: + * in_block_ptr -= RGB_WIDTH_BYTES * BLOCK_SIZE_PIXELS - 2 + * out_block_ptr -= RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + 2 * BLOCK_SIZE_PIXELS; + * + * It may perform vertical mirroring too depending on the vmirror flag. + */ +static int opl_rotate270_u16_by4(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + - BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + : dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS) * src_line_stride; + out_block_ptr = dst + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS * BYTES_PER_PIXEL) + + (width - 1) * dst_line_stride; + + /* + * For vertical mirroring the writing starts from the + * first line + */ + if (vmirror) + out_block_ptr -= dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + : "+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + : "0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + : "r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr -= IN_INDEX; + out_block_ptr -= OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_rotate270_u16); +EXPORT_SYMBOL(opl_rotate270_vmirror_u16); diff --git a/drivers/media/video/mxc/opl/rotate270_u16_qcif.S b/drivers/media/video/mxc/opl/rotate270_u16_qcif.S new file mode 100644 index 000000000000..6a28af0e3edf --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate270_u16_qcif.S @@ -0,0 +1,70 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include + + .text + .align 2 +ENTRY(opl_rotate270_u16_qcif) + STMFD sp!,{r4-r10} + MOV r12,#0x160 + MOV r10,#0x90 + MOV r3,r10,LSR #4 +.L1.16: + RSB r2,r3,r10,LSR #4 + MOV r5,r2,LSL #5 + MOV r4,r12,LSR #1 + SMULBB r4,r5,r4 + ADD r2,r1,r2,LSL #5 + ADD r5,r2,#0xc000 + ADD r5,r5,#0x4e0 + MOV r2,r12,LSR #1 + ADD r4,r0,r4 +.L1.52: + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + SUBS r2,r2,#1 + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + SUB r4,r4,#0x1500 + STMIA r5,{r6,r7} + SUB r5,r5,#0x138 + SUB r4,r4,#0xfe + BGT .L1.52 + SUBS r3,r3,#1 + BGT .L1.16 + LDMFD sp!,{r4-r10} + BX lr + .size opl_rotate270_u16_qcif, . - opl_rotate270_u16_qcif diff --git a/drivers/media/video/mxc/opl/rotate90_u16.c b/drivers/media/video/mxc/opl/rotate90_u16.c new file mode 100644 index 000000000000..553d41ba45c8 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate90_u16.c @@ -0,0 +1,220 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include "opl.h" + +static int opl_rotate90_u16_by16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror); +static int opl_rotate90_u16_by4(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror); +static int opl_rotate90_vmirror_u16_both(const u8 *src, int src_line_stride, + int width, int height, u8 *dst, + int dst_line_stride, int vmirror); +int opl_rotate90_u16_qcif(const u8 *src, u8 *dst); + +int opl_rotate90_u16(const u8 *src, int src_line_stride, int width, int height, + u8 *dst, int dst_line_stride) +{ + return opl_rotate90_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 0); +} + +int opl_rotate90_vmirror_u16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride) +{ + return opl_rotate90_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 1); +} + +static int opl_rotate90_vmirror_u16_both(const u8 *src, int src_line_stride, + int width, int height, u8 *dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_PIXELS_BY4 = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + /* The QCIF algorithm doesn't support vertical mirroring */ + if (vmirror == 0 && width == QCIF_Y_WIDTH && height == QCIF_Y_HEIGHT + && src_line_stride == QCIF_Y_WIDTH * 2 + && src_line_stride == QCIF_Y_HEIGHT * 2) + return opl_rotate90_u16_qcif(src, dst); + else if (width % BLOCK_SIZE_PIXELS == 0 + && height % BLOCK_SIZE_PIXELS == 0) + return opl_rotate90_u16_by16(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else if (width % BLOCK_SIZE_PIXELS_BY4 == 0 + && height % BLOCK_SIZE_PIXELS_BY4 == 0) + return opl_rotate90_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, vmirror); + else + return OPLERR_BAD_ARG; +} + +/* + * Performs clockwise rotation (and possibly vertical mirroring depending + * on the vmirror flag) using block sizes of 16x16 + * The algorithm is similar to 270 degree clockwise rotation algorithm + */ +static int opl_rotate90_u16_by16(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_BYTES = BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + + BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride - BLOCK_SIZE_BYTES + : dst_line_stride - BLOCK_SIZE_BYTES; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + src_line_stride * (height - 1) + - (src_line_stride * BLOCK_SIZE_PIXELS * + (height / BLOCK_SIZE_PIXELS - k)); + out_block_ptr = dst + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS * + ((height / BLOCK_SIZE_PIXELS) - k); + + /* + * For vertical mirroring the writing starts from the + * bottom line + */ + if (vmirror) + out_block_ptr += dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + : "+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + : "0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + : "r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr += IN_INDEX; + out_block_ptr += OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +/* + * Performs clockwise rotation (and possibly vertical mirroring depending + * on the vmirror flag) using block sizes of 4x4 + * The algorithm is similar to 270 degree clockwise rotation algorithm + */ +static int opl_rotate90_u16_by4(const u8 *src, int src_line_stride, int width, + int height, u8 *dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + const int BLOCK_SIZE_BYTES = BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + + BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride - BLOCK_SIZE_BYTES + : dst_line_stride - BLOCK_SIZE_BYTES; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + src_line_stride * (height - 1) + - (src_line_stride * BLOCK_SIZE_PIXELS * + (height / BLOCK_SIZE_PIXELS - k)); + out_block_ptr = dst + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + * ((height / BLOCK_SIZE_PIXELS) - k); + + /* + * For horizontal mirroring the writing starts from the + * bottom line + */ + if (vmirror) + out_block_ptr += dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + : "+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + : "0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + : "r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr += IN_INDEX; + out_block_ptr += OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_rotate90_u16); +EXPORT_SYMBOL(opl_rotate90_vmirror_u16); diff --git a/drivers/media/video/mxc/opl/rotate90_u16_qcif.S b/drivers/media/video/mxc/opl/rotate90_u16_qcif.S new file mode 100644 index 000000000000..f76e0e69a7c6 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate90_u16_qcif.S @@ -0,0 +1,71 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include + + .text + .align 2 +ENTRY(opl_rotate90_u16_qcif) + STMFD sp!,{r4-r10} + MOV r12,#0x160 + MOV r10,#0x90 + MOV r3,r10,LSR #4 +.L1.216: + RSB r2,r3,r10,LSR #4 + MOV r4,#0x20 + SMULBB r5,r4,r2 + MOV r4,#0x1600 + SMULBB r2,r4,r2 + ADD r4,r0,#0xc000 + ADD r4,r4,#0x4a0 + SUB r4,r4,r2 + MOV r2,r12,LSR #1 + ADD r5,r1,r5 +.L1.256: + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + SUBS r2,r2,#1 + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + ADD r4,r4,#0x1600 + STMIA r5!,{r6,r7} + ADD r5,r5,#0x100 + ADD r4,r4,#2 + BGT .L1.256 + SUBS r3,r3,#1 + BGT .L1.216 + LDMFD sp!,{r4-r10} + BX lr + .size opl_rotate90_u16_qcif, . - opl_rotate90_u16_qcif diff --git a/drivers/media/video/mxc/opl/vmirror_u16.c b/drivers/media/video/mxc/opl/vmirror_u16.c new file mode 100644 index 000000000000..05b6bbc5fe74 --- /dev/null +++ b/drivers/media/video/mxc/opl/vmirror_u16.c @@ -0,0 +1,46 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include "opl.h" + +int opl_vmirror_u16(const u8 *src, int src_line_stride, int width, int height, + u8 *dst, int dst_line_stride) +{ + const u8 *src_row_addr; + u8 *dst_row_addr; + int i; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + src_row_addr = src; + dst_row_addr = dst + (height - 1) * dst_line_stride; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* memcpy each row */ + memcpy(dst_row_addr, src_row_addr, BYTES_PER_PIXEL * width); + src_row_addr += src_line_stride; + dst_row_addr -= dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_vmirror_u16); diff --git a/drivers/media/video/mxc/output/Kconfig b/drivers/media/video/mxc/output/Kconfig new file mode 100644 index 000000000000..2153ad248907 --- /dev/null +++ b/drivers/media/video/mxc/output/Kconfig @@ -0,0 +1,28 @@ +config VIDEO_MXC_IPU_OUTPUT + bool "IPU v4l2 support" + depends on VIDEO_MXC_OUTPUT && MXC_IPU + default y + ---help--- + This is the video4linux2 driver for IPU post processing video output. + +config VIDEO_MXC_IPUV1_WVGA_OUTPUT + bool "IPUv1 WVGA v4l2 display support" + depends on VIDEO_MXC_OUTPUT && MXC_IPU + default n + ---help--- + This is the video4linux2 driver for IPUv1 WVGA post processing video output. + +config VIDEO_MXC_EMMA_OUTPUT + bool + depends on VIDEO_MXC_OUTPUT && MXC_EMMA && FB_MXC_SYNC_PANEL + default y + ---help--- + This is the video4linux2 driver for EMMA post processing video output. + +config VIDEO_MXC_OUTPUT_FBSYNC + bool "Synchronize the output with LCDC refresh" + depends on VIDEO_MXC_EMMA_OUTPUT + default y + ---help--- + Synchronize the post-processing with LCDC EOF (End of Frame) to + prevent tearing issue. If unsure, say Y. diff --git a/drivers/media/video/mxc/output/Makefile b/drivers/media/video/mxc/output/Makefile new file mode 100644 index 000000000000..500442544902 --- /dev/null +++ b/drivers/media/video/mxc/output/Makefile @@ -0,0 +1,14 @@ +ifeq ($(CONFIG_VIDEO_MXC_EMMA_OUTPUT),y) + mx27_output-objs := mx27_v4l2_output.o mx27_pp.o + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx27_output.o +endif + +ifeq ($(CONFIG_VIDEO_MXC_IPU_OUTPUT),y) + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mxc_v4l2_output.o +endif +ifeq ($(CONFIG_VIDEO_MXC_PXP_V4L2),y) + obj-$(CONFIG_VIDEO_MXC_PXP_V4L2) += mxc_pxp_v4l2.o +endif +ifeq ($(CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT),y) + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx31_v4l2_wvga_output.o +endif diff --git a/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c new file mode 100644 index 000000000000..a165c61c9a48 --- /dev/null +++ b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c @@ -0,0 +1,1928 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/output/mxc_v4l2_output.c + * + * @brief MXC V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "mxc_v4l2_output.h" + +vout_data *g_vout; +#define SDC_FG_FB_FORMAT IPU_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[2] = { + { + .index = MXC_V4L2_OUT_2_SDC, + .name = "DISP3 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, + { + .index = MXC_V4L2_OUT_2_ADC, + .name = "DISPx Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN} +}; + +static int video_nr = 16; +static DEFINE_SPINLOCK(g_lock); +static unsigned int g_pp_out_number; +static unsigned int g_pp_in_number; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((type == IPU_INPUT_BUFFER) ? ((uint32_t) ch & 0xFF) : + ((type == IPU_OUTPUT_BUFFER) ? (((uint32_t) ch >> 8) & 0xFF) + : (((uint32_t) ch >> 16) & 0xFF))); +}; + +static inline uint32_t DMAParamAddr(uint32_t dma_ch) +{ + return 0x10000 | (dma_ch << 4); +}; + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static inline int queue_size(v4l_queue *q) +{ + if (q->tail >= q->head) + return q->tail - q->head; + else + return (q->tail + QUEUE_SIZE) - q->head; +} + +static inline int queue_buf(v4l_queue *q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static inline int dequeue_buf(v4l_queue *q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static inline int peek_next_buf(v4l_queue *q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + printk(KERN_ERR "dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, + * V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB24: + bpp = 24; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + bpp = 32; + break; + default: + bpp = 8; + break; + } + return bpp; +} + +static u32 bpp_to_fmt(struct fb_info *fbi) +{ + if (fbi->var.nonstd) + return fbi->var.nonstd; + + if (fbi->var.bits_per_pixel == 24) + return V4L2_PIX_FMT_BGR24; + else if (fbi->var.bits_per_pixel == 32) + return V4L2_PIX_FMT_BGR32; + else if (fbi->var.bits_per_pixel == 16) + return V4L2_PIX_FMT_RGB565; + + return 0; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index; + unsigned long timeout; + unsigned long lock_flags = 0; + vout_data *vout = (vout_data *) arg; + + dev_dbg(vout->video_dev->dev, "timer handler: %lu\n", jiffies); + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_STOPPING) + || (vout->state == STATE_STREAM_OFF)) + goto exit0; + /* + * If timer occurs before IPU h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP comletes + */ + if (vout->ipu_buf[0] != -1) { + dev_dbg(vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* One frame buffer should be ready here */ + if (vout->frame_count % 2 == 1) { + /* set BUF0 rdy */ + if (ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0) < + 0) + pr_debug("error selecting display buf 0"); + } else { + if (ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1) < + 0) + pr_debug("error selecting display buf 1"); + } + + /* Dequeue buffer and pass to IPU */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + + g_buf_dq_cnt++; + vout->frame_count++; + vout->ipu_buf[1] = vout->ipu_buf[0] = index; + + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 0, + vout->v4l2_bufs[vout->ipu_buf[0]].m. + offset) < 0) + goto exit0; + + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 1, + vout->v4l2_bufs[vout->ipu_buf[0]].m. + offset + vout->v2f.fmt.pix.width / 2) < 0) + goto exit0; + + /* All buffer should now ready in IPU out, tranfer to display buf */ + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 0, + vout-> + display_bufs[(vout->frame_count - + 1) % 2]) < 0) { + dev_err(vout->video_dev->dev, + "unable to update buffer %d address\n", + vout->next_rdy_ipu_buf); + goto exit0; + } + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 1, + vout-> + display_bufs[(vout->frame_count - + 1) % 2] + + vout->crop_current.width / 2 * + bytes_per_pixel(SDC_FG_FB_FORMAT)) < 0) { + dev_err(vout->video_dev->dev, + "unable to update buffer %d address\n", + vout->next_rdy_ipu_buf); + goto exit0; + } + + if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0) < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + goto exit0; + } + + if (ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0) < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + goto exit0; + } + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + dev_dbg(vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + if (mod_timer(&vout->output_timer, timeout)) + dev_dbg(vout->video_dev->dev, + "warning: timer was already set\n"); + + dev_dbg(vout->video_dev->dev, + "timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + } + +exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +extern void _ipu_write_param_mem(uint32_t addr, uint32_t *data, + uint32_t numWords); + +static irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + unsigned long lock_flags = 0; + vout_data *vout = dev_id; + uint32_t u_offset; + uint32_t v_offset; + uint32_t local_params[4]; + uint32_t width, height; + uint32_t dma_chan; + + spin_lock_irqsave(&g_lock, lock_flags); + g_irq_cnt++; + + dma_chan = channel_2_dma(vout->post_proc_ch, IPU_INPUT_BUFFER); + memset(&local_params, 0, sizeof(local_params)); + + if (g_pp_in_number % 2 == 1) { + u_offset = vout->offset.u_offset - vout->v2f.fmt.pix.width / 4; + v_offset = vout->offset.v_offset - vout->v2f.fmt.pix.width / 4; + width = vout->v2f.fmt.pix.width / 2; + height = vout->v2f.fmt.pix.height; + local_params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - + 1) << 24); + local_params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + local_params[2] = u_offset >> (64 - 53); + local_params[2] |= v_offset << (79 - 64); + local_params[3] |= v_offset >> (96 - 79); + _ipu_write_param_mem(DMAParamAddr(dma_chan), local_params, 4); + + if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1) < + 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + } else { + u_offset = vout->offset.u_offset; + v_offset = vout->offset.v_offset; + width = vout->v2f.fmt.pix.width / 2; + height = vout->v2f.fmt.pix.height; + local_params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - + 1) << 24); + local_params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + local_params[2] = u_offset >> (64 - 53); + local_params[2] |= v_offset << (79 - 64); + local_params[3] |= v_offset >> (96 - 79); + _ipu_write_param_mem(DMAParamAddr(dma_chan), local_params, 4); + } + g_pp_in_number++; + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_v4l2out_pp_out_irq_handler(int irq, void *dev_id) +{ + vout_data *vout = dev_id; + int index; + unsigned long timeout; + u32 lock_flags = 0; + + spin_lock_irqsave(&g_lock, lock_flags); + + if (g_pp_out_number % 2 == 1) { + if (ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1) + < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + } else { + if (vout->ipu_buf[0] != -1) { + vout->v4l2_bufs[vout->ipu_buf[0]].flags = + V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, vout->ipu_buf[0]); + wake_up_interruptible(&vout->v4l_bufq); + vout->ipu_buf[0] = -1; + } + index = peek_next_buf(&vout->ready_q); + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) + && (vout->ipu_buf[1] == -1)) + vout->state = STATE_STREAM_OFF; + } else if ((vout->state == STATE_STREAM_PAUSED) + && (index != -1)) { + /*! + * Setup timer for next buffer, + * when stream has been paused + */ + pr_debug("next index %d\n", index); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + pr_debug + ("warning: timer timeout" + "already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + } + g_pp_out_number++; + + spin_unlock_irqrestore(&g_lock, lock_flags); + return IRQ_HANDLED; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data *vout) +{ + struct device *dev = vout->video_dev->dev; + ipu_channel_params_t params; + struct mxcfb_pos fb_pos; + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int pp_in_buf[2]; + u16 out_width; + u16 out_height; + ipu_channel_t display_input_ch = MEM_PP_MEM; + bool use_direct_adc = false; + mm_segment_t old_fs; + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 2) { + dev_err(dev, "2 buffers not been queued yet!\n"); + return -EINVAL; + } + + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + + vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0; + vout->ipu_buf[0] = pp_in_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[1] = pp_in_buf[1] = vout->ipu_buf[0]; + vout->frame_count = 1; + g_pp_out_number = 1; + g_pp_in_number = 1; + + ipu_enable_irq(IPU_IRQ_PP_IN_EOF); + ipu_enable_irq(IPU_IRQ_PP_OUT_EOF); + + /* Init Display Channel */ +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, 0); + fbi = NULL; + if (ipu_can_rotate_in_place(vout->rotate)) { + dev_dbg(dev, "Using PP direct to ADC channel\n"); + use_direct_adc = true; + vout->display_ch = MEM_PP_ADC; + vout->post_proc_ch = MEM_PP_ADC; + + memset(¶ms, 0, sizeof(params)); + params.mem_pp_adc.in_width = vout->v2f.fmt.pix.width; + params.mem_pp_adc.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_adc.in_pixel_fmt = + vout->v2f.fmt.pix.pixelformat; + params.mem_pp_adc.out_width = out_width; + params.mem_pp_adc.out_height = out_height; + params.mem_pp_adc.out_pixel_fmt = SDC_FG_FB_FORMAT; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.mem_pp_adc.out_left = + 2 + vout->crop_current.left; +#else + params.mem_pp_adc.out_left = + 12 + vout->crop_current.left; +#endif + params.mem_pp_adc.out_top = vout->crop_current.top; + if (ipu_init_channel( + vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP chan\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_adc. + in_pixel_fmt, + params.mem_pp_adc.in_width, + params.mem_pp_adc.in_height, + vout->v2f.fmt.pix. + bytesperline / + bytes_per_pixel(params. + mem_pp_adc. + in_pixel_fmt), + vout->rotate, + vout-> + v4l2_bufs[pp_in_buf[0]].m. + offset, + vout-> + v4l2_bufs[pp_in_buf[1]].m. + offset, + vout->offset.u_offset, + vout->offset.v_offset) != + 0) { + dev_err(dev, "Error initializing PP in buf\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_adc. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, 0, 0, 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + + } else { + dev_dbg(dev, "Using ADC SYS2 channel\n"); + vout->display_ch = ADC_SYS2; + vout->post_proc_ch = MEM_PP_MEM; + + if (vout->display_bufs[0]) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + } + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + memset(¶ms, 0, sizeof(params)); + params.adc_sys2.disp = vout->cur_disp_output; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left = 2 + vout->crop_current.left; +#else + params.adc_sys2.out_left = 12 + vout->crop_current.left; +#endif + params.adc_sys2.out_top = vout->crop_current.top; + if (ipu_init_channel(ADC_SYS2, ¶ms) < 0) + return -EINVAL; + + if (ipu_init_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + SDC_FG_FB_FORMAT, + out_width, out_height, + out_width, IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing SDC FG buffer\n"); + return -EINVAL; + } + } + } else +#endif + { /* Use SDC */ + dev_dbg(dev, "Using SDC channel\n"); + + fbvar = fbi->var; + if (vout->cur_disp_output == 3) { + vout->display_ch = MEM_FG_SYNC; + fbvar.bits_per_pixel = 16; + fbvar.nonstd = IPU_PIX_FMT_UYVY; + + fbvar.xres = fbvar.xres_virtual = out_width; + fbvar.yres = out_height; + fbvar.yres_virtual = out_height * 2; + } else if (vout->cur_disp_output == 5) { + vout->display_ch = MEM_DC_SYNC; + fbvar.bits_per_pixel = 16; + fbvar.nonstd = IPU_PIX_FMT_UYVY; + + fbvar.xres = fbvar.xres_virtual = out_width; + fbvar.yres = out_height; + fbvar.yres_virtual = out_height * 2; + } else { + vout->display_ch = MEM_BG_SYNC; + } + + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + fb_pos.x = vout->crop_current.left; + fb_pos.y = vout->crop_current.top; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + + vout->display_bufs[1] = fbi->fix.smem_start; + vout->display_bufs[0] = fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres); + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * fbi->var.bits_per_pixel / 8; + + vout->post_proc_ch = MEM_PP_MEM; + } + + /* Init PP */ + if (use_direct_adc == false) { + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.height; + out_height = vout->crop_current.width; + } + memset(¶ms, 0, sizeof(params)); + params.mem_pp_mem.in_width = vout->v2f.fmt.pix.width / 2; + params.mem_pp_mem.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params.mem_pp_mem.out_width = out_width / 2; + params.mem_pp_mem.out_height = out_height; + if (vout->display_ch == ADC_SYS2) + params.mem_pp_mem.out_pixel_fmt = SDC_FG_FB_FORMAT; + else + params.mem_pp_mem.out_pixel_fmt = bpp_to_fmt(fbi); + if (ipu_init_channel(vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_mem.in_pixel_fmt, + params.mem_pp_mem.in_width, + params.mem_pp_mem.in_height, + vout->v2f.fmt.pix.bytesperline / + bytes_per_pixel(params.mem_pp_mem. + in_pixel_fmt), + IPU_ROTATE_NONE, + vout->v4l2_bufs[pp_in_buf[0]].m. + offset, + vout->v4l2_bufs[pp_in_buf[0]].m. + offset + params.mem_pp_mem.in_width, + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + dev_err(dev, "Error initializing PP input buffer\n"); + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) + return -ENOBUFS; + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing" + "PP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) { + dev_err(dev, + "Error initializing PP ROT channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_INPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP ROT" + "input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, + MEM_ROT_PP_MEM) < 0) + return -EINVAL; + + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1); + + ipu_enable_channel(MEM_ROT_PP_MEM); + + display_input_ch = MEM_ROT_PP_MEM; + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, + out_width / 2, + out_height, + out_width, + vout->rotate, + vout->display_bufs[0], + vout->display_bufs[0] + + + out_width / 2 * + bytes_per_pixel + (SDC_FG_FB_FORMAT), 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + } + if (ipu_unlink_channels( + display_input_ch, vout->display_ch) < 0) { + dev_err(dev, "Error linking ipu channels\n"); + return -EINVAL; + } + } + + vout->state = STATE_STREAM_PAUSED; + + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + + if (use_direct_adc == false) { + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0); + ipu_enable_channel(vout->post_proc_ch); + + if (fbi) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_UNBLANK); + release_console_sem(); + } else { + ipu_enable_channel(vout->display_ch); + } + } else { + ipu_enable_channel(vout->post_proc_ch); + } + + vout->start_jiffies = jiffies; + dev_dbg(dev, + "streamon: start time = %lu jiffies\n", vout->start_jiffies); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int i, retval = 0; + unsigned long lockflag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) + return 0; + + spin_lock_irqsave(&g_lock, lockflag); + + del_timer(&vout->output_timer); + + if (vout->state == STATE_STREAM_ON) + vout->state = STATE_STREAM_STOPPING; + + ipu_disable_irq(IPU_IRQ_PP_IN_EOF); + ipu_disable_irq(IPU_IRQ_PP_OUT_EOF); + + spin_unlock_irqrestore(&g_lock, lockflag); + + if (vout->post_proc_ch == MEM_PP_MEM) { /* SDC or ADC with Rotation */ + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM); + ipu_unlink_channels(MEM_ROT_PP_MEM, vout->display_ch); + ipu_disable_channel(MEM_ROT_PP_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } else { + ipu_unlink_channels(MEM_PP_MEM, vout->display_ch); + } + ipu_disable_channel(MEM_PP_MEM, true); + + if (vout->display_ch == ADC_SYS2) { + ipu_disable_channel(vout->display_ch, true); + ipu_uninit_channel(vout->display_ch); + } else { + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + + if (vout->display_ch == MEM_FG_SYNC) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + } + + ipu_uninit_channel(MEM_PP_MEM); + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_PP_MEM); + } else { /* ADC Direct */ + ipu_disable_channel(MEM_PP_ADC, true); + ipu_uninit_channel(MEM_PP_ADC); + } + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + if (vout->display_bufs[0] != 0) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size); + } + + mxcfb_set_refresh_mode(registered_fb + [vout-> + output_fb_num[vout->cur_disp_output]], + MXCFB_REFRESH_PARTIAL, 0); + } +#endif + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_NV12) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data *vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data *vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + dev_err(vout->video_dev->dev, "pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + if (f->fmt.pix.sizeimage < size) + f->fmt.pix.sizeimage = size; + else + size = f->fmt.pix.sizeimage; + + vout->v2f.fmt.pix = f->fmt.pix; + if (vout->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&vout->offset, + (void *)vout->v2f.fmt.pix.priv, + sizeof(vout->offset))) { + retval = -EFAULT; + goto err0; + } + } else { + vout->offset.u_offset = 0; + vout->offset.v_offset = 0; + } + + retval = 0; +err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data *vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data *vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_VFLIP: + vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_MXC_ROT: + vout->rotate = c->value; + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) + return -ENODEV; + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + if (vout->open_count++ == 0) { + ipu_request_irq(IPU_IRQ_PP_IN_EOF, + mxc_v4l2out_pp_in_irq_handler, + 0, dev->name, vout); + ipu_request_irq(IPU_IRQ_PP_OUT_EOF, + mxc_v4l2out_pp_out_irq_handler, + 0, dev->name, vout); + + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + vout->rotate = IPU_ROTATE_NONE; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + + up(&vout->busy_lock); + + return 0; + +oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + ipu_free_irq(IPU_IRQ_PP_IN_EOF, vout); + ipu_free_irq(IPU_IRQ_PP_OUT_EOF, vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *vdev = file->private_data; + vout_data *vout = video_get_drvdata(vdev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: incorrect" + "buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS:" + "freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) + req->count = MIN_FRAME_NUM; + else if (req->count > MAX_FRAME_NUM) + req->count = MAX_FRAME_NUM; + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + dev_dbg(vdev->dev, + "VIDIOC_QUERYBUFS: incorrect" + "buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + retval = -EINVAL; + break; + } + + dev_dbg(vdev->dev, "VIDIOC_QBUF: %d\n", buf->index); + + /* mmapped buffers are L1 WB cached, + * so we need to clean them */ + if (buf->flags & V4L2_BUF_FLAG_MAPPED) + flush_cache_all(); + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + queue_buf(&vout->ready_q, index); + if (vout->state == STATE_STREAM_PAUSED) { + unsigned long timeout; + + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + dev_dbg(vout->video_dev->dev, + "warning: timer timeout" + "already expired.\n"); + } + vout->output_timer.expires = timeout; + dev_dbg(vdev->dev, + "QBUF: frame #%u timeout @" + " %lu jiffies, current = %lu\n", + vout->frame_count, timeout, jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + dev_dbg(vdev->dev, "VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: no free buffers\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: buffer in done q, " + "but not flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + dev_dbg(vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + /* only full screen supported for SDC BG */ + if (vout->cur_disp_output == 4) { + crop->c = vout->crop_current; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top >= b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.left = b->left; + if (crop->c.left >= b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 5) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + if (output->index < 3) { + *output = mxc_outputs[MXC_V4L2_OUT_2_ADC]; + output->name[4] = '0' + output->index; + } else { + *output = mxc_outputs[MXC_V4L2_OUT_2_SDC]; + } + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + int fbnum; + struct v4l2_rect *b; + + if ((*p_output_num >= MXC_V4L2_OUT_NUM_OUTPUTS) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + + /* Update bounds in case they have changed */ + b = &vout->crop_bounds[vout->cur_disp_output]; + + fbnum = vout->output_fb_num[vout->cur_disp_output]; + if (vout->cur_disp_output == 3) + fbnum = vout->output_fb_num[4]; + + b->width = registered_fb[fbnum]->var.xres; + b->height = registered_fb[fbnum]->var.yres; + + vout->crop_current = *b; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + int i; + vout_data *vout = video_get_drvdata(vdev); + + dev_dbg(vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + for (i = 0; i < vout->buffer_cnt; i++) { + if ((vout->v4l2_bufs[i].m.offset == + (vma->vm_pgoff << PAGE_SHIFT)) && + (vout->v4l2_bufs[i].length >= size)) { + vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED; + break; + } + } + if (i == vout->buffer_cnt) { + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + /* make buffers inner write-back, outer write-thru cacheable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + dev_dbg(vdev->dev, "mmap remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + +mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .owner = THIS_MODULE, + .name = "MXC Video Output", + .type = 0, + .type2 = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->dev = &pdev->dev; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + dev_dbg(&pdev->dev, "video_register_device failed\n"); + return 0; + } + dev_info(&pdev->dev, "Registered device video%d\n", + vout->video_dev->minor & 0x1f); + vout->video_dev->dev = &pdev->dev; + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = idstr[4] - '0'; + if (disp_num == 3) { + if (strcmp(idstr, "DISP3 BG - DI1") == 0) + disp_num = 5; + else if (strncmp(idstr, "DISP3 BG", 8) == 0) + disp_num = 4; + } + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) + vout->cur_disp_output = disp_num; + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + platform_set_drvdata(pdev, vout); + + return 0; +} + +static int mxc_v4l2out_remove(struct platform_device *pdev) +{ + vout_data *vout = platform_get_drvdata(pdev); + + if (vout->video_dev) { + if (-1 != vout->video_dev->minor) + video_unregister_device(vout->video_dev); + else + video_device_release(vout->video_dev); + vout->video_dev = NULL; + } + + platform_set_drvdata(pdev, NULL); + + kfree(vout); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + }, + .probe = mxc_v4l2out_probe, + .remove = mxc_v4l2out_remove, +}; + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) + platform_device_register(&mxc_v4l2out_device); + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mxc_pxp_v4l2.c b/drivers/media/video/mxc/output/mxc_pxp_v4l2.c new file mode 100644 index 000000000000..a26b5d918a26 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_pxp_v4l2.c @@ -0,0 +1,1237 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * Based on STMP378X PxP driver + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mxc_pxp_v4l2.h" + +#define PXP_DRIVER_NAME "pxp-v4l2" +#define PXP_DRIVER_MAJOR 2 +#define PXP_DRIVER_MINOR 0 + +#define PXP_DEF_BUFS 2 +#define PXP_MIN_PIX 8 + +#define V4L2_OUTPUT_TYPE_INTERNAL 4 + +static struct pxp_data_format pxp_s0_formats[] = { + { + .name = "24-bit RGB", + .bpp = 4, + .fourcc = V4L2_PIX_FMT_RGB24, + .colorspace = V4L2_COLORSPACE_SRGB, + }, { + .name = "16-bit RGB 5:6:5", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_RGB565, + .colorspace = V4L2_COLORSPACE_SRGB, + }, { + .name = "16-bit RGB 5:5:5", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_RGB555, + .colorspace = V4L2_COLORSPACE_SRGB, + }, { + .name = "YUV 4:2:0 Planar", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_YUV420, + .colorspace = V4L2_COLORSPACE_JPEG, + }, { + .name = "YUV 4:2:2 Planar", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_YUV422P, + .colorspace = V4L2_COLORSPACE_JPEG, + }, +}; + +static unsigned int v4l2_fmt_to_pxp_fmt(u32 v4l2_pix_fmt) +{ + u32 pxp_fmt = 0; + + if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB24) + pxp_fmt = PXP_PIX_FMT_RGB24; + else if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB565) + pxp_fmt = PXP_PIX_FMT_RGB565; + else if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB555) + pxp_fmt = PXP_PIX_FMT_RGB555; + else if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB555) + pxp_fmt = PXP_PIX_FMT_RGB555; + else if (v4l2_pix_fmt == V4L2_PIX_FMT_YUV420) + pxp_fmt = PXP_PIX_FMT_YUV420P; + else if (v4l2_pix_fmt == V4L2_PIX_FMT_YUV422P) + pxp_fmt = PXP_PIX_FMT_YUV422P; + + return pxp_fmt; +} +struct v4l2_queryctrl pxp_controls[] = { + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_PRIVATE_BASE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Rotation", + .minimum = 0, + .maximum = 270, + .step = 90, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_PRIVATE_BASE + 1, + .name = "Background Color", + .minimum = 0, + .maximum = 0xFFFFFF, + .step = 1, + .default_value = 0, + .flags = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_PRIVATE_BASE + 2, + .name = "Set S0 Chromakey", + .minimum = -1, + .maximum = 0xFFFFFF, + .step = 1, + .default_value = -1, + .flags = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_PRIVATE_BASE + 3, + .name = "YUV Colorspace", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, +}; + +/* callback function */ +static void video_dma_done(void *arg) +{ + struct pxp_tx_desc *tx_desc = to_tx_desc(arg); + struct dma_chan *chan = tx_desc->txd.chan; + struct pxp_channel *pxp_chan = to_pxp_channel(chan); + struct pxps *pxp = pxp_chan->client; + struct videobuf_buffer *vb; + + dev_dbg(chan->device->dev, "callback cookie %d, active DMA 0x%08x\n", + tx_desc->txd.cookie, + pxp->active ? sg_dma_address(&pxp->active->sg[0]) : 0); + + spin_lock(&pxp->lock); + if (pxp->active) { + vb = &pxp->active->vb; + + list_del_init(&vb->queue); + vb->state = VIDEOBUF_DONE; + do_gettimeofday(&vb->ts); + vb->field_count++; + wake_up(&vb->done); + } + + if (list_empty(&pxp->outq)) { + pxp->active = NULL; + spin_unlock(&pxp->lock); + + return; + } + + pxp->active = list_entry(pxp->outq.next, + struct pxp_buffer, vb.queue); + pxp->active->vb.state = VIDEOBUF_ACTIVE; + spin_unlock(&pxp->lock); +} + +static int acquire_dma_channel(struct pxps *pxp) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + struct pxp_channel **pchan = &pxp->pxp_channel[0]; + + if (*pchan) { + struct videobuf_buffer *vb, *_vb; + dma_release_channel(&(*pchan)->dma_chan); + *pchan = NULL; + pxp->active = NULL; + list_for_each_entry_safe(vb, _vb, &pxp->outq, queue) { + list_del_init(&vb->queue); + vb->state = VIDEOBUF_ERROR; + wake_up(&vb->done); + } + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + chan = dma_request_channel(mask, NULL, NULL); + if (!chan) + return -EBUSY; + + *pchan = to_pxp_channel(chan); + (*pchan)->client = pxp; + + return 0; +} + +static int _get_fbinfo(struct fb_info **fbi) +{ + int i; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, "mxc_elcdif_fb") == 0) { + *fbi = registered_fb[i]; + return 0; + } + } + + return -ENODEV; +} + +static int pxp_set_fbinfo(struct pxps *pxp) +{ + struct fb_info *fbi; + struct v4l2_framebuffer *fb = &pxp->fb; + int err; + + err = _get_fbinfo(&fbi); + if (err) + return err; + + fb->fmt.width = fbi->var.xres; + fb->fmt.height = fbi->var.yres; + if (fbi->var.bits_per_pixel == 16) + fb->fmt.pixelformat = V4L2_PIX_FMT_RGB565; + else + fb->fmt.pixelformat = V4L2_PIX_FMT_RGB24; + fb->base = (void *)fbi->fix.smem_start; + + return 0; +} + +static int _get_cur_fb_blank(struct pxps *pxp) +{ + struct fb_info *fbi; + mm_segment_t old_fs; + int err = 0; + + err = _get_fbinfo(&fbi); + if (err) + return err; + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_BLANK, + (unsigned int)(&pxp->fb_blank)); + set_fs(old_fs); + } + + return err; +} + +static int set_fb_blank(int blank) +{ + struct fb_info *fbi; + int err = 0; + + err = _get_fbinfo(&fbi); + if (err) + return err; + + acquire_console_sem(); + fb_blank(fbi, blank); + release_console_sem(); + + return err; +} + +static int pxp_set_cstate(struct pxps *pxp, struct v4l2_control *vc) +{ + + if (vc->id == V4L2_CID_HFLIP) { + pxp->pxp_conf.proc_data.hflip = vc->value; + } else if (vc->id == V4L2_CID_VFLIP) { + pxp->pxp_conf.proc_data.vflip = vc->value; + } else if (vc->id == V4L2_CID_PRIVATE_BASE) { + if (vc->value % 90) + return -ERANGE; + pxp->pxp_conf.proc_data.rotate = vc->value; + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) { + pxp->pxp_conf.proc_data.bgcolor = vc->value; + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) { + pxp->pxp_conf.s0_param.color_key = vc->value; + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) { + pxp->pxp_conf.proc_data.yuv = vc->value; + } + + return 0; +} + +static int pxp_get_cstate(struct pxps *pxp, struct v4l2_control *vc) +{ + if (vc->id == V4L2_CID_HFLIP) + vc->value = pxp->pxp_conf.proc_data.hflip; + else if (vc->id == V4L2_CID_VFLIP) + vc->value = pxp->pxp_conf.proc_data.vflip; + else if (vc->id == V4L2_CID_PRIVATE_BASE) + vc->value = pxp->pxp_conf.proc_data.rotate; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) + vc->value = pxp->pxp_conf.proc_data.bgcolor; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) + vc->value = pxp->pxp_conf.s0_param.color_key; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) + vc->value = pxp->pxp_conf.proc_data.yuv; + + return 0; +} + +static int pxp_enumoutput(struct file *file, void *fh, + struct v4l2_output *o) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if ((o->index < 0) || (o->index > 1)) + return -EINVAL; + + memset(o, 0, sizeof(struct v4l2_output)); + if (o->index == 0) { + strcpy(o->name, "PxP Display Output"); + pxp->output = 0; + } else { + strcpy(o->name, "PxP Virtual Output"); + pxp->output = 1; + } + o->type = V4L2_OUTPUT_TYPE_INTERNAL; + o->std = 0; + o->reserved[0] = pxp->outb_phys; + + return 0; +} + +static int pxp_g_output(struct file *file, void *fh, + unsigned int *i) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + *i = pxp->output; + + return 0; +} + +static int pxp_s_output(struct file *file, void *fh, + unsigned int i) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_pix_format *fmt = &pxp->fb.fmt; + int bpp; + + if ((i < 0) || (i > 1)) + return -EINVAL; + + if (pxp->outb) + return 0; + + /* Output buffer is same format as fbdev */ + if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) + bpp = 4; + else + bpp = 2; + + pxp->outb_size = fmt->width * fmt->height * bpp; + pxp->outb = kmalloc(fmt->width * fmt->height * bpp, GFP_KERNEL); + pxp->outb_phys = virt_to_phys(pxp->outb); + dma_map_single(NULL, pxp->outb, + fmt->width * fmt->height * bpp, DMA_TO_DEVICE); + + pxp->pxp_conf.out_param.width = fmt->width; + pxp->pxp_conf.out_param.height = fmt->height; + if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) + pxp->pxp_conf.out_param.pixel_fmt = PXP_PIX_FMT_RGB24; + else + pxp->pxp_conf.out_param.pixel_fmt = PXP_PIX_FMT_RGB565; + + return 0; +} + +static int pxp_enum_fmt_video_output(struct file *file, void *fh, + struct v4l2_fmtdesc *fmt) +{ + enum v4l2_buf_type type = fmt->type; + int index = fmt->index; + + if ((fmt->index < 0) || (fmt->index >= ARRAY_SIZE(pxp_s0_formats))) + return -EINVAL; + + memset(fmt, 0, sizeof(struct v4l2_fmtdesc)); + fmt->index = index; + fmt->type = type; + fmt->pixelformat = pxp_s0_formats[index].fourcc; + strcpy(fmt->description, pxp_s0_formats[index].name); + + return 0; +} + +static int pxp_g_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pf = &f->fmt.pix; + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct pxp_data_format *fmt = pxp->s0_fmt; + + pf->width = pxp->pxp_conf.s0_param.width; + pf->height = pxp->pxp_conf.s0_param.height; + pf->pixelformat = fmt->fourcc; + pf->field = V4L2_FIELD_NONE; + pf->bytesperline = fmt->bpp * pf->width; + pf->sizeimage = pf->bytesperline * pf->height; + pf->colorspace = fmt->colorspace; + pf->priv = 0; + + return 0; +} + +static struct pxp_data_format *pxp_get_format(struct v4l2_format *f) +{ + struct pxp_data_format *fmt; + int i; + + for (i = 0; i < ARRAY_SIZE(pxp_s0_formats); i++) { + fmt = &pxp_s0_formats[i]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (i == ARRAY_SIZE(pxp_s0_formats)) + return NULL; + + return &pxp_s0_formats[i]; +} + +static int pxp_try_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + int w = f->fmt.pix.width; + int h = f->fmt.pix.height; + struct pxp_data_format *fmt = pxp_get_format(f); + + if (!fmt) + return -EINVAL; + + w = min(w, 2040); + w = max(w, 8); + h = min(h, 2040); + h = max(h, 8); + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = w; + f->fmt.pix.height = h; + f->fmt.pix.pixelformat = fmt->fourcc; + + return 0; +} + +static int pxp_s_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_pix_format *pf = &f->fmt.pix; + int ret; + + ret = acquire_dma_channel(pxp); + if (ret < 0) + return ret; + + ret = pxp_try_fmt_video_output(file, fh, f); + if (ret == 0) { + pxp->s0_fmt = pxp_get_format(f); + pxp->pxp_conf.s0_param.pixel_fmt = + v4l2_fmt_to_pxp_fmt(pxp->s0_fmt->fourcc); + pxp->pxp_conf.s0_param.width = pf->width; + pxp->pxp_conf.s0_param.height = pf->height; + } + + + return ret; +} + +static int pxp_g_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + + memset(wf, 0, sizeof(struct v4l2_window)); + wf->chromakey = pxp->s1_chromakey; + wf->global_alpha = pxp->global_alpha; + wf->field = V4L2_FIELD_NONE; + wf->clips = NULL; + wf->clipcount = 0; + wf->bitmap = NULL; + wf->w.left = pxp->pxp_conf.proc_data.srect.left; + wf->w.top = pxp->pxp_conf.proc_data.srect.top; + wf->w.width = pxp->pxp_conf.proc_data.srect.width; + wf->w.height = pxp->pxp_conf.proc_data.srect.height; + + return 0; +} + +static int pxp_try_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + struct v4l2_rect srect; + u32 s1_chromakey = wf->chromakey; + u8 global_alpha = wf->global_alpha; + + memcpy(&srect, &(wf->w), sizeof(struct v4l2_rect)); + + pxp_g_fmt_output_overlay(file, fh, f); + + wf->chromakey = s1_chromakey; + wf->global_alpha = global_alpha; + + /* Constrain parameters to the input buffer */ + wf->w.left = srect.left; + wf->w.top = srect.top; + wf->w.width = min(srect.width, + ((__s32)pxp->pxp_conf.s0_param.width - wf->w.left)); + wf->w.height = min(srect.height, + ((__s32)pxp->pxp_conf.s0_param.height - wf->w.top)); + + return 0; +} + +static int pxp_s_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + int ret = pxp_try_fmt_output_overlay(file, fh, f); + + if (ret == 0) { + pxp->global_alpha = wf->global_alpha; + pxp->s1_chromakey = wf->chromakey; + pxp->pxp_conf.proc_data.srect.left = wf->w.left; + pxp->pxp_conf.proc_data.srect.top = wf->w.top; + pxp->pxp_conf.proc_data.srect.width = wf->w.width; + pxp->pxp_conf.proc_data.srect.height = wf->w.height; + pxp->pxp_conf.ol_param[0].global_alpha = pxp->global_alpha; + pxp->pxp_conf.ol_param[0].color_key = pxp->s1_chromakey; + pxp->pxp_conf.ol_param[0].color_key_enable = + pxp->s1_chromakey_state; + } + + return ret; +} + +static int pxp_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *r) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_reqbufs(&pxp->s0_vbq, r); +} + +static int pxp_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_querybuf(&pxp->s0_vbq, b); +} + +static int pxp_qbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_qbuf(&pxp->s0_vbq, b); +} + +static int pxp_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_dqbuf(&pxp->s0_vbq, b, file->f_flags & O_NONBLOCK); +} + +static int pxp_streamon(struct file *file, void *priv, + enum v4l2_buf_type t) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + if (t != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + _get_cur_fb_blank(pxp); + set_fb_blank(FB_BLANK_UNBLANK); + + ret = videobuf_streamon(&pxp->s0_vbq); + + if (!ret && (pxp->output == 0)) + mxc_elcdif_frame_addr_setup(pxp->outb_phys); + + return ret; +} + +static int pxp_streamoff(struct file *file, void *priv, + enum v4l2_buf_type t) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + ret = videobuf_streamoff(&pxp->s0_vbq); + + if (!ret) + mxc_elcdif_frame_addr_setup((dma_addr_t)pxp->fb.base); + + if (pxp->fb_blank) + set_fb_blank(FB_BLANK_POWERDOWN); + + return ret; +} + +static int pxp_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned *size) +{ + struct pxps *pxp = q->priv_data; + + *size = pxp->pxp_conf.s0_param.width * + pxp->pxp_conf.s0_param.height * pxp->s0_fmt->bpp; + + if (0 == *count) + *count = PXP_DEF_BUFS; + + return 0; +} + +static void pxp_buf_free(struct videobuf_queue *q, struct pxp_buffer *buf) +{ + struct videobuf_buffer *vb = &buf->vb; + struct dma_async_tx_descriptor *txd = buf->txd; + + BUG_ON(in_interrupt()); + + pr_debug("%s (vb=0x%p) 0x%08lx %d\n", __func__, + vb, vb->baddr, vb->bsize); + + /* + * This waits until this buffer is out of danger, i.e., until it is no + * longer in STATE_QUEUED or STATE_ACTIVE + */ + videobuf_waiton(vb, 0, 0); + if (txd) + async_tx_ack(txd); + + videobuf_dma_contig_free(q, vb); + buf->txd = NULL; + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static int pxp_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct pxps *pxp = q->priv_data; + struct pxp_config_data *pxp_conf = &pxp->pxp_conf; + struct pxp_proc_data *proc_data = &pxp_conf->proc_data; + struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb); + struct pxp_tx_desc *desc; + int ret = 0; + int i, length; + + vb->width = pxp->pxp_conf.s0_param.width; + vb->height = pxp->pxp_conf.s0_param.height; + vb->size = vb->width * vb->height * pxp->s0_fmt->bpp; + vb->field = V4L2_FIELD_NONE; + if (vb->state != VIDEOBUF_NEEDS_INIT) + pxp_buf_free(q, buf); + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + struct pxp_channel *pchan = pxp->pxp_channel[0]; + struct scatterlist *sg = &buf->sg; + + /* This actually (allocates and) maps buffers */ + ret = videobuf_iolock(q, vb, NULL); + if (ret) { + pr_err("fail to call videobuf_iolock, ret = %d\n", ret); + goto fail; + } + + /* + * sg[0] for input(S0) + * Sg[1] for output + */ + sg_init_table(sg, 3); + + buf->txd = pchan->dma_chan.device->device_prep_slave_sg( + &pchan->dma_chan, sg, 3, DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT); + if (!buf->txd) { + ret = -EIO; + goto fail; + } + + buf->txd->callback_param = buf->txd; + buf->txd->callback = video_dma_done; + + desc = to_tx_desc(buf->txd); + length = desc->len; + for (i = 0; i < length; i++) { + if (i == 0) {/* S0 */ + memcpy(&desc->proc_data, proc_data, + sizeof(struct pxp_proc_data)); + pxp_conf->s0_param.paddr = + videobuf_to_dma_contig(vb); + memcpy(&desc->layer_param.s0_param, + &pxp_conf->s0_param, + sizeof(struct pxp_layer_param)); + } else if (i == 1) { /* Output */ + if (proc_data->rotate % 180) { + pxp_conf->out_param.width = + pxp->fb.fmt.height; + pxp_conf->out_param.height = + pxp->fb.fmt.width; + } else { + pxp_conf->out_param.width = + pxp->fb.fmt.width; + pxp_conf->out_param.height = + pxp->fb.fmt.height; + } + + pxp_conf->out_param.paddr = pxp->outb_phys; + memcpy(&desc->layer_param.out_param, + &pxp_conf->out_param, + sizeof(struct pxp_layer_param)); + } else if (pxp_conf->ol_param[0].combine_enable) { + /* Overlay */ + pxp_conf->ol_param[0].paddr = + (dma_addr_t)pxp->fb.base; + pxp_conf->ol_param[0].width = pxp->fb.fmt.width; + pxp_conf->ol_param[0].height = + pxp->fb.fmt.height; + pxp_conf->ol_param[0].pixel_fmt = + pxp_conf->out_param.pixel_fmt; + memcpy(&desc->layer_param.ol_param, + &pxp_conf->ol_param[0], + sizeof(struct pxp_layer_param)); + } + + desc = desc->next; + } + + vb->state = VIDEOBUF_PREPARED; + } + + return 0; + +fail: + pxp_buf_free(q, buf); + return ret; +} + + +static void pxp_buf_queue(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct pxps *pxp = q->priv_data; + struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb); + struct dma_async_tx_descriptor *txd = buf->txd; + struct pxp_channel *pchan = pxp->pxp_channel[0]; + dma_cookie_t cookie; + + BUG_ON(!irqs_disabled()); + + list_add_tail(&vb->queue, &pxp->outq); + + if (!pxp->active) { + pxp->active = buf; + vb->state = VIDEOBUF_ACTIVE; + } else { + vb->state = VIDEOBUF_QUEUED; + } + + spin_unlock_irq(&pxp->lock); + + cookie = txd->tx_submit(txd); + dev_dbg(&pxp->pdev->dev, "Submitted cookie %d DMA 0x%08x\n", + cookie, sg_dma_address(&buf->sg[0])); + mdelay(5); + /* trigger ePxP */ + dma_async_issue_pending(&pchan->dma_chan); + + spin_lock_irq(&pxp->lock); + + if (cookie >= 0) + return; + + /* Submit error */ + pr_err("%s: Submit error\n", __func__); + vb->state = VIDEOBUF_PREPARED; + + list_del_init(&vb->queue); + + if (pxp->active == buf) + pxp->active = NULL; +} + +static void pxp_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct pxps *pxp = q->priv_data; + struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb); + unsigned long flags; + + spin_lock_irqsave(&pxp->lock, flags); + if ((vb->state == VIDEOBUF_ACTIVE || vb->state == VIDEOBUF_QUEUED) && + !list_empty(&vb->queue)) { + vb->state = VIDEOBUF_ERROR; + + list_del_init(&vb->queue); + if (pxp->active == buf) + pxp->active = NULL; + } + spin_unlock_irqrestore(&pxp->lock, flags); + + pxp_buf_free(q, buf); +} + +static struct videobuf_queue_ops pxp_vbq_ops = { + .buf_setup = pxp_buf_setup, + .buf_prepare = pxp_buf_prepare, + .buf_queue = pxp_buf_queue, + .buf_release = pxp_buf_release, +}; + +static int pxp_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + memset(cap, 0, sizeof(*cap)); + strcpy(cap->driver, "pxp"); + strcpy(cap->card, "pxp"); + strlcpy(cap->bus_info, dev_name(&pxp->pdev->dev), + sizeof(cap->bus_info)); + + cap->version = (PXP_DRIVER_MAJOR << 8) + PXP_DRIVER_MINOR; + + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OUTPUT_OVERLAY | + V4L2_CAP_STREAMING; + + return 0; +} + +static int pxp_g_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fb) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + memset(fb, 0, sizeof(*fb)); + + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY | + V4L2_FBUF_CAP_CHROMAKEY | + V4L2_FBUF_CAP_LOCAL_ALPHA | + V4L2_FBUF_CAP_GLOBAL_ALPHA; + + if (pxp->global_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA; + if (pxp->s1_chromakey_state) + fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY; + + return 0; +} + +static int pxp_s_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fb) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + pxp->overlay_state = + (fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0; + pxp->global_alpha_state = + (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0; + pxp->s1_chromakey_state = + (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0; + + pxp->pxp_conf.ol_param[0].combine_enable = pxp->overlay_state; + pxp->pxp_conf.ol_param[0].global_alpha_enable = pxp->global_alpha_state; + + return 0; +} + +static int pxp_g_crop(struct file *file, void *fh, + struct v4l2_crop *c) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) + return -EINVAL; + + c->c.left = pxp->pxp_conf.proc_data.drect.left; + c->c.top = pxp->pxp_conf.proc_data.drect.top; + c->c.width = pxp->pxp_conf.proc_data.drect.width; + c->c.height = pxp->pxp_conf.proc_data.drect.height; + + return 0; +} + +static int pxp_s_crop(struct file *file, void *fh, + struct v4l2_crop *c) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int l = c->c.left; + int t = c->c.top; + int w = c->c.width; + int h = c->c.height; + int fbw = pxp->fb.fmt.width; + int fbh = pxp->fb.fmt.height; + + if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) + return -EINVAL; + + /* Constrain parameters to FB limits */ + w = min(w, fbw); + w = max(w, PXP_MIN_PIX); + h = min(h, fbh); + h = max(h, PXP_MIN_PIX); + if ((l + w) > fbw) + l = 0; + if ((t + h) > fbh) + t = 0; + + /* Round up values to PxP pixel block */ + l = roundup(l, PXP_MIN_PIX); + t = roundup(t, PXP_MIN_PIX); + w = roundup(w, PXP_MIN_PIX); + h = roundup(h, PXP_MIN_PIX); + + pxp->pxp_conf.proc_data.drect.left = l; + pxp->pxp_conf.proc_data.drect.top = t; + pxp->pxp_conf.proc_data.drect.width = w; + pxp->pxp_conf.proc_data.drect.height = h; + + return 0; +} + +static int pxp_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (qc->id && qc->id == pxp_controls[i].id) { + memcpy(qc, &(pxp_controls[i]), sizeof(*qc)); + return 0; + } + + return -EINVAL; +} + +static int pxp_g_ctrl(struct file *file, void *priv, + struct v4l2_control *vc) +{ + int i; + + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (vc->id == pxp_controls[i].id) + return pxp_get_cstate(pxp, vc); + + return -EINVAL; +} + +static int pxp_s_ctrl(struct file *file, void *priv, + struct v4l2_control *vc) +{ + int i; + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (vc->id == pxp_controls[i].id) { + if (vc->value < pxp_controls[i].minimum || + vc->value > pxp_controls[i].maximum) + return -ERANGE; + return pxp_set_cstate(pxp, vc); + } + + return -EINVAL; +} + +void pxp_release(struct video_device *vfd) +{ + struct pxps *pxp = video_get_drvdata(vfd); + + spin_lock(&pxp->lock); + video_device_release(vfd); + spin_unlock(&pxp->lock); +} + +static int pxp_open(struct file *file) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + mutex_lock(&pxp->mutex); + pxp->users++; + + if (pxp->users > 1) { + pxp->users--; + ret = -EBUSY; + goto out; + } +out: + mutex_unlock(&pxp->mutex); + if (ret) + return ret; + + videobuf_queue_dma_contig_init(&pxp->s0_vbq, + &pxp_vbq_ops, + &pxp->pdev->dev, + &pxp->lock, + V4L2_BUF_TYPE_VIDEO_OUTPUT, + V4L2_FIELD_NONE, + sizeof(struct pxp_buffer), + pxp); + dev_dbg(&pxp->pdev->dev, "call pxp_open\n"); + + return 0; +} + +static int pxp_close(struct file *file) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + videobuf_stop(&pxp->s0_vbq); + videobuf_mmap_free(&pxp->s0_vbq); + pxp->active = NULL; + kfree(pxp->outb); + pxp->outb = NULL; + + mutex_lock(&pxp->mutex); + pxp->users--; + mutex_unlock(&pxp->mutex); + + return 0; +} + +static int pxp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret; + + ret = videobuf_mmap_mapper(&pxp->s0_vbq, vma); + + return ret; +} + +static const struct v4l2_file_operations pxp_fops = { + .owner = THIS_MODULE, + .open = pxp_open, + .release = pxp_close, + .ioctl = video_ioctl2, + .mmap = pxp_mmap, +}; + +static const struct v4l2_ioctl_ops pxp_ioctl_ops = { + .vidioc_querycap = pxp_querycap, + + .vidioc_reqbufs = pxp_reqbufs, + .vidioc_querybuf = pxp_querybuf, + .vidioc_qbuf = pxp_qbuf, + .vidioc_dqbuf = pxp_dqbuf, + + .vidioc_streamon = pxp_streamon, + .vidioc_streamoff = pxp_streamoff, + + .vidioc_enum_output = pxp_enumoutput, + .vidioc_g_output = pxp_g_output, + .vidioc_s_output = pxp_s_output, + + .vidioc_enum_fmt_vid_out = pxp_enum_fmt_video_output, + .vidioc_try_fmt_vid_out = pxp_try_fmt_video_output, + .vidioc_g_fmt_vid_out = pxp_g_fmt_video_output, + .vidioc_s_fmt_vid_out = pxp_s_fmt_video_output, + + .vidioc_try_fmt_vid_out_overlay = pxp_try_fmt_output_overlay, + .vidioc_g_fmt_vid_out_overlay = pxp_g_fmt_output_overlay, + .vidioc_s_fmt_vid_out_overlay = pxp_s_fmt_output_overlay, + + .vidioc_g_fbuf = pxp_g_fbuf, + .vidioc_s_fbuf = pxp_s_fbuf, + + .vidioc_g_crop = pxp_g_crop, + .vidioc_s_crop = pxp_s_crop, + + .vidioc_queryctrl = pxp_queryctrl, + .vidioc_g_ctrl = pxp_g_ctrl, + .vidioc_s_ctrl = pxp_s_ctrl, +}; + +static const struct video_device pxp_template = { + .name = "PxP", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_STREAMING, + .fops = &pxp_fops, + .release = pxp_release, + .minor = -1, + .ioctl_ops = &pxp_ioctl_ops, +}; + +static int pxp_probe(struct platform_device *pdev) +{ + struct pxps *pxp; + int err = 0; + + pxp = kzalloc(sizeof(*pxp), GFP_KERNEL); + if (!pxp) { + dev_err(&pdev->dev, "failed to allocate control object\n"); + err = -ENOMEM; + goto exit; + } + + dev_set_drvdata(&pdev->dev, pxp); + + INIT_LIST_HEAD(&pxp->outq); + spin_lock_init(&pxp->lock); + mutex_init(&pxp->mutex); + + pxp->pdev = pdev; + + pxp->vdev = video_device_alloc(); + if (!pxp->vdev) { + dev_err(&pdev->dev, "video_device_alloc() failed\n"); + err = -ENOMEM; + goto freeirq; + } + + memcpy(pxp->vdev, &pxp_template, sizeof(pxp_template)); + video_set_drvdata(pxp->vdev, pxp); + + err = video_register_device(pxp->vdev, VFL_TYPE_GRABBER, 0); + if (err) { + dev_err(&pdev->dev, "failed to register video device\n"); + goto freevdev; + } + + err = pxp_set_fbinfo(pxp); + if (err) { + dev_err(&pdev->dev, "failed to call pxp_set_fbinfo\n"); + goto freevdev; + } + + dev_info(&pdev->dev, "initialized\n"); + +exit: + return err; + +freevdev: + video_device_release(pxp->vdev); + +freeirq: + kfree(pxp); + + return err; +} + +static int __devexit pxp_remove(struct platform_device *pdev) +{ + struct pxps *pxp = platform_get_drvdata(pdev); + + video_unregister_device(pxp->vdev); + video_device_release(pxp->vdev); + + kfree(pxp); + + return 0; +} + +static struct platform_driver pxp_driver = { + .driver = { + .name = PXP_DRIVER_NAME, + }, + .probe = pxp_probe, + .remove = __exit_p(pxp_remove), +}; + + +static int __devinit pxp_init(void) +{ + return platform_driver_register(&pxp_driver); +} + +static void __exit pxp_exit(void) +{ + platform_driver_unregister(&pxp_driver); +} + +module_init(pxp_init); +module_exit(pxp_exit); + +MODULE_DESCRIPTION("MXC PxP V4L2 driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/output/mxc_pxp_v4l2.h b/drivers/media/video/mxc/output/mxc_pxp_v4l2.h new file mode 100644 index 000000000000..9a531dd3f5e7 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_pxp_v4l2.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * Based on STMP378X PxP driver + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#ifndef _MXC_PXP_V4L2_H +#define _MXC_PXP_V4L2_H + +#include +#include + +struct pxp_buffer { + /* Must be first! */ + struct videobuf_buffer vb; + + /* One descriptor per scatterlist (per frame) */ + struct dma_async_tx_descriptor *txd; + + struct scatterlist sg[3]; +}; + +struct pxps { + struct platform_device *pdev; + + spinlock_t lock; + struct mutex mutex; + int users; + + struct video_device *vdev; + + struct videobuf_queue s0_vbq; + struct pxp_buffer *active; + struct list_head outq; + struct pxp_channel *pxp_channel[1]; /* We need 1 channel */ + struct pxp_config_data pxp_conf; + + int output; + u32 *outb; + dma_addr_t outb_phys; + u32 outb_size; + + /* Current S0 configuration */ + struct pxp_data_format *s0_fmt; + + struct v4l2_framebuffer fb; + + /* Output overlay support */ + int overlay_state; + int global_alpha_state; + u8 global_alpha; + int s1_chromakey_state; + u32 s1_chromakey; + + int fb_blank; +}; + +struct pxp_data_format { + char *name; + unsigned int bpp; + u32 fourcc; + enum v4l2_colorspace colorspace; +}; + +#endif diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.c b/drivers/media/video/mxc/output/mxc_v4l2_output.c new file mode 100644 index 000000000000..e558571438f8 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_v4l2_output.c @@ -0,0 +1,2795 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/output/mxc_v4l2_output.c + * + * @brief MXC V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mxc_v4l2_output.h" + +#define init_MUTEX(sem) sema_init(sem, 1) + +#define INTERLACED_CONTENT(vout) ((cpu_is_mx51() || \ + cpu_is_mx53()) && \ + (((vout)->field_fmt == V4L2_FIELD_INTERLACED_TB) || \ + ((vout)->field_fmt == V4L2_FIELD_INTERLACED_BT))) +#define LOAD_3FIELDS(vout) ((INTERLACED_CONTENT(vout)) && \ + ((vout)->motion_sel != HIGH_MOTION)) + +struct v4l2_output mxc_outputs[1] = { + { + .index = MXC_V4L2_OUT_2_SDC, + .name = "DISP3 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN} +}; + +static int video_nr = 16; +static DEFINE_SPINLOCK(g_lock); +static int last_index_n; +static unsigned int ipu_ic_out_max_width_size; +static unsigned int ipu_ic_out_max_height_size; +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static __inline int queue_size(v4l_queue *q) +{ + if (q->tail >= q->head) + return q->tail - q->head; + else + return (q->tail + QUEUE_SIZE) - q->head; +} + +static __inline int queue_buf(v4l_queue *q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static __inline int dequeue_buf(v4l_queue *q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static __inline int peek_next_buf(v4l_queue *q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static __inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + printk(KERN_ERR "dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + bpp = 8 * bytes_per_pixel(pixelformat); + return bpp; +} + +static bool format_is_yuv(u32 pixelformat) +{ + switch (pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_YUV444: + case V4L2_PIX_FMT_NV12: + return true; + break; + } + return false; +} + +static u32 bpp_to_fmt(struct fb_info *fbi) +{ + if (fbi->var.nonstd) + return fbi->var.nonstd; + + if (fbi->var.bits_per_pixel == 24) + return V4L2_PIX_FMT_BGR24; + else if (fbi->var.bits_per_pixel == 32) + return V4L2_PIX_FMT_BGR32; + else if (fbi->var.bits_per_pixel == 16) + return V4L2_PIX_FMT_RGB565; + + return 0; +} + +/* + * we are using double buffer for video playback, ipu need make + * sure current buffer should not be the same buffer of next display + * one. + */ +static int select_display_buffer(vout_data *vout, int next_buf) +{ + int ret = 0; + + vout->disp_buf_num = next_buf; + if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER) + != next_buf) + ret = ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, + next_buf); + else + dev_dbg(&vout->video_dev->dev, + "display buffer not ready for select\n"); + return ret; +} + +static void setup_next_buf_timer(vout_data *vout, int index) +{ + unsigned long timeout; + + /* Setup timer for next buffer */ + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0) + && vout->start_jiffies) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + dev_dbg(&vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + if (mod_timer(&vout->output_timer, timeout)) + dev_dbg(&vout->video_dev->dev, + "warning: timer was already set\n"); + + dev_dbg(&vout->video_dev->dev, + "timer handler next schedule: %lu\n", timeout); +} + +static int finish_previous_frame(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + mm_segment_t old_fs; + int ret = 0, try = 0; + + /* make sure buf[vout->disp_buf_num] in showing */ + while (ipu_check_buffer_ready(vout->display_ch, + IPU_INPUT_BUFFER, vout->disp_buf_num)) { + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = fbi->fbops->fb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC, + (unsigned int)NULL); + set_fs(old_fs); + + if ((ret < 0) || (try == 1)) { + /* + * ic_bypass need clear display buffer ready for next update. + * when fb doing blank and unblank, it has chance to go into + * dead loop: fb unblank just after buffer 1 ready selected. + */ + ipu_clear_buffer_ready(vout->display_ch, IPU_INPUT_BUFFER, + vout->disp_buf_num); + } + } + try++; + } + + return ret; +} + +static int show_current_frame(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + mm_segment_t old_fs; + int ret = 0; + + /* make sure buf[vout->disp_buf_num] begin to show */ + if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER) + != vout->disp_buf_num) { + /* wait for display frame finish */ + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = fbi->fbops->fb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC, + (unsigned int)NULL); + set_fs(old_fs); + } + } + + return ret; +} + +static void icbypass_work_func(struct work_struct *work) +{ + vout_data *vout = + container_of(work, vout_data, icbypass_work); + int index, ret; + int last_buf; + unsigned long lock_flags = 0; + + finish_previous_frame(vout); + + spin_lock_irqsave(&g_lock, lock_flags); + + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(&vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit; + } + g_buf_dq_cnt++; + vout->frame_count++; + + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + ret = ipu_update_channel_buffer(vout->display_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset); + ret += select_display_buffer(vout, vout->next_rdy_ipu_buf); + if (ret < 0) { + dev_err(&vout->video_dev->dev, + "unable to update buffer %d address rc=%d\n", + vout->next_rdy_ipu_buf, ret); + goto exit; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + show_current_frame(vout); + spin_lock_irqsave(&g_lock, lock_flags); + vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf; + + last_buf = vout->ipu_buf[vout->next_done_ipu_buf]; + if (last_buf != -1) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + wake_up_interruptible(&vout->v4l_bufq); + vout->ipu_buf[vout->next_done_ipu_buf] = -1; + vout->next_done_ipu_buf = !vout->next_done_ipu_buf; + } + + if (g_buf_output_cnt > 0) { + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) + setup_next_buf_timer(vout, index); + else + vout->state = STATE_STREAM_PAUSED; + + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } + } +exit: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +static int get_cur_fb_blank(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + mm_segment_t old_fs; + int ret = 0; + + /* Check BG blank first, if BG is blank, FG should be blank too */ + if (vout->display_ch == MEM_FG_SYNC) { + int i, bg_found = 0; + for (i = 0; i < num_registered_fb; i++) { + struct fb_info *bg_fbi; + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP3 BG", 8) == 0) { + bg_found = 1; + bg_fbi = registered_fb[i]; + if (bg_fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = bg_fbi->fbops->fb_ioctl(bg_fbi, + MXCFB_GET_FB_BLANK, + (unsigned int)(&vout->fb_blank)); + set_fs(old_fs); + } + } + if (bg_found) { + if (vout->fb_blank == FB_BLANK_UNBLANK) + break; + else + return ret; + } + } + } + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_BLANK, + (unsigned int)(&vout->fb_blank)); + set_fs(old_fs); + } + + return ret; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index, ret; + unsigned long lock_flags = 0; + vout_data *vout = (vout_data *) arg; + static int old_fb_blank = FB_BLANK_UNBLANK; + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_STOPPING) + || (vout->state == STATE_STREAM_OFF)) + goto exit0; + + /* + * If timer occurs before IPU h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP comletes + */ + if (vout->ipu_buf[vout->next_rdy_ipu_buf] != -1) { + dev_dbg(&vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* VDI need both buffer done before update buffer? */ + if (INTERLACED_CONTENT(vout) && + (vout->ipu_buf[!vout->next_rdy_ipu_buf] != -1)) { + dev_dbg(&vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* Handle ic bypass mode in work queue */ + if (vout->ic_bypass) { + if (queue_work(vout->v4l_wq, &vout->icbypass_work) == 0) { + dev_err(&vout->video_dev->dev, + "ic bypass work was in queue already!\n "); + vout->state = STATE_STREAM_PAUSED; + } + goto exit0; + } + + get_cur_fb_blank(vout); + if (vout->fb_blank == FB_BLANK_UNBLANK) { + /* if first come back from fb blank, recover correct stack */ + if (old_fb_blank != FB_BLANK_UNBLANK) { + if (vout->next_disp_ipu_buf == 1) + ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0); + else + ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1); + } + if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER) + == vout->next_disp_ipu_buf) { + dev_dbg(&vout->video_dev->dev, "IPU disp busy\n"); + index = peek_next_buf(&vout->ready_q); + setup_next_buf_timer(vout, index); + old_fb_blank = vout->fb_blank; + goto exit0; + } + } + old_fb_blank = vout->fb_blank; + + /* Dequeue buffer and pass to IPU */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(&vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + g_buf_dq_cnt++; + vout->frame_count++; + + /* update next buffer */ + if (LOAD_3FIELDS(vout)) { + int index_n = index; + int index_p = last_index_n; + vout->ipu_buf_p[vout->next_rdy_ipu_buf] = last_index_n; + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + vout->ipu_buf_n[vout->next_rdy_ipu_buf] = index; + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset); + ret += ipu_update_channel_buffer(MEM_VDI_PRP_VF_MEM_P, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index_p].m.offset + vout->bytesperline); + ret += ipu_update_channel_buffer(MEM_VDI_PRP_VF_MEM_N, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index_n].m.offset + vout->bytesperline); + last_index_n = index; + } else { + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + if (vout->pp_split) { + vout->ipu_buf[!vout->next_rdy_ipu_buf] = index; + /* always left stripe */ + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + 0,/* vout->next_rdy_ipu_buf,*/ + (vout->v4l2_bufs[index].m.offset) + + vout->pp_left_stripe.input_column + + vout->pp_up_stripe.input_column * vout->bytesperline); + + /* the U/V offset has to be updated inside of IDMAC */ + /* according to stripe offset */ + ret += ipu_update_channel_offset(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->v2f.fmt.pix.pixelformat, + vout->v2f.fmt.pix.width, + vout->v2f.fmt.pix.height, + vout->bytesperline, + vout->offset.u_offset, + vout->offset.v_offset, + vout->pp_up_stripe.input_column, + vout->pp_left_stripe.input_column); + } else + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset); + } + + if (ret < 0) { + dev_err(&vout->video_dev->dev, + "unable to update buffer %d address rc=%d\n", + vout->next_rdy_ipu_buf, ret); + goto exit0; + } + + /* set next buffer ready */ + if (LOAD_3FIELDS(vout)) + ret = ipu_select_multi_vdi_buffer(vout->next_rdy_ipu_buf); + else + ret = ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf); + if (ret < 0) { + dev_err(&vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + goto exit0; + } + + /* Split mode use buf 0 only, no need swith buf */ + if (!vout->pp_split) + vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf; + + /* Always assume display in double buffers */ + vout->next_disp_ipu_buf = !vout->next_disp_ipu_buf; + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) + setup_next_buf_timer(vout, index); + else + vout->state = STATE_STREAM_PAUSED; + + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return; + +exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +static irqreturn_t mxc_v4l2out_work_irq_handler(int irq, void *dev_id) +{ + int last_buf; + int index; + unsigned long lock_flags = 0; + vout_data *vout = dev_id; + int pp_out_buf_left_right = 0; + int disp_buf_num = 0; + int disp_buf_num_next = 1; + int local_buffer = 0; + int pp_out_buf_offset = 0; + int pp_out_buf_up_down = 0; + int release_buffer = 0; + u32 eba_offset = 0; + u32 vertical_offset = 0; + u16 x_pos; + u16 y_pos; + int ret = -1; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + /* Process previous buffer */ + if (LOAD_3FIELDS(vout)) + last_buf = vout->ipu_buf_p[vout->next_done_ipu_buf]; + else + last_buf = vout->ipu_buf[vout->next_done_ipu_buf]; + + if (last_buf != -1) { + /* If IC split mode on, update output buffer number */ + if (vout->pp_split) { + pp_out_buf_up_down = vout->pp_split_buf_num & 1;/* left/right stripe */ + pp_out_buf_left_right = (vout->pp_split_buf_num >> 1) & 1; /* up/down */ + local_buffer = (vout->pp_split == 1) ? pp_out_buf_up_down : + pp_out_buf_left_right; + disp_buf_num = vout->pp_split_buf_num >> 2; + disp_buf_num_next = + ((vout->pp_split_buf_num + (vout->pp_split << 0x1)) & 7) >> 2; + if ((!pp_out_buf_left_right) || + ((!pp_out_buf_up_down) && (vout->pp_split == 1))) { + if (vout->pp_split == 1) { + eba_offset = ((pp_out_buf_left_right + pp_out_buf_up_down) & 1) ? + vout->pp_right_stripe.input_column : + vout->pp_left_stripe.input_column; + vertical_offset = pp_out_buf_up_down ? + vout->pp_up_stripe.input_column : + vout->pp_down_stripe.input_column; + + } else { + eba_offset = pp_out_buf_left_right ? + vout->pp_left_stripe.input_column : + vout->pp_right_stripe.input_column; + vertical_offset = pp_out_buf_left_right ? + vout->pp_up_stripe.input_column : + vout->pp_down_stripe.input_column; + } + + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + (1 - local_buffer), + (vout->v4l2_bufs[vout->ipu_buf[disp_buf_num]].m.offset) + + eba_offset + vertical_offset * vout->bytesperline); + ret += ipu_update_channel_offset(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->v2f.fmt.pix.pixelformat, + vout->v2f.fmt.pix.width, + vout->v2f.fmt.pix.height, + vout->bytesperline, + vout->offset.u_offset, + vout->offset.v_offset, + vertical_offset, + eba_offset); + + /* select right stripe */ + ret += ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + (1 - local_buffer)); + if (ret < 0) + dev_err(&vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + + /* offset for next buffer's EBA */ + eba_offset = 0; + if (vout->pp_split == 1) { + pp_out_buf_offset = ((vout->pp_split_buf_num >> 1) & 1) ? + vout->pp_left_stripe.output_column : + vout->pp_right_stripe.output_column; + + eba_offset = ((vout->pp_split_buf_num & 1) ? + vout->pp_down_stripe.output_column : + vout->pp_up_stripe.output_column); + + } else { + pp_out_buf_offset = ((vout->pp_split_buf_num >> 1) & 1) ? + vout->pp_right_stripe.output_column : + vout->pp_left_stripe.output_column; + eba_offset = ((vout->pp_split_buf_num >> 1) & 1) ? + vout->pp_down_stripe.output_column : + vout->pp_up_stripe.output_column; + } + + if (vout->cur_disp_output == 5) { + x_pos = (vout->crop_current.left / 8) * 8; + y_pos = vout->crop_current.top; + eba_offset += (vout->xres * y_pos + x_pos) * vout->bpp / 8; + } + + + /* next buffer update */ + eba_offset = vout->display_bufs[disp_buf_num_next] + + pp_out_buf_offset + eba_offset; + + ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + local_buffer, eba_offset); + + /* next buffer ready */ + ret = ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, local_buffer); + + /* next stripe_buffer index 0..7 */ + vout->pp_split_buf_num = (vout->pp_split_buf_num + vout->pp_split) & 0x7; + } else { + disp_buf_num = vout->next_done_ipu_buf; + ret += ipu_select_buffer(vout->display_input_ch, IPU_OUTPUT_BUFFER, + vout->next_done_ipu_buf); + } + + /* release buffer. For split mode: if second stripe is done */ + release_buffer = vout->pp_split ? (!(vout->pp_split_buf_num & 0x3)) : 1; + if (release_buffer) { + if (vout->fb_blank == FB_BLANK_UNBLANK) + select_display_buffer(vout, disp_buf_num); + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + wake_up_interruptible(&vout->v4l_bufq); + vout->ipu_buf[vout->next_done_ipu_buf] = -1; + if (LOAD_3FIELDS(vout)) { + vout->ipu_buf_p[vout->next_done_ipu_buf] = -1; + vout->ipu_buf_n[vout->next_done_ipu_buf] = -1; + } + /* split mode use buf 0 only, no need switch buf */ + if (!vout->pp_split) + vout->next_done_ipu_buf = !vout->next_done_ipu_buf; + } + } /* end of last_buf != -1 */ + + index = peek_next_buf(&vout->ready_q); + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } else if ((vout->state == STATE_STREAM_PAUSED) + && (index != -1)) { + /* Setup timer for next buffer, when stream has been paused */ + pr_debug("next index %d\n", index); + setup_next_buf_timer(vout, index); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +/*! + * Initialize VDI channels + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI_channel(vout_data *vout, ipu_channel_params_t params) +{ + struct device *dev = &vout->video_dev->dev; + + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM, ¶ms) != 0) { + dev_dbg(dev, "Error initializing VDI current channel\n"); + return -EINVAL; + } + if (LOAD_3FIELDS(vout)) { + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM_P, ¶ms) != 0) { + dev_err(dev, "Error initializing VDI previous channel\n"); + return -EINVAL; + } + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM_N, ¶ms) != 0) { + dev_err(dev, "Error initializing VDI next channel\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize VDI channel buffers + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI_in_channel_buffer(vout_data *vout, uint32_t in_pixel_fmt, + uint16_t in_width, uint16_t in_height, + uint32_t stride, + uint32_t u_offset, uint32_t v_offset) +{ + struct device *dev = &vout->video_dev->dev; + + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM, IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, stride, + IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI current input buffer\n"); + return -EINVAL; + } + if (LOAD_3FIELDS(vout)) { + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM_P, + IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, + stride, IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf_p[0]].m.offset+vout->bytesperline, + vout->v4l2_bufs[vout->ipu_buf_p[0]].m.offset+vout->bytesperline, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI previous input buffer\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM_N, + IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, + stride, IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf_n[0]].m.offset+vout->bytesperline, + vout->v4l2_bufs[vout->ipu_buf_n[0]].m.offset+vout->bytesperline, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI next input buffer\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize VDI path + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI(ipu_channel_params_t params, vout_data *vout, + struct device *dev, struct fb_info *fbi, + u16 out_width, u16 out_height) +{ + params.mem_prp_vf_mem.in_width = vout->v2f.fmt.pix.width; + params.mem_prp_vf_mem.in_height = vout->v2f.fmt.pix.height; + params.mem_prp_vf_mem.motion_sel = vout->motion_sel; + params.mem_prp_vf_mem.field_fmt = vout->field_fmt; + params.mem_prp_vf_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params.mem_prp_vf_mem.out_width = out_width; + params.mem_prp_vf_mem.out_height = out_height; + params.mem_prp_vf_mem.out_pixel_fmt = bpp_to_fmt(fbi); + + if (init_VDI_channel(vout, params) != 0) { + dev_err(dev, "Error init_VDI_channel channel\n"); + return -EINVAL; + } + + if (init_VDI_in_channel_buffer(vout, + params.mem_prp_vf_mem.in_pixel_fmt, + params.mem_prp_vf_mem.in_width, + params.mem_prp_vf_mem.in_height, + bytes_per_pixel(params.mem_prp_vf_mem. + in_pixel_fmt), + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + return -ENOBUFS; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, "Error initializing PRP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_VF_MEM, NULL) != 0) { + dev_err(dev, "Error initializing PP ROT channel\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_ROT_VF_MEM, + IPU_INPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP ROT input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_VF_MEM, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP-VDI output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, MEM_ROT_VF_MEM) < 0) + return -EINVAL; + + vout->display_input_ch = MEM_ROT_VF_MEM; + ipu_enable_channel(MEM_ROT_VF_MEM); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->display_bufs[0], + vout->display_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP-VDI output buffer\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize PP path + * + * @param params structure ipu_channel_params_t + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_PP(ipu_channel_params_t *params, vout_data *vout, + struct device *dev, struct fb_info *fbi, + u16 out_width, u16 out_height) +{ + u16 in_width, out_stride; /* stride of output channel */ + u32 eba_offset; + u16 x_pos; + u16 y_pos; + dma_addr_t phy_addr0; + dma_addr_t phy_addr1; + + eba_offset = 0; + x_pos = 0; + y_pos = 0; + + params->mem_pp_mem.out_pixel_fmt = bpp_to_fmt(fbi); + + if (vout->cur_disp_output == 5) { + x_pos = (vout->crop_current.left / 8) * 8; + y_pos = vout->crop_current.top; + eba_offset = (vout->xres*y_pos + x_pos) * + bytes_per_pixel(params->mem_pp_mem.out_pixel_fmt); + } + + vout->bpp = fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt); + out_stride = vout->xres * + bytes_per_pixel(params->mem_pp_mem.out_pixel_fmt); + in_width = params->mem_pp_mem.in_width = vout->v2f.fmt.pix.width; + params->mem_pp_mem.in_height = vout->v2f.fmt.pix.height; + params->mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params->mem_pp_mem.out_width = out_width; + params->mem_pp_mem.out_height = out_height; + params->mem_pp_mem.outh_resize_ratio = 0; /* 0 means unused */ + params->mem_pp_mem.outv_resize_ratio = 0; /* 0 means unused */ + /* split IC by two stripes, the by pass is impossible*/ + if (vout->pp_split) { + vout->pp_left_stripe.input_column = 0; + vout->pp_left_stripe.output_column = 0; + vout->pp_right_stripe.input_column = 0; + vout->pp_right_stripe.output_column = 0; + vout->pp_up_stripe.input_column = 0; + vout->pp_up_stripe.output_column = 0; + vout->pp_down_stripe.input_column = 0; + vout->pp_down_stripe.output_column = 0; + if (vout->pp_split != 3) { + ipu_calc_stripes_sizes( + params->mem_pp_mem.in_width, /* input frame width;>1 */ + params->mem_pp_mem.out_width, /* output frame width; >1 */ + ipu_ic_out_max_width_size, + (((unsigned long long)1) << 32), /* 32bit for fractional*/ + 1, /* equal stripes */ + params->mem_pp_mem.in_pixel_fmt, + params->mem_pp_mem.out_pixel_fmt, + &(vout->pp_left_stripe), + &(vout->pp_right_stripe)); + + vout->pp_left_stripe.input_column = vout->pp_left_stripe.input_column * + fmt_to_bpp(vout->v2f.fmt.pix.pixelformat) / 8; + vout->pp_left_stripe.output_column = vout->pp_left_stripe.output_column * + fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt) / 8; + vout->pp_right_stripe.input_column = vout->pp_right_stripe.input_column * + fmt_to_bpp(vout->v2f.fmt.pix.pixelformat) / 8; + vout->pp_right_stripe.output_column = vout->pp_right_stripe.output_column * + fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt) / 8; + + + /* updare parameters */ + params->mem_pp_mem.in_width = vout->pp_left_stripe.input_width; + params->mem_pp_mem.out_width = vout->pp_left_stripe.output_width; + out_width = vout->pp_left_stripe.output_width; + /* for using in ic_init*/ + params->mem_pp_mem.outh_resize_ratio = vout->pp_left_stripe.irr; + } + if (vout->pp_split != 2) { + ipu_calc_stripes_sizes( + params->mem_pp_mem.in_height, /* input frame width;>1 */ + params->mem_pp_mem.out_height, /* output frame width; >1 */ + ipu_ic_out_max_height_size, + (((unsigned long long)1) << 32),/* 32bit for fractional */ + 1, /* equal stripes */ + params->mem_pp_mem.in_pixel_fmt, + params->mem_pp_mem.out_pixel_fmt, + &(vout->pp_up_stripe), + &(vout->pp_down_stripe)); + vout->pp_down_stripe.output_column = vout->pp_down_stripe.output_column * out_stride; + vout->pp_up_stripe.output_column = vout->pp_up_stripe.output_column * out_stride; + params->mem_pp_mem.outv_resize_ratio = vout->pp_up_stripe.irr; + params->mem_pp_mem.in_height = vout->pp_up_stripe.input_width;/*height*/ + out_height = vout->pp_up_stripe.output_width;/*height*/ + if (vout->pp_split == 3) + vout->pp_split = 2;/*2 vertical stripe as two horizontal stripes */ + } + vout->pp_split_buf_num = 0; + } + + if (ipu_init_channel(vout->post_proc_ch, params) != 0) { + dev_err(dev, "Error initializing PP channel\n"); + return -EINVAL; + } + + /* always enable double buffer */ + phy_addr0 = vout->v4l2_bufs[vout->ipu_buf[0]].m.offset; + if (vout->ipu_buf[1] == -1) + phy_addr1 = phy_addr0; + else + phy_addr1 = vout->v4l2_bufs[vout->ipu_buf[1]].m.offset; + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params->mem_pp_mem.in_pixel_fmt, + params->mem_pp_mem.in_width, + params->mem_pp_mem.in_height, + vout->v2f.fmt.pix.bytesperline / + bytes_per_pixel(params->mem_pp_mem. + in_pixel_fmt), + IPU_ROTATE_NONE, + phy_addr0, + phy_addr1, + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + dev_err(dev, "Error initializing PP input buffer\n"); + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + return -ENOBUFS; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params->mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_stride, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0] + eba_offset, + vout->rot_pp_bufs[1] + eba_offset, 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) { + dev_err(dev, "Error initializing PP ROT channel\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_INPUT_BUFFER, + params->mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_stride, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP ROT input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_OUTPUT_BUFFER, + params->mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_stride, + IPU_ROTATE_NONE, + vout->display_bufs[0] + eba_offset, + vout->display_bufs[1] + eba_offset, 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, MEM_ROT_PP_MEM) < 0) + return -EINVAL; + + vout->display_input_ch = MEM_ROT_PP_MEM; + ipu_enable_channel(MEM_ROT_PP_MEM); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params->mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_stride, + vout->rotate, + vout->display_bufs[0] + eba_offset, + vout->display_bufs[1] + eba_offset, 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + } + + /* fix EBAs for IDMAC channels */ + if (vout->pp_split) { + ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 0, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset + + vout->pp_left_stripe.input_column + + vout->pp_up_stripe.input_column * vout->bytesperline); + + + ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 1, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset + + vout->pp_right_stripe.input_column + + vout->pp_up_stripe.input_column * vout->bytesperline); + + ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 0, + vout->display_bufs[0] + eba_offset + + vout->pp_left_stripe.output_column + + vout->pp_up_stripe.output_column); + + ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 1, + vout->display_bufs[0] + eba_offset + + vout->pp_right_stripe.output_column + + vout->pp_up_stripe.output_column); + } + + return 0; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data *vout) +{ + struct device *dev = &vout->video_dev->dev; + ipu_channel_params_t params; + struct mxcfb_pos fb_pos; + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + u16 out_width; + u16 out_height; + mm_segment_t old_fs; + unsigned int ipu_ch = CHAN_NONE; + unsigned int fb_fmt; + int rc = 0; + + dev_dbg(dev, "mxc_v4l2out_streamon: field format=%d\n", + vout->field_fmt); + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 2) { + dev_err(dev, "2 buffers not been queued yet!\n"); + return -EINVAL; + } + + if ((vout->field_fmt == V4L2_FIELD_BOTTOM) || (vout->field_fmt == V4L2_FIELD_TOP)) { + dev_err(dev, "4 queued buffers need, not supported yet!\n"); + return -EINVAL; + } + + /* + * params init, check whether operation exceed the IC limitation: + * whether split mode used ( ipu version >= ipuv3 only) + */ + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + vout->disp_buf_num = 0; + vout->next_done_ipu_buf = 0; + vout->next_rdy_ipu_buf = vout->next_disp_ipu_buf = 1; + vout->pp_split = 0; + ipu_ic_out_max_height_size = 1024; +#ifdef CONFIG_MXC_IPU_V1 + if (cpu_is_mx35()) + ipu_ic_out_max_width_size = 800; + else + ipu_ic_out_max_width_size = 720; +#else + ipu_ic_out_max_width_size = 1024; +#endif + if ((out_width > ipu_ic_out_max_width_size) || + (out_height > ipu_ic_out_max_height_size)) + vout->pp_split = 4; + if (!INTERLACED_CONTENT(vout)) { + vout->ipu_buf[0] = dequeue_buf(&vout->ready_q); + /* split IC by two stripes, the by pass is impossible*/ + if ((out_width != vout->v2f.fmt.pix.width || + out_height != vout->v2f.fmt.pix.height) && + vout->pp_split) { + vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0; + vout->ipu_buf[1] = vout->ipu_buf[0]; + vout->frame_count = 1; + if ((out_width > ipu_ic_out_max_width_size) && + (out_height > ipu_ic_out_max_height_size)) + vout->pp_split = 1; /*4 stripes*/ + else if (!(out_height > ipu_ic_out_max_height_size)) + vout->pp_split = 2; /*two horizontal stripes */ + else + vout->pp_split = 3; /*2 vertical stripes*/ + } else { + vout->ipu_buf[1] = -1; + vout->frame_count = 1; + } + } else if (!LOAD_3FIELDS(vout)) { + vout->ipu_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[1] = -1; + vout->frame_count = 1; + } else { + vout->ipu_buf_p[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf_n[0] = vout->ipu_buf[0]; + vout->ipu_buf_p[1] = -1; + vout->ipu_buf[1] = -1; + vout->ipu_buf_n[1] = -1; + last_index_n = vout->ipu_buf_n[0]; + vout->frame_count = 2; + } + + /* + * Bypass IC if resizing and rotation are not needed + * Meanwhile, apply IC bypass to SDC only + */ + fbvar = fbi->var; + vout->xres = fbvar.xres; + vout->yres = fbvar.yres; + + if (vout->cur_disp_output == 3 || vout->cur_disp_output == 5) { + fb_fmt = vout->v2f.fmt.pix.pixelformat; + + /* DC channel can not use CSC */ + if (vout->cur_disp_output == 5) { + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_GET_DIFMT, + (unsigned long)&fb_fmt); + set_fs(old_fs); + } + } + + fbvar.bits_per_pixel = 16; + if (format_is_yuv(fb_fmt)) + fbvar.nonstd = IPU_PIX_FMT_UYVY; + else + fbvar.nonstd = 0; + if (vout->cur_disp_output == 3) { + fbvar.xres = out_width; + fbvar.yres = out_height; + vout->xres = fbvar.xres; + vout->yres = fbvar.yres; + } + + fbvar.xres_virtual = fbvar.xres; + fbvar.yres_virtual = fbvar.yres * 2; + } + + if (out_width == vout->v2f.fmt.pix.width && + out_height == vout->v2f.fmt.pix.height && + vout->xres == out_width && + vout->yres == out_height && + ipu_can_rotate_in_place(vout->rotate) && + (vout->bytesperline == + bytes_per_pixel(vout->v2f.fmt.pix.pixelformat) * out_width) && + !INTERLACED_CONTENT(vout)) { + vout->ic_bypass = 1; + } else { + vout->ic_bypass = 0; + } + +#ifdef CONFIG_MXC_IPU_V1 + /* IPUv1 needs IC to do CSC */ + if (format_is_yuv(vout->v2f.fmt.pix.pixelformat) != + format_is_yuv(bpp_to_fmt(fbi))) +#else + /* DC channel needs IC to do CSC */ + if ((format_is_yuv(vout->v2f.fmt.pix.pixelformat) != + format_is_yuv(fb_fmt)) && + (vout->cur_disp_output == 5)) + vout->ic_bypass = 0; +#endif + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_IPU_CHAN, + (unsigned long)&ipu_ch); + set_fs(old_fs); + } + + if (ipu_ch == CHAN_NONE) { + dev_err(dev, "Can not get display ipu channel\n"); + return -EINVAL; + } + + vout->display_ch = ipu_ch; + + if (vout->ic_bypass) { + dev_info(dev, "Bypassing IC\n"); + vout->pp_split = 0; + switch (vout->v2f.fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_NV12: + fbvar.bits_per_pixel = 12; + break; + case V4L2_PIX_FMT_YUV422P: + fbvar.bits_per_pixel = 16; + break; + default: + fbvar.bits_per_pixel = 8* + bytes_per_pixel(vout->v2f.fmt.pix.pixelformat); + } + fbvar.nonstd = vout->v2f.fmt.pix.pixelformat; + } + + /* Init display channel through fb API */ + fbvar.yoffset = 0; + fbvar.activate |= FB_ACTIVATE_FORCE; + console_lock(); + fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(fbi, &fbvar); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + + if (fbi->fbops->fb_ioctl && vout->display_ch == MEM_FG_SYNC) { + fb_pos.x = vout->crop_current.left; + fb_pos.y = vout->crop_current.top; + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + + vout->display_bufs[1] = fbi->fix.smem_start; + vout->display_bufs[0] = fbi->fix.smem_start + + (fbi->fix.line_length * vout->yres); + vout->display_buf_size = vout->xres * + vout->yres * fbi->var.bits_per_pixel / 8; + + /* fill black color for init fb, we assume fb has double buffer*/ + if (format_is_yuv(vout->v2f.fmt.pix.pixelformat)) { + int i; + + if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) || + (!vout->ic_bypass)) { + short * tmp = (short *) fbi->screen_base; + short color; + if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + color = 0x8000; + else + color = 0x80; + for (i = 0; i < (fbi->fix.line_length * fbi->var.yres_virtual)/2; + i++, tmp++) + *tmp = color; + } else if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YVU420) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12)) { + char * base = (char *)fbi->screen_base; + int j, screen_size = fbi->var.xres * fbi->var.yres; + + for (j = 0; j < 2; j++) { + memset(base, 0, screen_size); + base += screen_size; + for (i = 0; i < screen_size/2; i++, base++) + *base = 0x80; + } + } else if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) { + char * base = (char *)fbi->screen_base; + int j, screen_size = fbi->var.xres * fbi->var.yres; + + for (j = 0; j < 2; j++) { + memset(base, 0, screen_size); + base += screen_size; + for (i = 0; i < screen_size; i++, base++) + *base = 0x80; + } + } + } else + memset(fbi->screen_base, 0x0, + fbi->fix.line_length * fbi->var.yres_virtual); + + if (INTERLACED_CONTENT(vout)) + vout->post_proc_ch = MEM_VDI_PRP_VF_MEM; + else if (!vout->ic_bypass) + vout->post_proc_ch = MEM_PP_MEM; + + /* Init IC channel */ + if (!vout->ic_bypass) { + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.height; + out_height = vout->crop_current.width; + } + vout->display_input_ch = vout->post_proc_ch; + memset(¶ms, 0, sizeof(params)); + if (INTERLACED_CONTENT(vout)) { + if (vout->pp_split) { + dev_err(&vout->video_dev->dev, "VDI split has not supported yet.\n"); + return -1; + } else + rc = init_VDI(params, vout, dev, fbi, out_width, out_height); + } else { + rc = init_PP(¶ms, vout, dev, fbi, out_width, out_height); + } + if (rc < 0) + return rc; + } + + if (!vout->ic_bypass) { + switch (vout->display_input_ch) { + case MEM_PP_MEM: + vout->work_irq = IPU_IRQ_PP_OUT_EOF; + break; + case MEM_VDI_PRP_VF_MEM: + vout->work_irq = IPU_IRQ_PRP_VF_OUT_EOF; + break; + case MEM_ROT_VF_MEM: + vout->work_irq = IPU_IRQ_PRP_VF_ROT_OUT_EOF; + break; + case MEM_ROT_PP_MEM: + vout->work_irq = IPU_IRQ_PP_ROT_OUT_EOF; + break; + default: + dev_err(&vout->video_dev->dev, + "not support channel, should not be here\n"); + } + } else + vout->work_irq = -1; + + if (!vout->ic_bypass && (vout->work_irq > 0)) { + ipu_clear_irq(vout->work_irq); + ipu_request_irq(vout->work_irq, + mxc_v4l2out_work_irq_handler, + 0, vout->video_dev->name, vout); + } + + vout->state = STATE_STREAM_PAUSED; + + /* Enable display and IC channels */ + if (fbi) { + console_lock(); + fb_blank(fbi, FB_BLANK_UNBLANK); + console_unlock(); + vout->fb_blank = FB_BLANK_UNBLANK; + } else { + ipu_enable_channel(vout->display_ch); + } + + /* correct display ch buffer address */ + ipu_update_channel_buffer(vout->display_ch, IPU_INPUT_BUFFER, + 0, vout->display_bufs[0]); + ipu_update_channel_buffer(vout->display_ch, IPU_INPUT_BUFFER, + 1, vout->display_bufs[1]); + + if (!vout->ic_bypass) { +#ifndef CONFIG_MXC_IPU_V1 + ipu_enable_channel(vout->post_proc_ch); +#endif + if (LOAD_3FIELDS(vout)) { + ipu_enable_channel(MEM_VDI_PRP_VF_MEM_P); + ipu_enable_channel(MEM_VDI_PRP_VF_MEM_N); + ipu_select_multi_vdi_buffer(0); + } else + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1); +#ifdef CONFIG_MXC_IPU_V1 + ipu_enable_channel(vout->post_proc_ch); +#endif + } else { + ipu_update_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + 0, vout->v4l2_bufs[vout->ipu_buf[0]].m.offset); + if (vout->offset.u_offset || vout->offset.v_offset) + /* only update u/v offset */ + ipu_update_channel_offset(vout->display_ch, + IPU_INPUT_BUFFER, + vout->v2f.fmt.pix.pixelformat, + vout->v2f.fmt.pix.width, + vout->v2f.fmt.pix.height, + vout->bytesperline, + vout->offset.u_offset, + vout->offset.v_offset, + 0, + 0); + ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0); + queue_work(vout->v4l_wq, &vout->icbypass_work); + } + + vout->start_jiffies = jiffies; + + msleep(1); + + dev_dbg(dev, + "streamon: start time = %lu jiffies\n", vout->start_jiffies); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int i, retval = 0; + unsigned long lockflag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) { + return 0; + } + + if (!vout->ic_bypass) + ipu_free_irq(vout->work_irq, vout); + + if (vout->ic_bypass) + cancel_work_sync(&vout->icbypass_work); + + /* fill black color for fb, we assume fb has double buffer*/ + if (format_is_yuv(vout->v2f.fmt.pix.pixelformat)) { + int i; + + if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) || + (!vout->ic_bypass)) { + short * tmp = (short *) fbi->screen_base; + short color; + if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + color = 0x8000; + else + color = 0x80; + for (i = 0; i < (fbi->fix.line_length * fbi->var.yres_virtual)/2; + i++, tmp++) + *tmp = color; + } else if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YVU420) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12)) { + char * base = (char *)fbi->screen_base; + int j, screen_size = fbi->var.xres * fbi->var.yres; + + for (j = 0; j < 2; j++) { + memset(base, 0, screen_size); + base += screen_size; + for (i = 0; i < screen_size/2; i++, base++) + *base = 0x80; + } + } else if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) { + char * base = (char *)fbi->screen_base; + int j, screen_size = fbi->var.xres * fbi->var.yres; + + for (j = 0; j < 2; j++) { + memset(base, 0, screen_size); + base += screen_size; + for (i = 0; i < screen_size; i++, base++) + *base = 0x80; + } + } + } else + memset(fbi->screen_base, 0x0, + fbi->fix.line_length * fbi->var.yres_virtual); + + spin_lock_irqsave(&g_lock, lockflag); + + del_timer(&vout->output_timer); + + if (vout->state == STATE_STREAM_ON) { + vout->state = STATE_STREAM_STOPPING; + } + + spin_unlock_irqrestore(&g_lock, lockflag); + + if (vout->display_ch == MEM_FG_SYNC) { + struct mxcfb_pos fb_pos; + mm_segment_t old_fs; + + fb_pos.x = 0; + fb_pos.y = 0; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + } + + if (vout->ic_bypass) { + fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(fbi, &fbi->var); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + + if (vout->display_ch == MEM_FG_SYNC) { + console_lock(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + console_unlock(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + } else if (vout->post_proc_ch == MEM_PP_MEM || + vout->post_proc_ch == MEM_PRP_VF_MEM) { + /* SDC with Rotation */ + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM); + ipu_disable_channel(MEM_ROT_PP_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } + ipu_disable_channel(MEM_PP_MEM, true); + + fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(fbi, &fbi->var); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + + if (vout->display_ch == MEM_FG_SYNC) { + console_lock(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + console_unlock(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + + ipu_uninit_channel(MEM_PP_MEM); + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_PP_MEM); + } else if (INTERLACED_CONTENT(vout) && + (vout->post_proc_ch == MEM_VDI_PRP_VF_MEM)) { + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_VDI_PRP_VF_MEM, + MEM_ROT_VF_MEM); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } + + ipu_disable_channel(MEM_VDI_PRP_VF_MEM, true); + if (LOAD_3FIELDS(vout)) { + ipu_disable_channel(MEM_VDI_PRP_VF_MEM_P, true); + ipu_disable_channel(MEM_VDI_PRP_VF_MEM_N, true); + } + + fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(fbi, &fbi->var); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + + if (vout->display_ch == MEM_FG_SYNC) { + console_lock(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + console_unlock(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + + ipu_uninit_channel(MEM_VDI_PRP_VF_MEM); + if (LOAD_3FIELDS(vout)) { + ipu_uninit_channel(MEM_VDI_PRP_VF_MEM_P); + ipu_uninit_channel(MEM_VDI_PRP_VF_MEM_N); + } + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->post_proc_ch = CHAN_NONE; + vout->state = STATE_STREAM_OFF; + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_NV12) || + (palette == V4L2_PIX_FMT_UYVY) || + (palette == V4L2_PIX_FMT_YUYV) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_YUV444) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data *vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + return -EINVAL; + } + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data *vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + dev_err(&vout->video_dev->dev, "pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + vout->bytesperline = bytesperline; + + /* Based on http://v4l2spec.bytesex.org/spec/x6386.htm#V4L2-FIELD */ + vout->field_fmt = f->fmt.pix.field; + switch (vout->field_fmt) { + /* Images are in progressive format, not interlaced */ + case V4L2_FIELD_NONE: + break; + /* The two fields of a frame are passed in separate buffers, + in temporal order, i. e. the older one first. */ + case V4L2_FIELD_ALTERNATE: + dev_err(&vout->video_dev->dev, + "V4L2_FIELD_ALTERNATE field format not supported yet!\n"); + break; + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + if (cpu_is_mx51() || cpu_is_mx53()) + break; + dev_err(&vout->video_dev->dev, + "De-interlacing not supported in this device!\n"); + vout->field_fmt = V4L2_FIELD_NONE; + break; + default: + vout->field_fmt = V4L2_FIELD_NONE; + break; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + if (f->fmt.pix.sizeimage < size) { + f->fmt.pix.sizeimage = size; + } else { + size = f->fmt.pix.sizeimage; + } + + vout->v2f.fmt.pix = f->fmt.pix; + if (vout->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&vout->offset, + (void *)vout->v2f.fmt.pix.priv, + sizeof(vout->offset))) { + retval = -EFAULT; + goto err0; + } + } else { + vout->offset.u_offset = 0; + vout->offset.v_offset = 0; + } + + retval = 0; + err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data *vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data *vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_VFLIP: + vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_MXC_ROT: + vout->rotate = c->value; + break; + case V4L2_CID_MXC_MOTION: + vout->motion_sel = c->value; + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) { + return -ENODEV; + } + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + + if (vout->open_count++ == 0) { + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + vout->rotate = IPU_ROTATE_NONE; + + vout->v4l_wq = create_singlethread_workqueue("v4l2q"); + if (!vout->v4l_wq) { + dev_dbg(&dev->dev, + "Could not create work queue\n"); + err = -ENOMEM; + goto oops; + } + + INIT_WORK(&vout->icbypass_work, icbypass_work_func); + } + + file->private_data = dev; + + up(&vout->busy_lock); + + return 0; + + oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + + flush_workqueue(vout->v4l_wq); + destroy_workqueue(vout->v4l_wq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static long +mxc_v4l2out_do_ioctl(struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *vdev = file->private_data; + vout_data *vout = video_get_drvdata(vdev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) { + req->count = MIN_FRAME_NUM; + } else if (req->count > MAX_FRAME_NUM) { + req->count = MAX_FRAME_NUM; + } + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + dev_dbg(&vdev->dev, + "VIDIOC_QUERYBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + int param[5][3]; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + retval = -EINVAL; + break; + } + + dev_dbg(&vdev->dev, "VIDIOC_QBUF: %d field = %d\n", buf->index, buf->field); + + /* mmapped buffers are L1 WB cached, + * so we need to clean them */ + if (buf->memory & V4L2_MEMORY_MMAP) { + flush_cache_all(); + } + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + if (vout->v4l2_bufs[index].reserved) + if (!copy_from_user(¶m[0][0], + (void *)vout-> + v4l2_bufs[index] + .reserved, sizeof(param))) + ipu_set_csc_coefficients(vout-> + display_ch, + param); + queue_buf(&vout->ready_q, index); + if (vout->state == STATE_STREAM_PAUSED) { + index = peek_next_buf(&vout->ready_q); + setup_next_buf_timer(vout, index); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + dev_dbg(&vdev->dev, "VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: no free buffers, returning\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: buffer in done q, but not " + "flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + dev_dbg(&vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + /* only full screen supported for SDC BG and SDC DC */ + if (vout->cur_disp_output == 4) { + crop->c = vout->crop_current; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top >= b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.left = b->left; + if (crop->c.left >= b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 5) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + if (output->index >= 3) + *output = mxc_outputs[MXC_V4L2_OUT_2_SDC]; + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + int fbnum; + struct v4l2_rect *b; + + if ((*p_output_num >= MXC_V4L2_OUT_NUM_OUTPUTS) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + + /* Update bounds in case they have changed */ + b = &vout->crop_bounds[vout->cur_disp_output]; + + fbnum = vout->output_fb_num[vout->cur_disp_output]; + + /* + * For FG overlay, it uses BG window parameter as + * limitation reference; and BG must be enabled to + * support FG. + */ + if (vout->cur_disp_output == 3) { + unsigned int i, ipu_ch = CHAN_NONE; + struct fb_info *fbi; + mm_segment_t old_fs; + + for (i = 0; i < num_registered_fb; i++) { + fbi = registered_fb[i]; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_IPU_CHAN, + (unsigned long)&ipu_ch); + set_fs(old_fs); + } + if (ipu_ch == CHAN_NONE) { + dev_err(&vdev->dev, + "Can't get disp ipu channel\n"); + retval = -EINVAL; + break; + } + + if (ipu_ch == MEM_BG_SYNC) { + fbnum = i; + break; + } + } + } + + b->width = registered_fb[fbnum]->var.xres; + b->height = registered_fb[fbnum]->var.yres; + + vout->crop_current = *b; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static long +mxc_v4l2out_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + int i; + vout_data *vout = video_get_drvdata(vdev); + + dev_dbg(&vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + for (i = 0; i < vout->buffer_cnt; i++) { + if ((vout->v4l2_bufs[i].m.offset == + (vma->vm_pgoff << PAGE_SHIFT)) && + (vout->v4l2_bufs[i].length >= size)) { + vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED; + break; + } + } + if (i == vout->buffer_cnt) { + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + /* make buffers inner write-back, outer write-thru cacheable */ + /* vma->vm_page_prot = pgprot_outer_wrthru(vma->vm_page_prot);*/ + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + dev_dbg(&vdev->dev, "mmap remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table_struct * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, struct poll_table_struct * wait) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct +v4l2_file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .name = "MXC Video Output", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +static ssize_t show_streaming(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *video_dev = container_of(dev, + struct video_device, dev); + vout_data *vout = video_get_drvdata(video_dev); + + if (vout->state == STATE_STREAM_OFF) + return sprintf(buf, "stream off\n"); + else if (vout->state == STATE_STREAM_PAUSED) + return sprintf(buf, "stream paused\n"); + else + return sprintf(buf, "stream on\n"); +} +static DEVICE_ATTR(fsl_v4l2_output_property, S_IRUGO, show_streaming, NULL); + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + dev_dbg(&pdev->dev, "video_register_device failed\n"); + return 0; + } + dev_info(&pdev->dev, "Registered device video%d\n", + vout->video_dev->minor & 0x1f); + /*vout->video_dev->dev = &pdev->dev;*/ + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = idstr[4] - '0'; + if (disp_num == 3) { + if (strcmp(idstr, "DISP3 BG - DI1") == 0) + disp_num = 5; + else if (strncmp(idstr, "DISP3 BG", 8) == 0) + disp_num = 4; + } + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) { + vout->cur_disp_output = disp_num; + } + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + platform_set_drvdata(pdev, vout); + + if (device_create_file(&vout->video_dev->dev, + &dev_attr_fsl_v4l2_output_property)) + dev_err(&pdev->dev, "Error on creating file\n"); + + return 0; +} + +static int mxc_v4l2out_remove(struct platform_device *pdev) +{ + vout_data *vout = platform_get_drvdata(pdev); + + if (vout->video_dev) { + device_remove_file(&vout->video_dev->dev, + &dev_attr_fsl_v4l2_output_property); + video_unregister_device(vout->video_dev); + vout->video_dev = NULL; + } + + platform_set_drvdata(pdev, NULL); + + kfree(vout); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "mxc_v4l2_output", + }, + .probe = mxc_v4l2out_probe, + .remove = mxc_v4l2out_remove, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + return platform_driver_register(&mxc_v4l2out_driver); +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + platform_driver_unregister(&mxc_v4l2out_driver); +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.h b/drivers/media/video/mxc/output/mxc_v4l2_output.h new file mode 100644 index 000000000000..c38bfdd3b5f0 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_v4l2_output.h @@ -0,0 +1,157 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_V4L2_OUTPUT MXC V4L2 Video Output Driver + */ +/*! + * @file mxc_v4l2_output.h + * + * @brief MXC V4L2 Video Output Driver Header file + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#ifndef __MXC_V4L2_OUTPUT_H__ +#define __MXC_V4L2_OUTPUT_H__ + +#include + +#ifdef __KERNEL__ + +#include +#include +#include + +#define MIN_FRAME_NUM 2 +#define MAX_FRAME_NUM 30 + +#define MXC_V4L2_OUT_NUM_OUTPUTS 6 +#define MXC_V4L2_OUT_2_SDC 0 + + +typedef struct { + int list[MAX_FRAME_NUM + 1]; + int head; + int tail; +} v4l_queue; + +/*! + * States for the video stream + */ +typedef enum { + STATE_STREAM_OFF, + STATE_STREAM_ON, + STATE_STREAM_PAUSED, + STATE_STREAM_STOPPING, +} v4lout_state; + +/*! + * common v4l2 driver structure. + */ +typedef struct _vout_data { + struct video_device *video_dev; + /*! + * semaphore guard against SMP multithreading + */ + struct semaphore busy_lock; + + /*! + * number of process that have device open + */ + int open_count; + + /*! + * params lock for this camera + */ + struct semaphore param_lock; + + struct timer_list output_timer; + struct workqueue_struct *v4l_wq; + struct work_struct icbypass_work; + int disp_buf_num; + int fb_blank; + unsigned long start_jiffies; + u32 frame_count; + + v4l_queue ready_q; + v4l_queue done_q; + + s8 next_rdy_ipu_buf; + s8 next_done_ipu_buf; + s8 next_disp_ipu_buf; + s8 ipu_buf[2]; + s8 ipu_buf_p[2]; + s8 ipu_buf_n[2]; + volatile v4lout_state state; + + int cur_disp_output; + int output_fb_num[MXC_V4L2_OUT_NUM_OUTPUTS]; + int output_enabled[MXC_V4L2_OUT_NUM_OUTPUTS]; + struct v4l2_framebuffer v4l2_fb; + int ic_bypass; + u32 work_irq; + ipu_channel_t display_ch; + ipu_channel_t post_proc_ch; + ipu_channel_t display_input_ch; + + /*! + * FRAME_NUM-buffering, so we need a array + */ + int buffer_cnt; + dma_addr_t queue_buf_paddr[MAX_FRAME_NUM]; + void *queue_buf_vaddr[MAX_FRAME_NUM]; + u32 queue_buf_size; + struct v4l2_buffer v4l2_bufs[MAX_FRAME_NUM]; + u32 display_buf_size; + dma_addr_t display_bufs[2]; + void *display_bufs_vaddr[2]; + dma_addr_t rot_pp_bufs[2]; + void *rot_pp_bufs_vaddr[2]; + + /*! + * Poll wait queue + */ + wait_queue_head_t v4l_bufq; + + /*! + * v4l2 format + */ + struct v4l2_format v2f; + struct v4l2_mxc_offset offset; + ipu_rotate_mode_t rotate; + + /* crop */ + struct v4l2_rect crop_bounds[MXC_V4L2_OUT_NUM_OUTPUTS]; + struct v4l2_rect crop_current; + u32 bytesperline; + enum v4l2_field field_fmt; + ipu_motion_sel motion_sel; + + /* PP split fot two stripes*/ + int pp_split; /* 0,1 */ + struct stripe_param pp_left_stripe; + struct stripe_param pp_right_stripe; /* struct for split parameters */ + struct stripe_param pp_up_stripe; + struct stripe_param pp_down_stripe; + /* IC ouput buffer number. Counting from 0 to 7 */ + int pp_split_buf_num; /* 0..7 */ + u16 bpp ; /* bit per pixel */ + u16 xres; /* width of physical frame (BGs) */ + u16 yres; /* heigth of physical frame (BGs)*/ + +} vout_data; + +#endif +#endif /* __MXC_V4L2_OUTPUT_H__ */ diff --git a/drivers/mxc/Kconfig b/drivers/mxc/Kconfig new file mode 100644 index 000000000000..6e67087d2efa --- /dev/null +++ b/drivers/mxc/Kconfig @@ -0,0 +1,39 @@ +# drivers/video/mxc/Kconfig + +if ARCH_MXC + +menu "MXC support drivers" + +config MXC_IPU + bool "Image Processing Unit Driver" + depends on !ARCH_MX21 + depends on !ARCH_MX27 + depends on !ARCH_MX25 + select MXC_IPU_V1 if !ARCH_MX37 && !ARCH_MX5 + select MXC_IPU_V3 if ARCH_MX37 || ARCH_MX5 + select MXC_IPU_V3D if ARCH_MX37 + select MXC_IPU_V3EX if ARCH_MX5 + help + If you plan to use the Image Processing unit, say + Y here. IPU is needed by Framebuffer and V4L2 drivers. + +source "drivers/mxc/ipu/Kconfig" +source "drivers/mxc/ipu3/Kconfig" + +source "drivers/mxc/ssi/Kconfig" +source "drivers/mxc/dam/Kconfig" +source "drivers/mxc/pmic/Kconfig" +source "drivers/mxc/mcu_pmic/Kconfig" +source "drivers/mxc/security/Kconfig" +source "drivers/mxc/hmp4e/Kconfig" +source "drivers/mxc/hw_event/Kconfig" +source "drivers/mxc/vpu/Kconfig" +source "drivers/mxc/asrc/Kconfig" +source "drivers/mxc/bt/Kconfig" +source "drivers/mxc/gps_ioctrl/Kconfig" +source "drivers/mxc/mlb/Kconfig" +source "drivers/mxc/adc/Kconfig" + +endmenu + +endif diff --git a/drivers/mxc/Makefile b/drivers/mxc/Makefile new file mode 100644 index 000000000000..6416bc429888 --- /dev/null +++ b/drivers/mxc/Makefile @@ -0,0 +1,17 @@ +obj-$(CONFIG_MXC_IPU_V1) += ipu/ +obj-$(CONFIG_MXC_IPU_V3) += ipu3/ +obj-$(CONFIG_MXC_SSI) += ssi/ +obj-$(CONFIG_MXC_DAM) += dam/ + +obj-$(CONFIG_MXC_PMIC_MC9SDZ60) += mcu_pmic/ +obj-$(CONFIG_MXC_PMIC) += pmic/ + +obj-$(CONFIG_MXC_HMP4E) += hmp4e/ +obj-y += security/ +obj-$(CONFIG_MXC_VPU) += vpu/ +obj-$(CONFIG_MXC_HWEVENT) += hw_event/ +obj-$(CONFIG_MXC_ASRC) += asrc/ +obj-$(CONFIG_MXC_BLUETOOTH) += bt/ +obj-$(CONFIG_GPS_IOCTRL) += gps_ioctrl/ +obj-$(CONFIG_MXC_MLB) += mlb/ +obj-$(CONFIG_IMX_ADC) += adc/ diff --git a/drivers/mxc/adc/Kconfig b/drivers/mxc/adc/Kconfig new file mode 100644 index 000000000000..91ad23bc7cbe --- /dev/null +++ b/drivers/mxc/adc/Kconfig @@ -0,0 +1,14 @@ +# +# i.MX ADC devices +# + +menu "i.MX ADC support" + +config IMX_ADC + tristate "i.MX ADC" + depends on ARCH_MXC + default n + help + This selects the Freescale i.MX on-chip ADC driver. + +endmenu diff --git a/drivers/mxc/adc/Makefile b/drivers/mxc/adc/Makefile new file mode 100644 index 000000000000..e21e48ee1185 --- /dev/null +++ b/drivers/mxc/adc/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for i.MX adc devices. +# +obj-$(CONFIG_IMX_ADC) += imx_adc.o diff --git a/drivers/mxc/adc/imx_adc.c b/drivers/mxc/adc/imx_adc.c new file mode 100644 index 000000000000..07011b33f31d --- /dev/null +++ b/drivers/mxc/adc/imx_adc.c @@ -0,0 +1,1134 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file adc/imx_adc.c + * @brief This is the main file of i.MX ADC driver. + * + * @ingroup IMX_ADC + */ + +/* + * Includes + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "imx_adc_reg.h" + +static int imx_adc_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait; + +/*! + * To indicate whether any of the adc devices are suspending + */ +static int suspend_flag; + +/*! + * The suspendq is used by blocking application calls + */ +static wait_queue_head_t suspendq; +static wait_queue_head_t tsq; + +static bool imx_adc_ready; +static bool ts_data_ready; +static int tsi_data = TSI_DATA; +static unsigned short ts_data_buf[16]; + +static struct class *imx_adc_class; +static struct imx_adc_data *adc_data; + +static DECLARE_MUTEX(general_convert_mutex); +static DECLARE_MUTEX(ts_convert_mutex); + +unsigned long tsc_base; + +int is_imx_adc_ready(void) +{ + return imx_adc_ready; +} +EXPORT_SYMBOL(is_imx_adc_ready); + +void tsc_clk_enable(void) +{ + unsigned long reg; + + clk_enable(adc_data->adc_clk); + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_IPG_CLK_EN; + __raw_writel(reg, tsc_base + TGCR); +} + +void tsc_clk_disable(void) +{ + unsigned long reg; + + clk_disable(adc_data->adc_clk); + + reg = __raw_readl(tsc_base + TGCR); + reg &= ~TGCR_IPG_CLK_EN; + __raw_writel(reg, tsc_base + TGCR); +} + +void tsc_self_reset(void) +{ + unsigned long reg; + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_TSC_RST; + __raw_writel(reg, tsc_base + TGCR); + + while (__raw_readl(tsc_base + TGCR) & TGCR_TSC_RST) + continue; +} + +/* Internal reference */ +void tsc_intref_enable(void) +{ + unsigned long reg; + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_INTREFEN; + __raw_writel(reg, tsc_base + TGCR); +} + +/* initialize touchscreen */ +void imx_tsc_init(void) +{ + unsigned long reg; + int lastitemid; + + /* Level sense */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_PD_CFG; /* edge sensitive */ + reg |= (0xf << CQCR_FIFOWATERMARK_SHIFT); /* watermark */ + __raw_writel(reg, tsc_base + TCQCR); + + /* Configure 4-wire */ + reg = TSC_4WIRE_PRECHARGE; + reg |= CC_IGS; + __raw_writel(reg, tsc_base + TCC0); + + reg = TSC_4WIRE_TOUCH_DETECT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 32 << CC_SETTLING_TIME_SHIFT; /* it's important! */ + __raw_writel(reg, tsc_base + TCC1); + + reg = TSC_4WIRE_X_MEASUMENT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 16 << CC_SETTLING_TIME_SHIFT; /* settling time */ + __raw_writel(reg, tsc_base + TCC2); + + reg = TSC_4WIRE_Y_MEASUMENT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 16 << CC_SETTLING_TIME_SHIFT; /* settling time */ + __raw_writel(reg, tsc_base + TCC3); + + reg = (TCQ_ITEM_TCC0 << TCQ_ITEM7_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM6_SHIFT) | + (TCQ_ITEM_TCC1 << TCQ_ITEM5_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM4_SHIFT) | + (TCQ_ITEM_TCC3 << TCQ_ITEM3_SHIFT) | + (TCQ_ITEM_TCC2 << TCQ_ITEM2_SHIFT) | + (TCQ_ITEM_TCC1 << TCQ_ITEM1_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM0_SHIFT); + __raw_writel(reg, tsc_base + TCQ_ITEM_7_0); + + lastitemid = 5; + reg = __raw_readl(tsc_base + TCQCR); + reg = (reg & ~CQCR_LAST_ITEM_ID_MASK) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT); + __raw_writel(reg, tsc_base + TCQCR); + + /* Config idle for 4-wire */ + reg = TSC_4WIRE_PRECHARGE; + __raw_writel(reg, tsc_base + TICR); + + reg = TSC_4WIRE_TOUCH_DETECT; + __raw_writel(reg, tsc_base + TICR); + + /* pen down mask */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_PD_MSK; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQMR); + reg &= ~TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + + /* Debounce time = dbtime*8 adc clock cycles */ + reg = __raw_readl(tsc_base + TGCR); + reg &= ~TGCR_PDBTIME_MASK; + reg |= TGCR_PDBTIME128 | TGCR_HSYNC_EN; + __raw_writel(reg, tsc_base + TGCR); + + /* pen down enable */ + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_PDB_EN; + __raw_writel(reg, tsc_base + TGCR); + reg |= TGCR_PD_EN; + __raw_writel(reg, tsc_base + TGCR); +} + +static irqreturn_t imx_adc_interrupt(int irq, void *dev_id) +{ + unsigned long reg; + + if (__raw_readl(tsc_base + TGSR) & 0x4) { + /* deep sleep wakeup interrupt */ + /* clear tgsr */ + __raw_writel(0, tsc_base + TGSR); + /* clear deep sleep wakeup irq */ + reg = __raw_readl(tsc_base + TGCR); + reg &= ~TGCR_SLPC; + __raw_writel(reg, tsc_base + TGCR); + /* un-mask pen down and pen down irq */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_PD_MSK; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQMR); + reg &= ~TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + } else if ((__raw_readl(tsc_base + TGSR) & 0x1) && + (__raw_readl(tsc_base + TCQSR) & 0x1)) { + + /* mask pen down detect irq */ + reg = __raw_readl(tsc_base + TCQMR); + reg |= TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + + ts_data_ready = 1; + wake_up_interruptible(&tsq); + } + return IRQ_HANDLED; +} + +enum IMX_ADC_STATUS imx_adc_read_general(unsigned short *result) +{ + unsigned long reg; + unsigned int data_num = 0; + + reg = __raw_readl(tsc_base + GCQCR); + reg |= CQCR_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + while (!(__raw_readl(tsc_base + GCQSR) & CQSR_EOQ)) + continue; + reg = __raw_readl(tsc_base + GCQCR); + reg &= ~CQCR_FQS; + __raw_writel(reg, tsc_base + GCQCR); + reg = __raw_readl(tsc_base + GCQSR); + reg |= CQSR_EOQ; + __raw_writel(reg, tsc_base + GCQSR); + + while (!(__raw_readl(tsc_base + GCQSR) & CQSR_EMPT)) { + result[data_num] = __raw_readl(tsc_base + GCQFIFO) >> + GCQFIFO_ADCOUT_SHIFT; + data_num++; + } + return IMX_ADC_SUCCESS; +} + +/*! + * This function will get raw (X,Y) value by converting the voltage + * @param touch_sample Pointer to touch sample + * + * return This funciton returns 0 if successful. + * + * + */ +enum IMX_ADC_STATUS imx_adc_read_ts(struct t_touch_screen *touch_sample, + int wait_tsi) +{ + unsigned long reg; + int data_num = 0; + int detect_sample1, detect_sample2; + + memset(ts_data_buf, 0, sizeof ts_data_buf); + touch_sample->valid_flag = 1; + + if (wait_tsi) { + /* Config idle for 4-wire */ + reg = TSC_4WIRE_TOUCH_DETECT; + __raw_writel(reg, tsc_base + TICR); + + /* Pen interrupt starts new conversion queue */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + + /* unmask pen down detect irq */ + reg = __raw_readl(tsc_base + TCQMR); + reg &= ~TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + + wait_event_interruptible(tsq, ts_data_ready); + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EOQ)) + continue; + + /* stop the conversion */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + __raw_writel(reg, tsc_base + TCQCR); + reg = CQSR_PD | CQSR_EOQ; + __raw_writel(reg, tsc_base + TCQSR); + + /* change configuration for FQS mode */ + tsi_data = TSI_DATA; + reg = (0x1 << CC_YPLLSW_SHIFT) | (0x1 << CC_XNURSW_SHIFT) | + CC_XPULSW; + __raw_writel(reg, tsc_base + TICR); + } else { + /* FQS semaphore */ + down(&ts_convert_mutex); + + reg = (0x1 << CC_YPLLSW_SHIFT) | (0x1 << CC_XNURSW_SHIFT) | + CC_XPULSW; + __raw_writel(reg, tsc_base + TICR); + + /* FQS */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQCR); + reg |= CQCR_FQS; + __raw_writel(reg, tsc_base + TCQCR); + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EOQ)) + continue; + + /* stop FQS */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_FQS; + __raw_writel(reg, tsc_base + TCQCR); + + /* clear status bit */ + reg = __raw_readl(tsc_base + TCQSR); + reg |= CQSR_EOQ; + __raw_writel(reg, tsc_base + TCQSR); + tsi_data = FQS_DATA; + + /* Config idle for 4-wire */ + reg = TSC_4WIRE_PRECHARGE; + __raw_writel(reg, tsc_base + TICR); + + reg = TSC_4WIRE_TOUCH_DETECT; + __raw_writel(reg, tsc_base + TICR); + + } + + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EMPT)) { + reg = __raw_readl(tsc_base + TCQFIFO); + ts_data_buf[data_num] = reg; + data_num++; + } + + touch_sample->x_position1 = ts_data_buf[4] >> 4; + touch_sample->x_position2 = ts_data_buf[5] >> 4; + touch_sample->x_position3 = ts_data_buf[6] >> 4; + touch_sample->y_position1 = ts_data_buf[9] >> 4; + touch_sample->y_position2 = ts_data_buf[10] >> 4; + touch_sample->y_position3 = ts_data_buf[11] >> 4; + + detect_sample1 = ts_data_buf[0]; + detect_sample2 = ts_data_buf[12]; + + if ((detect_sample1 > 0x6000) || (detect_sample2 > 0x6000)) + touch_sample->valid_flag = 0; + + ts_data_ready = 0; + + if (!(touch_sample->x_position1 || + touch_sample->x_position2 || touch_sample->x_position3)) + touch_sample->contact_resistance = 0; + else + touch_sample->contact_resistance = 1; + + if (tsi_data == FQS_DATA) + up(&ts_convert_mutex); + return IMX_ADC_SUCCESS; +} + +/*! + * This function performs filtering and rejection of excessive noise prone + * sampl. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int imx_adc_filter(struct t_touch_screen *ts_curr) +{ + + unsigned int ydiff1, ydiff2, ydiff3, xdiff1, xdiff2, xdiff3; + unsigned int sample_sumx, sample_sumy; + static unsigned int prev_x[FILTLEN], prev_y[FILTLEN]; + int index = 0; + unsigned int y_curr, x_curr; + static int filt_count; + /* Added a variable filt_type to decide filtering at run-time */ + unsigned int filt_type = 0; + + /* ignore the data converted when pen down and up */ + if ((ts_curr->contact_resistance == 0) || tsi_data == TSI_DATA) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + filt_count = 0; + return 0; + } + /* ignore the data valid */ + if (ts_curr->valid_flag == 0) + return -1; + + ydiff1 = abs(ts_curr->y_position1 - ts_curr->y_position2); + ydiff2 = abs(ts_curr->y_position2 - ts_curr->y_position3); + ydiff3 = abs(ts_curr->y_position1 - ts_curr->y_position3); + if ((ydiff1 > DELTA_Y_MAX) || + (ydiff2 > DELTA_Y_MAX) || (ydiff3 > DELTA_Y_MAX)) { + pr_debug("imx_adc_filter: Ret pos 1\n"); + return -1; + } + + xdiff1 = abs(ts_curr->x_position1 - ts_curr->x_position2); + xdiff2 = abs(ts_curr->x_position2 - ts_curr->x_position3); + xdiff3 = abs(ts_curr->x_position1 - ts_curr->x_position3); + + if ((xdiff1 > DELTA_X_MAX) || + (xdiff2 > DELTA_X_MAX) || (xdiff3 > DELTA_X_MAX)) { + pr_debug("imx_adc_filter: Ret pos 2\n"); + return -1; + } + /* Compute two closer values among the three available Y readouts */ + + if (ydiff1 < ydiff2) { + if (ydiff1 < ydiff3) { + /* Sample 0 & 1 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position2; + } else { + /* Sample 0 & 2 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } else { + if (ydiff2 < ydiff3) { + /* Sample 1 & 2 closest together */ + sample_sumy = ts_curr->y_position2 + + ts_curr->y_position3; + } else { + /* Sample 0 & 2 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } + + /* + * Compute two closer values among the three available X + * readouts + */ + if (xdiff1 < xdiff2) { + if (xdiff1 < xdiff3) { + /* Sample 0 & 1 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position2; + } else { + /* Sample 0 & 2 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } else { + if (xdiff2 < xdiff3) { + /* Sample 1 & 2 closest together */ + sample_sumx = ts_curr->x_position2 + + ts_curr->x_position3; + } else { + /* Sample 0 & 2 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } + + /* + * Wait FILTER_MIN_DELAY number of samples to restart + * filtering + */ + if (filt_count < FILTER_MIN_DELAY) { + /* + * Current output is the average of the two closer + * values and no filtering is used + */ + y_curr = (sample_sumy / 2); + x_curr = (sample_sumx / 2); + ts_curr->y_position = y_curr; + ts_curr->x_position = x_curr; + filt_count++; + + } else { + if (abs(sample_sumx - (prev_x[0] + prev_x[1])) > + (DELTA_X_MAX * 16)) { + pr_debug("imx_adc_filter: : Ret pos 3\n"); + return -1; + } + if (abs(sample_sumy - (prev_y[0] + prev_y[1])) > + (DELTA_Y_MAX * 16)) { + pr_debug("imx_adc_filter: : Ret pos 4\n"); + return -1; + } + sample_sumy /= 2; + sample_sumx /= 2; + /* Use hard filtering if the sample difference < 10 */ + if ((abs(sample_sumy - prev_y[0]) > 10) || + (abs(sample_sumx - prev_x[0]) > 10)) + filt_type = 1; + + /* + * Current outputs are the average of three previous + * values and the present readout + */ + y_curr = sample_sumy; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) + y_curr = y_curr + (prev_y[index]); + else + y_curr = y_curr + (prev_y[index] / 3); + } + if (filt_type == 0) + y_curr = y_curr >> 2; + else + y_curr = y_curr >> 1; + ts_curr->y_position = y_curr; + + x_curr = sample_sumx; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) + x_curr = x_curr + (prev_x[index]); + else + x_curr = x_curr + (prev_x[index] / 3); + } + if (filt_type == 0) + x_curr = x_curr >> 2; + else + x_curr = x_curr >> 1; + ts_curr->x_position = x_curr; + + } + + /* Update previous X and Y values */ + for (index = (FILTLEN - 1); index > 0; index--) { + prev_x[index] = prev_x[index - 1]; + prev_y[index] = prev_y[index - 1]; + } + + /* + * Current output will be the most recent past for the + * next sample + */ + prev_y[0] = y_curr; + prev_x[0] = x_curr; + + return 0; + +} + +/*! + * This function retrieves the current touch screen (X,Y) coordinates. + * + * @param touch_sample Pointer to touch sample. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_get_touch_sample(struct t_touch_screen + *touch_sample, int wait_tsi) +{ + if (imx_adc_read_ts(touch_sample, wait_tsi)) + return IMX_ADC_ERROR; + if (!imx_adc_filter(touch_sample)) + return IMX_ADC_SUCCESS; + else + return IMX_ADC_ERROR; +} +EXPORT_SYMBOL(imx_adc_get_touch_sample); + +void imx_adc_set_hsync(int on) +{ + unsigned long reg; + if (imx_adc_ready) { + reg = __raw_readl(tsc_base + TGCR); + if (on) + reg |= TGCR_HSYNC_EN; + else + reg &= ~TGCR_HSYNC_EN; + __raw_writel(reg, tsc_base + TGCR); + } +} +EXPORT_SYMBOL(imx_adc_set_hsync); + +/*! + * This is the suspend of power management for the i.MX ADC API. + * It supports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int imx_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + unsigned long reg; + + /* Config idle for 4-wire */ + reg = TSC_4WIRE_PRECHARGE; + __raw_writel(reg, tsc_base + TICR); + + reg = TSC_4WIRE_TOUCH_DETECT; + __raw_writel(reg, tsc_base + TICR); + + /* enable deep sleep wake up */ + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_SLPC; + __raw_writel(reg, tsc_base + TGCR); + + /* mask pen down and pen down irq */ + reg = __raw_readl(tsc_base + TCQCR); + reg |= CQCR_PD_MSK; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQMR); + reg |= TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + + /* Set power mode to off */ + reg = __raw_readl(tsc_base + TGCR) & ~TGCR_POWER_MASK; + reg |= TGCR_POWER_OFF; + __raw_writel(reg, tsc_base + TGCR); + + if (device_may_wakeup(&pdev->dev)) { + enable_irq_wake(adc_data->irq); + } else { + suspend_flag = 1; + tsc_clk_disable(); + } + return 0; +}; + +/*! + * This is the resume of power management for the i.MX adc API. + * It supports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int imx_adc_resume(struct platform_device *pdev) +{ + unsigned long reg; + + if (device_may_wakeup(&pdev->dev)) { + disable_irq_wake(adc_data->irq); + } else { + suspend_flag = 0; + tsc_clk_enable(); + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + } + + /* recover power mode */ + reg = __raw_readl(tsc_base + TGCR) & ~TGCR_POWER_MASK; + reg |= TGCR_POWER_SAVE; + __raw_writel(reg, tsc_base + TGCR); + + return 0; +} + +/*! + * This function implements the open method on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int imx_adc_open(struct inode *inode, struct file *file) +{ + while (suspend_flag) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, !suspend_flag)) + return -ERESTARTSYS; + } + pr_debug("imx_adc : imx_adc_open()\n"); + return 0; +} + +/*! + * This function implements the release method on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int imx_adc_free(struct inode *inode, struct file *file) +{ + pr_debug("imx_adc : imx_adc_free()\n"); + return 0; +} + +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +int imx_adc_init(void) +{ + unsigned long reg; + + pr_debug("imx_adc_init()\n"); + + if (suspend_flag) + return -EBUSY; + + tsc_clk_enable(); + + /* Reset */ + tsc_self_reset(); + + /* Internal reference */ + tsc_intref_enable(); + + /* Set power mode */ + reg = __raw_readl(tsc_base + TGCR) & ~TGCR_POWER_MASK; + reg |= TGCR_POWER_SAVE; + __raw_writel(reg, tsc_base + TGCR); + + imx_tsc_init(); + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_init); + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_deinit(void) +{ + pr_debug("imx_adc_deinit()\n"); + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_deinit); + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_convert(enum t_channel channel, + unsigned short *result) +{ + unsigned long reg; + int lastitemid; + struct t_touch_screen touch_sample; + + switch (channel) { + + case TS_X_POS: + imx_adc_get_touch_sample(&touch_sample, 0); + result[0] = touch_sample.x_position; + + /* if no pen down ,recover the register configuration */ + if (touch_sample.contact_resistance == 0) { + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + + reg = __raw_readl(tsc_base + TCQMR); + reg &= ~TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + } + break; + + case TS_Y_POS: + imx_adc_get_touch_sample(&touch_sample, 0); + result[1] = touch_sample.y_position; + + /* if no pen down ,recover the register configuration */ + if (touch_sample.contact_resistance == 0) { + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + + reg = __raw_readl(tsc_base + TCQMR); + reg &= ~TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + } + break; + + case GER_PURPOSE_ADC0: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC0; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_ADC1: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC1; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_ADC2: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC2; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_MULTICHNNEL: + down(&general_convert_mutex); + + reg = TSC_GENERAL_ADC_GCC0; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + reg = TSC_GENERAL_ADC_GCC1; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC1); + + reg = TSC_GENERAL_ADC_GCC2; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC2); + + reg = (GCQ_ITEM_GCC2 << GCQ_ITEM2_SHIFT) | + (GCQ_ITEM_GCC1 << GCQ_ITEM1_SHIFT) | + (GCQ_ITEM_GCC0 << GCQ_ITEM0_SHIFT); + __raw_writel(reg, tsc_base + GCQ_ITEM_7_0); + + lastitemid = 2; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + default: + pr_debug("%s: bad channel number\n", __func__); + return IMX_ADC_ERROR; + } + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_convert); + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_convert_multichnnel(enum t_channel channels, + unsigned short *result) +{ + imx_adc_convert(GER_PURPOSE_MULTICHNNEL, result); + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_convert_multichnnel); + +/*! + * This function implements IOCTL controls on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int imx_adc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct t_adc_convert_param *convert_param; + + if ((_IOC_TYPE(cmd) != 'p') && (_IOC_TYPE(cmd) != 'D')) + return -ENOTTY; + + while (suspend_flag) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, !suspend_flag)) + return -ERESTARTSYS; + } + + switch (cmd) { + case IMX_ADC_INIT: + pr_debug("init adc\n"); + CHECK_ERROR(imx_adc_init()); + break; + + case IMX_ADC_DEINIT: + pr_debug("deinit adc\n"); + CHECK_ERROR(imx_adc_deinit()); + break; + + case IMX_ADC_CONVERT: + convert_param = kmalloc(sizeof(*convert_param), GFP_KERNEL); + if (convert_param == NULL) + return -ENOMEM; + if (copy_from_user(convert_param, + (struct t_adc_convert_param *)arg, + sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(imx_adc_convert(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((struct t_adc_convert_param *)arg, + convert_param, sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case IMX_ADC_CONVERT_MULTICHANNEL: + convert_param = kmalloc(sizeof(*convert_param), GFP_KERNEL); + if (convert_param == NULL) + return -ENOMEM; + if (copy_from_user(convert_param, + (struct t_adc_convert_param *)arg, + sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(imx_adc_convert_multichnnel + (convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((struct t_adc_convert_param *)arg, + convert_param, sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + default: + pr_debug("imx_adc_ioctl: unsupported ioctl command 0x%x\n", + cmd); + return -EINVAL; + } + return 0; +} + +static struct file_operations imx_adc_fops = { + .owner = THIS_MODULE, + .ioctl = imx_adc_ioctl, + .open = imx_adc_open, + .release = imx_adc_free, +}; + +static int imx_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + int retval; + struct device *temp_class; + struct resource *res; + void __iomem *base; + + /* ioremap the base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No TSC base address provided\n"); + goto err_out0; + } + base = ioremap(res->start, res->end - res->start); + if (base == NULL) { + dev_err(&pdev->dev, "failed to rebase TSC base address\n"); + goto err_out0; + } + tsc_base = (unsigned long)base; + + /* create the chrdev */ + imx_adc_major = register_chrdev(0, "imx_adc", &imx_adc_fops); + + if (imx_adc_major < 0) { + dev_err(&pdev->dev, "Unable to get a major for imx_adc\n"); + return imx_adc_major; + } + init_waitqueue_head(&suspendq); + init_waitqueue_head(&tsq); + + imx_adc_class = class_create(THIS_MODULE, "imx_adc"); + if (IS_ERR(imx_adc_class)) { + dev_err(&pdev->dev, "Error creating imx_adc class.\n"); + ret = PTR_ERR(imx_adc_class); + goto err_out1; + } + + temp_class = device_create(imx_adc_class, NULL, + MKDEV(imx_adc_major, 0), NULL, "imx_adc"); + if (IS_ERR(temp_class)) { + dev_err(&pdev->dev, "Error creating imx_adc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + adc_data = kmalloc(sizeof(struct imx_adc_data), GFP_KERNEL); + if (adc_data == NULL) + return -ENOMEM; + adc_data->irq = platform_get_irq(pdev, 0); + retval = request_irq(adc_data->irq, imx_adc_interrupt, + 0, MOD_NAME, MOD_NAME); + if (retval) { + return retval; + } + adc_data->adc_clk = clk_get(&pdev->dev, "tchscrn_clk"); + + ret = imx_adc_init(); + + if (ret != IMX_ADC_SUCCESS) { + dev_err(&pdev->dev, "Error in imx_adc_init.\n"); + goto err_out4; + } + imx_adc_ready = 1; + + /* By default, devices should wakeup if they can */ + /* So TouchScreen is set as "should wakeup" as it can */ + device_init_wakeup(&pdev->dev, 1); + + pr_info("i.MX ADC at 0x%x irq %d\n", (unsigned int)res->start, + adc_data->irq); + return ret; + +err_out4: + device_destroy(imx_adc_class, MKDEV(imx_adc_major, 0)); +err_out2: + class_destroy(imx_adc_class); +err_out1: + unregister_chrdev(imx_adc_major, "imx_adc"); +err_out0: + return ret; +} + +static int imx_adc_module_remove(struct platform_device *pdev) +{ + imx_adc_ready = 0; + imx_adc_deinit(); + device_destroy(imx_adc_class, MKDEV(imx_adc_major, 0)); + class_destroy(imx_adc_class); + unregister_chrdev(imx_adc_major, "imx_adc"); + free_irq(adc_data->irq, MOD_NAME); + kfree(adc_data); + pr_debug("i.MX ADC successfully removed\n"); + return 0; +} + +static struct platform_driver imx_adc_driver = { + .driver = { + .name = "imx_adc", + }, + .suspend = imx_adc_suspend, + .resume = imx_adc_resume, + .probe = imx_adc_module_probe, + .remove = imx_adc_module_remove, +}; + +/* + * Initialization and Exit + */ +static int __init imx_adc_module_init(void) +{ + pr_debug("i.MX ADC driver loading...\n"); + return platform_driver_register(&imx_adc_driver); +} + +static void __exit imx_adc_module_exit(void) +{ + platform_driver_unregister(&imx_adc_driver); + pr_debug("i.MX ADC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(imx_adc_module_init); +module_exit(imx_adc_module_exit); + +MODULE_DESCRIPTION("i.MX ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/adc/imx_adc_reg.h b/drivers/mxc/adc/imx_adc_reg.h new file mode 100644 index 000000000000..05c0a19048db --- /dev/null +++ b/drivers/mxc/adc/imx_adc_reg.h @@ -0,0 +1,242 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef __IMX_ADC_H__ +#define __IMX_ADC_H__ + +/* TSC General Config Register */ +#define TGCR 0x000 +#define TGCR_IPG_CLK_EN (1 << 0) +#define TGCR_TSC_RST (1 << 1) +#define TGCR_FUNC_RST (1 << 2) +#define TGCR_SLPC (1 << 4) +#define TGCR_STLC (1 << 5) +#define TGCR_HSYNC_EN (1 << 6) +#define TGCR_HSYNC_POL (1 << 7) +#define TGCR_POWERMODE_SHIFT 8 +#define TGCR_POWER_OFF (0x0 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_SAVE (0x1 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_ON (0x3 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_MASK (0x3 << TGCR_POWERMODE_SHIFT) +#define TGCR_INTREFEN (1 << 10) +#define TGCR_ADCCLKCFG_SHIFT 16 +#define TGCR_PD_EN (1 << 23) +#define TGCR_PDB_EN (1 << 24) +#define TGCR_PDBTIME_SHIFT 25 +#define TGCR_PDBTIME128 (0x3f << TGCR_PDBTIME_SHIFT) +#define TGCR_PDBTIME_MASK (0x7f << TGCR_PDBTIME_SHIFT) + +/* TSC General Status Register */ +#define TGSR 0x004 +#define TCQ_INT (1 << 0) +#define GCQ_INT (1 << 1) +#define SLP_INT (1 << 2) +#define TCQ_DMA (1 << 16) +#define GCQ_DMA (1 << 17) + +/* TSC IDLE Config Register */ +#define TICR 0x008 + +/* TouchScreen Convert Queue FIFO Register */ +#define TCQFIFO 0x400 +/* TouchScreen Convert Queue Control Register */ +#define TCQCR 0x404 +#define CQCR_QSM_SHIFT 0 +#define CQCR_QSM_STOP (0x0 << CQCR_QSM_SHIFT) +#define CQCR_QSM_PEN (0x1 << CQCR_QSM_SHIFT) +#define CQCR_QSM_FQS (0x2 << CQCR_QSM_SHIFT) +#define CQCR_QSM_FQS_PEN (0x3 << CQCR_QSM_SHIFT) +#define CQCR_QSM_MASK (0x3 << CQCR_QSM_SHIFT) +#define CQCR_FQS (1 << 2) +#define CQCR_RPT (1 << 3) +#define CQCR_LAST_ITEM_ID_SHIFT 4 +#define CQCR_LAST_ITEM_ID_MASK (0xf << CQCR_LAST_ITEM_ID_SHIFT) +#define CQCR_FIFOWATERMARK_SHIFT 8 +#define CQCR_FIFOWATERMARK_MASK (0xf << CQCR_FIFOWATERMARK_SHIFT) +#define CQCR_REPEATWAIT_SHIFT 12 +#define CQCR_REPEATWAIT_MASK (0xf << CQCR_REPEATWAIT_SHIFT) +#define CQCR_QRST (1 << 16) +#define CQCR_FRST (1 << 17) +#define CQCR_PD_MSK (1 << 18) +#define CQCR_PD_CFG (1 << 19) + +/* TouchScreen Convert Queue Status Register */ +#define TCQSR 0x408 +#define CQSR_PD (1 << 0) +#define CQSR_EOQ (1 << 1) +#define CQSR_FOR (1 << 4) +#define CQSR_FUR (1 << 5) +#define CQSR_FER (1 << 6) +#define CQSR_EMPT (1 << 13) +#define CQSR_FULL (1 << 14) +#define CQSR_FDRY (1 << 15) + +/* TouchScreen Convert Queue Mask Register */ +#define TCQMR 0x40c +#define TCQMR_PD_IRQ_MSK (1 << 0) +#define TCQMR_EOQ_IRQ_MSK (1 << 1) +#define TCQMR_FOR_IRQ_MSK (1 << 4) +#define TCQMR_FUR_IRQ_MSK (1 << 5) +#define TCQMR_FER_IRQ_MSK (1 << 6) +#define TCQMR_PD_DMA_MSK (1 << 16) +#define TCQMR_EOQ_DMA_MSK (1 << 17) +#define TCQMR_FOR_DMA_MSK (1 << 20) +#define TCQMR_FUR_DMA_MSK (1 << 21) +#define TCQMR_FER_DMA_MSK (1 << 22) +#define TCQMR_FDRY_DMA_MSK (1 << 31) + +/* TouchScreen Convert Queue ITEM 7~0 */ +#define TCQ_ITEM_7_0 0x420 + +/* TouchScreen Convert Queue ITEM 15~8 */ +#define TCQ_ITEM_15_8 0x424 + +#define TCQ_ITEM7_SHIFT 28 +#define TCQ_ITEM6_SHIFT 24 +#define TCQ_ITEM5_SHIFT 20 +#define TCQ_ITEM4_SHIFT 16 +#define TCQ_ITEM3_SHIFT 12 +#define TCQ_ITEM2_SHIFT 8 +#define TCQ_ITEM1_SHIFT 4 +#define TCQ_ITEM0_SHIFT 0 + +#define TCQ_ITEM_TCC0 0x0 +#define TCQ_ITEM_TCC1 0x1 +#define TCQ_ITEM_TCC2 0x2 +#define TCQ_ITEM_TCC3 0x3 +#define TCQ_ITEM_TCC4 0x4 +#define TCQ_ITEM_TCC5 0x5 +#define TCQ_ITEM_TCC6 0x6 +#define TCQ_ITEM_TCC7 0x7 +#define TCQ_ITEM_GCC7 0x8 +#define TCQ_ITEM_GCC6 0x9 +#define TCQ_ITEM_GCC5 0xa +#define TCQ_ITEM_GCC4 0xb +#define TCQ_ITEM_GCC3 0xc +#define TCQ_ITEM_GCC2 0xd +#define TCQ_ITEM_GCC1 0xe +#define TCQ_ITEM_GCC0 0xf + +/* TouchScreen Convert Config 0-7 */ +#define TCC0 0x440 +#define TCC1 0x444 +#define TCC2 0x448 +#define TCC3 0x44c +#define TCC4 0x450 +#define TCC5 0x454 +#define TCC6 0x458 +#define TCC7 0x45c +#define CC_PEN_IACK (1 << 1) +#define CC_SEL_REFN_SHIFT 2 +#define CC_SEL_REFN_YNLR (0x1 << CC_SEL_REFN_SHIFT) +#define CC_SEL_REFN_AGND (0x2 << CC_SEL_REFN_SHIFT) +#define CC_SEL_REFN_MASK (0x3 << CC_SEL_REFN_SHIFT) +#define CC_SELIN_SHIFT 4 +#define CC_SELIN_XPUL (0x0 << CC_SELIN_SHIFT) +#define CC_SELIN_YPLL (0x1 << CC_SELIN_SHIFT) +#define CC_SELIN_XNUR (0x2 << CC_SELIN_SHIFT) +#define CC_SELIN_YNLR (0x3 << CC_SELIN_SHIFT) +#define CC_SELIN_WIPER (0x4 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX0 (0x5 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX1 (0x6 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX2 (0x7 << CC_SELIN_SHIFT) +#define CC_SELIN_MASK (0x7 << CC_SELIN_SHIFT) +#define CC_SELREFP_SHIFT 7 +#define CC_SELREFP_YPLL (0x0 << CC_SELREFP_SHIFT) +#define CC_SELREFP_XPUL (0x1 << CC_SELREFP_SHIFT) +#define CC_SELREFP_EXT (0x2 << CC_SELREFP_SHIFT) +#define CC_SELREFP_INT (0x3 << CC_SELREFP_SHIFT) +#define CC_SELREFP_MASK (0x3 << CC_SELREFP_SHIFT) +#define CC_XPULSW (1 << 9) +#define CC_XNURSW_SHIFT 10 +#define CC_XNURSW_HIGH (0x0 << CC_XNURSW_SHIFT) +#define CC_XNURSW_OFF (0x1 << CC_XNURSW_SHIFT) +#define CC_XNURSW_LOW (0x3 << CC_XNURSW_SHIFT) +#define CC_XNURSW_MASK (0x3 << CC_XNURSW_SHIFT) +#define CC_YPLLSW_SHIFT 12 +#define CC_YPLLSW_MASK (0x3 << CC_YPLLSW_SHIFT) +#define CC_YNLRSW (1 << 14) +#define CC_WIPERSW (1 << 15) +#define CC_NOS_SHIFT 16 +#define CC_YPLLSW_HIGH (0x0 << CC_NOS_SHIFT) +#define CC_YPLLSW_OFF (0x1 << CC_NOS_SHIFT) +#define CC_YPLLSW_LOW (0x3 << CC_NOS_SHIFT) +#define CC_NOS_MASK (0xf << CC_NOS_SHIFT) +#define CC_IGS (1 << 20) +#define CC_SETTLING_TIME_SHIFT 24 +#define CC_SETTLING_TIME_MASK (0xff << CC_SETTLING_TIME_SHIFT) + +#define TSC_4WIRE_PRECHARGE 0x158c +#define TSC_4WIRE_TOUCH_DETECT 0x578e + +#define TSC_4WIRE_X_MEASUMENT 0x1c90 +#define TSC_4WIRE_Y_MEASUMENT 0x4604 + +#define TSC_GENERAL_ADC_GCC0 0x17dc +#define TSC_GENERAL_ADC_GCC1 0x17ec +#define TSC_GENERAL_ADC_GCC2 0x17fc + +/* GeneralADC Convert Queue FIFO Register */ +#define GCQFIFO 0x800 +#define GCQFIFO_ADCOUT_SHIFT 4 +#define GCQFIFO_ADCOUT_MASK (0xfff << GCQFIFO_ADCOUT_SHIFT) +/* GeneralADC Convert Queue Control Register */ +#define GCQCR 0x804 +/* GeneralADC Convert Queue Status Register */ +#define GCQSR 0x808 +/* GeneralADC Convert Queue Mask Register */ +#define GCQMR 0x80c + +/* GeneralADC Convert Queue ITEM 7~0 */ +#define GCQ_ITEM_7_0 0x820 +/* GeneralADC Convert Queue ITEM 15~8 */ +#define GCQ_ITEM_15_8 0x824 + +#define GCQ_ITEM7_SHIFT 28 +#define GCQ_ITEM6_SHIFT 24 +#define GCQ_ITEM5_SHIFT 20 +#define GCQ_ITEM4_SHIFT 16 +#define GCQ_ITEM3_SHIFT 12 +#define GCQ_ITEM2_SHIFT 8 +#define GCQ_ITEM1_SHIFT 4 +#define GCQ_ITEM0_SHIFT 0 + +#define GCQ_ITEM_GCC0 0x0 +#define GCQ_ITEM_GCC1 0x1 +#define GCQ_ITEM_GCC2 0x2 +#define GCQ_ITEM_GCC3 0x3 + +/* GeneralADC Convert Config 0-7 */ +#define GCC0 0x840 +#define GCC1 0x844 +#define GCC2 0x848 +#define GCC3 0x84c +#define GCC4 0x850 +#define GCC5 0x854 +#define GCC6 0x858 +#define GCC7 0x85c + +/* TSC Test Register R/W */ +#define TTR 0xc00 +/* TSC Monitor Register 1, 2 */ +#define MNT1 0xc04 +#define MNT2 0xc04 + +#define DETECT_ITEM_ID_1 1 +#define DETECT_ITEM_ID_2 5 +#define TS_X_ITEM_ID 2 +#define TS_Y_ITEM_ID 3 +#define TSI_DATA 1 +#define FQS_DATA 0 + +#endif /* __IMX_ADC_H__ */ diff --git a/drivers/mxc/asrc/Kconfig b/drivers/mxc/asrc/Kconfig new file mode 100644 index 000000000000..91c657079007 --- /dev/null +++ b/drivers/mxc/asrc/Kconfig @@ -0,0 +1,13 @@ +# +# ASRC configuration +# + +menu "MXC Asynchronous Sample Rate Converter support" + +config MXC_ASRC + tristate "ASRC support" + depends on ARCH_MX35 || ARCH_MX53 + ---help--- + Say Y to get the ASRC service. + +endmenu diff --git a/drivers/mxc/asrc/Makefile b/drivers/mxc/asrc/Makefile new file mode 100644 index 000000000000..7e9aba3abb56 --- /dev/null +++ b/drivers/mxc/asrc/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for the kernel Asynchronous Sample Rate Converter driver +# +obj-$(CONFIG_MXC_ASRC) += mxc_asrc.o diff --git a/drivers/mxc/asrc/mxc_asrc.c b/drivers/mxc/asrc/mxc_asrc.c new file mode 100644 index 000000000000..932e7bbb19c6 --- /dev/null +++ b/drivers/mxc/asrc/mxc_asrc.c @@ -0,0 +1,1728 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_asrc.c + * + * @brief MXC Asynchronous Sample Rate Converter + * + * @ingroup SOUND + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int asrc_major; +static struct class *asrc_class; +#define ASRC_PROC_PATH "driver/asrc" + +#define ASRC_RATIO_DECIMAL_DEPTH 26 + +DEFINE_SPINLOCK(data_lock); +DEFINE_SPINLOCK(input_int_lock); +DEFINE_SPINLOCK(output_int_lock); + +#define AICPA 0 /* Input Clock Divider A Offset */ +#define AICDA 3 /* Input Clock Prescaler A Offset */ +#define AICPB 6 /* Input Clock Divider B Offset */ +#define AICDB 9 /* Input Clock Prescaler B Offset */ +#define AOCPA 12 /* Output Clock Divider A Offset */ +#define AOCDA 15 /* Output Clock Prescaler A Offset */ +#define AOCPB 18 /* Output Clock Divider B Offset */ +#define AOCDB 21 /* Output Clock Prescaler B Offset */ +#define AICPC 0 /* Input Clock Divider C Offset */ +#define AICDC 3 /* Input Clock Prescaler C Offset */ +#define AOCDC 6 /* Output Clock Prescaler C Offset */ +#define AOCPC 9 /* Output Clock Divider C Offset */ + +char *asrc_pair_id[] = { + [0] = "ASRC RX PAIR A", + [1] = "ASRC TX PAIR A", + [2] = "ASRC RX PAIR B", + [3] = "ASRC TX PAIR B", + [4] = "ASRC RX PAIR C", + [5] = "ASRC TX PAIR C", +}; + +enum asrc_status { + ASRC_ASRSTR_AIDEA = 0x01, + ASRC_ASRSTR_AIDEB = 0x02, + ASRC_ASRSTR_AIDEC = 0x04, + ASRC_ASRSTR_AODFA = 0x08, + ASRC_ASRSTR_AODFB = 0x10, + ASRC_ASRSTR_AODFC = 0x20, + ASRC_ASRSTR_AOLE = 0x40, + ASRC_ASRSTR_FPWT = 0x80, + ASRC_ASRSTR_AIDUA = 0x100, + ASRC_ASRSTR_AIDUB = 0x200, + ASRC_ASRSTR_AIDUC = 0x400, + ASRC_ASRSTR_AODOA = 0x800, + ASRC_ASRSTR_AODOB = 0x1000, + ASRC_ASRSTR_AODOC = 0x2000, + ASRC_ASRSTR_AIOLA = 0x4000, + ASRC_ASRSTR_AIOLB = 0x8000, + ASRC_ASRSTR_AIOLC = 0x10000, + ASRC_ASRSTR_AOOLA = 0x20000, + ASRC_ASRSTR_AOOLB = 0x40000, + ASRC_ASRSTR_AOOLC = 0x80000, + ASRC_ASRSTR_ATQOL = 0x100000, + ASRC_ASRSTR_DSLCNT = 0x200000, +}; + +/* Sample rates are aligned with that defined in pcm.h file */ +static const unsigned char asrc_process_table[][8][2] = { + /* 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */ +/*5512Hz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*8kHz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*11025Hz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*16kHz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*22050Hz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*32kHz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, +/*44.1kHz*/ + {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, +/*48kHz*/ + {{0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, +/*64kHz*/ + {{1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, +/*88.2kHz*/ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, +/*96kHz*/ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, +/*176kHz*/ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, +/*192kHz*/ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, +}; + +static const unsigned char asrc_divider_table[] = { +/*5500Hz 8kHz 11025Hz 16kHz 22050kHz 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176400Hz 192kHz*/ + 0x07, 0x15, 0x06, 0x14, 0x05, 0x13, 0x04, 0x04, 0x12, 0x03, 0x03, 0x02, + 0x02, +}; + +static struct asrc_data *g_asrc_data; +static struct proc_dir_entry *proc_asrc; +static unsigned long asrc_vrt_base_addr; +static struct mxc_asrc_platform_data *mxc_asrc_data; + +/* The following tables map the relationship between asrc_inclk/asrc_outclk in + * mxc_asrc.h and the registers of ASRCSR + */ +static unsigned char input_clk_map_v1[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, +}; + +static unsigned char output_clk_map_v1[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, +}; + +static unsigned char input_clk_map_v2[] = { + 0, 1, 2, 3, 4, 5, 0xf, 0xf, 0xf, 8, 9, 0xa, 0xb, 0xc, 0xf, 0xd, +}; + +static unsigned char output_clk_map_v2[] = { + 8, 9, 0xa, 0, 0xc, 0x5, 0xf, 0xf, 0, 1, 2, 0xf, 0xf, 4, 0xf, 0xd, +}; + +static unsigned char *input_clk_map, *output_clk_map; + +static int asrc_set_clock_ratio(enum asrc_pair_index index, + int input_sample_rate, int output_sample_rate) +{ + int i; + int integ = 0; + unsigned long reg_val = 0; + + if (output_sample_rate == 0) + return -1; + while (input_sample_rate >= output_sample_rate) { + input_sample_rate -= output_sample_rate; + integ++; + } + reg_val |= (integ << 26); + + for (i = 1; i <= ASRC_RATIO_DECIMAL_DEPTH; i++) { + if ((input_sample_rate * 2) >= output_sample_rate) { + reg_val |= (1 << (ASRC_RATIO_DECIMAL_DEPTH - i)); + input_sample_rate = + input_sample_rate * 2 - output_sample_rate; + } else + input_sample_rate = input_sample_rate << 1; + + if (input_sample_rate == 0) + break; + } + + __raw_writel(reg_val, + (asrc_vrt_base_addr + ASRC_ASRIDRLA_REG + (index << 3))); + __raw_writel((reg_val >> 24), + (asrc_vrt_base_addr + ASRC_ASRIDRHA_REG + (index << 3))); + return 0; +} + +static int asrc_set_process_configuration(enum asrc_pair_index index, + int input_sample_rate, + int output_sample_rate) +{ + int i = 0, j = 0; + unsigned long reg; + switch (input_sample_rate) { + case 5512: + i = 0; + case 8000: + i = 1; + break; + case 11025: + i = 2; + break; + case 16000: + i = 3; + break; + case 22050: + i = 4; + break; + case 32000: + i = 5; + break; + case 44100: + i = 6; + break; + case 48000: + i = 7; + break; + case 64000: + i = 8; + break; + case 88200: + i = 9; + break; + case 96000: + i = 10; + break; + case 176400: + i = 11; + break; + case 192000: + i = 12; + break; + default: + return -1; + } + + switch (output_sample_rate) { + case 32000: + j = 0; + break; + case 44100: + j = 1; + break; + case 48000: + j = 2; + break; + case 64000: + j = 3; + break; + case 88200: + j = 4; + break; + case 96000: + j = 5; + break; + case 176400: + j = 6; + break; + case 192000: + j = 7; + break; + default: + return -1; + } + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + reg &= ~(0x0f << (6 + (index << 2))); + reg |= + ((asrc_process_table[i][j][0] << (6 + (index << 2))) | + (asrc_process_table[i][j][1] << (8 + (index << 2)))); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCFG_REG); + + return 0; +} + +static int asrc_get_asrck_clock_divider(int sample_rate) +{ + int i = 0; + switch (sample_rate) { + case 5500: + i = 0; + break; + case 8000: + i = 1; + break; + case 11025: + i = 2; + break; + case 16000: + i = 3; + break; + case 22050: + i = 4; + break; + case 32000: + i = 5; + break; + case 44100: + i = 6; + break; + case 48000: + i = 7; + break; + case 64000: + i = 8; + break; + case 88200: + i = 9; + break; + case 96000: + i = 10; + break; + case 176400: + i = 11; + break; + case 192000: + i = 12; + break; + default: + return -1; + } + + return asrc_divider_table[i]; +} + +int asrc_req_pair(int chn_num, enum asrc_pair_index *index) +{ + int err = 0; + unsigned long lock_flags; + spin_lock_irqsave(&data_lock, lock_flags); + + if (chn_num > 2) { + if (g_asrc_data->asrc_pair[ASRC_PAIR_C].active + || (chn_num > g_asrc_data->asrc_pair[ASRC_PAIR_C].chn_max)) + err = -EBUSY; + else { + *index = ASRC_PAIR_C; + g_asrc_data->asrc_pair[ASRC_PAIR_C].chn_num = chn_num; + g_asrc_data->asrc_pair[ASRC_PAIR_C].active = 1; + } + } else { + if (g_asrc_data->asrc_pair[ASRC_PAIR_A].active || + (g_asrc_data->asrc_pair[ASRC_PAIR_A].chn_max == 0)) { + if (g_asrc_data->asrc_pair[ASRC_PAIR_B]. + active + || (g_asrc_data->asrc_pair[ASRC_PAIR_B]. + chn_max == 0)) + err = -EBUSY; + else { + *index = ASRC_PAIR_B; + g_asrc_data->asrc_pair[ASRC_PAIR_B].chn_num = 2; + g_asrc_data->asrc_pair[ASRC_PAIR_B].active = 1; + } + } else { + *index = ASRC_PAIR_A; + g_asrc_data->asrc_pair[ASRC_PAIR_A].chn_num = 2; + g_asrc_data->asrc_pair[ASRC_PAIR_A].active = 1; + } + } + spin_unlock_irqrestore(&data_lock, lock_flags); + return err; +} + +EXPORT_SYMBOL(asrc_req_pair); + +void asrc_release_pair(enum asrc_pair_index index) +{ + unsigned long reg; + unsigned long lock_flags; + + spin_lock_irqsave(&data_lock, lock_flags); + g_asrc_data->asrc_pair[index].active = 0; + g_asrc_data->asrc_pair[index].overload_error = 0; + /********Disable PAIR*************/ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (index + 1)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + spin_unlock_irqrestore(&data_lock, lock_flags); +} + +EXPORT_SYMBOL(asrc_release_pair); + +int asrc_config_pair(struct asrc_config *config) +{ + int err = 0; + int reg, tmp, channel_num; + unsigned long lock_flags; + /* Set the channel number */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + spin_lock_irqsave(&data_lock, lock_flags); + g_asrc_data->asrc_pair[config->pair].chn_num = config->channel_num; + spin_unlock_irqrestore(&data_lock, lock_flags); + reg &= + ~((0xFFFFFFFF >> (32 - mxc_asrc_data->channel_bits)) << + (mxc_asrc_data->channel_bits * config->pair)); + if (mxc_asrc_data->channel_bits > 3) + channel_num = config->channel_num; + else + channel_num = (config->channel_num + 1) / 2; + tmp = channel_num << (mxc_asrc_data->channel_bits * config->pair); + reg |= tmp; + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + /* Set the clock source */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCSR_REG); + tmp = ~(0x0f << (config->pair << 2)); + reg &= tmp; + tmp = ~(0x0f << (12 + (config->pair << 2))); + reg &= tmp; + reg |= + ((input_clk_map[config->inclk] << (config->pair << 2)) | (output_clk_map[config-> + outclk] << (12 + + (config-> + pair << + 2)))); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCSR_REG); + + /* default setting */ + /* automatic selection for processing mode */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg |= (1 << (20 + config->pair)); + reg &= ~(1 << (14 + (config->pair << 1))); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRRA_REG); + reg &= 0xffbfffff; + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRRA_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg = reg & (~(1 << 23)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + /* Default Clock Divider Setting */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + if (config->pair == ASRC_PAIR_A) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + reg &= 0xfc0fc0; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX) + reg |= 7 << AICPA; + else if ((config->inclk & 0x0f) == INCLK_SPDIF_TX) + reg |= 6 << AICPA; + else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPA; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPA; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPA; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == OUTCLK_SPDIF_RX) + reg |= 7 << AOCPA; + else if ((config->outclk & 0x0f) == OUTCLK_SPDIF_TX) + reg |= 6 << AOCPA; + else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPA; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPA; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPA; + else + err = -EFAULT; + } + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + + } else if (config->pair == ASRC_PAIR_B) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + reg &= 0x03f03f; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX) + reg |= 7 << AICPB; + else if ((config->inclk & 0x0f) == INCLK_SPDIF_TX) + reg |= 6 << AICPB; + else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPB; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPB; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPB; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == OUTCLK_SPDIF_RX) + reg |= 7 << AOCPB; + else if ((config->outclk & 0x0f) == OUTCLK_SPDIF_TX) + reg |= 6 << AOCPB; + else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPB; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPB; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPB; + else + err = -EFAULT; + } + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + + } else { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR2_REG); + reg &= 0; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX) + reg |= 7 << AICPC; + else if ((config->inclk & 0x0f) == INCLK_SPDIF_TX) + reg |= 6 << AICPC; + else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPC; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPC; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPC; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == OUTCLK_SPDIF_RX) + reg |= 7 << AOCPC; + else if ((config->outclk & 0x0f) == OUTCLK_SPDIF_TX) + reg |= 6 << AOCPC; + else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPC; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPC; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPC; + else + err = -EFAULT; + } + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR2_REG); + + } + + /* check whether ideal ratio is a must */ + if ((config->inclk & 0x0f) == INCLK_NONE) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (20 + config->pair)); + reg |= (0x03 << (13 + (config->pair << 1))); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + err = asrc_set_clock_ratio(config->pair, + config->input_sample_rate, + config->output_sample_rate); + if (err < 0) + return err; + + err = asrc_set_process_configuration(config->pair, + config->input_sample_rate, + config-> + output_sample_rate); + if (err < 0) + return err; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + if (config->input_sample_rate == 44100 + || config->input_sample_rate == 88200) { + pr_info + ("ASRC core clock cann't support sample rate %d\n", + config->input_sample_rate); + err = -EFAULT; + } + } else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) { + if (config->output_sample_rate == 44100 + || config->output_sample_rate == 88200) { + pr_info + ("ASRC core clock cann't support sample rate %d\n", + config->input_sample_rate); + err = -EFAULT; + } + } + + return err; +} + +EXPORT_SYMBOL(asrc_config_pair); + +void asrc_start_conv(enum asrc_pair_index index) +{ + int reg, reg_1; + unsigned long lock_flags; + int i; + + spin_lock_irqsave(&data_lock, lock_flags); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + if ((reg & 0x0E) == 0) + clk_enable(mxc_asrc_data->asrc_audio_clk); + reg |= (1 << (1 + index)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + while (!(reg & (1 << (index + 21)))) + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + reg_1 = __raw_readl(asrc_vrt_base_addr + ASRC_ASRSTR_REG); + + reg = 0; + for (i = 0; i < 20; i++) { + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + } + + __raw_writel(0x40, asrc_vrt_base_addr + ASRC_ASRIER_REG); + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_start_conv); + +void asrc_stop_conv(enum asrc_pair_index index) +{ + int reg; + unsigned long lock_flags; + spin_lock_irqsave(&data_lock, lock_flags); + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (1 + index)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + if ((reg & 0x0E) == 0) + clk_disable(mxc_asrc_data->asrc_audio_clk); + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_stop_conv); + +/*! + * @brief asrc interrupt handler + */ +static irqreturn_t asrc_isr(int irq, void *dev_id) +{ + unsigned long status; + int reg = 0x40; + + status = __raw_readl(asrc_vrt_base_addr + ASRC_ASRSTR_REG); + if (g_asrc_data->asrc_pair[ASRC_PAIR_A].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } else if (g_asrc_data->asrc_pair[ASRC_PAIR_B].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } else if (g_asrc_data->asrc_pair[ASRC_PAIR_C].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } + + /* try to clean the overload error */ + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRSTR_REG); + + return IRQ_HANDLED; +} + +void asrc_get_status(struct asrc_status_flags *flags) +{ + unsigned long lock_flags; + enum asrc_pair_index index; + + spin_lock_irqsave(&data_lock, lock_flags); + index = flags->index; + flags->overload_error = g_asrc_data->asrc_pair[index].overload_error; + + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_get_status); + +static int mxc_init_asrc(void) +{ + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ + __raw_writel(0x0001, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + /* Enable overflow interrupt */ + __raw_writel(0x00, asrc_vrt_base_addr + ASRC_ASRIER_REG); + + /* Default 6: 2: 2 channel assignment */ + __raw_writel((0x06 << mxc_asrc_data->channel_bits * + 2) | (0x02 << mxc_asrc_data->channel_bits) | 0x02, + asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + /* Parameter Registers recommended settings */ + __raw_writel(0x7fffff, asrc_vrt_base_addr + ASRC_ASRPM1_REG); + __raw_writel(0x255555, asrc_vrt_base_addr + ASRC_ASRPM2_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM3_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM4_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM5_REG); + + __raw_writel(0x001f00, asrc_vrt_base_addr + ASRC_ASRTFR1); + + /* Set the processing clock for 76KHz, 133M */ + __raw_writel(0x30E, asrc_vrt_base_addr + ASRC_ASR76K_REG); + + /* Set the processing clock for 56KHz, 133M */ + __raw_writel(0x0426, asrc_vrt_base_addr + ASRC_ASR56K_REG); + + return 0; +} + +static int asrc_get_output_buffer_size(int input_buffer_size, + int input_sample_rate, + int output_sample_rate) +{ + int i = 0; + int outbuffer_size = 0; + int outsample = output_sample_rate; + while (outsample >= input_sample_rate) { + ++i; + outsample -= input_sample_rate; + } + outbuffer_size = i * input_buffer_size; + i = 1; + while (((input_buffer_size >> i) > 2) && (outsample != 0)) { + if (((outsample << 1) - input_sample_rate) >= 0) { + outsample = (outsample << 1) - input_sample_rate; + outbuffer_size += (input_buffer_size >> i); + } else { + outsample = outsample << 1; + } + i++; + } + outbuffer_size = (outbuffer_size >> 3) << 3; + return outbuffer_size; +} + +static void asrc_input_dma_callback(void *data, int error, unsigned int count) +{ + struct asrc_pair_params *params; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + + params = data; + + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_queue_empty--; + if (!list_empty(¶ms->input_queue)) { + block = + list_entry(params->input_queue.next, + struct dma_block, queue); + dma_request.src_addr = (dma_addr_t) block->dma_paddr; + dma_request.dst_addr = + (ASRC_BASE_ADDR + ASRC_ASRDIA_REG + (params->index << 3)); + dma_request.num_of_bytes = block->length; + mxc_dma_config(params->input_dma_channel, &dma_request, + 1, MXC_DMA_MODE_WRITE); + list_del(params->input_queue.next); + list_add_tail(&block->queue, ¶ms->input_done_queue); + params->input_queue_empty++; + } + params->input_counter++; + wake_up_interruptible(¶ms->input_wait_queue); + spin_unlock_irqrestore(&input_int_lock, lock_flags); + return; +} + +static void asrc_output_dma_callback(void *data, int error, unsigned int count) +{ + struct asrc_pair_params *params; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + + params = data; + + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_queue_empty--; + + if (!list_empty(¶ms->output_queue)) { + block = + list_entry(params->output_queue.next, + struct dma_block, queue); + dma_request.src_addr = + (ASRC_BASE_ADDR + ASRC_ASRDOA_REG + (params->index << 3)); + dma_request.dst_addr = (dma_addr_t) block->dma_paddr; + dma_request.num_of_bytes = block->length; + mxc_dma_config(params->output_dma_channel, &dma_request, + 1, MXC_DMA_MODE_READ); + list_del(params->output_queue.next); + list_add_tail(&block->queue, ¶ms->output_done_queue); + params->output_queue_empty++; + } + params->output_counter++; + wake_up_interruptible(¶ms->output_wait_queue); + spin_unlock_irqrestore(&output_int_lock, lock_flags); + return; +} + +static void mxc_free_dma_buf(struct asrc_pair_params *params) +{ + int i; + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + if (params->input_dma[i].dma_vaddr != NULL) { + dma_free_coherent(0, + params->input_buffer_size, + params->input_dma[i]. + dma_vaddr, + params->input_dma[i].dma_paddr); + params->input_dma[i].dma_vaddr = NULL; + } + if (params->output_dma[i].dma_vaddr != NULL) { + dma_free_coherent(0, + params->output_buffer_size, + params->output_dma[i]. + dma_vaddr, + params->output_dma[i].dma_paddr); + params->output_dma[i].dma_vaddr = NULL; + } + } + + return; +} + +static int mxc_allocate_dma_buf(struct asrc_pair_params *params) +{ + int i; + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + params->input_dma[i].dma_vaddr = + dma_alloc_coherent(0, params->input_buffer_size, + ¶ms->input_dma[i].dma_paddr, + GFP_DMA | GFP_KERNEL); + if (params->input_dma[i].dma_vaddr == NULL) { + mxc_free_dma_buf(params); + pr_info("can't allocate buff\n"); + return -ENOBUFS; + } + } + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + params->output_dma[i].dma_vaddr = + dma_alloc_coherent(0, + params->output_buffer_size, + ¶ms->output_dma[i].dma_paddr, + GFP_DMA | GFP_KERNEL); + if (params->output_dma[i].dma_vaddr == NULL) { + mxc_free_dma_buf(params); + return -ENOBUFS; + } + } + + return 0; +} + +/*! + * asrc interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param cmd unsigned int + * + * @param arg unsigned long + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int asrc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct asrc_pair_params *params; + params = file->private_data; + + if (down_interruptible(¶ms->busy_lock)) + return -EBUSY; + switch (cmd) { + case ASRC_REQ_PAIR: + { + struct asrc_req req; + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct asrc_req))) { + err = -EFAULT; + break; + } + err = asrc_req_pair(req.chn_num, &req.index); + if (err < 0) + break; + params->pair_hold = 1; + params->index = req.index; + if (copy_to_user + ((void __user *)arg, &req, sizeof(struct asrc_req))) + err = -EFAULT; + + break; + } + case ASRC_CONFIG_PAIR: + { + struct asrc_config config; + mxc_dma_device_t rx_id, tx_id; + char *rx_name, *tx_name; + int channel = -1; + if (copy_from_user + (&config, (void __user *)arg, + sizeof(struct asrc_config))) { + err = -EFAULT; + break; + } + err = asrc_config_pair(&config); + if (err < 0) + break; + params->output_buffer_size = + asrc_get_output_buffer_size(config. + dma_buffer_size, + config. + input_sample_rate, + config. + output_sample_rate); + params->input_buffer_size = config.dma_buffer_size; + if (config.buffer_num > ASRC_DMA_BUFFER_NUM) + params->buffer_num = ASRC_DMA_BUFFER_NUM; + else + params->buffer_num = config.buffer_num; + err = mxc_allocate_dma_buf(params); + if (err < 0) + break; + + /* TBD - need to update when new SDMA interface ready */ + if (config.pair == ASRC_PAIR_A) { + rx_id = MXC_DMA_ASRC_A_RX; + tx_id = MXC_DMA_ASRC_A_TX; + rx_name = asrc_pair_id[0]; + tx_name = asrc_pair_id[1]; + } else if (config.pair == ASRC_PAIR_B) { + rx_id = MXC_DMA_ASRC_B_RX; + tx_id = MXC_DMA_ASRC_B_TX; + rx_name = asrc_pair_id[2]; + tx_name = asrc_pair_id[3]; + } else { + rx_id = MXC_DMA_ASRC_C_RX; + tx_id = MXC_DMA_ASRC_C_TX; + rx_name = asrc_pair_id[4]; + tx_name = asrc_pair_id[5]; + } + channel = mxc_dma_request(rx_id, rx_name); + params->input_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_input_dma_callback, + (void *)params); + channel = mxc_dma_request(tx_id, tx_name); + params->output_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_output_dma_callback, + (void *)params); + /* TBD - need to update when new SDMA interface ready */ + params->input_queue_empty = 0; + params->output_queue_empty = 0; + INIT_LIST_HEAD(¶ms->input_queue); + INIT_LIST_HEAD(¶ms->input_done_queue); + INIT_LIST_HEAD(¶ms->output_queue); + INIT_LIST_HEAD(¶ms->output_done_queue); + init_waitqueue_head(¶ms->input_wait_queue); + init_waitqueue_head(¶ms->output_wait_queue); + + if (copy_to_user + ((void __user *)arg, &config, + sizeof(struct asrc_config))) + err = -EFAULT; + break; + } + case ASRC_QUERYBUF: + { + struct asrc_querybuf buffer; + if (copy_from_user + (&buffer, (void __user *)arg, + sizeof(struct asrc_querybuf))) { + err = -EFAULT; + break; + } + buffer.input_offset = + (unsigned long)params->input_dma[buffer. + buffer_index]. + dma_paddr; + buffer.input_length = params->input_buffer_size; + buffer.output_offset = + (unsigned long)params->output_dma[buffer. + buffer_index]. + dma_paddr; + buffer.output_length = params->output_buffer_size; + if (copy_to_user + ((void __user *)arg, &buffer, + sizeof(struct asrc_querybuf))) + err = -EFAULT; + break; + } + case ASRC_RELEASE_PAIR: + { + enum asrc_pair_index index; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + + mxc_dma_free(params->input_dma_channel); + mxc_dma_free(params->output_dma_channel); + mxc_free_dma_buf(params); + asrc_release_pair(index); + params->pair_hold = 0; + break; + } + case ASRC_Q_INBUF: + { + struct asrc_buffer buf; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_dma[buf.index].index = buf.index; + params->input_dma[buf.index].length = buf.length; + list_add_tail(¶ms->input_dma[buf.index]. + queue, ¶ms->input_queue); + if (params->asrc_active == 0 + || params->input_queue_empty == 0) { + block = + list_entry(params->input_queue.next, + struct dma_block, queue); + dma_request.src_addr = + (dma_addr_t) block->dma_paddr; + dma_request.dst_addr = + (ASRC_BASE_ADDR + ASRC_ASRDIA_REG + + (params->index << 3)); + dma_request.num_of_bytes = block->length; + mxc_dma_config(params-> + input_dma_channel, + &dma_request, 1, + MXC_DMA_MODE_WRITE); + params->input_queue_empty++; + list_del(params->input_queue.next); + list_add_tail(&block->queue, + ¶ms->input_done_queue); + } + + spin_unlock_irqrestore(&input_int_lock, lock_flags); + break; + } + case ASRC_DQ_INBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + /* if ASRC is inactive, nonsense to DQ buffer */ + if (params->asrc_active == 0) { + err = -EFAULT; + buf.buf_valid = ASRC_BUF_NA; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + break; + } + + if (!wait_event_interruptible_timeout + (params->input_wait_queue, + params->input_counter != 0, 10 * HZ)) { + pr_info + ("ASRC_DQ_INBUF timeout counter %x\n", + params->input_counter); + err = -ETIME; + break; + } else if (signal_pending(current)) { + pr_info("ASRC_DQ_INBUF interrupt received\n"); + err = -ERESTARTSYS; + break; + } + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_counter--; + block = + list_entry(params->input_done_queue.next, + struct dma_block, queue); + list_del(params->input_done_queue.next); + spin_unlock_irqrestore(&input_int_lock, lock_flags); + buf.index = block->index; + buf.length = block->length; + buf.buf_valid = ASRC_BUF_AV; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + + break; + } + case ASRC_Q_OUTBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_dma[buf.index].index = buf.index; + params->output_dma[buf.index].length = buf.length; + list_add_tail(¶ms->output_dma[buf.index]. + queue, ¶ms->output_queue); + if (params->asrc_active == 0 + || params->output_queue_empty == 0) { + block = + list_entry(params->output_queue. + next, struct dma_block, queue); + dma_request.src_addr = + (ASRC_BASE_ADDR + ASRC_ASRDOA_REG + + (params->index << 3)); + dma_request.dst_addr = + (dma_addr_t) block->dma_paddr; + dma_request.num_of_bytes = block->length; + mxc_dma_config(params-> + output_dma_channel, + &dma_request, 1, + MXC_DMA_MODE_READ); + list_del(params->output_queue.next); + list_add_tail(&block->queue, + ¶ms->output_done_queue); + params->output_queue_empty++; + } + + spin_unlock_irqrestore(&output_int_lock, lock_flags); + break; + } + case ASRC_DQ_OUTBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + /* if ASRC is inactive, nonsense to DQ buffer */ + if (params->asrc_active == 0) { + buf.buf_valid = ASRC_BUF_NA; + err = -EFAULT; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + break; + } + + if (!wait_event_interruptible_timeout + (params->output_wait_queue, + params->output_counter != 0, 10 * HZ)) { + pr_info + ("ASRC_DQ_OUTBUF timeout counter %x\n", + params->output_counter); + err = -ETIME; + break; + } else if (signal_pending(current)) { + pr_info("ASRC_DQ_INBUF interrupt received\n"); + err = -ERESTARTSYS; + break; + } + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_counter--; + block = + list_entry(params->output_done_queue.next, + struct dma_block, queue); + list_del(params->output_done_queue.next); + spin_unlock_irqrestore(&output_int_lock, lock_flags); + buf.index = block->index; + buf.length = block->length; + buf.buf_valid = ASRC_BUF_AV; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + + break; + } + case ASRC_START_CONV:{ + enum asrc_pair_index index; + unsigned long lock_flags; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + + spin_lock_irqsave(&input_int_lock, lock_flags); + if (params->input_queue_empty == 0) { + err = -EFAULT; + pr_info + ("ASRC_START_CONV - no block available\n"); + break; + } + spin_unlock_irqrestore(&input_int_lock, lock_flags); + params->asrc_active = 1; + + asrc_start_conv(index); + mxc_dma_enable(params->input_dma_channel); + mxc_dma_enable(params->output_dma_channel); + break; + } + case ASRC_STOP_CONV:{ + enum asrc_pair_index index; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + mxc_dma_disable(params->input_dma_channel); + mxc_dma_disable(params->output_dma_channel); + asrc_stop_conv(index); + params->asrc_active = 0; + break; + } + case ASRC_STATUS:{ + struct asrc_status_flags flags; + if (copy_from_user + (&flags, (void __user *)arg, + sizeof(struct asrc_status_flags))) { + err = -EFAULT; + break; + } + asrc_get_status(&flags); + if (copy_to_user + ((void __user *)arg, &flags, + sizeof(struct asrc_status_flags))) + err = -EFAULT; + break; + } + case ASRC_FLUSH:{ + /* flush input dma buffer */ + unsigned long lock_flags; + mxc_dma_device_t rx_id, tx_id; + char *rx_name, *tx_name; + int channel = -1; + spin_lock_irqsave(&input_int_lock, lock_flags); + while (!list_empty(¶ms->input_queue)) + list_del(params->input_queue.next); + while (!list_empty(¶ms->input_done_queue)) + list_del(params->input_done_queue.next); + params->input_counter = 0; + params->input_queue_empty = 0; + spin_unlock_irqrestore(&input_int_lock, lock_flags); + + /* flush output dma buffer */ + spin_lock_irqsave(&output_int_lock, lock_flags); + while (!list_empty(¶ms->output_queue)) + list_del(params->output_queue.next); + while (!list_empty(¶ms->output_done_queue)) + list_del(params->output_done_queue.next); + params->output_counter = 0; + params->output_queue_empty = 0; + spin_unlock_irqrestore(&output_int_lock, lock_flags); + + /* release DMA and request again */ + mxc_dma_free(params->input_dma_channel); + mxc_dma_free(params->output_dma_channel); + if (params->index == ASRC_PAIR_A) { + rx_id = MXC_DMA_ASRC_A_RX; + tx_id = MXC_DMA_ASRC_A_TX; + rx_name = asrc_pair_id[0]; + tx_name = asrc_pair_id[1]; + } else if (params->index == ASRC_PAIR_B) { + rx_id = MXC_DMA_ASRC_B_RX; + tx_id = MXC_DMA_ASRC_B_TX; + rx_name = asrc_pair_id[2]; + tx_name = asrc_pair_id[3]; + } else { + rx_id = MXC_DMA_ASRC_C_RX; + tx_id = MXC_DMA_ASRC_C_TX; + rx_name = asrc_pair_id[4]; + tx_name = asrc_pair_id[5]; + } + channel = mxc_dma_request(rx_id, rx_name); + params->input_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_input_dma_callback, + (void *)params); + channel = mxc_dma_request(tx_id, tx_name); + params->output_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_output_dma_callback, + (void *)params); + + break; + } + default: + break; + } + + up(¶ms->busy_lock); + return err; +} + +/*! + * asrc interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENOBUFS failed to allocate buffer, ERESTARTSYS interrupted by user + */ +static int mxc_asrc_open(struct inode *inode, struct file *file) +{ + int err = 0; + struct asrc_pair_params *pair_params; + if (signal_pending(current)) + return -EINTR; + pair_params = kzalloc(sizeof(struct asrc_pair_params), GFP_KERNEL); + if (pair_params == NULL) { + pr_debug("Failed to allocate pair_params\n"); + err = -ENOBUFS; + } + + init_MUTEX(&pair_params->busy_lock); + file->private_data = pair_params; + return err; +} + +/*! + * asrc interface - close function + * + * @param inode struct inode * + * @param file structure file * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_asrc_close(struct inode *inode, struct file *file) +{ + struct asrc_pair_params *pair_params; + pair_params = file->private_data; + if (pair_params->asrc_active == 1) { + mxc_dma_disable(pair_params->input_dma_channel); + mxc_dma_disable(pair_params->output_dma_channel); + asrc_stop_conv(pair_params->index); + wake_up_interruptible(&pair_params->input_wait_queue); + wake_up_interruptible(&pair_params->output_wait_queue); + } + if (pair_params->pair_hold == 1) { + mxc_dma_free(pair_params->input_dma_channel); + mxc_dma_free(pair_params->output_dma_channel); + mxc_free_dma_buf(pair_params); + asrc_release_pair(pair_params->index); + } + kfree(pair_params); + file->private_data = NULL; + return 0; +} + +/*! + * asrc interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_asrc_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size; + int res = 0; + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) + return -ENOBUFS; + + vma->vm_flags &= ~VM_IO; + return res; +} + +static struct file_operations asrc_fops = { + .owner = THIS_MODULE, + .ioctl = asrc_ioctl, + .mmap = mxc_asrc_mmap, + .open = mxc_asrc_open, + .release = mxc_asrc_close, +}; + +static int asrc_read_proc_attr(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + unsigned long reg; + int len = 0; + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + len += sprintf(page, "ANCA: %d\n", + (int)(reg & + (0xFFFFFFFF >> + (32 - mxc_asrc_data->channel_bits)))); + len += + sprintf(page + len, "ANCB: %d\n", + (int)((reg >> mxc_asrc_data-> + channel_bits) & (0xFFFFFFFF >> (32 - + mxc_asrc_data-> + channel_bits)))); + len += + sprintf(page + len, "ANCC: %d\n", + (int)((reg >> (mxc_asrc_data->channel_bits * 2)) & + (0xFFFFFFFF >> (32 - mxc_asrc_data->channel_bits)))); + + if (off > len) + return 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return min(count, len - (int)off); +} + +static int asrc_write_proc_attr(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + char buf[50]; + unsigned long reg; + int na, nb, nc; + int total; + if (count > 48) + return -EINVAL; + if (copy_from_user(buf, buffer, count)) { + pr_debug("Attr proc write, Failed to copy buffer from user\n"); + return -EFAULT; + } + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + sscanf(buf, "ANCA: %d\nANCB: %d\nANCC: %d", &na, &nb, &nc); + if (mxc_asrc_data->channel_bits > 3) + total = 10; + else + total = 5; + if ((na + nb + nc) != total) { + pr_info("Wrong ASRCNR settings\n"); + return -EFAULT; + } + reg = na | (nb << mxc_asrc_data-> + channel_bits) | (nc << (mxc_asrc_data->channel_bits * 2)); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + return count; +} + +static void asrc_proc_create(void) +{ + struct proc_dir_entry *proc_attr; + proc_asrc = proc_mkdir(ASRC_PROC_PATH, NULL); + if (proc_asrc) { + proc_attr = create_proc_entry("ChSettings", + S_IFREG | S_IRUGO | + S_IWUSR, proc_asrc); + if (proc_attr) { + proc_attr->read_proc = asrc_read_proc_attr; + proc_attr->write_proc = asrc_write_proc_attr; + proc_attr->size = 48; + proc_attr->uid = proc_attr->gid = 0; + } else { + remove_proc_entry(ASRC_PROC_PATH, NULL); + pr_info("Failed to create proc attribute entry \n"); + } + } else { + pr_info("ASRC: Failed to create proc entry %s\n", + ASRC_PROC_PATH); + } +} + +/*! + * Entry point for the asrc device + * + * @param pdev Pionter to the registered platform device + * @return Error code indicating success or failure + */ +static int mxc_asrc_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + struct device *temp_class; + int irq; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + g_asrc_data = kzalloc(sizeof(struct asrc_data), GFP_KERNEL); + + if (g_asrc_data == NULL) { + pr_info("Failed to allocate g_asrc_data\n"); + return -ENOMEM; + } + + g_asrc_data->asrc_pair[0].chn_max = 2; + g_asrc_data->asrc_pair[1].chn_max = 2; + g_asrc_data->asrc_pair[2].chn_max = 6; + g_asrc_data->asrc_pair[0].overload_error = 0; + g_asrc_data->asrc_pair[1].overload_error = 0; + g_asrc_data->asrc_pair[2].overload_error = 0; + + asrc_major = register_chrdev(asrc_major, "mxc_asrc", &asrc_fops); + if (asrc_major < 0) { + pr_info("Unable to register asrc device\n"); + err = -EBUSY; + goto error; + } + + asrc_class = class_create(THIS_MODULE, "mxc_asrc"); + if (IS_ERR(asrc_class)) { + err = PTR_ERR(asrc_class); + goto err_out_chrdev; + } + + temp_class = device_create(asrc_class, NULL, MKDEV(asrc_major, 0), + NULL, "mxc_asrc"); + if (IS_ERR(temp_class)) { + err = PTR_ERR(temp_class); + goto err_out_class; + } + + asrc_vrt_base_addr = + (unsigned long)ioremap(res->start, res->end - res->start + 1); + + mxc_asrc_data = + (struct mxc_asrc_platform_data *)pdev->dev.platform_data; + clk_enable(mxc_asrc_data->asrc_core_clk); + + switch (mxc_asrc_data->clk_map_ver) { + case 1: + input_clk_map = &input_clk_map_v1[0]; + output_clk_map = &output_clk_map_v1[0]; + break; + case 2: + default: + input_clk_map = &input_clk_map_v2[0]; + output_clk_map = &output_clk_map_v2[0]; + break; + } + irq = platform_get_irq(pdev, 0); + if (request_irq(irq, asrc_isr, 0, "asrc", NULL)) + return -1; + + asrc_proc_create(); + err = mxc_init_asrc(); + if (err < 0) + goto err_out_class; + + goto out; + + err_out_class: + clk_disable(mxc_asrc_data->asrc_core_clk); + device_destroy(asrc_class, MKDEV(asrc_major, 0)); + class_destroy(asrc_class); + err_out_chrdev: + unregister_chrdev(asrc_major, "mxc_asrc"); + error: + kfree(g_asrc_data); + out: + pr_info("mxc_asrc registered\n"); + return err; +} + +/*! + * Exit asrc device + * + * @param pdev Pionter to the registered platform device + * @return Error code indicating success or failure + */ +static int mxc_asrc_remove(struct platform_device *pdev) +{ + int irq = platform_get_irq(pdev, 0); + free_irq(irq, NULL); + kfree(g_asrc_data); + clk_disable(mxc_asrc_data->asrc_core_clk); + mxc_asrc_data = NULL; + iounmap((unsigned long __iomem *)asrc_vrt_base_addr); + remove_proc_entry("ChSettings", proc_asrc); + remove_proc_entry(ASRC_PROC_PATH, NULL); + device_destroy(asrc_class, MKDEV(asrc_major, 0)); + class_destroy(asrc_class); + unregister_chrdev(asrc_major, "mxc_asrc"); + return 0; +} + +/*! mxc asrc driver definition + * + */ +static struct platform_driver mxc_asrc_driver = { + .driver = { + .name = "mxc_asrc", + }, + .probe = mxc_asrc_probe, + .remove = mxc_asrc_remove, +}; + +/*! + * Register asrc driver + * + */ +static __init int asrc_init(void) +{ + int ret; + ret = platform_driver_register(&mxc_asrc_driver); + return ret; +} + +/*! + * Exit and free the asrc data + * + */ static void __exit asrc_exit(void) +{ + platform_driver_unregister(&mxc_asrc_driver); + return; +} + +module_init(asrc_init); +module_exit(asrc_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Asynchronous Sample Rate Converter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/bt/Kconfig b/drivers/mxc/bt/Kconfig new file mode 100644 index 000000000000..9dbfbe57a14f --- /dev/null +++ b/drivers/mxc/bt/Kconfig @@ -0,0 +1,13 @@ +# +# Bluetooth configuration +# + +menu "MXC Bluetooth support" + +config MXC_BLUETOOTH + tristate "MXC Bluetooth support" + depends on MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS + ---help--- + Say Y to get the third party Bluetooth service. + +endmenu diff --git a/drivers/mxc/bt/Makefile b/drivers/mxc/bt/Makefile new file mode 100644 index 000000000000..91bc4cff380e --- /dev/null +++ b/drivers/mxc/bt/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for the kernel Bluetooth power-on/reset +# +obj-$(CONFIG_MXC_BLUETOOTH) += mxc_bt.o diff --git a/drivers/mxc/bt/mxc_bt.c b/drivers/mxc/bt/mxc_bt.c new file mode 100644 index 000000000000..ba92318061de --- /dev/null +++ b/drivers/mxc/bt/mxc_bt.c @@ -0,0 +1,128 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_bt.c + * + * @brief MXC Thirty party Bluetooth + * + */ + +#include +#include +#include +#include +#include +#include + +static struct regulator *bt_vdd; +static struct regulator *bt_vdd_parent; +static struct regulator *bt_vusb; +static struct regulator *bt_vusb_parent; + +/*! + * This function poweron the bluetooth hardware module + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ +static int mxc_bt_probe(struct platform_device *pdev) +{ + struct mxc_bt_platform_data *platform_data; + platform_data = (struct mxc_bt_platform_data *)pdev->dev.platform_data; + if (platform_data->bt_vdd) { + bt_vdd = regulator_get(&pdev->dev, platform_data->bt_vdd); + regulator_enable(bt_vdd); + } + if (platform_data->bt_vdd_parent) { + bt_vdd_parent = + regulator_get(&pdev->dev, platform_data->bt_vdd_parent); + regulator_enable(bt_vdd_parent); + } + if (platform_data->bt_vusb) { + bt_vusb = regulator_get(&pdev->dev, platform_data->bt_vusb); + regulator_enable(bt_vusb); + } + if (platform_data->bt_vusb_parent) { + bt_vusb_parent = + regulator_get(&pdev->dev, platform_data->bt_vusb_parent); + regulator_enable(bt_vusb_parent); + } + + if (platform_data->bt_reset != NULL) + platform_data->bt_reset(); + return 0; + +} + +/*! + * This function poweroff the bluetooth hardware module + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ +static int mxc_bt_remove(struct platform_device *pdev) +{ + struct mxc_bt_platform_data *platform_data; + platform_data = (struct mxc_bt_platform_data *)pdev->dev.platform_data; + if (bt_vdd) { + regulator_disable(bt_vdd); + regulator_put(bt_vdd); + } + if (bt_vdd_parent) { + regulator_disable(bt_vdd_parent); + regulator_put(bt_vdd_parent); + } + if (bt_vusb) { + regulator_disable(bt_vusb); + regulator_put(bt_vusb); + } + if (bt_vusb_parent) { + regulator_disable(bt_vusb_parent); + regulator_put(bt_vusb_parent); + } + return 0; + +} + +static struct platform_driver bluetooth_driver = { + .driver = { + .name = "mxc_bt", + }, + .probe = mxc_bt_probe, + .remove = mxc_bt_remove, +}; + +/*! + * Register bluetooth driver module + * + */ +static __init int bluetooth_init(void) +{ + return platform_driver_register(&bluetooth_driver); +} + +/*! + * Exit and free the bluetooth module + * + */ +static void __exit bluetooth_exit(void) +{ + platform_driver_unregister(&bluetooth_driver); +} + +module_init(bluetooth_init); +module_exit(bluetooth_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Thirty party Bluetooth"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/dam/Kconfig b/drivers/mxc/dam/Kconfig new file mode 100644 index 000000000000..7b4bee92f648 --- /dev/null +++ b/drivers/mxc/dam/Kconfig @@ -0,0 +1,13 @@ +# +# DAM API configuration +# + +menu "MXC Digital Audio Multiplexer support" + +config MXC_DAM + tristate "DAM support" + depends on ARCH_MXC + ---help--- + Say Y to get the Digital Audio Multiplexer services API available on MXC platform. + +endmenu diff --git a/drivers/mxc/dam/Makefile b/drivers/mxc/dam/Makefile new file mode 100644 index 000000000000..b5afdc143dfa --- /dev/null +++ b/drivers/mxc/dam/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the kernel Digital Audio MUX (DAM) device driver. +# + +ifeq ($(CONFIG_ARCH_MX27),y) + obj-$(CONFIG_MXC_DAM) += dam_v1.o +else + obj-$(CONFIG_MXC_DAM) += dam.o +endif diff --git a/drivers/mxc/dam/dam.c b/drivers/mxc/dam/dam.c new file mode 100644 index 000000000000..9e57e1a260ea --- /dev/null +++ b/drivers/mxc/dam/dam.c @@ -0,0 +1,427 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include + +#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..56c6d46600fa --- /dev/null +++ b/drivers/mxc/dam/dam.h @@ -0,0 +1,258 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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..5e3cdf8a9c3d --- /dev/null +++ b/drivers/mxc/dam/dam_v1.c @@ -0,0 +1,617 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include +#include +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include + +#define DAM_VIRT_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR) + +#define ModifyRegister32(a, b, c) do {\ + __raw_writel(((__raw_readl(c)) & (~(a))) | (b), (c));\ +} while (0) + +#ifndef _reg_DAM_HPCR1 +#define _reg_DAM_HPCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x00))) +#endif + +#ifndef _reg_DAM_HPCR2 +#define _reg_DAM_HPCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x04))) +#endif + +#ifndef _reg_DAM_HPCR3 +#define _reg_DAM_HPCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x08))) +#endif + +#ifndef _reg_DAM_PPCR1 +#define _reg_DAM_PPCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x10))) +#endif + +#ifndef _reg_DAM_PPCR2 +#define _reg_DAM_PPCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x14))) +#endif + +#ifndef _reg_DAM_PPCR3 +#define _reg_DAM_PPCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x1c))) +#endif + +#ifndef _reg_DAM_HPCR +#define _reg_DAM_HPCR(a) ((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + (a)*4)) +#endif + +#ifndef _reg_DAM_PPCR +#define _reg_DAM_PPCR(a) ((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x0c + (0x04 << (a-3)))) +#endif + +/*! + * HPCR/PPCR Registers bit shift definitions + */ +#define dam_transmit_frame_sync_direction_shift 31 +#define dam_transmit_clock_direction_shift 30 +#define dam_transmit_frame_sync_select_shift 26 +#define dam_transmit_clock_select_shift 26 +#define dam_receive_frame_sync_direction_shift 25 +#define dam_receive_clock_direction_shift 24 +#define dam_receive_clock_select_shift 20 +#define dam_receive_frame_sync_select_shift 20 + +#define dam_receive_data_select_shift 13 +#define dam_synchronous_mode_shift 12 + +#define dam_transmit_receive_switch_shift 10 + +#define dam_mode_shift 8 +#define dam_internal_network_mode_shift 0 + +/*! + * HPCR/PPCR Register bit masq definitions + */ +/*#define dam_selection_mask 0xF*/ +#define dam_fs_selection_mask 0xF +#define dam_clk_selection_mask 0xF +#define dam_dat_selection_mask 0x7 +/*#define dam_mode_masq 0x03*/ +#define dam_internal_network_mode_mask 0xFF + +/*! + * HPCR/PPCR Register reset value definitions + */ +#define dam_hpcr_default_value 0x00001000 +#define dam_ppcr_default_value 0x00001000 + +#define DAM_NAME "dam" +static struct class *mxc_dam_class; + +EXPORT_SYMBOL(dam_select_mode); +EXPORT_SYMBOL(dam_select_RxClk_direction); +EXPORT_SYMBOL(dam_select_RxClk_source); +EXPORT_SYMBOL(dam_select_RxD_source); +EXPORT_SYMBOL(dam_select_RxFS_direction); +EXPORT_SYMBOL(dam_select_RxFS_source); +EXPORT_SYMBOL(dam_select_TxClk_direction); +EXPORT_SYMBOL(dam_select_TxClk_source); +EXPORT_SYMBOL(dam_select_TxFS_direction); +EXPORT_SYMBOL(dam_select_TxFS_source); +EXPORT_SYMBOL(dam_set_internal_network_mode_mask); +EXPORT_SYMBOL(dam_set_synchronous); +EXPORT_SYMBOL(dam_switch_Tx_Rx); +EXPORT_SYMBOL(dam_reset_register); + +/*! + * DAM major + */ +#ifdef TEST_DAM +static int major_dam; + +typedef struct _mxc_cfg { + int reg; + int val; +} mxc_cfg; + +#endif + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode) +{ + int result; + result = 0; + + if (port >= 3) + the_mode = normal_mode; + ModifyRegister32(1 << dam_mode_shift, + the_mode << dam_mode_shift, _reg_DAM_HPCR(port)); + + return result; +} + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_clk_selection_mask << + dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_clk_selection_mask << + dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_dat_selection_mask << + dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_dat_selection_mask << + dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << + dam_receive_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << + dam_receive_frame_sync_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_fs_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_fs_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << + dam_transmit_clock_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << + dam_transmit_clock_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_clk_selection_mask << + dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_clk_selection_mask << + dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << + dam_transmit_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << + dam_transmit_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } + return; +} + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_fs_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_fs_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function sets a bit mask that selects the port from which of the RxD + * signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask) +{ + int result; + result = 0; + + ModifyRegister32(dam_internal_network_mode_mask << + dam_internal_network_mode_shift, + bit_mask << dam_internal_network_mode_shift, + _reg_DAM_HPCR(port)); + return result; +} + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous) +{ + if (port < 3) { + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) + * to (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port) +{ + if (port < 3) { + ModifyRegister32(0xFFFFFFFF, dam_hpcr_default_value, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(0xFFFFFFFF, dam_ppcr_default_value, + _reg_DAM_PPCR(port)); + } + return; +} + +#ifdef TEST_DAM + +/*! + * This function implements IOCTL controls on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter :\n + * DAM_CONFIG_SSI1:\n + * data from port 1 to port 4, clock and FS from port 1 (SSI1)\n + * DAM_CONFIG_SSI2:\n + * data from port 2 to port 5, clock and FS from port 2 (SSI2)\n + * DAM_CONFIG_SSI_NETWORK_MODE:\n + * network mode for mix digital with data from port 1 to port4,\n + * data from port 2 to port 4, clock and FS from port 1 (SSI1) + * + * @return This function returns 0 if successful. + */ +static int dam_ioctl(struct inode *inode, + struct file *file, unsigned int cmd, unsigned long arg) +{ + return 0; +} + +/*! + * This function implements the open method on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int dam_open(struct inode *inode, struct file *file) +{ + /* DBG_PRINTK("ssi : dam_open()\n"); */ + return 0; +} + +/*! + * This function implements the release method on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int dam_free(struct inode *inode, struct file *file) +{ + /* DBG_PRINTK("ssi : dam_free()\n"); */ + return 0; +} + +/*! + * This structure defines file operations for a DAM device. + */ +static struct file_operations dam_fops = { + + /*! + * the owner + */ + .owner = THIS_MODULE, + + /*! + * the ioctl operation + */ + .ioctl = dam_ioctl, + + /*! + * the open operation + */ + .open = dam_open, + + /*! + * the release operation + */ + .release = dam_free, +}; + +#endif + +/*! + * This function implements the init function of the DAM device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init dam_init(void) +{ +#ifdef TEST_DAM + struct device *temp_class; + printk(KERN_DEBUG "dam : dam_init(void) \n"); + + major_dam = register_chrdev(0, DAM_NAME, &dam_fops); + if (major_dam < 0) { + printk(KERN_WARNING "Unable to get a major for dam"); + return major_dam; + } + + mxc_dam_class = class_create(THIS_MODULE, DAM_NAME); + if (IS_ERR(mxc_dam_class)) { + goto err_out; + } + + temp_class = device_create(mxc_dam_class, NULL, + MKDEV(major_dam, 0), NULL, DAM_NAME); + if (IS_ERR(temp_class)) { + goto err_out; + } +#endif + return 0; + + err_out: + printk(KERN_ERR "Error creating dam class device.\n"); + device_destroy(mxc_dam_class, MKDEV(major_dam, 0)); + class_destroy(mxc_dam_class); + unregister_chrdev(major_dam, DAM_NAME); + return -1; +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit dam_exit(void) +{ +#ifdef TEST_DAM + device_destroy(mxc_dam_class, MKDEV(major_dam, 0)); + class_destroy(mxc_dam_class); + unregister_chrdev(major_dam, DAM_NAME); + printk(KERN_DEBUG "dam : successfully unloaded\n"); +#endif +} + +module_init(dam_init); +module_exit(dam_exit); + +MODULE_DESCRIPTION("DAM char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/gps_ioctrl/Kconfig b/drivers/mxc/gps_ioctrl/Kconfig new file mode 100644 index 000000000000..0a85d1636dd7 --- /dev/null +++ b/drivers/mxc/gps_ioctrl/Kconfig @@ -0,0 +1,13 @@ +# +# BROADCOM GPS configuration +# + +menu "Broadcom GPS ioctrl support" + +config GPS_IOCTRL + tristate "GPS ioctrl support" + depends on MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS + ---help--- + Say Y to enable Broadcom GPS ioctrl on MXC platform. + +endmenu diff --git a/drivers/mxc/gps_ioctrl/Makefile b/drivers/mxc/gps_ioctrl/Makefile new file mode 100644 index 000000000000..c52d7c9f151c --- /dev/null +++ b/drivers/mxc/gps_ioctrl/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the GPIO device driver module. +# +obj-$(CONFIG_GPS_IOCTRL) += gps_gpiodrv.o +gps_gpiodrv-objs := agpsgpiodev.o diff --git a/drivers/mxc/gps_ioctrl/agpsgpiodev.c b/drivers/mxc/gps_ioctrl/agpsgpiodev.c new file mode 100644 index 000000000000..f47826320d73 --- /dev/null +++ b/drivers/mxc/gps_ioctrl/agpsgpiodev.c @@ -0,0 +1,332 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file agpsgpiodev.c + * + * @brief Main file for GPIO kernel module. Contains driver entry/exit + * + */ + +#include +#include /* Async notification */ +#include /* for get_user, put_user, access_ok */ +#include /* jiffies */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "agpsgpiodev.h" + +extern void gpio_gps_active(void); +extern void gpio_gps_inactive(void); +extern int gpio_gps_access(int para); + +struct mxc_gps_platform_data *mxc_gps_ioctrl_data; +static int Device_Open; /* Only allow a single user of this device */ +static struct cdev mxc_gps_cdev; +static dev_t agps_gpio_dev; +static struct class *gps_class; +static struct device *gps_class_dev; + +/* Write GPIO from user space */ +static int ioctl_writegpio(int arg) +{ + + /* Bit 0 of arg identifies the GPIO pin to write: + 0 = GPS_RESET_GPIO, 1 = GPS_POWER_GPIO. + Bit 1 of arg identifies the value to write (0 or 1). */ + + /* Bit 2 should be 0 to show this access is write */ + return gpio_gps_access(arg & (~0x4)); +} + +/* Read GPIO from user space */ +static int ioctl_readgpio(int arg) +{ + /* Bit 0 of arg identifies the GPIO pin to read: + 0 = GPS_RESET_GPIO. 1 = GPS_POWER_GPIO + Bit 2 should be 1 to show this access is read */ + return gpio_gps_access(arg | 0x4); +} + +static int device_open(struct inode *inode, struct file *fp) +{ + /* We don't want to talk to two processes at the same time. */ + if (Device_Open) { + printk(KERN_DEBUG "device_open() - Returning EBUSY. \ + Device already open... \n"); + return -EBUSY; + } + Device_Open++; /* BUGBUG : Not protected! */ + try_module_get(THIS_MODULE); + + return 0; +} + +static int device_release(struct inode *inode, struct file *fp) +{ + /* We're now ready for our next caller */ + Device_Open--; + module_put(THIS_MODULE); + + return 0; +} + +static int device_ioctl(struct inode *inode, struct file *fp, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + + /* Extract the type and number bitfields, and don't decode wrong cmds. + Return ENOTTY (inappropriate ioctl) before access_ok() */ + if (_IOC_TYPE(cmd) != MAJOR_NUM) { + printk(KERN_ERR + "device_ioctl() - Error! IOC_TYPE = %d. Expected %d\n", + _IOC_TYPE(cmd), MAJOR_NUM); + return -ENOTTY; + } + if (_IOC_NR(cmd) > IOCTL_MAXNUMBER) { + printk(KERN_ERR + "device_ioctl() - Error!" + "IOC_NR = %d greater than max supported(%d)\n", + _IOC_NR(cmd), IOCTL_MAXNUMBER); + return -ENOTTY; + } + + /* The direction is a bitmask, and VERIFY_WRITE catches R/W transfers. + `Type' is user-oriented, while access_ok is kernel-oriented, so the + concept of "read" and "write" is reversed. I think this is primarily + for good coding practice. You can easily do any kind of R/W access + without these checks and IOCTL code can be implemented "randomly"! */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = + !access_ok(VERIFY_WRITE, (void __user *)arg, + _IOC_SIZE(cmd)); + + else if (_IOC_DIR(cmd) & _IOC_WRITE) + err = + !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); + if (err) { + printk(KERN_ERR + "device_ioctl() - Error! User arg not valid" + "for selected access (R/W/RW). Cmd %d\n", + _IOC_TYPE(cmd)); + return -EFAULT; + } + + /* Note: Read and writing data to user buffer can be done using regular + pointer stuff but we may also use get_user() or put_user() */ + + /* Cmd and arg has been verified... */ + switch (cmd) { + case IOCTL_WRITEGPIO: + return ioctl_writegpio((int)arg); + case IOCTL_READGPIO: + return ioctl_readgpio((int)arg); + default: + printk(KERN_ERR "device_ioctl() - Invalid IOCTL (0x%x)\n", cmd); + return EINVAL; + } + return 0; +} + +struct file_operations Fops = { + .ioctl = device_ioctl, + .open = device_open, + .release = device_release, +}; + +/* Initialize the module - Register the character device */ +int init_chrdev(struct device *dev) +{ + int ret, gps_major; + + ret = alloc_chrdev_region(&agps_gpio_dev, 1, 1, "agps_gpio"); + gps_major = MAJOR(agps_gpio_dev); + if (ret < 0) { + dev_err(dev, "can't get major %d\n", gps_major); + goto err3; + } + + cdev_init(&mxc_gps_cdev, &Fops); + mxc_gps_cdev.owner = THIS_MODULE; + + ret = cdev_add(&mxc_gps_cdev, agps_gpio_dev, 1); + if (ret) { + dev_err(dev, "can't add cdev\n"); + goto err2; + } + + /* create class and device for udev information */ + gps_class = class_create(THIS_MODULE, "gps"); + if (IS_ERR(gps_class)) { + dev_err(dev, "failed to create gps class\n"); + ret = -ENOMEM; + goto err1; + } + + gps_class_dev = device_create(gps_class, NULL, MKDEV(gps_major, 1), NULL, + AGPSGPIO_DEVICE_FILE_NAME); + if (IS_ERR(gps_class_dev)) { + dev_err(dev, "failed to create gps gpio class device\n"); + ret = -ENOMEM; + goto err0; + } + + return 0; +err0: + class_destroy(gps_class); +err1: + cdev_del(&mxc_gps_cdev); +err2: + unregister_chrdev_region(agps_gpio_dev, 1); +err3: + return ret; +} + +/* Cleanup - unregister the appropriate file from /proc. */ +void cleanup_chrdev(void) +{ + /* destroy gps device class */ + device_destroy(gps_class, MKDEV(MAJOR(agps_gpio_dev), 1)); + class_destroy(gps_class); + + /* Unregister the device */ + cdev_del(&mxc_gps_cdev); + unregister_chrdev_region(agps_gpio_dev, 1); +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __init gps_ioctrl_probe(struct platform_device *pdev) +{ + struct regulator *gps_regu; + + mxc_gps_ioctrl_data = + (struct mxc_gps_platform_data *)pdev->dev.platform_data; + + /* open GPS GPO3 1v8 for GL gps support */ + if (mxc_gps_ioctrl_data->core_reg != NULL) { + mxc_gps_ioctrl_data->gps_regu_core = + regulator_get(&(pdev->dev), mxc_gps_ioctrl_data->core_reg); + gps_regu = mxc_gps_ioctrl_data->gps_regu_core; + if (!IS_ERR_VALUE((u32)gps_regu)) { + regulator_set_voltage(gps_regu, 1800000, 1800000); + regulator_enable(gps_regu); + } else { + return -1; + } + } + /* open GPS GPO1 2v8 for GL gps support */ + if (mxc_gps_ioctrl_data->analog_reg != NULL) { + mxc_gps_ioctrl_data->gps_regu_analog = + regulator_get(&(pdev->dev), + mxc_gps_ioctrl_data->analog_reg); + gps_regu = mxc_gps_ioctrl_data->gps_regu_analog; + if (!IS_ERR_VALUE((u32)gps_regu)) { + regulator_set_voltage(gps_regu, 2800000, 2800000); + regulator_enable(gps_regu); + } else { + return -1; + } + } + gpio_gps_active(); + + /* Register character device */ + init_chrdev(&(pdev->dev)); + return 0; +} + +static int gps_ioctrl_remove(struct platform_device *pdev) +{ + struct regulator *gps_regu; + + mxc_gps_ioctrl_data = + (struct mxc_gps_platform_data *)pdev->dev.platform_data; + + /* Character device cleanup.. */ + cleanup_chrdev(); + gpio_gps_inactive(); + + /* close GPS GPO3 1v8 for GL gps */ + gps_regu = mxc_gps_ioctrl_data->gps_regu_core; + if (mxc_gps_ioctrl_data->core_reg != NULL) { + regulator_disable(gps_regu); + regulator_put(gps_regu); + } + /* close GPS GPO1 2v8 for GL gps */ + gps_regu = mxc_gps_ioctrl_data->gps_regu_analog; + if (mxc_gps_ioctrl_data->analog_reg != NULL) { + regulator_disable(gps_regu); + regulator_put(gps_regu); + } + + return 0; +} + +static int gps_ioctrl_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* PowerEn toggle off */ + ioctl_writegpio(0x1); + return 0; +} + +static int gps_ioctrl_resume(struct platform_device *pdev) +{ + /* PowerEn pull up */ + ioctl_writegpio(0x3); + return 0; +} + +static struct platform_driver gps_ioctrl_driver = { + .probe = gps_ioctrl_probe, + .remove = gps_ioctrl_remove, + .suspend = gps_ioctrl_suspend, + .resume = gps_ioctrl_resume, + .driver = { + .name = "gps_ioctrl", + }, +}; + +/*! + * Entry point for GPS ioctrl module. + * + */ +static int __init gps_ioctrl_init(void) +{ + return platform_driver_register(&gps_ioctrl_driver); +} + +/*! + * unloading module. + * + */ +static void __exit gps_ioctrl_exit(void) +{ + platform_driver_unregister(&gps_ioctrl_driver); +} + +module_init(gps_ioctrl_init); +module_exit(gps_ioctrl_exit); +MODULE_DESCRIPTION("GPIO DEVICE DRIVER"); +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/gps_ioctrl/agpsgpiodev.h b/drivers/mxc/gps_ioctrl/agpsgpiodev.h new file mode 100644 index 000000000000..b8ba14041818 --- /dev/null +++ b/drivers/mxc/gps_ioctrl/agpsgpiodev.h @@ -0,0 +1,46 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file agpsgpiodev.h + * + * @brief head file of Simple character device interface for AGPS kernel module. + * + * @ingroup + */ + +#ifndef AGPSGPIODEV_H +#define AGPSGPIODEV_H + +#include + +#define USE_BLOCKING /* Test driver with blocking calls */ +#undef USE_FASYNC /* Test driver with async notification */ + +/* The major device number. We can't rely on dynamic registration any more + because ioctls need to know it */ +#define MAJOR_NUM 100 + +#define IOCTL_WRITEGPIO _IOWR(MAJOR_NUM, 1, char *) +#define IOCTL_READGPIO _IOR(MAJOR_NUM, 2, char *) +#define IOCTL_MAXNUMBER 2 + +/* The name of the device file */ +#define AGPSGPIO_DEVICE_FILE_NAME "agpsgpio" + +/* Exported prototypes */ +int init_chrdev(struct device *dev); +void cleanup_chrdev(void); +void wakeup(void); + +#endif diff --git a/drivers/mxc/hmp4e/Kconfig b/drivers/mxc/hmp4e/Kconfig new file mode 100644 index 000000000000..fdd7dbc041ba --- /dev/null +++ b/drivers/mxc/hmp4e/Kconfig @@ -0,0 +1,24 @@ +# +# MPEG4 Encoder kernel module configuration +# + +menu "MXC MPEG4 Encoder Kernel module support" + +config MXC_HMP4E + tristate "MPEG4 Encoder support" + depends on ARCH_MXC + depends on !ARCH_MX27 + default y + ---help--- + Say Y to get the MPEG4 Encoder kernel module available on + MXC platform. + +config MXC_HMP4E_DEBUG + bool "MXC MPEG4 Debug messages" + depends on MXC_HMP4E != n + default n + ---help--- + Say Y here if you need the Encoder driver to print debug messages. + This is an option for developers, most people should say N here. + +endmenu diff --git a/drivers/mxc/hmp4e/Makefile b/drivers/mxc/hmp4e/Makefile new file mode 100644 index 000000000000..3f69ec842af7 --- /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 +CFLAGS_mxc_hmp4e.o = -DIMX_NEEDS_DEPRECATED_SYMBOLS + +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..85c77dd062d4 --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.c @@ -0,0 +1,812 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include /* __init,__exit directives */ +#include /* remap_page_range / remap_pfn_range */ +#include /* for struct file_operations */ +#include /* standard error codes */ +#include /* for device registeration for PM */ +#include /* for msleep_interruptible */ +#include +#include /* for dma_alloc_consistent */ +#include +#include /* for ioctl __get_user, __put_user */ +#include +#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; + +ulong base_port = MPEG4_ENC_BASE_ADDR; +u32 irq = MXC_INT_MPEG4_ENCODER; + +module_param(base_port, long, 000); +module_param(irq, int, 000); + +/*! + * These variables store the register values when HMP4E is in suspend mode. + */ +#ifdef CONFIG_PM +u32 io_regs[64]; +#endif + +static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma); +static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma); +static void hmp4e_reset(hmp4e_t *dev); +irqreturn_t hmp4e_isr(s32 irq, void *dev_id); + +/*! + * This funtion is called to write h/w register. + * + * @param val value to be written into the register + * @param offset register offset + * + */ +static inline void hmp4e_write(u32 val, u32 offset) +{ + hmp4e_t *dev = &hmp4e_data; + __raw_writel(val, (dev->hwregs + offset)); +} + +/*! + * This funtion is called to read h/w register. + * + * @param offset register offset + * + * @return This function returns the value read from the register. + * + */ +static inline u32 hmp4e_read(u32 offset) +{ + hmp4e_t *dev = &hmp4e_data; + u32 val; + + val = __raw_readl(dev->hwregs + offset); + + return val; +} + +/*! + * The device's mmap method. The VFS has kindly prepared the process's + * vm_area_struct for us, so we examine this to see what was requested. + * + * @param filp pointer to struct file + * @param vma pointer to struct vma_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_mmap(struct file *filp, struct vm_area_struct *vma) +{ + s32 result; + ulong offset = vma->vm_pgoff << PAGE_SHIFT; + + pr_debug("hmp4e_mmap: size = %lu off = 0x%08lx\n", + (unsigned long)(vma->vm_end - vma->vm_start), offset); + + if (offset == 0) { + result = hmp4e_map_buffer(filp, vma); + } else if (offset == hmp4e_data.iobaseaddr) { + result = hmp4e_map_hwregs(filp, vma); + } else { + pr_debug("hmp4e: mmap invalid value\n"); + result = -EINVAL; + } + + return result; +} + +/*! + * This funtion is called to handle ioctls. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * @param cmd ioctl command + * @param arg user data + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_ioctl(struct inode *inode, struct file *filp, + u32 cmd, ulong arg) +{ + s32 err = 0, retval = 0; + ulong offset = 0; + hmp4e_t *dev = &hmp4e_data; + write_t bwrite; + +#ifdef CONFIG_PM + wait_event_interruptible(hmp4e_data.power_queue, + hmp4e_data.suspend_state == 0); +#endif + + /* + * extract the type and number bitfields, and don't decode + * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() + */ + if (_IOC_TYPE(cmd) != HMP4E_IOC_MAGIC) { + pr_debug("hmp4e: ioctl invalid magic\n"); + return -ENOTTY; + } + + if (_IOC_NR(cmd) > HMP4E_IOC_MAXNR) { + pr_debug("hmp4e: ioctl exceeds max ioctl\n"); + return -ENOTTY; + } + + /* + * the direction is a bitmask, and VERIFY_WRITE catches R/W + * transfers. `Type' is user-oriented, while + * access_ok is kernel-oriented, so the concept of "read" and + * "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) { + err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); + } else if (_IOC_DIR(cmd) & _IOC_WRITE) { + err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); + } + + if (err) { + pr_debug("hmp4e: ioctl invalid direction\n"); + return -EFAULT; + } + + switch (cmd) { + case HMP4E_IOCHARDRESET: + break; + + case HMP4E_IOCGBUFBUSADDRESS: + retval = __put_user((ulong) hmp4e_phys, (u32 *) arg); + break; + + case HMP4E_IOCGBUFSIZE: + retval = __put_user(hmp4e_data.buffsize, (u32 *) arg); + break; + + case HMP4E_IOCSREGWRITE: + if (dev->type != 1) { /* This ioctl only for VGA */ + pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); + retval = -EINVAL; + break; + } + + retval = __copy_from_user(&bwrite, (u32 *) arg, + sizeof(write_t)); + + if (bwrite.offset <= hmp4e_data.iosize - 4) { + hmp4e_write(bwrite.data, (bwrite.offset / 4)); + } else { + pr_debug("hmp4e: HMP4E_IOCSREGWRITE failed\n"); + retval = -EFAULT; + } + break; + + case HMP4E_IOCXREGREAD: + if (dev->type != 1) { /* This ioctl only for VGA */ + pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); + retval = -EINVAL; + break; + } + + retval = __get_user(offset, (ulong *) arg); + if (offset <= hmp4e_data.iosize - 4) { + __put_user(hmp4e_read((offset / 4)), (ulong *) arg); + } else { + pr_debug("hmp4e: HMP4E_IOCXREGREAD failed\n"); + retval = -EFAULT; + } + break; + + case HMP4E_IOCGHWOFFSET: + __put_user(hmp4e_data.iobaseaddr, (ulong *) arg); + break; + + case HMP4E_IOCGHWIOSIZE: + __put_user(hmp4e_data.iosize, (u32 *) arg); + break; + + case HMP4E_IOC_CLI: + if (irq_enable == true) { + disable_irq(hmp4e_data.irq); + irq_enable = false; + } + break; + + case HMP4E_IOC_STI: + if (irq_enable == false) { + enable_irq(hmp4e_data.irq); + irq_enable = true; + } + break; + + default: + pr_debug("unknown case %x\n", cmd); + } + + return retval; +} + +/*! + * This funtion is called when the device is opened. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_open(struct inode *inode, struct file *filp) +{ + hmp4e_t *dev = &hmp4e_data; + + filp->private_data = (void *)dev; + + if (request_irq(dev->irq, hmp4e_isr, 0, "mxc_hmp4e", dev) != 0) { + pr_debug("hmp4e: request irq failed\n"); + return -EBUSY; + } + + if (irq_enable == false) { + irq_enable = true; + } + clk_enable(hmp4e_clk); + return 0; +} + +static s32 hmp4e_fasync(s32 fd, struct file *filp, s32 mode) +{ + hmp4e_t *dev = (hmp4e_t *) filp->private_data; + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + +/*! + * This funtion is called when the device is closed. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * + * @return This function returns 0. + * + */ +static s32 hmp4e_release(struct inode *inode, struct file *filp) +{ + hmp4e_t *dev = (hmp4e_t *) filp->private_data; + + /* this is necessary if user process exited asynchronously */ + if (irq_enable == true) { + disable_irq(dev->irq); + irq_enable = false; + } + + /* reset hardware */ + hmp4e_reset(&hmp4e_data); + + /* free the encoder IRQ */ + free_irq(dev->irq, (void *)dev); + + /* remove this filp from the asynchronusly notified filp's */ + hmp4e_fasync(-1, filp, 0); + clk_disable(hmp4e_clk); + return 0; +} + +/* VFS methods */ +static struct file_operations hmp4e_fops = { + .owner = THIS_MODULE, + .open = hmp4e_open, + .release = hmp4e_release, + .ioctl = hmp4e_ioctl, + .mmap = hmp4e_mmap, + .fasync = hmp4e_fasync, +}; + +/*! + * This funtion allocates physical contigous memory. + * + * @param size size of memory to be allocated + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_alloc(u32 size) +{ + hmp4e_data.buffsize = PAGE_ALIGN(size); + hmp4e_data.buffaddr = + (ulong) dma_alloc_coherent(NULL, hmp4e_data.buffsize, + (dma_addr_t *) &hmp4e_phys, + GFP_DMA | GFP_KERNEL); + + if (hmp4e_data.buffaddr == 0) { + printk(KERN_ERR "hmp4e: couldn't allocate data buffer\n"); + return -ENOMEM; + } + + memset((s8 *) hmp4e_data.buffaddr, 0, hmp4e_data.buffsize); + return 0; +} + +/*! + * This funtion frees the DMAed memory. + */ +static void hmp4e_free(void) +{ + if (hmp4e_data.buffaddr != 0) { + dma_free_coherent(NULL, hmp4e_data.buffsize, + (void *)hmp4e_data.buffaddr, hmp4e_phys); + hmp4e_data.buffaddr = 0; + } +} + +/*! + * This funtion maps the shared buffer in memory. + * + * @param filp pointer to struct file + * @param vma pointer to struct vm_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma) +{ + ulong phys; + ulong start = (u32) vma->vm_start; + ulong size = (u32) (vma->vm_end - vma->vm_start); + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > hmp4e_data.buffsize) { + pr_debug("hmp4e: hmp4e_map_buffer, invalid size\n"); + return -EINVAL; + } + + vma->vm_flags |= VM_RESERVED | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + phys = hmp4e_phys; + + if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, size, + vma->vm_page_prot)) { + pr_debug("hmp4e: failed mmapping shared buffer\n"); + return -EAGAIN; + } + + return 0; +} + +/*! + * This funtion maps the h/w register space in memory. + * + * @param filp pointer to struct file + * @param vma pointer to struct vm_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma) +{ + ulong phys; + ulong start = (unsigned long)vma->vm_start; + ulong size = (unsigned long)(vma->vm_end - vma->vm_start); + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > PAGE_SIZE) { + pr_debug("hmp4e: hmp4e_map_hwregs, invalid size\n"); + return -EINVAL; + } + + vma->vm_flags |= VM_RESERVED | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remember this won't work for vmalloc()d memory ! */ + phys = hmp4e_data.iobaseaddr; + + if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, hmp4e_data.iosize, + vma->vm_page_prot)) { + pr_debug("hmp4e: failed mmapping HW registers\n"); + return -EAGAIN; + } + + return 0; +} + +/*! + * This function is the interrupt service routine. + * + * @param irq the irq number + * @param dev_id driver data when ISR was regiatered + * + * @return The return value is IRQ_HANDLED. + * + */ +irqreturn_t hmp4e_isr(s32 irq, void *dev_id) +{ + hmp4e_t *dev = (hmp4e_t *) dev_id; + u32 offset = dev->intr_offset; + + u32 irq_status = hmp4e_read(offset); + + /* clear enc IRQ */ + hmp4e_write(irq_status & (~0x01), offset); + + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + return IRQ_HANDLED; +} + +/*! + * This function is called to reset the encoder. + * + * @param dev pointer to struct hmp4e_data + * + */ +static void hmp4e_reset(hmp4e_t *dev) +{ + s32 i; + + /* enable HCLK for register reset */ + hmp4e_write(dev->clk_gate, 0); + + /* Reset registers, except ECR0 (0x00) and ID (read-only) */ + for (i = 1; i < (dev->iosize / 4); i += 1) { + if (i == dev->hwid_offset) /* ID is read only */ + continue; + + /* Only for CIF, not used */ + if ((dev->type == 0) && (i == 14)) + continue; + + hmp4e_write(0, i); + } + + /* disable HCLK */ + hmp4e_write(0, 0); + return; +} + +/*! + * This function is called during the driver binding process. This function + * does the hardware initialization. + * + * @param dev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 if successful. + */ +static s32 hmp4e_probe(struct platform_device *pdev) +{ + s32 result; + u32 hwid; + struct device *temp_class; + + hmp4e_data.iobaseaddr = base_port; + hmp4e_data.irq = irq; + hmp4e_data.buffaddr = 0; + + /* map hw i/o registers into kernel space */ + hmp4e_data.hwregs = (volatile void *)IO_ADDRESS(hmp4e_data.iobaseaddr); + + hmp4e_clk = clk_get(&pdev->dev, "mpeg4_clk"); + if (IS_ERR(hmp4e_clk)) { + printk(KERN_INFO "hmp4e: Unable to get clock\n"); + return -EIO; + } + + clk_enable(hmp4e_clk); + + /* check hw id for encoder signature */ + hwid = hmp4e_read(7); + if ((hwid & 0xffff) == 0x1882) { /* CIF first */ + hmp4e_data.type = 0; + hmp4e_data.iosize = (16 * 4); + hmp4e_data.hwid_offset = 7; + hmp4e_data.intr_offset = 5; + hmp4e_data.clk_gate = (1 << 1); + hmp4e_data.buffsize = 512000; + hmp4e_data.busy_offset = 0; + hmp4e_data.busy_val = 1; + } else { + hwid = hmp4e_read((0x88 / 4)); + if ((hwid & 0xffff0000) == 0x52510000) { /* VGA */ + hmp4e_data.type = 1; + hmp4e_data.iosize = (35 * 4); + hmp4e_data.hwid_offset = (0x88 / 4); + hmp4e_data.intr_offset = (0x10 / 4); + hmp4e_data.clk_gate = (1 << 12); + hmp4e_data.buffsize = 1048576; + hmp4e_data.busy_offset = (0x10 / 4); + hmp4e_data.busy_val = (1 << 1); + } else { + printk(KERN_INFO "hmp4e: HW ID not found\n"); + goto error1; + } + } + + /* Reset hardware */ + hmp4e_reset(&hmp4e_data); + + /* allocate memory shared with ewl */ + result = hmp4e_alloc(hmp4e_data.buffsize); + if (result < 0) + goto error1; + + result = register_chrdev(hmp4e_major, "hmp4e", &hmp4e_fops); + if (result <= 0) { + pr_debug("hmp4e: unable to get major %d\n", hmp4e_major); + goto error2; + } + + hmp4e_major = result; + + hmp4e_class = class_create(THIS_MODULE, "hmp4e"); + if (IS_ERR(hmp4e_class)) { + pr_debug("Error creating hmp4e class.\n"); + goto error3; + } + + temp_class = device_create(hmp4e_class, NULL, MKDEV(hmp4e_major, 0), NULL, + "hmp4e"); + if (IS_ERR(temp_class)) { + pr_debug("Error creating hmp4e class device.\n"); + goto error4; + } + + platform_set_drvdata(pdev, &hmp4e_data); + +#ifdef CONFIG_PM + hmp4e_data.async_queue = NULL; + hmp4e_data.suspend_state = 0; + init_waitqueue_head(&hmp4e_data.power_queue); +#endif + + printk(KERN_INFO "hmp4e: %s encoder initialized\n", + hmp4e_data.type ? "VGA" : "CIF"); + clk_disable(hmp4e_clk); + return 0; + + error4: + class_destroy(hmp4e_class); + error3: + unregister_chrdev(hmp4e_major, "hmp4e"); + error2: + hmp4e_free(); + error1: + clk_disable(hmp4e_clk); + clk_put(hmp4e_clk); + printk(KERN_INFO "hmp4e: module not inserted\n"); + return -EIO; +} + +/*! + * Dissociates the driver. + * + * @param dev the device structure + * + * @return The function always returns 0. + */ +static s32 hmp4e_remove(struct platform_device *pdev) +{ + device_destroy(hmp4e_class, MKDEV(hmp4e_major, 0)); + class_destroy(hmp4e_class); + unregister_chrdev(hmp4e_major, "hmp4e"); + hmp4e_free(); + clk_disable(hmp4e_clk); + clk_put(hmp4e_clk); + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +/*! + * This is the suspend of power management for the Hantro MPEG4 module + * + * @param dev the device + * @param state the state + * + * @return This function always returns 0. + */ +static s32 hmp4e_suspend(struct platform_device *pdev, pm_message_t state) +{ + s32 i; + hmp4e_t *pdata = &hmp4e_data; + + /* + * how many times msleep_interruptible will be called before + * giving up + */ + s32 timeout = 10; + + pr_debug("hmp4e: Suspend\n"); + hmp4e_data.suspend_state = 1; + + /* check if encoder is currently running */ + while ((hmp4e_read(pdata->busy_offset) & (pdata->busy_val)) && + --timeout) { + pr_debug("hmp4e: encoder is running, going to sleep\n"); + msleep_interruptible((unsigned int)30); + } + + if (!timeout) { + pr_debug("hmp4e: timeout suspending, resetting encoder\n"); + hmp4e_write(hmp4e_read(pdata->busy_offset) & + (~pdata->busy_val), pdata->busy_offset); + } + + /* first read register 0 */ + io_regs[0] = hmp4e_read(0); + + /* then override HCLK to make sure other registers can be read */ + hmp4e_write(pdata->clk_gate, 0); + + /* read other registers */ + for (i = 1; i < (pdata->iosize / 4); i += 1) { + + /* Only for CIF, not used */ + if ((pdata->type == 0) && (i == 14)) + continue; + + io_regs[i] = hmp4e_read(i); + } + + /* restore value of register 0 */ + hmp4e_write(io_regs[0], 0); + + /* stop HCLK */ + hmp4e_write(0, 0); + clk_disable(hmp4e_clk); + return 0; +}; + +/*! + * This is the resume of power management for the Hantro MPEG4 module + * It suports RESTORE state. + * + * @param pdev the platform device + * + * @return This function always returns 0 + */ +static s32 hmp4e_resume(struct platform_device *pdev) +{ + s32 i; + u32 status; + hmp4e_t *pdata = &hmp4e_data; + + pr_debug("hmp4e: Resume\n"); + clk_enable(hmp4e_clk); + + /* override HCLK to make sure registers can be written */ + hmp4e_write(pdata->clk_gate, 0x00); + + for (i = 1; i < (pdata->iosize / 4); i += 1) { + if (i == pdata->hwid_offset) /* Read only */ + continue; + + /* Only for CIF, not used */ + if ((pdata->type == 0) && (i == 14)) + continue; + + hmp4e_write(io_regs[i], i); + } + + /* write register 0 last */ + hmp4e_write(io_regs[0], 0x00); + + /* Clear the suspend flag */ + hmp4e_data.suspend_state = 0; + + /* Unblock the wait queue */ + wake_up_interruptible(&hmp4e_data.power_queue); + + /* Continue operations */ + status = hmp4e_read(pdata->intr_offset); + if (status & 0x1) { + hmp4e_write(status & (~0x01), pdata->intr_offset); + if (hmp4e_data.async_queue) + kill_fasync(&hmp4e_data.async_queue, SIGIO, POLL_IN); + } + + return 0; +}; + +#endif + +static struct platform_driver hmp4e_driver = { + .driver = { + .name = "mxc_hmp4e", + }, + .probe = hmp4e_probe, + .remove = hmp4e_remove, +#ifdef CONFIG_PM + .suspend = hmp4e_suspend, + .resume = hmp4e_resume, +#endif +}; + +static s32 __init hmp4e_init(void) +{ + printk(KERN_INFO "hmp4e: init\n"); + platform_driver_register(&hmp4e_driver); + return 0; +} + +static void __exit hmp4e_cleanup(void) +{ + platform_driver_unregister(&hmp4e_driver); + printk(KERN_INFO "hmp4e: module removed\n"); +} + +module_init(hmp4e_init); +module_exit(hmp4e_cleanup); + +/* module description */ +MODULE_AUTHOR("Hantro Products Oy"); +MODULE_DESCRIPTION("Device driver for Hantro's hardware based MPEG4 encoder"); +MODULE_SUPPORTED_DEVICE("5251/4251 MPEG4 Encoder"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/hmp4e/mxc_hmp4e.h b/drivers/mxc/hmp4e/mxc_hmp4e.h new file mode 100644 index 000000000000..57ea81be762a --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.h @@ -0,0 +1,70 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 /* needed for the _IOW etc stuff used later */ + +/* this is for writing data through ioctl to registers*/ +typedef struct { + unsigned long data; + unsigned long offset; +} write_t; + +/* + * Ioctl definitions + */ + +/* Use 'k' as magic number */ +#define HMP4E_IOC_MAGIC 'k' +/* + * S means "Set" through a ptr, + * T means "Tell" directly with the argument value + * G means "Get": reply by setting through a pointer + * Q means "Query": response is on the return value + * X means "eXchange": G and S atomically + * H means "sHift": T and Q atomically + */ +#define HMP4E_IOCGBUFBUSADDRESS _IOR(HMP4E_IOC_MAGIC, 1, unsigned long *) +#define HMP4E_IOCGBUFSIZE _IOR(HMP4E_IOC_MAGIC, 2, unsigned int *) +#define HMP4E_IOCGHWOFFSET _IOR(HMP4E_IOC_MAGIC, 3, unsigned long *) +#define HMP4E_IOCGHWIOSIZE _IOR(HMP4E_IOC_MAGIC, 4, unsigned int *) +#define HMP4E_IOC_CLI _IO(HMP4E_IOC_MAGIC, 5) +#define HMP4E_IOC_STI _IO(HMP4E_IOC_MAGIC, 6) +#define HMP4E_IOCHARDRESET _IO(HMP4E_IOC_MAGIC, 7) +#define HMP4E_IOCSREGWRITE _IOW(HMP4E_IOC_MAGIC, 8, write_t) +#define HMP4E_IOCXREGREAD _IOWR(HMP4E_IOC_MAGIC, 9, unsigned long) + +#define HMP4E_IOC_MAXNR 9 + +#endif /* !_HMP4ENC_H_ */ diff --git a/drivers/mxc/hw_event/Kconfig b/drivers/mxc/hw_event/Kconfig new file mode 100644 index 000000000000..bcf479689501 --- /dev/null +++ b/drivers/mxc/hw_event/Kconfig @@ -0,0 +1,11 @@ +menu "MXC HARDWARE EVENT" + +config MXC_HWEVENT + bool "MXC Hardware Event Handler" + default y + depends on ARCH_MXC + help + If you plan to use the Hardware Event Handler in the MXC, say + Y here. If unsure, select Y. + +endmenu diff --git a/drivers/mxc/hw_event/Makefile b/drivers/mxc/hw_event/Makefile new file mode 100644 index 000000000000..a53fe2b45e04 --- /dev/null +++ b/drivers/mxc/hw_event/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXC_HWEVENT) += mxc_hw_event.o diff --git a/drivers/mxc/hw_event/mxc_hw_event.c b/drivers/mxc/hw_event/mxc_hw_event.c new file mode 100644 index 000000000000..2ccd4079bbbc --- /dev/null +++ b/drivers/mxc/hw_event/mxc_hw_event.c @@ -0,0 +1,266 @@ +/* + * Copyright 2007-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * mxc_hw_event.c + * Collect the hardware events, send to user by netlink + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define EVENT_POOL_SIZE 10 + +struct hw_event_elem { + struct mxc_hw_event event; + struct list_head list; +}; + +static struct sock *nl_event_sock; /* netlink socket */ +static struct list_head event_head; +static struct list_head free_head; +static struct hw_event_elem events_pool[EVENT_POOL_SIZE]; /* event pool */ +static DEFINE_SPINLOCK(list_lock); +static DECLARE_WAIT_QUEUE_HEAD(event_wq); +static unsigned int seq; /* send seq */ +static int initialized; +static struct task_struct *hwevent_kthread; + +/*! + * main HW event handler thread + */ +static int hw_event_thread(void *data) +{ + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + unsigned int size; + struct hw_event_elem *event, *n; + LIST_HEAD(tmp_head); + DEFINE_WAIT(wait); + + while (1) { + + prepare_to_wait(&event_wq, &wait, TASK_INTERRUPTIBLE); + /* wait for event coming */ + if (!freezing(current) && !kthread_should_stop() && + list_empty(&event_head)) + schedule(); + finish_wait(&event_wq, &wait); + + try_to_freeze(); + + if (kthread_should_stop()) + break; + + /* fetch event from list */ + spin_lock_irq(&list_lock); + tmp_head = event_head; + tmp_head.prev->next = &tmp_head; + tmp_head.next->prev = &tmp_head; + /* clear the event list head */ + INIT_LIST_HEAD(&event_head); + spin_unlock_irq(&list_lock); + + list_for_each_entry_safe(event, n, &tmp_head, list) { + + size = NLMSG_SPACE(sizeof(struct mxc_hw_event)); + skb = alloc_skb(size, GFP_KERNEL); + if (!skb) { + /* if failed alloc skb, we drop this event */ + printk(KERN_WARNING + "mxc_hw_event: skb_alloc() failed\n"); + goto alloc_failure; + } + + /* put the netlink header struct to skb */ + nlh = + NLMSG_PUT(skb, 0, seq++, NLMSG_DONE, + size - sizeof(*nlh)); + + /* fill the netlink data */ + memcpy((struct mxc_hw_event *)NLMSG_DATA(nlh), + &event->event, sizeof(struct mxc_hw_event)); + + /* free the event node, set to unused */ + spin_lock_irq(&list_lock); + list_move(&event->list, &free_head); + spin_unlock_irq(&list_lock); + + /* send to all process that create this socket */ + NETLINK_CB(skb).pid = 0; /* sender pid */ + NETLINK_CB(skb).dst_group = HW_EVENT_GROUP; + /* broadcast the event */ + netlink_broadcast(nl_event_sock, skb, 0, HW_EVENT_GROUP, + GFP_KERNEL); + + continue; + nlmsg_failure: + printk(KERN_WARNING + "mxc_hw_event: No tailroom for NLMSG in skb\n"); + alloc_failure: + /* free the event node, set to unused */ + spin_lock_irq(&list_lock); + list_del(&event->list); + list_add_tail(&event->list, &free_head); + spin_unlock_irq(&list_lock); + } + } + + return 0; +} + +/*! + * + * @priority the event priority, REALTIME, EMERENCY, NORMAL + * @new_event event id to be send + */ +int hw_event_send(int priority, struct mxc_hw_event *new_event) +{ + unsigned int size; + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + struct mxc_hw_event *event; + struct hw_event_elem *event_elem; + int ret; + unsigned long flag; + struct list_head *list_node; + + if (!initialized) { + pr_info("HW Event module has not been initialized\n"); + return -1; + } + + if (priority == HWE_HIGH_PRIORITY) { + /** + * the most high priority event, + * we send it immediatly. + */ + + size = NLMSG_SPACE(sizeof(struct mxc_hw_event)); + + /* alloc skb */ + if (in_interrupt()) { + skb = alloc_skb(size, GFP_ATOMIC); + } else { + skb = alloc_skb(size, GFP_KERNEL); + } + if (!skb) { + /* if failed alloc skb, we drop this event */ + printk(KERN_WARNING + "hw_event send: skb_alloc() failed\n"); + goto send_later; + } + + /* put the netlink header struct to skb */ + nlh = NLMSG_PUT(skb, 0, seq++, NLMSG_DONE, size - sizeof(*nlh)); + + /* fill the netlink data */ + event = (struct mxc_hw_event *)NLMSG_DATA(nlh); + memcpy(event, new_event, sizeof(struct mxc_hw_event)); + + /* send to all process that create this socket */ + NETLINK_CB(skb).pid = 0; /* sender pid */ + NETLINK_CB(skb).dst_group = HW_EVENT_GROUP; + /* broadcast the event */ + ret = netlink_broadcast(nl_event_sock, skb, 0, HW_EVENT_GROUP, + in_interrupt() ? GFP_ATOMIC : + GFP_KERNEL); + if (ret) { + + nlmsg_failure: + /* send failed */ + kfree_skb(skb); + goto send_later; + } + + return 0; + } + + send_later: + spin_lock_irqsave(&list_lock, flag); + if (list_empty(&free_head)) { + spin_unlock_irqrestore(&list_lock, flag); + /* no more free event node */ + printk(KERN_WARNING "mxc_event send: no more free node\n"); + return -1; + } + + /* get a free node from free list, and added to event list */ + list_node = free_head.next; + /* fill event */ + event_elem = list_entry(list_node, struct hw_event_elem, list); + event_elem->event = *new_event; + list_move(list_node, &event_head); + spin_unlock_irqrestore(&list_lock, flag); + + wake_up(&event_wq); + + return 0; +} + +static int __init mxc_hw_event_init(void) +{ + int i; + + /* initial the list head for event and free */ + INIT_LIST_HEAD(&free_head); + INIT_LIST_HEAD(&event_head); + + /* initial the free list */ + for (i = 0; i < EVENT_POOL_SIZE; i++) + list_add_tail(&events_pool[i].list, &free_head); + + /* create netlink kernel sock */ + nl_event_sock = + netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0, NULL, NULL, + THIS_MODULE); + if (!nl_event_sock) { + printk(KERN_WARNING + "mxc_hw_event: Fail to create netlink socket.\n"); + return 1; + } + + hwevent_kthread = kthread_run(hw_event_thread, NULL, "hwevent"); + if (IS_ERR(hwevent_kthread)) { + printk(KERN_WARNING + "mxc_hw_event: Fail to create hwevent thread.\n"); + return 1; + } + + initialized = 1; + + return 0; +} + +static void __exit mxc_hw_event_exit(void) +{ + kthread_stop(hwevent_kthread); + /* wait for thread completion */ + sock_release(nl_event_sock->sk_socket); +} + +module_init(mxc_hw_event_init); +module_exit(mxc_hw_event_exit); + +EXPORT_SYMBOL(hw_event_send); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ipu/Kconfig b/drivers/mxc/ipu/Kconfig new file mode 100644 index 000000000000..919cb598c294 --- /dev/null +++ b/drivers/mxc/ipu/Kconfig @@ -0,0 +1,4 @@ +config MXC_IPU_V1 + bool + +source "drivers/mxc/ipu/pf/Kconfig" diff --git a/drivers/mxc/ipu/Makefile b/drivers/mxc/ipu/Makefile new file mode 100644 index 000000000000..4e9f19f9afa5 --- /dev/null +++ b/drivers/mxc/ipu/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_MXC_IPU_V1) = mxc_ipu.o + +mxc_ipu-objs := ipu_common.o ipu_sdc.o ipu_adc.o ipu_ic.o ipu_csi.o ipu_device.o ipu_calc_stripes_sizes.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..0c2ce89eb923 --- /dev/null +++ b/drivers/mxc/ipu/ipu_adc.c @@ -0,0 +1,689 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/*#define ADC_CHAN1_SA_MASK 0xFF800000 */ + +static void _ipu_set_cmd_data_mappings(display_port_t disp, + uint32_t pixel_fmt, int ifc_width); + +int32_t _ipu_adc_init_channel(ipu_channel_t chan, display_port_t disp, + mcu_mode_t cmd, int16_t x_pos, int16_t y_pos) +{ + uint32_t reg; + uint32_t start_addr, stride; + unsigned long lock_flags; + uint32_t size; + + size = 0; + + switch (disp) { + case DISP0: + reg = __raw_readl(ADC_DISP0_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + case DISP1: + reg = __raw_readl(ADC_DISP1_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + case DISP2: + reg = __raw_readl(ADC_DISP2_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + default: + return -EINVAL; + } + + if (stride == 0) + return -EINVAL; + + stride++; + start_addr = (y_pos * stride) + x_pos; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(ADC_CONF); + + switch (chan) { + case ADC_SYS1: + reg &= ~0x00FF4000; + reg |= + ((uint32_t) size << 21 | (uint32_t) disp << 19 | (uint32_t) + cmd << 16); + + __raw_writel(start_addr, ADC_SYSCHA1_SA); + break; + + case ADC_SYS2: + reg &= ~0xFF008000; + reg |= + ((uint32_t) size << 29 | (uint32_t) disp << 27 | (uint32_t) + cmd << 24); + + __raw_writel(start_addr, ADC_SYSCHA2_SA); + break; + + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + reg &= ~0x000000F9; + reg |= + ((uint32_t) size << 5 | (uint32_t) disp << 3 | + ADC_CONF_PRP_EN); + + __raw_writel(start_addr, ADC_PRPCHAN_SA); + break; + + case MEM_PP_ADC: + reg &= ~0x00003F02; + reg |= + ((uint32_t) size << 10 | (uint32_t) disp << 8 | + ADC_CONF_PP_EN); + + __raw_writel(start_addr, ADC_PPCHAN_SA); + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -1; + break; + } + __raw_writel(reg, ADC_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +int32_t _ipu_adc_uninit_channel(ipu_channel_t chan) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(ADC_CONF); + + switch (chan) { + case ADC_SYS1: + reg &= ~0x00FF4000; + break; + case ADC_SYS2: + reg &= ~0xFF008000; + break; + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + reg &= ~0x000000F9; + break; + case MEM_PP_ADC: + reg &= ~0x00003F02; + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -1; + break; + } + __raw_writel(reg, ADC_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +int32_t ipu_adc_write_template(display_port_t disp, uint32_t *pCmd, bool write) +{ + uint32_t ima_addr = 0; + uint32_t row_nu; + int i; + + /* Set IPU_IMA_ADDR (IPU Internal Memory Access Address) */ + /* MEM_NU = 0x0001 (CPM) */ + /* ROW_NU = 2*N ( N is channel number) */ + /* WORD_NU = 0 */ + if (write) { + row_nu = (uint32_t) disp * 2 * ATM_ADDR_RANGE; + } else { + row_nu = ((uint32_t) disp * 2 + 1) * ATM_ADDR_RANGE; + } + + /* form template addr for IPU_IMA_ADDR */ + ima_addr = (0x3 << 16 /*Template memory */ | row_nu << 3); + + __raw_writel(ima_addr, IPU_IMA_ADDR); + + /* write template data for IPU_IMA_DATA */ + for (i = 0; i < TEMPLATE_BUF_SIZE; i++) + /* only DATA field are needed */ + __raw_writel(pCmd[i], IPU_IMA_DATA); + + return 0; +} + +int32_t +ipu_adc_write_cmd(display_port_t disp, cmddata_t type, + uint32_t cmd, const uint32_t *params, uint16_t numParams) +{ + uint16_t i; + int disable_di = 0; + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(IPU_CONF); + if ((reg & IPU_CONF_DI_EN) == 0) { + disable_di = 1; + reg |= IPU_CONF_DI_EN; + __raw_writel(reg, IPU_CONF); + } + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + __raw_writel((uint32_t) ((type ? 0x0 : 0x1) | disp << 1 | 0x10), + DI_DISP_LLA_CONF); + __raw_writel(cmd, DI_DISP_LLA_DATA); + udelay(3); + + __raw_writel((uint32_t) (0x10 | disp << 1 | 0x11), DI_DISP_LLA_CONF); + for (i = 0; i < numParams; i++) { + __raw_writel(params[i], DI_DISP_LLA_DATA); + udelay(3); + } + + if (disable_di) { + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(IPU_CONF); + reg &= ~IPU_CONF_DI_EN; + __raw_writel(reg, IPU_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } + + return 0; +} + +int32_t ipu_adc_set_update_mode(ipu_channel_t channel, + ipu_adc_update_mode_t mode, + uint32_t refresh_rate, unsigned long addr, + uint32_t *size) +{ + int32_t err = 0; + uint32_t ref_per, reg, src = 0; + unsigned long lock_flags; + uint32_t ipu_freq; + + ipu_freq = clk_get_rate(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPU_FS_DISP_FLOW); + reg &= ~FS_AUTO_REF_PER_MASK; + switch (mode) { + case IPU_ADC_REFRESH_NONE: + src = 0; + break; + case IPU_ADC_AUTO_REFRESH: + if (refresh_rate == 0) { + err = -EINVAL; + goto err0; + } + ref_per = ipu_freq / ((1UL << 17) * refresh_rate); + ref_per--; + reg |= ref_per << FS_AUTO_REF_PER_OFFSET; + + src = FS_SRC_AUTOREF; + break; + case IPU_ADC_AUTO_REFRESH_SNOOP: + if (refresh_rate == 0) { + err = -EINVAL; + goto err0; + } + ref_per = ipu_freq / ((1UL << 17) * refresh_rate); + ref_per--; + reg |= ref_per << FS_AUTO_REF_PER_OFFSET; + + src = FS_SRC_AUTOREF_SNOOP; + break; + case IPU_ADC_SNOOPING: + src = FS_SRC_SNOOP; + break; + } + + switch (channel) { + case ADC_SYS1: + reg &= ~FS_ADC1_SRC_SEL_MASK; + reg |= src << FS_ADC1_SRC_SEL_OFFSET; + break; + case ADC_SYS2: + reg &= ~FS_ADC2_SRC_SEL_MASK; + reg |= src << FS_ADC2_SRC_SEL_OFFSET; + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + __raw_writel(reg, IPU_FS_DISP_FLOW); + + /* Setup bus snooping */ + if ((mode == IPU_ADC_AUTO_REFRESH_SNOOP) || (mode == IPU_ADC_SNOOPING)) { + err = mxc_snoop_set_config(0, addr, *size); + if (err > 0) { + *size = err; + err = 0; + } + } else { + mxc_snoop_set_config(0, 0, 0); + } + + err0: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return err; +} + +int32_t ipu_adc_get_snooping_status(uint32_t *statl, uint32_t *stath) +{ + return mxc_snoop_get_status(0, statl, stath); +} + +int32_t ipu_adc_init_panel(display_port_t disp, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint32_t stride, + ipu_adc_sig_cfg_t sig, + display_addressing_t addr, + uint32_t vsync_width, vsync_t mode) +{ + uint32_t temp; + unsigned long lock_flags; + uint32_t ser_conf; + uint32_t disp_conf; + uint32_t adc_disp_conf; + uint32_t adc_disp_vsync; + uint32_t old_pol; + + if ((disp != DISP1) && (disp != DISP2) && + (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL)) { + return -EINVAL; + } +/* adc_disp_conf = ((uint32_t)((((size == 3)||(size == 2))?1:0)<<14) | */ +/* (uint32_t)addr<<12 | (stride-1)); */ + adc_disp_conf = (uint32_t) addr << 12 | (stride - 1); + + _ipu_set_cmd_data_mappings(disp, pixel_fmt, sig.ifc_width); + + spin_lock_irqsave(&ipu_lock, lock_flags); + disp_conf = __raw_readl(DI_DISP_IF_CONF); + old_pol = __raw_readl(DI_DISP_SIG_POL); + adc_disp_vsync = __raw_readl(ADC_DISP_VSYNC); + + switch (disp) { + case DISP0: + __raw_writel(adc_disp_conf, ADC_DISP0_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP0_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D0_MODE_MASK | + ADC_DISP_VSYNC_D0_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x2000003FL; + old_pol |= sig.data_pol | sig.cs_pol << 1 | + sig.addr_pol << 2 | sig.read_pol << 3 | + sig.write_pol << 4 | sig.Vsync_pol << 5 | + sig.burst_pol << 29; + __raw_writel(old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x0000001FL; + disp_conf |= (sig.burst_mode << 3) | (sig.ifc_mode << 1) | + DI_CONF_DISP0_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + case DISP1: + __raw_writel(adc_disp_conf, ADC_DISP1_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP12_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D12_MODE_MASK | + ADC_DISP_VSYNC_D12_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x4000FF00L; + old_pol |= (sig.Vsync_pol << 6 | sig.data_pol << 8 | + sig.cs_pol << 9 | sig.addr_pol << 10 | + sig.read_pol << 11 | sig.write_pol << 12 | + sig.clk_pol << 14 | sig.burst_pol << 30); + __raw_writel(old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x00003F00L; + if (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL) { + ser_conf = (sig.ifc_width - 1) << + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET; + if (sig.ser_preamble_len) { + ser_conf |= DI_SER_DISPx_CONF_PREAMBLE_EN; + ser_conf |= sig.ser_preamble << + DI_SER_DISPx_CONF_PREAMBLE_OFFSET; + ser_conf |= (sig.ser_preamble_len - 1) << + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET; + } + + ser_conf |= + sig.ser_rw_mode << DI_SER_DISPx_CONF_RW_CFG_OFFSET; + + if (sig.burst_mode == IPU_ADC_BURST_SERIAL) + ser_conf |= DI_SER_DISPx_CONF_BURST_MODE_EN; + __raw_writel(ser_conf, DI_SER_DISP1_CONF); + } else { /* parallel interface */ + disp_conf |= (uint32_t) (sig.burst_mode << 12); + } + disp_conf |= (sig.ifc_mode << 9) | DI_CONF_DISP1_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + case DISP2: + __raw_writel(adc_disp_conf, ADC_DISP2_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP12_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D12_MODE_MASK | + ADC_DISP_VSYNC_D12_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x80FF0000L; + temp = (uint32_t) (sig.data_pol << 16 | sig.cs_pol << 17 | + sig.addr_pol << 18 | sig.read_pol << 19 | + sig.write_pol << 20 | sig.Vsync_pol << 6 | + sig.burst_pol << 31 | sig.clk_pol << 22); + __raw_writel(temp | old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x003F0000L; + if (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL) { + ser_conf = (sig.ifc_width - 1) << + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET; + if (sig.ser_preamble_len) { + ser_conf |= DI_SER_DISPx_CONF_PREAMBLE_EN; + ser_conf |= sig.ser_preamble << + DI_SER_DISPx_CONF_PREAMBLE_OFFSET; + ser_conf |= (sig.ser_preamble_len - 1) << + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET; + + } + + ser_conf |= + sig.ser_rw_mode << DI_SER_DISPx_CONF_RW_CFG_OFFSET; + + if (sig.burst_mode == IPU_ADC_BURST_SERIAL) + ser_conf |= DI_SER_DISPx_CONF_BURST_MODE_EN; + __raw_writel(ser_conf, DI_SER_DISP2_CONF); + } else { /* parallel interface */ + disp_conf |= (uint32_t) (sig.burst_mode << 20); + } + disp_conf |= (sig.ifc_mode << 17) | DI_CONF_DISP2_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + default: + break; + } + + __raw_writel(adc_disp_vsync, ADC_DISP_VSYNC); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_adc_init_ifc_timing(display_port_t disp, bool read, + uint32_t cycle_time, + uint32_t up_time, + uint32_t down_time, + uint32_t read_latch_time, uint32_t pixel_clk) +{ + uint32_t reg; + uint32_t time_conf3 = 0; + uint32_t clk_per; + uint32_t up_per; + uint32_t down_per; + uint32_t read_per; + uint32_t pixclk_per = 0; + uint32_t ipu_freq; + + ipu_freq = clk_get_rate(g_ipu_clk); + + clk_per = (cycle_time * (ipu_freq / 1000L) * 16L) / 1000000L; + up_per = (up_time * (ipu_freq / 1000L) * 4L) / 1000000L; + down_per = (down_time * (ipu_freq / 1000L) * 4L) / 1000000L; + + reg = (clk_per << DISPx_IF_CLK_PER_OFFSET) | + (up_per << DISPx_IF_CLK_UP_OFFSET) | + (down_per << DISPx_IF_CLK_DOWN_OFFSET); + + if (read) { + read_per = + (read_latch_time * (ipu_freq / 1000L) * 4L) / 1000000L; + if (pixel_clk) + pixclk_per = (ipu_freq * 16L) / pixel_clk; + time_conf3 = (read_per << DISPx_IF_CLK_READ_EN_OFFSET) | + (pixclk_per << DISPx_PIX_CLK_PER_OFFSET); + } + + dev_dbg(g_ipu_dev, "DI_DISPx_TIME_CONF_1/2 = 0x%08X\n", reg); + dev_dbg(g_ipu_dev, "DI_DISPx_TIME_CONF_3 = 0x%08X\n", time_conf3); + + switch (disp) { + case DISP0: + if (read) { + __raw_writel(reg, DI_DISP0_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP0_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP0_TIME_CONF_1); + } + break; + case DISP1: + if (read) { + __raw_writel(reg, DI_DISP1_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP1_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP1_TIME_CONF_1); + } + break; + case DISP2: + if (read) { + __raw_writel(reg, DI_DISP2_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP2_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP2_TIME_CONF_1); + } + break; + default: + return -EINVAL; + break; + } + + return 0; +} + +struct ipu_adc_di_map { + uint32_t map_byte1; + uint32_t map_byte2; + uint32_t map_byte3; + uint32_t cycle_cnt; +}; + +static const struct ipu_adc_di_map di_mappings[] = { + [0] = { + /* RGB888, 8-bit bus */ + .map_byte1 = 0x1600AAAA, + .map_byte2 = 0x00E05555, + .map_byte2 = 0x00070000, + .cycle_cnt = 3, + }, + [1] = { + /* RGB666, 8-bit bus */ + .map_byte1 = 0x1C00AAAF, + .map_byte2 = 0x00E0555F, + .map_byte3 = 0x0007000F, + .cycle_cnt = 3, + }, + [2] = { + /* RGB565, 8-bit bus */ + .map_byte1 = 0x008055BF, + .map_byte2 = 0x0142015F, + .map_byte3 = 0x0007003F, + .cycle_cnt = 2, + }, + [3] = { + /* RGB888, 24-bit bus */ + .map_byte1 = 0x0007000F, + .map_byte2 = 0x000F000F, + .map_byte3 = 0x0017000F, + .cycle_cnt = 1, + }, + [4] = { + /* RGB666, 18-bit bus */ + .map_byte1 = 0x0005000F, + .map_byte2 = 0x000B000F, + .map_byte3 = 0x0011000F, + .cycle_cnt = 1, + }, + [5] = { + /* RGB565, 16-bit bus */ + .map_byte1 = 0x0004003F, + .map_byte2 = 0x000A000F, + .map_byte3 = 0x000F003F, + .cycle_cnt = 1, + }, +}; + +/* Private methods */ +static void _ipu_set_cmd_data_mappings(display_port_t disp, + uint32_t pixel_fmt, int ifc_width) +{ + uint32_t reg; + u32 map = 0; + + if (ifc_width == 8) { + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + map = 0; + break; + case IPU_PIX_FMT_RGB666: + map = 1; + break; + case IPU_PIX_FMT_RGB565: + map = 2; + break; + default: + break; + } + } else if (ifc_width >= 16) { + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + map = 3; + break; + case IPU_PIX_FMT_RGB666: + map = 4; + break; + case IPU_PIX_FMT_RGB565: + map = 5; + break; + default: + break; + } + } + + switch (disp) { + case DISP0: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP0_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP0_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP0_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP0_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP0_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP0_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP0_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP0_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + case DISP1: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP1_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP1_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP1_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP1_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP1_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP1_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP1_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP1_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + case DISP2: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP2_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP2_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP2_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP2_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP2_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP2_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP2_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP2_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + default: + break; + } +} + +void ipu_disp_direct_write(ipu_channel_t channel, u32 value, u32 offset) +{ + /*TODO*/ +} + +int ipu_init_async_panel(int disp, int type, uint32_t cycle_time, + uint32_t pixel_fmt, ipu_adc_sig_cfg_t sig) +{ + /*TODO:uniform interface for ipu async panel init*/ + return -1; +} + +EXPORT_SYMBOL(ipu_adc_write_template); +EXPORT_SYMBOL(ipu_adc_write_cmd); +EXPORT_SYMBOL(ipu_adc_set_update_mode); +EXPORT_SYMBOL(ipu_adc_init_panel); +EXPORT_SYMBOL(ipu_adc_init_ifc_timing); diff --git a/drivers/mxc/ipu/ipu_calc_stripes_sizes.c b/drivers/mxc/ipu/ipu_calc_stripes_sizes.c new file mode 100644 index 000000000000..b6230e819503 --- /dev/null +++ b/drivers/mxc/ipu/ipu_calc_stripes_sizes.c @@ -0,0 +1,374 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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_calc_stripes_sizes.c + * + * @brief IPU IC functions + * + * @ingroup IPU + */ + +#include +#include +#include + +#define BPP_32 0 +#define BPP_16 3 +#define BPP_8 5 +#define BPP_24 1 +#define BPP_12 4 +#define BPP_18 2 + +static u64 _do_div(u64 a, u32 b) +{ + u64 div; + div = a; + do_div(div, b); + return div; +} + +static u32 truncate(u32 up, /* 0: down; else: up */ + u64 a, /* must be non-negative */ + u32 b) +{ + u32 d; + u64 div; + div = _do_div(a, b); + d = b * (div >> 32); + if (up && (a > (((u64)d) << 32))) + return d+b; + else + return d; +} + +static unsigned int f_calc(unsigned int pfs, unsigned int bpp, unsigned int *write) +{/* return input_f */ + unsigned int f_calculated = 0; + switch (pfs) { + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + f_calculated = 16; + break; + + case IPU_PIX_FMT_NV12: + f_calculated = 8; + break; + + default: + f_calculated = 0; + break; + + } + if (!f_calculated) { + switch (bpp) { + case BPP_32: + f_calculated = 2; + break; + + case BPP_16: + f_calculated = 4; + break; + + case BPP_8: + case BPP_24: + f_calculated = 8; + break; + + case BPP_12: + f_calculated = 16; + break; + + case BPP_18: + f_calculated = 32; + break; + + default: + f_calculated = 0; + break; + } + } + return f_calculated; +} + + +static unsigned int m_calc(unsigned int pfs) +{ + unsigned int m_calculated = 0; + switch (pfs) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + m_calculated = 8; + break; + + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + m_calculated = 2; + break; + + default: + m_calculated = 1; + break; + + } + return m_calculated; +} + + +/* Stripe parameters calculator */ +/************************************************************************** +Notes: +MSW = the maximal width allowed for a stripe + i.MX31: 720, i.MX35: 800, i.MX37/51/53: 1024 +cirr = the maximal inverse resizing ratio for which overlap in the input + is requested; typically cirr~2 +equal_stripes: + 0: each stripe is allowed to have independent parameters + for maximal image quality + 1: the stripes are requested to have identical parameters + (except the base address), for maximal performance +If performance is the top priority (above image quality) + Avoid overlap, by setting CIRR = 0 + This will also force effectively identical_stripes = 1 + Choose IF & OF that corresponds to the same IOX/SX for both stripes + Choose IFW & OFW such that + IFW/IM, IFW/IF, OFW/OM, OFW/OF are even integers + The function returns an error status: + 0: no error + 1: invalid input parameters -> aborted without result + Valid parameters should satisfy the following conditions + IFW <= OFW, otherwise downsizing is required + - which is not supported yet + 4 <= IFW,OFW, so some interpolation may be needed even without overlap + IM, OM, IF, OF should not vanish + 2*IF <= IFW + so the frame can be split to two equal stripes, even without overlap + 2*(OF+IF/irr_opt) <= OFW + so a valid positive INW exists even for equal stripes + OF <= MSW, otherwise, the left stripe cannot be sufficiently large + MSW < OFW, so splitting to stripes is required + OFW <= 2*MSW, so two stripes are sufficient + (this also implies that 2<=MSW) + 2: OF is not a multiple of OM - not fully-supported yet + Output is produced but OW is not guaranited to be a multiple of OM + 4: OFW reduced to be a multiple of OM + 8: CIRR > 1: truncated to 1 + Overlap is not supported (and not needed) y for upsizing) +**************************************************************************/ +int ipu_calc_stripes_sizes(const unsigned int input_frame_width, + /* input frame width;>1 */ + unsigned int output_frame_width, /* output frame width; >1 */ + const unsigned int maximal_stripe_width, + /* the maximal width allowed for a stripe */ + const unsigned long long cirr, /* see above */ + const unsigned int equal_stripes, /* see above */ + u32 input_pixelformat,/* pixel format after of read channel*/ + u32 output_pixelformat,/* pixel format after of write channel*/ + struct stripe_param *left, + struct stripe_param *right) +{ + const unsigned int irr_frac_bits = 13; + const unsigned long irr_steps = 1 << irr_frac_bits; + const u64 dirr = ((u64)1) << (32 - 2); + /* The maximum relative difference allowed between the irrs */ + const u64 cr = ((u64)4) << 32; + /* The importance ratio between the two terms in the cost function below */ + + unsigned int status; + unsigned int temp; + unsigned int onw_min; + unsigned int inw, onw, inw_best = 0; + /* number of pixels in the left stripe NOT hidden by the right stripe */ + u64 irr_opt; /* the optimal inverse resizing ratio */ + u64 rr_opt; /* the optimal resizing ratio = 1/irr_opt*/ + u64 dinw; /* the misalignment between the stripes */ + /* (measured in units of input columns) */ + u64 difwl, difwr; + /* The number of input columns not reflected in the output */ + /* the resizing ratio used for the right stripe is */ + /* left->irr and right->irr respectively */ + u64 cost, cost_min; + u64 div; /* result of division */ + + unsigned int input_m, input_f, output_m, output_f; /* parameters for upsizing by stripes */ + + status = 0; + + /* M, F calculations */ + /* read back pfs from params */ + + input_f = f_calc(input_pixelformat, 0, NULL); + input_m = 16; + /* BPP should be used in the out_F calc */ + /* Temporarily not used */ + /* out_F = F_calc(idmac->pfs, idmac->bpp, NULL); */ + + output_f = 16; + output_m = m_calc(output_pixelformat); + + + if ((output_frame_width < input_frame_width) || (input_frame_width < 4) + || (output_frame_width < 4)) + return 1; + + irr_opt = _do_div((((u64)(input_frame_width - 1)) << 32), + (output_frame_width - 1)); + rr_opt = _do_div((((u64)(output_frame_width - 1)) << 32), + (input_frame_width - 1)); + + if ((input_m == 0) || (output_m == 0) || (input_f == 0) || (output_f == 0) + || (input_frame_width < (2 * input_f)) + || ((((u64)output_frame_width) << 32) < + (2 * ((((u64)output_f) << 32) + (input_f * rr_opt)))) + || (maximal_stripe_width < output_f) + || (output_frame_width <= maximal_stripe_width) + || ((2 * maximal_stripe_width) < output_frame_width)) + return 1; + + if (output_f % output_m) + status += 2; + + temp = truncate(0, (((u64)output_frame_width) << 32), output_m); + if (temp < output_frame_width) { + output_frame_width = temp; + status += 4; + } + + if (equal_stripes) { + if ((irr_opt > cirr) /* overlap in the input is not requested */ + && ((input_frame_width % (input_m << 1)) == 0) + && ((input_frame_width % (input_f << 1)) == 0) + && ((output_frame_width % (output_m << 1)) == 0) + && ((output_frame_width % (output_f << 1)) == 0)) { + /* without overlap */ + left->input_width = right->input_width = right->input_column = + input_frame_width >> 1; + left->output_width = right->output_width = right->output_column = + output_frame_width >> 1; + left->input_column = right->input_column = 0; + div = _do_div(((((u64)irr_steps) << 32) * + (right->input_width - 1)), (right->output_width - 1)); + left->irr = right->irr = truncate(0, div, 1); + } else { /* with overlap */ + onw = truncate(0, (((u64)output_frame_width) << 32) >> 1, + output_f); + inw = truncate(0, onw * irr_opt, input_f); + /* this is the maximal inw which allows the same resizing ratio */ + /* in both stripes */ + onw = truncate(1, (inw * rr_opt), output_f); + div = _do_div((((u64)(irr_steps * inw)) << + 32), onw); + left->irr = right->irr = truncate(0, div, 1); + left->output_width = right->output_width = + output_frame_width - onw; + /* These are valid assignments for output_width, */ + /* assuming output_f is a multiple of output_m */ + div = (((u64)(left->output_width-1) * (left->irr)) << 32); + div = (((u64)1) << 32) + _do_div(div, irr_steps); + + left->input_width = right->input_width = truncate(1, div, input_m); + + div = _do_div((((u64)((right->output_width - 1) * right->irr)) << + 32), irr_steps); + difwr = (((u64)(input_frame_width - 1 - inw)) << 32) - div; + div = _do_div((difwr + (((u64)input_f) << 32)), 2); + left->input_column = truncate(0, div, input_f); + + + /* This splits the truncated input columns evenly */ + /* between the left and right margins */ + right->input_column = left->input_column + inw; + left->output_column = 0; + right->output_column = onw; + } + } else { /* independent stripes */ + onw_min = output_frame_width - maximal_stripe_width; + /* onw is a multiple of output_f, in the range */ + /* [max(output_f,output_frame_width-maximal_stripe_width),*/ + /*min(output_frame_width-2,maximal_stripe_width)] */ + /* definitely beyond the cost of any valid setting */ + cost_min = (((u64)input_frame_width) << 32) + cr; + onw = truncate(0, ((u64)maximal_stripe_width), output_f); + if (output_frame_width - onw == 1) + onw -= output_f; /* => onw and output_frame_width-1-onw are positive */ + inw = truncate(0, onw * irr_opt, input_f); + /* this is the maximal inw which allows the same resizing ratio */ + /* in both stripes */ + onw = truncate(1, inw * rr_opt, output_f); + do { + div = _do_div((((u64)(irr_steps * inw)) << 32), onw); + left->irr = truncate(0, div, 1); + div = _do_div((((u64)(onw * left->irr)) << 32), + irr_steps); + dinw = (((u64)inw) << 32) - div; + + div = _do_div((((u64)((output_frame_width - 1 - onw) * left->irr)) << + 32), irr_steps); + + difwl = (((u64)(input_frame_width - 1 - inw)) << 32) - div; + + cost = difwl + (((u64)(cr * dinw)) >> 32); + + if (cost < cost_min) { + inw_best = inw; + cost_min = cost; + } + + inw -= input_f; + onw = truncate(1, inw * rr_opt, output_f); + /* This is the minimal onw which allows the same resizing ratio */ + /* in both stripes */ + } while (onw >= onw_min); + + inw = inw_best; + onw = truncate(1, inw * rr_opt, output_f); + div = _do_div((((u64)(irr_steps * inw)) << 32), onw); + left->irr = truncate(0, div, 1); + + left->output_width = onw; + right->output_width = output_frame_width - onw; + /* These are valid assignments for output_width, */ + /* assuming output_f is a multiple of output_m */ + left->input_width = truncate(1, ((u64)(inw + 1)) << 32, input_m); + right->input_width = truncate(1, ((u64)(input_frame_width - inw)) << + 32, input_m); + + div = _do_div((((u64)(irr_steps * (input_frame_width - 1 - inw))) << + 32), (right->output_width - 1)); + right->irr = truncate(0, div, 1); + temp = truncate(0, ((u64)left->irr) * ((((u64)1) << 32) + dirr), 1); + if (temp < right->irr) + right->irr = temp; + div = _do_div(((u64)((right->output_width - 1) * right->irr) << + 32), irr_steps); + difwr = (u64)(input_frame_width - 1 - inw) - div; + + + div = _do_div((difwr + (((u64)input_f) << 32)), 2); + left->input_column = truncate(0, div, input_f); + + /* This splits the truncated input columns evenly */ + /* between the left and right margins */ + right->input_column = left->input_column + inw; + left->output_column = 0; + right->output_column = onw; + } + return status; +} +EXPORT_SYMBOL(ipu_calc_stripes_sizes); diff --git a/drivers/mxc/ipu/ipu_common.c b/drivers/mxc/ipu/ipu_common.c new file mode 100644 index 000000000000..fd4345283180 --- /dev/null +++ b/drivers/mxc/ipu/ipu_common.c @@ -0,0 +1,1970 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +static uint32_t g_channel_init_mask_backup; +static bool g_csi_used; + +/* 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); + +static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((type == IPU_INPUT_BUFFER) ? ((uint32_t) ch & 0xFF) : + ((type == IPU_OUTPUT_BUFFER) ? (((uint32_t) ch >> 8) & 0xFF) + : (((uint32_t) ch >> 16) & 0xFF))); +}; + +static inline uint32_t DMAParamAddr(uint32_t dma_ch) +{ + return 0x10000 | (dma_ch << 4); +}; + +/*! + * 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 mxc_ipu_config *ipu_conf = pdev->dev.platform_data; + + spin_lock_init(&ipu_lock); + + g_ipu_dev = &pdev->dev; + g_ipu_hw_rev = ipu_conf->rev; + + /* Register IPU interrupts */ + g_ipu_irq[0] = platform_get_irq(pdev, 0); + if (g_ipu_irq[0] < 0) + return -EINVAL; + + if (request_irq(g_ipu_irq[0], ipu_irq_handler, 0, driver_name, 0) != 0) { + dev_err(g_ipu_dev, "request SYNC interrupt failed\n"); + return -EBUSY; + } + /* Some platforms have 2 IPU interrupts */ + g_ipu_irq[1] = platform_get_irq(pdev, 1); + if (g_ipu_irq[1] >= 0) { + if (request_irq + (g_ipu_irq[1], ipu_irq_handler, 0, driver_name, 0) != 0) { + dev_err(g_ipu_dev, "request ERR interrupt failed\n"); + return -EBUSY; + } + } + + /* Enable IPU and CSI clocks */ + /* Get IPU clock freq */ + g_ipu_clk = clk_get(&pdev->dev, "ipu_clk"); + dev_dbg(g_ipu_dev, "ipu_clk = %lu\n", clk_get_rate(g_ipu_clk)); + + g_ipu_csi_clk = clk_get(&pdev->dev, "csi_clk"); + + dfm_clk = clk_get(NULL, "dfm_clk"); + + clk_enable(g_ipu_clk); + + /* resetting the CONF register of the IPU */ + __raw_writel(0x00000000, IPU_CONF); + + __raw_writel(0x00100010L, DI_HSP_CLK_PER); + + /* Set SDC refresh channels as high priority */ + __raw_writel(0x0000C000L, IDMAC_CHA_PRI); + + /* Set to max back to back burst requests */ + __raw_writel(0x00000000L, IDMAC_CONF); + + register_ipu_device(); + + return 0; +} + +/*! + * This function is called to initialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to initalize. + * + * @param params Input parameter containing union of channel initialization + * parameters. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel(ipu_channel_t channel, ipu_channel_params_t *params) +{ + uint32_t ipu_conf; + uint32_t reg; + unsigned long lock_flags; + + dev_dbg(g_ipu_dev, "init channel = %d\n", IPU_CHAN_ID(channel)); + + if ((channel != MEM_SDC_BG) && (channel != MEM_SDC_FG) && + (channel != MEM_ROT_ENC_MEM) && (channel != MEM_ROT_VF_MEM) && + (channel != MEM_ROT_PP_MEM) && (channel != CSI_MEM) + && (params == NULL)) { + return -EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = __raw_readl(IPU_CONF); + if (ipu_conf == 0) { + clk_enable(g_ipu_clk); + } + + if (g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already initialized %d\n", + IPU_CHAN_ID(channel)); + } + + switch (channel) { + case CSI_PRP_VF_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, true); + break; + case CSI_PRP_VF_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | (FS_DEST_ADC << FS_PRPVF_DEST_SEL_OFFSET), + IPU_FS_PROC_FLOW); + + _ipu_adc_init_channel(CSI_PRP_VF_ADC, + params->csi_prp_vf_adc.disp, + WriteTemplateNonSeq, + params->csi_prp_vf_adc.out_left, + params->csi_prp_vf_adc.out_top); + + _ipu_ic_init_prpvf(params, true); + break; + case MEM_PRP_VF_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | FS_VF_IN_VALID, IPU_FS_PROC_FLOW); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, false); + break; + case MEM_ROT_VF_MEM: + _ipu_ic_init_rotate_vf(params); + break; + case CSI_PRP_ENC_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW); + _ipu_ic_init_prpenc(params, true); + break; + case MEM_PRP_ENC_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | FS_ENC_IN_VALID, IPU_FS_PROC_FLOW); + _ipu_ic_init_prpenc(params, false); + break; + case MEM_ROT_ENC_MEM: + _ipu_ic_init_rotate_enc(params); + break; + case MEM_PP_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | (FS_DEST_ADC << FS_PP_DEST_SEL_OFFSET), + IPU_FS_PROC_FLOW); + + _ipu_adc_init_channel(MEM_PP_ADC, params->mem_pp_adc.disp, + WriteTemplateNonSeq, + params->mem_pp_adc.out_left, + params->mem_pp_adc.out_top); + + if (params->mem_pp_adc.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_pp(params); + break; + case MEM_PP_MEM: + if (params->mem_pp_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_pp(params); + break; + case MEM_ROT_PP_MEM: + _ipu_ic_init_rotate_pp(params); + break; + case CSI_MEM: + _ipu_ic_init_csi(params); + break; + + case MEM_PF_Y_MEM: + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + /* Enable PF block */ + _ipu_pf_init(params); + break; + + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + _ipu_adc_init_channel(ADC_SYS1, params->adc_sys1.disp, + params->adc_sys1.ch_mode, + params->adc_sys1.out_left, + params->adc_sys1.out_top); + break; + case ADC_SYS2: + _ipu_adc_init_channel(ADC_SYS2, params->adc_sys2.disp, + params->adc_sys2.ch_mode, + params->adc_sys2.out_left, + params->adc_sys2.out_top); + break; + default: + dev_err(g_ipu_dev, "Missing channel initialization\n"); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + /* Enable IPU sub module */ + g_channel_init_mask |= 1L << IPU_CHAN_ID(channel); + + if (g_channel_init_mask & 0x00000066L) { /*CSI */ + ipu_conf |= IPU_CONF_CSI_EN; + if (cpu_is_mx31() || cpu_is_mx32()) { + g_csi_used = true; + } + } + if (g_channel_init_mask & 0x00001FFFL) { /*IC */ + ipu_conf |= IPU_CONF_IC_EN; + } + if (g_channel_init_mask & 0x00000A10L) { /*ROT */ + ipu_conf |= IPU_CONF_ROT_EN; + } + if (g_channel_init_mask & 0x0001C000L) { /*SDC */ + ipu_conf |= IPU_CONF_SDC_EN | IPU_CONF_DI_EN; + } + if (g_channel_init_mask & 0x00061140L) { /*ADC */ + ipu_conf |= IPU_CONF_ADC_EN | IPU_CONF_DI_EN; + } + if (g_channel_init_mask & 0x00380000L) { /*PF */ + ipu_conf |= IPU_CONF_PF_EN; + } + __raw_writel(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function is called to uninitialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to uninitalize. + */ +void ipu_uninit_channel(ipu_channel_t channel) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t dma, mask = 0; + uint32_t ipu_conf; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if ((g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already uninitialized %d\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return; + } + + /* Make sure channel is disabled */ + /* Get input and output dma channels */ + dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) + mask |= 1UL << dma; + dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) + mask |= 1UL << dma; + /* Get secondary input dma channel */ + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) { + dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) { + mask |= 1UL << dma; + } + } + if (mask & __raw_readl(IDMAC_CHA_EN)) { + dev_err(g_ipu_dev, + "Channel %d is not disabled, disable first\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return; + } + + /* Reset the double buffer */ + reg = __raw_readl(IPU_CHA_DB_MODE_SEL); + __raw_writel(reg & ~mask, IPU_CHA_DB_MODE_SEL); + + g_sec_chan_en[IPU_CHAN_ID(channel)] = false; + + switch (channel) { + case CSI_MEM: + _ipu_ic_uninit_csi(); + break; + case CSI_PRP_VF_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_PRPVF_DEST_SEL_MASK, IPU_FS_PROC_FLOW); + + _ipu_adc_uninit_channel(CSI_PRP_VF_ADC); + + /* Fall thru */ + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + _ipu_ic_uninit_prpvf(); + break; + case MEM_PRP_VF_ADC: + break; + case MEM_ROT_VF_MEM: + _ipu_ic_uninit_rotate_vf(); + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + _ipu_ic_uninit_prpenc(); + break; + case MEM_ROT_ENC_MEM: + _ipu_ic_uninit_rotate_enc(); + break; + case MEM_PP_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_PP_DEST_SEL_MASK, IPU_FS_PROC_FLOW); + + _ipu_adc_uninit_channel(MEM_PP_ADC); + + /* Fall thru */ + case MEM_PP_MEM: + _ipu_ic_uninit_pp(); + break; + case MEM_ROT_PP_MEM: + _ipu_ic_uninit_rotate_pp(); + break; + + case MEM_PF_Y_MEM: + _ipu_pf_uninit(); + break; + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + break; + + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + _ipu_adc_uninit_channel(ADC_SYS1); + break; + case ADC_SYS2: + _ipu_adc_uninit_channel(ADC_SYS2); + break; + case MEM_SDC_MASK: + case CHAN_NONE: + break; + } + + g_channel_init_mask &= ~(1L << IPU_CHAN_ID(channel)); + + ipu_conf = __raw_readl(IPU_CONF); + if ((g_channel_init_mask & 0x00000066L) == 0) { /*CSI */ + ipu_conf &= ~IPU_CONF_CSI_EN; + } + if ((g_channel_init_mask & 0x00001FFFL) == 0) { /*IC */ + ipu_conf &= ~IPU_CONF_IC_EN; + } + if ((g_channel_init_mask & 0x00000A10L) == 0) { /*ROT */ + ipu_conf &= ~IPU_CONF_ROT_EN; + } + if ((g_channel_init_mask & 0x0001C000L) == 0) { /*SDC */ + ipu_conf &= ~IPU_CONF_SDC_EN; + } + if ((g_channel_init_mask & 0x00061140L) == 0) { /*ADC */ + ipu_conf &= ~IPU_CONF_ADC_EN; + } + if ((g_channel_init_mask & 0x0007D140L) == 0) { /*DI */ + ipu_conf &= ~IPU_CONF_DI_EN; + } + if ((g_channel_init_mask & 0x00380000L) == 0) { /*PF */ + ipu_conf &= ~IPU_CONF_PF_EN; + } + __raw_writel(ipu_conf, IPU_CONF); + if (ipu_conf == 0) { + clk_disable(g_ipu_clk); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function is called to initialize a buffer for logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel + * format is a FOURCC ASCII code. + * + * @param width Input parameter for width of buffer in pixels. + * + * @param height Input parameter for height of buffer in pixels. + * + * @param stride Input parameter for stride length of buffer + * in pixels. + * + * @param rot_mode Input parameter for rotation setting of buffer. + * A rotation setting other than \b IPU_ROTATE_VERT_FLIP + * should only be used for input buffers of rotation + * channels. + * + * @param phyaddr_0 Input parameter buffer 0 physical address. + * + * @param phyaddr_1 Input parameter buffer 1 physical address. + * Setting this to a value other than NULL enables + * double buffering mode. + * + * @param u private u offset for additional cropping, + * zero if not used. + * + * @param v private v offset for additional cropping, + * zero if not used. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u, uint32_t v) +{ + uint32_t params[10]; + unsigned long lock_flags; + uint32_t reg; + uint32_t dma_chan; + + dma_chan = channel_2_dma(channel, type); + + if (stride < width * bytes_per_pixel(pixel_fmt)) + stride = width * bytes_per_pixel(pixel_fmt); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (stride % 4) { + dev_err(g_ipu_dev, + "Stride must be 32-bit aligned, stride = %d\n", stride); + return -EINVAL; + } + /* IC channels' width must be multiple of 8 pixels */ + if ((dma_chan <= 13) && (width % 8)) { + dev_err(g_ipu_dev, "width must be 8 pixel multiple\n"); + return -EINVAL; + } + /* Build parameter memory data for DMA channel */ + _ipu_ch_param_set_size(params, pixel_fmt, width, height, stride, u, v); + _ipu_ch_param_set_buffer(params, phyaddr_0, phyaddr_1); + _ipu_ch_param_set_rotation(params, rot_mode); + /* Some channels (rotation) have restriction on burst length */ + if ((dma_chan == 10) || (dma_chan == 11) || (dma_chan == 13)) { + _ipu_ch_param_set_burst_size(params, 8); + } else if (dma_chan == 24) { /* PF QP channel */ + _ipu_ch_param_set_burst_size(params, 4); + } else if (dma_chan == 25) { /* PF H264 BS channel */ + _ipu_ch_param_set_burst_size(params, 16); + } else if (((dma_chan == 14) || (dma_chan == 15)) && + pixel_fmt == IPU_PIX_FMT_RGB565) { + _ipu_ch_param_set_burst_size(params, 16); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + _ipu_write_param_mem(DMAParamAddr(dma_chan), params, 10); + + reg = __raw_readl(IPU_CHA_DB_MODE_SEL); + if (phyaddr_1) { + reg |= 1UL << dma_chan; + } else { + reg &= ~(1UL << dma_chan); + } + __raw_writel(reg, IPU_CHA_DB_MODE_SEL); + + /* Reset to buffer 0 */ + __raw_writel(1UL << dma_chan, IPU_CHA_CUR_BUF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function is called to update the physical address of a buffer for + * a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number to update. + * 0 or 1 are the only valid values. + * + * @param phyaddr Input parameter buffer physical address. + * + * @return This function returns 0 on success or negative error code on + * fail. This function will fail if the buffer is set to ready. + */ +int32_t ipu_update_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum, dma_addr_t phyaddr) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bufNum == 0) { + reg = __raw_readl(IPU_CHA_BUF0_RDY); + if (reg & (1UL << dma_chan)) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + __raw_writel(DMAParamAddr(dma_chan) + 0x0008UL, IPU_IMA_ADDR); + __raw_writel(phyaddr, IPU_IMA_DATA); + } else { + reg = __raw_readl(IPU_CHA_BUF1_RDY); + if (reg & (1UL << dma_chan)) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + __raw_writel(DMAParamAddr(dma_chan) + 0x0009UL, IPU_IMA_ADDR); + __raw_writel(phyaddr, IPU_IMA_DATA); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + dev_dbg(g_ipu_dev, "IPU: update IDMA ch %d buf %d = 0x%08X\n", + dma_chan, bufNum, phyaddr); + return 0; +} + +/*! + * This function is called to 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 u predefined private u offset for additional cropping, + * zero if not used. + * + * @param v predefined private v offset for additional cropping, + * zero if not used. + * + * @param vertical_offset vertical offset for Y coordinate + * in the existed frame + * + * + * @param horizontal_offset horizontal offset for X coordinate + * in the existed frame + * + * + * @return Returns 0 on success or negative error code on fail + * This function will fail if any buffer is set to ready. + */ + +int32_t ipu_update_channel_offset(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + uint32_t u, uint32_t v, + uint32_t vertical_offset, uint32_t horizontal_offset) +{ + uint32_t reg; + int ret = 0; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ret = -EACCES; + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return ret; +} +EXPORT_SYMBOL(ipu_update_channel_offset); + +/*! + * 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 check buffer ready for a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to clear. + * + * @param bufNum Input parameter for which buffer number clear + * ready state. + * + */ +int32_t ipu_check_buffer_ready(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + uint32_t dma_chan = channel_2_dma(channel, type); + uint32_t reg; + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (bufNum == 0) + reg = __raw_readl(IPU_CHA_BUF0_RDY); + else + reg = __raw_readl(IPU_CHA_BUF1_RDY); + + if (reg & (1UL << dma_chan)) + return 1; + else + return 0; +} +EXPORT_SYMBOL(ipu_check_buffer_ready); + +/*! + * This function links 2 channels together for automatic frame + * synchronization. The output of the source channel is linked to the input of + * the destination channel. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_link_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + unsigned long lock_flags; + uint32_t out_dma; + uint32_t in_dma; + bool isProc; + uint32_t value; + uint32_t mask; + uint32_t offset; + uint32_t fs_proc_flow; + uint32_t fs_disp_flow; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow = __raw_readl(IPU_FS_PROC_FLOW); + fs_disp_flow = __raw_readl(IPU_FS_DISP_FLOW); + + out_dma = (1UL << channel_2_dma(src_ch, IPU_OUTPUT_BUFFER)); + in_dma = (1UL << channel_2_dma(dest_ch, IPU_INPUT_BUFFER)); + + /* PROCESS THE OUTPUT DMA CH */ + switch (out_dma) { + /*VF-> */ + case IDMA_IC_1: + pr_debug("Link VF->"); + isProc = true; + mask = FS_PRPVF_DEST_SEL_MASK; + offset = FS_PRPVF_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_11) ? FS_DEST_ROT : /*->VF_ROT */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /*->ADC1 */ + /* ->ADCDirect */ + 0; + break; + + /*VF_ROT-> */ + case IDMA_IC_9: + pr_debug("Link VF_ROT->"); + isProc = true; + mask = FS_PRPVF_ROT_DEST_SEL_MASK; + offset = FS_PRPVF_ROT_DEST_SEL_OFFSET; + value = (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /*->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + 0; + break; + + /*ENC-> */ + case IDMA_IC_0: + pr_debug("Link ENC->"); + isProc = true; + mask = 0; + offset = 0; + value = (in_dma == IDMA_IC_10) ? FS_PRPENC_DEST_SEL : /*->ENC_ROT */ + 0; + break; + + /*PP-> */ + case IDMA_IC_2: + pr_debug("Link PP->"); + isProc = true; + mask = FS_PP_DEST_SEL_MASK; + offset = FS_PP_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_13) ? FS_DEST_ROT : /*->PP_ROT */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + /* ->ADCDirect */ + 0; + break; + + /*PP_ROT-> */ + case IDMA_IC_12: + pr_debug("Link PP_ROT->"); + isProc = true; + mask = FS_PP_ROT_DEST_SEL_MASK; + offset = FS_PP_ROT_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_5) ? FS_DEST_ROT : /*->PP */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + 0; + break; + + /*PF-> */ + case IDMA_PF_Y_OUT: + case IDMA_PF_U_OUT: + case IDMA_PF_V_OUT: + pr_debug("Link PF->"); + isProc = true; + mask = FS_PF_DEST_SEL_MASK; + offset = FS_PF_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_5) ? FS_PF_DEST_PP : + (in_dma == IDMA_IC_13) ? FS_PF_DEST_ROT : 0; + break; + + /* Invalid Chainings: ENC_ROT-> */ + default: + pr_debug("Link Invalid->"); + value = 0; + break; + + } + + if (value) { + if (isProc) { + fs_proc_flow &= ~mask; + fs_proc_flow |= (value << offset); + } else { + fs_disp_flow &= ~mask; + fs_disp_flow |= (value << offset); + } + } else { + dev_err(g_ipu_dev, "Invalid channel chaining %d -> %d\n", + out_dma, in_dma); + return -EINVAL; + } + + /* PROCESS THE INPUT DMA CH */ + switch (in_dma) { + /* ->VF_ROT */ + case IDMA_IC_11: + pr_debug("VF_ROT\n"); + isProc = true; + mask = 0; + offset = 0; + value = (out_dma == IDMA_IC_1) ? FS_PRPVF_ROT_SRC_SEL : /*VF-> */ + 0; + break; + + /* ->ENC_ROT */ + case IDMA_IC_10: + pr_debug("ENC_ROT\n"); + isProc = true; + mask = 0; + offset = 0; + value = (out_dma == IDMA_IC_0) ? FS_PRPENC_ROT_SRC_SEL : /*ENC-> */ + 0; + break; + + /* ->PP */ + case IDMA_IC_5: + pr_debug("PP\n"); + isProc = true; + mask = FS_PP_SRC_SEL_MASK; + offset = FS_PP_SRC_SEL_OFFSET; + value = (out_dma == IDMA_PF_Y_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_U_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_V_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_IC_12) ? FS_PP_SRC_ROT : /*PP_ROT-> */ + 0; + break; + + /* ->PP_ROT */ + case IDMA_IC_13: + pr_debug("PP_ROT\n"); + isProc = true; + mask = FS_PP_ROT_SRC_SEL_MASK; + offset = FS_PP_ROT_SRC_SEL_OFFSET; + value = (out_dma == IDMA_PF_Y_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_U_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_V_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_IC_2) ? FS_ROT_SRC_PP : /*PP-> */ + 0; + break; + + /* ->SDC_BG */ + case IDMA_SDC_BG: + pr_debug("SDC_BG\n"); + isProc = false; + mask = FS_SDC_BG_SRC_SEL_MASK; + offset = FS_SDC_BG_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->SDC_FG */ + case IDMA_SDC_FG: + pr_debug("SDC_FG\n"); + isProc = false; + mask = FS_SDC_FG_SRC_SEL_MASK; + offset = FS_SDC_FG_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->ADC1 */ + case IDMA_ADC_SYS1_WR: + pr_debug("ADC_SYS1\n"); + isProc = false; + mask = FS_ADC1_SRC_SEL_MASK; + offset = FS_ADC1_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->ADC2 */ + case IDMA_ADC_SYS2_WR: + pr_debug("ADC_SYS2\n"); + isProc = false; + mask = FS_ADC2_SRC_SEL_MASK; + offset = FS_ADC2_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /*Invalid chains: */ + /* ->ENC, ->VF, ->PF, ->VF_COMBINE, ->PP_COMBINE */ + default: + pr_debug("Invalid\n"); + value = 0; + break; + + } + + if (value) { + if (isProc) { + fs_proc_flow &= ~mask; + fs_proc_flow |= (value << offset); + } else { + fs_disp_flow &= ~mask; + fs_disp_flow |= (value << offset); + } + } else { + dev_err(g_ipu_dev, "Invalid channel chaining %d -> %d\n", + out_dma, in_dma); + return -EINVAL; + } + + __raw_writel(fs_proc_flow, IPU_FS_PROC_FLOW); + __raw_writel(fs_disp_flow, IPU_FS_DISP_FLOW); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function unlinks 2 channels and disables automatic frame + * synchronization. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_unlink_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + unsigned long lock_flags; + uint32_t out_dma; + uint32_t in_dma; + uint32_t fs_proc_flow; + uint32_t fs_disp_flow; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow = __raw_readl(IPU_FS_PROC_FLOW); + fs_disp_flow = __raw_readl(IPU_FS_DISP_FLOW); + + out_dma = (1UL << channel_2_dma(src_ch, IPU_OUTPUT_BUFFER)); + in_dma = (1UL << channel_2_dma(dest_ch, IPU_INPUT_BUFFER)); + + /*clear the src_ch's output destination */ + switch (out_dma) { + /*VF-> */ + case IDMA_IC_1: + pr_debug("Unlink VF->"); + fs_proc_flow &= ~FS_PRPVF_DEST_SEL_MASK; + break; + + /*VF_ROT-> */ + case IDMA_IC_9: + pr_debug("Unlink VF_Rot->"); + fs_proc_flow &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + break; + + /*ENC-> */ + case IDMA_IC_0: + pr_debug("Unlink ENC->"); + fs_proc_flow &= ~FS_PRPENC_DEST_SEL; + break; + + /*PP-> */ + case IDMA_IC_2: + pr_debug("Unlink PP->"); + fs_proc_flow &= ~FS_PP_DEST_SEL_MASK; + break; + + /*PP_ROT-> */ + case IDMA_IC_12: + pr_debug("Unlink PP_ROT->"); + fs_proc_flow &= ~FS_PP_ROT_DEST_SEL_MASK; + break; + + /*PF-> */ + case IDMA_PF_Y_OUT: + case IDMA_PF_U_OUT: + case IDMA_PF_V_OUT: + pr_debug("Unlink PF->"); + fs_proc_flow &= ~FS_PF_DEST_SEL_MASK; + break; + + default: /*ENC_ROT-> */ + pr_debug("Unlink Invalid->"); + break; + } + + /*clear the dest_ch's input source */ + switch (in_dma) { + /*->VF_ROT*/ + case IDMA_IC_11: + pr_debug("VF_ROT\n"); + fs_proc_flow &= ~FS_PRPVF_ROT_SRC_SEL; + break; + + /*->Enc_ROT*/ + case IDMA_IC_10: + pr_debug("ENC_ROT\n"); + fs_proc_flow &= ~FS_PRPENC_ROT_SRC_SEL; + break; + + /*->PP*/ + case IDMA_IC_5: + pr_debug("PP\n"); + fs_proc_flow &= ~FS_PP_SRC_SEL_MASK; + break; + + /*->PP_ROT*/ + case IDMA_IC_13: + pr_debug("PP_ROT\n"); + fs_proc_flow &= ~FS_PP_ROT_SRC_SEL_MASK; + break; + + /*->SDC_FG*/ + case IDMA_SDC_FG: + pr_debug("SDC_FG\n"); + fs_disp_flow &= ~FS_SDC_FG_SRC_SEL_MASK; + break; + + /*->SDC_BG*/ + case IDMA_SDC_BG: + pr_debug("SDC_BG\n"); + fs_disp_flow &= ~FS_SDC_BG_SRC_SEL_MASK; + break; + + /*->ADC1*/ + case IDMA_ADC_SYS1_WR: + pr_debug("ADC_SYS1\n"); + fs_disp_flow &= ~FS_ADC1_SRC_SEL_MASK; + break; + + /*->ADC2*/ + case IDMA_ADC_SYS2_WR: + pr_debug("ADC_SYS2\n"); + fs_disp_flow &= ~FS_ADC2_SRC_SEL_MASK; + break; + + default: /*->VF, ->ENC, ->VF_COMBINE, ->PP_COMBINE, ->PF*/ + pr_debug("Invalid\n"); + break; + } + + __raw_writel(fs_proc_flow, IPU_FS_PROC_FLOW); + __raw_writel(fs_disp_flow, IPU_FS_DISP_FLOW); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function check whether a logical channel was enabled. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 1 while request channel is enabled or + * 0 for not enabled. + */ +int32_t ipu_is_channel_busy(ipu_channel_t channel) +{ + uint32_t reg; + uint32_t in_dma; + uint32_t out_dma; + + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + + reg = __raw_readl(IDMAC_CHA_EN); + + if (reg & ((1UL << out_dma) | (1UL << in_dma))) + return 1; + return 0; +} +EXPORT_SYMBOL(ipu_is_channel_busy); + +/*! + * This function enables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_enable_channel(ipu_channel_t channel) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t sec_dma; + uint32_t out_dma; + uint32_t chan_mask = 0; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN); + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (out_dma != IDMA_CHAN_INVALID) + reg |= 1UL << out_dma; + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (in_dma != IDMA_CHAN_INVALID) + reg |= 1UL << in_dma; + + /* Get secondary input dma channel */ + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) { + sec_dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (sec_dma != IDMA_CHAN_INVALID) { + reg |= 1UL << sec_dma; + } + } + + __raw_writel(reg | chan_mask, IDMAC_CHA_EN); + + if (IPU_CHAN_ID(channel) <= IPU_CHAN_ID(MEM_PP_ADC)) { + _ipu_ic_enable_task(channel); + } else if (channel == MEM_SDC_BG) { + dev_dbg(g_ipu_dev, "Initializing SDC BG\n"); + _ipu_sdc_bg_init(NULL); + } else if (channel == MEM_SDC_FG) { + dev_dbg(g_ipu_dev, "Initializing SDC FG\n"); + _ipu_sdc_fg_init(NULL); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function clear buffer ready for a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to clear. + * + * @param bufNum Input parameter for which buffer number clear + * ready state. + * + */ +void ipu_clear_buffer_ready(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + /*TODO*/ +} +EXPORT_SYMBOL(ipu_clear_buffer_ready); + +uint32_t ipu_get_cur_buffer_idx(ipu_channel_t channel, ipu_buffer_t type) +{ + uint32_t reg, dma_chan; + + dma_chan = channel_2_dma(channel, type); + + reg = __raw_readl(IPU_CHA_CUR_BUF); + if (reg & (1UL << dma_chan)) + return 1; + else + return 0; +} +EXPORT_SYMBOL(ipu_get_cur_buffer_idx); + +/*! + * This function disables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param wait_for_stop Flag to set whether to wait for channel end + * of frame or return immediately. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_disable_channel(ipu_channel_t channel, bool wait_for_stop) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t sec_dma; + uint32_t in_dma; + uint32_t out_dma; + uint32_t chan_mask = 0; + uint32_t timeout; + uint32_t eof_intr; + uint32_t enabled; + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (out_dma != IDMA_CHAN_INVALID) + chan_mask = 1UL << out_dma; + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (in_dma != IDMA_CHAN_INVALID) + chan_mask |= 1UL << in_dma; + sec_dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (sec_dma != IDMA_CHAN_INVALID) + chan_mask |= 1UL << sec_dma; + + if (wait_for_stop && channel != MEM_SDC_FG && channel != MEM_SDC_BG) { + timeout = 40; + while ((__raw_readl(IDMAC_CHA_BUSY) & chan_mask) || + (_ipu_channel_status(channel) == TASK_STAT_ACTIVE)) { + timeout--; + msleep(10); + if (timeout == 0) { + printk + (KERN_INFO + "MXC IPU: Warning - timeout waiting for channel to stop,\n" + "\tbuf0_rdy = 0x%08X, buf1_rdy = 0x%08X\n" + "\tbusy = 0x%08X, tstat = 0x%08X\n\tmask = 0x%08X\n", + __raw_readl(IPU_CHA_BUF0_RDY), + __raw_readl(IPU_CHA_BUF1_RDY), + __raw_readl(IDMAC_CHA_BUSY), + __raw_readl(IPU_TASKS_STAT), chan_mask); + break; + } + } + dev_dbg(g_ipu_dev, "timeout = %d * 10ms\n", 40 - timeout); + } + /* SDC BG and FG must be disabled before DMA is disabled */ + if ((channel == MEM_SDC_BG) || (channel == MEM_SDC_FG)) { + + if (channel == MEM_SDC_BG) + eof_intr = IPU_IRQ_SDC_BG_EOF; + else + eof_intr = IPU_IRQ_SDC_FG_EOF; + + /* Wait for any buffer flips to finsh */ + timeout = 4; + while (timeout && + ((__raw_readl(IPU_CHA_BUF0_RDY) & chan_mask) || + (__raw_readl(IPU_CHA_BUF1_RDY) & chan_mask))) { + msleep(10); + timeout--; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + ipu_clear_irq(eof_intr); + if (channel == MEM_SDC_BG) + enabled = _ipu_sdc_bg_uninit(); + else + enabled = _ipu_sdc_fg_uninit(); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (enabled && wait_for_stop) { + timeout = 5; + } else { + timeout = 0; + } + while (timeout && !ipu_get_irq_status(eof_intr)) { + msleep(5); + timeout--; + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Disable IC task */ + if (IPU_CHAN_ID(channel) <= IPU_CHAN_ID(MEM_PP_ADC)) { + _ipu_ic_disable_task(channel); + } + + /* Disable DMA channel(s) */ + reg = __raw_readl(IDMAC_CHA_EN); + __raw_writel(reg & ~chan_mask, IDMAC_CHA_EN); + + /* Clear DMA related interrupts */ + __raw_writel(chan_mask, IPU_INT_STAT_1); + __raw_writel(chan_mask, IPU_INT_STAT_2); + __raw_writel(chan_mask, IPU_INT_STAT_4); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_enable_csi(uint32_t csi) +{ + return 0; +} + + +int32_t ipu_disable_csi(uint32_t csi) +{ + return 0; +} + +static +irqreturn_t ipu_irq_handler(int irq, void *desc) +{ + uint32_t line_base = 0; + uint32_t line; + irqreturn_t result = IRQ_NONE; + uint32_t int_stat; + + int_stat = __raw_readl(IPU_INT_STAT_1); + int_stat &= __raw_readl(IPU_INT_CTRL_1); + __raw_writel(int_stat, IPU_INT_STAT_1); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 32; + int_stat = __raw_readl(IPU_INT_STAT_2); + int_stat &= __raw_readl(IPU_INT_CTRL_2); + __raw_writel(int_stat, IPU_INT_STAT_2); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 64; + int_stat = __raw_readl(IPU_INT_STAT_3); + int_stat &= __raw_readl(IPU_INT_CTRL_3); + __raw_writel(int_stat, IPU_INT_STAT_3); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 96; + int_stat = __raw_readl(IPU_INT_STAT_4); + int_stat &= __raw_readl(IPU_INT_CTRL_4); + __raw_writel(int_stat, IPU_INT_STAT_4); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 128; + int_stat = __raw_readl(IPU_INT_STAT_5); + int_stat &= __raw_readl(IPU_INT_CTRL_5); + __raw_writel(int_stat, IPU_INT_STAT_5); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + return result; +} + +/*! + * This function enables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to enable interrupt for. + * + */ +void ipu_enable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg |= IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function disables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to disable interrupt for. + * + */ +void ipu_disable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg &= ~IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function clears the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to clear interrupt for. + * + */ +void ipu_clear_irq(uint32_t irq) +{ + __raw_writel(IPUIRQ_2_MASK(irq), IPUIRQ_2_STATREG(irq)); +} + +/*! + * This function returns the current interrupt status for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @return Returns true if the interrupt is pending/asserted or false if + * the interrupt is not pending. + */ +bool ipu_get_irq_status(uint32_t irq) +{ + uint32_t reg = __raw_readl(IPUIRQ_2_STATREG(irq)); + + if (reg & IPUIRQ_2_MASK(irq)) + return true; + else + return false; +} + +/*! + * This function registers an interrupt handler function for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param handler Input parameter for address of the handler + * function. + * + * @param irq_flags Flags for interrupt mode. Currently not used. + * + * @param devname Input parameter for string name of driver + * registering the handler. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int ipu_request_irq(uint32_t irq, + irqreturn_t(*handler) (int, void *), + uint32_t irq_flags, const char *devname, void *dev_id) +{ + unsigned long lock_flags; + + BUG_ON(irq >= IPU_IRQ_COUNT); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (ipu_irq_list[irq].handler != NULL) { + dev_err(g_ipu_dev, + "ipu_request_irq - handler already installed on irq %d\n", + irq); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + ipu_irq_list[irq].handler = handler; + ipu_irq_list[irq].flags = irq_flags; + ipu_irq_list[irq].dev_id = dev_id; + ipu_irq_list[irq].name = devname; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + ipu_enable_irq(irq); /* enable the interrupt */ + + return 0; +} + +/*! + * This function unregisters an interrupt handler for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. This must match value passed to + * ipu_request_irq(). + * + */ +void ipu_free_irq(uint32_t irq, void *dev_id) +{ + ipu_disable_irq(irq); /* disable the interrupt */ + + if (ipu_irq_list[irq].dev_id == dev_id) { + ipu_irq_list[irq].handler = NULL; + } +} + +/*! + * This function sets the post-filter pause row for h.264 mode. + * + * @param pause_row The last row to process before pausing. + * + * @return This function returns 0 on success or negative error code on + * fail. + * + */ +int32_t ipu_pf_set_pause_row(uint32_t pause_row) +{ + int32_t retval = 0; + uint32_t timeout = 5; + unsigned long lock_flags; + uint32_t reg; + + reg = __raw_readl(IPU_TASKS_STAT); + while ((reg & TSTAT_PF_MASK) && ((reg & TSTAT_PF_H264_PAUSE) == 0)) { + timeout--; + msleep(5); + if (timeout == 0) { + dev_err(g_ipu_dev, "PF Timeout - tstat = 0x%08X\n", + __raw_readl(IPU_TASKS_STAT)); + retval = -ETIMEDOUT; + goto err0; + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(PF_CONF); + + /* Set the pause row */ + if (pause_row) { + reg &= ~PF_CONF_PAUSE_ROW_MASK; + reg |= PF_CONF_PAUSE_EN | pause_row << PF_CONF_PAUSE_ROW_SHIFT; + } else { + reg &= ~(PF_CONF_PAUSE_EN | PF_CONF_PAUSE_ROW_MASK); + } + __raw_writel(reg, PF_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + err0: + return retval; +} + +/* Private functions */ +void _ipu_write_param_mem(uint32_t addr, uint32_t *data, uint32_t numWords) +{ + for (; numWords > 0; numWords--) { + dev_dbg(g_ipu_dev, + "write param mem - addr = 0x%08X, data = 0x%08X\n", + addr, *data); + __raw_writel(addr, IPU_IMA_ADDR); + __raw_writel(*data++, IPU_IMA_DATA); + addr++; + if ((addr & 0x7) == 5) { + addr &= ~0x7; /* set to word 0 */ + addr += 8; /* increment to next row */ + } + } +} + +static void _ipu_pf_init(ipu_channel_params_t *params) +{ + uint32_t reg; + + /*Setup the type of filtering required */ + switch (params->mem_pf_mem.operation) { + case PF_MPEG4_DEBLOCK: + case PF_MPEG4_DERING: + case PF_MPEG4_DEBLOCK_DERING: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = true; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; + break; + case PF_H264_DEBLOCK: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = true; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = true; + break; + default: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = false; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; + return; + break; + } + reg = params->mem_pf_mem.operation; + __raw_writel(reg, PF_CONF); +} + +static void _ipu_pf_uninit(void) +{ + __raw_writel(0x0L, PF_CONF); + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = false; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; +} + +uint32_t _ipu_channel_status(ipu_channel_t channel) +{ + uint32_t stat = 0; + uint32_t task_stat_reg = __raw_readl(IPU_TASKS_STAT); + + switch (channel) { + case CSI_MEM: + stat = + (task_stat_reg & TSTAT_CSI2MEM_MASK) >> + TSTAT_CSI2MEM_OFFSET; + break; + case CSI_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_ADC: + case MEM_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_ROT_VF_MEM: + stat = + (task_stat_reg & TSTAT_VF_ROT_MASK) >> TSTAT_VF_ROT_OFFSET; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + stat = (task_stat_reg & TSTAT_ENC_MASK) >> TSTAT_ENC_OFFSET; + break; + case MEM_ROT_ENC_MEM: + stat = + (task_stat_reg & TSTAT_ENC_ROT_MASK) >> + TSTAT_ENC_ROT_OFFSET; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + stat = (task_stat_reg & TSTAT_PP_MASK) >> TSTAT_PP_OFFSET; + break; + case MEM_ROT_PP_MEM: + stat = + (task_stat_reg & TSTAT_PP_ROT_MASK) >> TSTAT_PP_ROT_OFFSET; + break; + + case MEM_PF_Y_MEM: + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + stat = (task_stat_reg & TSTAT_PF_MASK) >> TSTAT_PF_OFFSET; + break; + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + stat = + (task_stat_reg & TSTAT_ADCSYS1_MASK) >> + TSTAT_ADCSYS1_OFFSET; + break; + case ADC_SYS2: + stat = + (task_stat_reg & TSTAT_ADCSYS2_MASK) >> + TSTAT_ADCSYS2_OFFSET; + break; + case MEM_SDC_MASK: + default: + stat = TASK_STAT_IDLE; + break; + } + return stat; +} + +uint32_t bytes_per_pixel(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: /*generic data */ + case IPU_PIX_FMT_RGB332: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV422P: + return 1; + break; + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + return 2; + break; + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 3; + break; + case IPU_PIX_FMT_GENERIC_32: /*generic data */ + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_ABGR32: + return 4; + break; + default: + return 1; + break; + } + return 0; +} + +void ipu_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + /* TODO */ +} +EXPORT_SYMBOL(ipu_set_csc_coefficients); + +ipu_color_space_t format_to_colorspace(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGB32: + return RGB; + break; + + default: + return YCbCr; + break; + } + return RGB; + +} + +static u32 saved_disp3_time_conf; +static bool in_lpdr_mode; +static struct clk *default_ipu_parent_clk; + +int ipu_lowpwr_display_enable(void) +{ + unsigned long rate, div; + struct clk *parent_clk = g_ipu_clk; + + if (in_lpdr_mode || IS_ERR(dfm_clk)) { + return -EINVAL; + } + + if (g_channel_init_mask != (1L << IPU_CHAN_ID(MEM_SDC_BG))) { + dev_err(g_ipu_dev, "LPDR mode requires only SDC BG active.\n"); + return -EINVAL; + } + + default_ipu_parent_clk = clk_get_parent(g_ipu_clk); + in_lpdr_mode = true; + + /* Calculate current pixel clock rate */ + rate = clk_get_rate(g_ipu_clk) * 16; + saved_disp3_time_conf = __raw_readl(DI_DISP3_TIME_CONF); + div = saved_disp3_time_conf & 0xFFF; + rate /= div; + rate *= 4; /* min hsp clk is 4x pixel clk */ + + /* Initialize DFM clock */ + rate = clk_round_rate(dfm_clk, rate); + clk_set_rate(dfm_clk, rate); + clk_enable(dfm_clk); + + /* Wait for next VSYNC */ + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC), + IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)) & + IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC)) == 0) + msleep_interruptible(1); + + /* Set display clock divider to divide by 4 */ + __raw_writel(((0x8) << 22) | 0x40, DI_DISP3_TIME_CONF); + + clk_set_parent(parent_clk, dfm_clk); + + return 0; +} + +int ipu_lowpwr_display_disable(void) +{ + struct clk *parent_clk = g_ipu_clk; + + if (!in_lpdr_mode || IS_ERR(dfm_clk)) { + return -EINVAL; + } + + if (g_channel_init_mask != (1L << IPU_CHAN_ID(MEM_SDC_BG))) { + dev_err(g_ipu_dev, "LPDR mode requires only SDC BG active.\n"); + return -EINVAL; + } + + in_lpdr_mode = false; + + /* Wait for next VSYNC */ + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC), + IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)) & + IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC)) == 0) + msleep_interruptible(1); + + __raw_writel(saved_disp3_time_conf, DI_DISP3_TIME_CONF); + clk_set_parent(parent_clk, default_ipu_parent_clk); + clk_disable(dfm_clk); + + return 0; +} + +static int ipu_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (cpu_is_mx31() || cpu_is_mx32()) { + /* work-around for i.Mx31 SR mode after camera related test */ + if (g_csi_used) { + g_ipu_config = __raw_readl(IPU_CONF); + clk_enable(g_ipu_csi_clk); + __raw_writel(0x51, IPU_CONF); + g_channel_init_mask_backup = g_channel_init_mask; + g_channel_init_mask |= 2; + } + } else if (cpu_is_mx35()) { + g_ipu_config = __raw_readl(IPU_CONF); + /* Disable the clock of display otherwise the backlight cannot + * be close after camera/tvin related test */ + __raw_writel(g_ipu_config & 0xfbf, IPU_CONF); + } + + return 0; +} + +static int ipu_resume(struct platform_device *pdev) +{ + if (cpu_is_mx31() || cpu_is_mx32()) { + /* work-around for i.Mx31 SR mode after camera related test */ + if (g_csi_used) { + __raw_writel(g_ipu_config, IPU_CONF); + clk_disable(g_ipu_csi_clk); + g_channel_init_mask = g_channel_init_mask_backup; + } + } else if (cpu_is_mx35()) { + __raw_writel(g_ipu_config, IPU_CONF); + } + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcipu_driver = { + .driver = { + .name = "mxc_ipu", + }, + .probe = ipu_probe, + .suspend = ipu_suspend, + .resume = ipu_resume, +}; + +int32_t __init ipu_gen_init(void) +{ + int32_t ret; + + ret = platform_driver_register(&mxcipu_driver); + return 0; +} + +subsys_initcall(ipu_gen_init); + +static void __exit ipu_gen_uninit(void) +{ + if (g_ipu_irq[0]) + free_irq(g_ipu_irq[0], 0); + if (g_ipu_irq[1]) + free_irq(g_ipu_irq[1], 0); + + platform_driver_unregister(&mxcipu_driver); +} + +module_exit(ipu_gen_uninit); + +EXPORT_SYMBOL(ipu_init_channel); +EXPORT_SYMBOL(ipu_uninit_channel); +EXPORT_SYMBOL(ipu_init_channel_buffer); +EXPORT_SYMBOL(ipu_unlink_channels); +EXPORT_SYMBOL(ipu_update_channel_buffer); +EXPORT_SYMBOL(ipu_select_buffer); +EXPORT_SYMBOL(ipu_link_channels); +EXPORT_SYMBOL(ipu_enable_channel); +EXPORT_SYMBOL(ipu_disable_channel); +EXPORT_SYMBOL(ipu_enable_csi); +EXPORT_SYMBOL(ipu_disable_csi); +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..ec09c6756bdc --- /dev/null +++ b/drivers/mxc/ipu/ipu_csi.c @@ -0,0 +1,238 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include + +#include "ipu_prv.h" +#include "ipu_regs.h" + +static bool gipu_csi_get_mclk_flag; +static int csi_mclk_flag; + +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) { + __raw_writel(0x40030, CSI_CCIR_CODE_1); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3); + } else if (sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED) { + __raw_writel(0xD07DF, CSI_CCIR_CODE_1); + __raw_writel(0x40596, CSI_CCIR_CODE_2); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3); + } + + dev_dbg(g_ipu_dev, "CSI_SENS_CONF = 0x%08X\n", + __raw_readl(CSI_SENS_CONF)); + dev_dbg(g_ipu_dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", + __raw_readl(CSI_ACT_FRM_SIZE)); + + return 0; +} + +/*! + * ipu_csi_flash_strobe + * + * @param flag true to turn on flash strobe + * + * @return 0 for success + */ +void ipu_csi_flash_strobe(bool flag) +{ + if (flag == true) { + __raw_writel(__raw_readl(CSI_FLASH_STROBE_2) | 0x1, + CSI_FLASH_STROBE_2); + } +} + +/*! + * ipu_csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t ipu_csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + csi_mclk_flag |= src; + } else { + csi_mclk_flag &= ~src; + } + + if (gipu_csi_get_mclk_flag == flag) + return 0; + + if (flag == true) { + clk_enable(g_ipu_csi_clk); + if (wait == true) + msleep(10); + /*printk("enable csi clock from source %d\n", src); */ + gipu_csi_get_mclk_flag = true; + } else if (csi_mclk_flag == 0) { + clk_disable(g_ipu_csi_clk); + /*printk("disable csi clock from source %d\n", src); */ + gipu_csi_get_mclk_flag = flag; + } + + return 0; +} + +/*! + * ipu_csi_read_mclk_flag + * + * @return csi_mclk_flag + */ +int ipu_csi_read_mclk_flag(void) +{ + return csi_mclk_flag; +} + +/*! + * ipu_csi_get_window_size + * + * @param width pointer to window width + * @param height pointer to window height + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_get_window_size(uint32_t *width, uint32_t *height, + uint32_t dummy) +{ + uint32_t reg; + + reg = __raw_readl(CSI_ACT_FRM_SIZE); + *width = (reg & 0xFFFF) + 1; + *height = (reg >> 16 & 0xFFFF) + 1; +} + +/*! + * ipu_csi_set_window_size + * + * @param width window width + * @param height window height + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_set_window_size(uint32_t width, uint32_t height, uint32_t dummy) +{ + __raw_writel((width - 1) | (height - 1) << 16, CSI_ACT_FRM_SIZE); +} + +/*! + * ipu_csi_set_window_pos + * + * @param left uint32 window x start + * @param top uint32 window y start + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_set_window_pos(uint32_t left, uint32_t top, uint32_t dummy) +{ + uint32_t temp = __raw_readl(CSI_OUT_FRM_CTRL); + temp &= 0xffff0000; + temp = top | (left << 8) | temp; + __raw_writel(temp, CSI_OUT_FRM_CTRL); +} + +/*! + * ipu_csi_get_sensor_protocol + * + * @param csi csi 0 or csi 1 + * + * @return Returns sensor protocol + */ +int32_t ipu_csi_get_sensor_protocol(uint32_t csi) +{ + /* TODO */ +} +EXPORT_SYMBOL(ipu_csi_get_sensor_protocol); + +/* 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..bd8d9e35c141 --- /dev/null +++ b/drivers/mxc/ipu/ipu_device.c @@ -0,0 +1,696 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +int read_ptr; +int write_ptr; + +ipu_event_info events[MAX_Q_SIZE]; + +int register_ipu_device(void); + +/* Static functions */ + +int get_events(ipu_event_info *p) +{ + unsigned long flags; + int ret = 0, i, cnt, found = 0; + spin_lock_irqsave(&queue_lock, flags); + if (pending_events != 0) { + if (write_ptr > read_ptr) + cnt = write_ptr - read_ptr; + else + cnt = MAX_Q_SIZE - read_ptr + write_ptr; + for (i = 0; i < cnt; i++) { + if (p->irq == events[read_ptr].irq) { + *p = events[read_ptr]; + events[read_ptr].irq = 0; + read_ptr++; + if (read_ptr >= MAX_Q_SIZE) + read_ptr = 0; + found = 1; + break; + } + + if (events[read_ptr].irq) { + events[write_ptr] = events[read_ptr]; + events[read_ptr].irq = 0; + write_ptr++; + if (write_ptr >= MAX_Q_SIZE) + write_ptr = 0; + } else + pending_events--; + + read_ptr++; + if (read_ptr >= MAX_Q_SIZE) + read_ptr = 0; + } + if (found) + pending_events--; + else + ret = -1; + } else { + ret = -1; + } + + spin_unlock_irqrestore(&queue_lock, flags); + return ret; +} + +static irqreturn_t mxc_ipu_generic_handler(int irq, void *dev_id) +{ + ipu_event_info e; + + e.irq = irq; + e.dev = dev_id; + events[write_ptr] = e; + write_ptr++; + if (write_ptr >= MAX_Q_SIZE) + write_ptr = 0; + pending_events++; + /* Wakeup any blocking user context */ + wake_up_interruptible(&waitq); + return IRQ_HANDLED; +} + +static int mxc_ipu_open(struct inode *inode, struct file *file) +{ + int ret = 0; + return ret; +} +static int mxc_ipu_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + + case IPU_INIT_CHANNEL: + { + ipu_channel_parm parm; + if (copy_from_user + (&parm, (ipu_channel_parm *) arg, + sizeof(ipu_channel_parm))) { + return -EFAULT; + } + if (!parm.flag) { + ret = + ipu_init_channel(parm.channel, + &parm.params); + } else { + ret = ipu_init_channel(parm.channel, NULL); + } + } + break; + + case IPU_UNINIT_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_uninit_channel(ch); + } + break; + + case IPU_INIT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + ret = + ipu_init_channel_buffer(parm.channel, parm.type, + parm.pixel_fmt, + parm.width, parm.height, + parm.stride, + parm.rot_mode, + parm.phyaddr_0, + parm.phyaddr_1, + parm.u_offset, + parm.v_offset); + + } + break; + + case IPU_UPDATE_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + if ((parm.phyaddr_0 != (dma_addr_t) NULL) + && (parm.phyaddr_1 == (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer(parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_0); + } else if ((parm.phyaddr_0 == (dma_addr_t) NULL) + && (parm.phyaddr_1 != (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer(parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_1); + } else { + ret = -1; + } + + } + break; + case IPU_SELECT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + ret = + ipu_select_buffer(parm.channel, parm.type, + parm.bufNum); + + } + break; + case IPU_LINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) { + return -EFAULT; + } + ret = ipu_link_channels(link.src_ch, link.dest_ch); + + } + break; + case IPU_UNLINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) { + return -EFAULT; + } + ret = ipu_unlink_channels(link.src_ch, link.dest_ch); + + } + break; + case IPU_ENABLE_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_enable_channel(ch); + } + break; + case IPU_DISABLE_CHANNEL: + { + ipu_channel_info info; + if (copy_from_user + (&info, (ipu_channel_info *) arg, + sizeof(ipu_channel_info))) { + return -EFAULT; + } + ret = ipu_disable_channel(info.channel, info.stop); + } + break; + case IPU_ENABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_enable_irq(irq); + } + break; + case IPU_DISABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_disable_irq(irq); + } + break; + case IPU_CLEAR_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_clear_irq(irq); + } + break; + case IPU_FREE_IRQ: + { + ipu_irq_info info; + if (copy_from_user + (&info, (ipu_irq_info *) arg, + sizeof(ipu_irq_info))) { + return -EFAULT; + } + ipu_free_irq(info.irq, info.dev_id); + } + break; + case IPU_REQUEST_IRQ_STATUS: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ret = ipu_get_irq_status(irq); + } + break; + case IPU_SDC_INIT_PANEL: + { + ipu_sdc_panel_info sinfo; + if (copy_from_user + (&sinfo, (ipu_sdc_panel_info *) arg, + sizeof(ipu_sdc_panel_info))) { + return -EFAULT; + } + ret = + ipu_sdc_init_panel(sinfo.panel, sinfo.pixel_clk, + sinfo.width, sinfo.height, + sinfo.pixel_fmt, + sinfo.hStartWidth, + sinfo.hSyncWidth, + sinfo.hEndWidth, + sinfo.vStartWidth, + sinfo.vSyncWidth, + sinfo.vEndWidth, sinfo.signal); + } + break; + case IPU_SDC_SET_WIN_POS: + { + ipu_sdc_window_pos pos; + if (copy_from_user + (&pos, (ipu_sdc_window_pos *) arg, + sizeof(ipu_sdc_window_pos))) { + return -EFAULT; + } + ret = + ipu_disp_set_window_pos(pos.channel, pos.x_pos, + pos.y_pos); + + } + break; + case IPU_SDC_SET_GLOBAL_ALPHA: + { + ipu_sdc_global_alpha g; + if (copy_from_user + (&g, (ipu_sdc_global_alpha *) arg, + sizeof(ipu_sdc_global_alpha))) { + return -EFAULT; + } + ret = ipu_sdc_set_global_alpha(g.enable, g.alpha); + } + break; + case IPU_SDC_SET_COLOR_KEY: + { + ipu_sdc_color_key c; + if (copy_from_user + (&c, (ipu_sdc_color_key *) arg, + sizeof(ipu_sdc_color_key))) { + return -EFAULT; + } + ret = + ipu_sdc_set_color_key(c.channel, c.enable, + c.colorKey); + } + break; + case IPU_SDC_SET_BRIGHTNESS: + { + uint8_t b; + int __user *argp = (void __user *)arg; + if (get_user(b, argp)) + return -EFAULT; + ret = ipu_sdc_set_brightness(b); + + } + break; + case IPU_REGISTER_GENERIC_ISR: + { + ipu_event_info info; + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) { + return -EFAULT; + } + ret = + ipu_request_irq(info.irq, mxc_ipu_generic_handler, + 0, "video_sink", info.dev); + } + break; + case IPU_GET_EVENT: + /* User will have to allocate event_type structure and pass the pointer in arg */ + { + ipu_event_info info; + int r = -1; + + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + r = get_events(&info); + if (r == -1) { + wait_event_interruptible_timeout(waitq, + (pending_events != 0), 2 * HZ); + r = get_events(&info); + } + ret = -1; + if (r == 0) { + if (!copy_to_user((ipu_event_info *) arg, + &info, sizeof(ipu_event_info))) + ret = 0; + } + } + break; + case IPU_ADC_WRITE_TEMPLATE: + { + ipu_adc_template temp; + if (copy_from_user + (&temp, (ipu_adc_template *) arg, sizeof(temp))) { + return -EFAULT; + } + ret = + ipu_adc_write_template(temp.disp, temp.pCmd, + temp.write); + } + break; + case IPU_ADC_UPDATE: + { + ipu_adc_update update; + if (copy_from_user + (&update, (ipu_adc_update *) arg, sizeof(update))) { + return -EFAULT; + } + ret = + ipu_adc_set_update_mode(update.channel, update.mode, + update.refresh_rate, + update.addr, update.size); + } + break; + case IPU_ADC_SNOOP: + { + ipu_adc_snoop snoop; + if (copy_from_user + (&snoop, (ipu_adc_snoop *) arg, sizeof(snoop))) { + return -EFAULT; + } + ret = + ipu_adc_get_snooping_status(snoop.statl, + snoop.stath); + } + break; + case IPU_ADC_CMD: + { + ipu_adc_cmd cmd; + if (copy_from_user + (&cmd, (ipu_adc_cmd *) arg, sizeof(cmd))) { + return -EFAULT; + } + ret = + ipu_adc_write_cmd(cmd.disp, cmd.type, cmd.cmd, + cmd.params, cmd.numParams); + } + break; + case IPU_ADC_INIT_PANEL: + { + ipu_adc_panel panel; + if (copy_from_user + (&panel, (ipu_adc_panel *) arg, sizeof(panel))) { + return -EFAULT; + } + ret = + ipu_adc_init_panel(panel.disp, panel.width, + panel.height, panel.pixel_fmt, + panel.stride, panel.signal, + panel.addr, panel.vsync_width, + panel.mode); + } + break; + case IPU_ADC_IFC_TIMING: + { + ipu_adc_ifc_timing t; + if (copy_from_user + (&t, (ipu_adc_ifc_timing *) arg, sizeof(t))) { + return -EFAULT; + } + ret = + ipu_adc_init_ifc_timing(t.disp, t.read, + t.cycle_time, t.up_time, + t.down_time, + t.read_latch_time, + t.pixel_clk); + } + break; + case IPU_CSI_INIT_INTERFACE: + { + ipu_csi_interface c; + if (copy_from_user + (&c, (ipu_csi_interface *) arg, sizeof(c))) + return -EFAULT; + ret = + ipu_csi_init_interface(c.width, c.height, + c.pixel_fmt, c.signal); + } + break; + case IPU_CSI_ENABLE_MCLK: + { + ipu_csi_mclk m; + if (copy_from_user(&m, (ipu_csi_mclk *) arg, sizeof(m))) + return -EFAULT; + ret = ipu_csi_enable_mclk(m.src, m.flag, m.wait); + } + break; + case IPU_CSI_READ_MCLK_FLAG: + { + ret = ipu_csi_read_mclk_flag(); + } + break; + case IPU_CSI_FLASH_STROBE: + { + bool strobe; + int __user *argp = (void __user *)arg; + if (get_user(strobe, argp)) + return -EFAULT; + ipu_csi_flash_strobe(strobe); + } + break; + case IPU_CSI_GET_WIN_SIZE: + { + ipu_csi_window_size w; + int dummy = 0; + ipu_csi_get_window_size(&w.width, &w.height, dummy); + if (copy_to_user + ((ipu_csi_window_size *) arg, &w, sizeof(w))) + return -EFAULT; + } + break; + case IPU_CSI_SET_WIN_SIZE: + { + ipu_csi_window_size w; + int dummy = 0; + if (copy_from_user + (&w, (ipu_csi_window_size *) arg, sizeof(w))) + return -EFAULT; + ipu_csi_set_window_size(w.width, w.height, dummy); + } + break; + case IPU_CSI_SET_WINDOW: + { + ipu_csi_window p; + int dummy = 0; + if (copy_from_user + (&p, (ipu_csi_window *) arg, sizeof(p))) + return -EFAULT; + ipu_csi_set_window_pos(p.left, p.top, dummy); + } + break; + case IPU_PF_SET_PAUSE_ROW: + { + uint32_t p; + int __user *argp = (void __user *)arg; + if (get_user(p, argp)) + return -EFAULT; + ret = ipu_pf_set_pause_row(p); + } + break; + case IPU_ALOC_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + info.vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(info.size), + &info.paddr, + GFP_DMA | GFP_KERNEL); + if (info.vaddr == 0) { + printk(KERN_ERR "dma alloc failed!\n"); + return -ENOBUFS; + } + if (copy_to_user((ipu_mem_info *) arg, &info, + sizeof(ipu_mem_info)) > 0) + return -EFAULT; + } + break; + case IPU_FREE_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + if (info.vaddr != 0) + dma_free_coherent(0, PAGE_ALIGN(info.size), + info.vaddr, info.paddr); + else + return -EFAULT; + } + break; + case IPU_IS_CHAN_BUSY: + { + ipu_channel_t chan; + if (copy_from_user + (&chan, (ipu_channel_t *)arg, + sizeof(ipu_channel_t))) + return -EFAULT; + + if (ipu_is_channel_busy(chan)) + ret = 1; + else + ret = 0; + } + break; + default: + break; + + } + return ret; +} + +static int mxc_ipu_mmap(struct file *file, struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_writethru(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + printk(KERN_ERR + "mmap failed!\n"); + return -ENOBUFS; + } + return 0; +} + +static int mxc_ipu_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations mxc_ipu_fops = { + .owner = THIS_MODULE, + .open = mxc_ipu_open, + .mmap = mxc_ipu_mmap, + .release = mxc_ipu_release, + .ioctl = mxc_ipu_ioctl +}; + +int register_ipu_device() +{ + int ret = 0; + struct device *temp; + mxc_ipu_major = register_chrdev(0, "mxc_ipu", &mxc_ipu_fops); + if (mxc_ipu_major < 0) { + printk(KERN_ERR + "Unable to register Mxc Ipu as a char device\n"); + return mxc_ipu_major; + } + + mxc_ipu_class = class_create(THIS_MODULE, "mxc_ipu"); + if (IS_ERR(mxc_ipu_class)) { + printk(KERN_ERR "Unable to create class for Mxc Ipu\n"); + ret = PTR_ERR(mxc_ipu_class); + goto err1; + } + + temp = device_create(mxc_ipu_class, NULL, MKDEV(mxc_ipu_major, 0), NULL, + "mxc_ipu"); + + if (IS_ERR(temp)) { + printk(KERN_ERR "Unable to create class device for Mxc Ipu\n"); + ret = PTR_ERR(temp); + goto err2; + } + spin_lock_init(&queue_lock); + init_waitqueue_head(&waitq); + return ret; + + err2: + class_destroy(mxc_ipu_class); + err1: + unregister_chrdev(mxc_ipu_major, "mxc_ipu"); + return ret; + +} diff --git a/drivers/mxc/ipu/ipu_ic.c b/drivers/mxc/ipu/ipu_ic.c new file mode 100644 index 000000000000..a00521d60415 --- /dev/null +++ b/drivers/mxc/ipu/ipu_ic.c @@ -0,0 +1,590 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum { + IC_TASK_VIEWFINDER, + IC_TASK_ENCODER, + IC_TASK_POST_PROCESSOR +}; + +extern int g_ipu_hw_rev; +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format); +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t *resizeCoeff, + uint32_t *downsizeCoeff); + +void _ipu_ic_enable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf |= IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + ic_conf |= IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf |= IC_CONF_PP_ROT_EN; + break; + case CSI_MEM: + ic_conf |= IC_CONF_RWS_EN | IC_CONF_PRPENC_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_disable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + ic_conf &= ~IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf &= ~IC_CONF_PP_ROT_EN; + break; + case CSI_MEM: + ic_conf &= ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN); + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_init_prpvf(ipu_channel_params_t *params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_height, + params->mem_prp_vf_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_width, + params->mem_prp_vf_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_VF_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->YCBCR CSC */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PRPVF_CMB; + + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->RGB CSC */ + + if (params->mem_prp_vf_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + } else { + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + } + + if (params->mem_prp_vf_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + +#ifndef CONFIG_VIRTIO_SUPPORT /* Setting RWS_EN doesn't work in Virtio */ + if (src_is_csi) { + ic_conf &= ~IC_CONF_RWS_EN; + } else { + ic_conf |= IC_CONF_RWS_EN; + } +#endif + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpvf(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPVF_EN | IC_CONF_PRPVF_CMB | + IC_CONF_PRPVF_CSC2 | IC_CONF_PRPVF_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_vf(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_vf(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PRPVF_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_csi(ipu_channel_params_t *params) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_CSI_MEM_WR_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_uninit_csi(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_prpenc(ipu_channel_params_t *params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_height, + params->mem_prp_enc_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_width, + params->mem_prp_enc_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_ENC_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* TODO: ERROR! */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_ENCODER, YCbCr, RGB); + ic_conf |= IC_CONF_PRPENC_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (src_is_csi) { + ic_conf &= ~IC_CONF_RWS_EN; + } else { + ic_conf |= IC_CONF_RWS_EN; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpenc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_enc(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_enc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_ROT_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_pp(ipu_channel_params_t *params) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_height, + params->mem_pp_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_width, + params->mem_pp_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PP_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_pp_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt); + ic_conf |= IC_CONF_PP_CSC2; /* Enable RGB->YCBCR CSC */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB); + ic_conf |= IC_CONF_PP_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PP_CMB; + + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB); + ic_conf |= IC_CONF_PP_CSC1; /* Enable RGB->RGB CSC */ + + if (params->mem_pp_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + } else { + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + } + + if (params->mem_pp_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_pp(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PP_EN | IC_CONF_PP_CSC1 | IC_CONF_PP_CSC2 | + IC_CONF_PP_CMB); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_pp(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_pp(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PP_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format) +{ +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ + static const uint32_t rgb2ycbcr_coeff[4][3] = { + {0x004D, 0x0096, 0x001D}, + {0x01D5, 0x01AB, 0x0080}, + {0x0080, 0x0195, 0x01EB}, + {0x0000, 0x0200, 0x0200}, /* A0, A1, A2 */ + }; + + /* transparent RGB->RGB matrix for combining + */ + static const uint32_t rgb2rgb_coeff[4][3] = { + {0x0080, 0x0000, 0x0000}, + {0x0000, 0x0080, 0x0000}, + {0x0000, 0x0000, 0x0080}, + {0x0000, 0x0000, 0x0000}, /* A0, A1, A2 */ + }; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ + static const uint32_t ycbcr2rgb_coeff[4][3] = { + {149, 0, 204}, + {149, 462, 408}, + {149, 255, 0}, + {8192 - 446, 266, 8192 - 554}, /* A0, A1, A2 */ + }; + + uint32_t param[2]; + uint32_t address = 0; + + if (g_ipu_hw_rev > 1) { + if (ic_task == IC_TASK_VIEWFINDER) { + address = 0x645 << 3; + } else if (ic_task == IC_TASK_ENCODER) { + address = 0x321 << 3; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + address = 0x96C << 3; + } else { + BUG(); + } + } else { + if (ic_task == IC_TASK_VIEWFINDER) { + address = 0x5a5 << 3; + } else if (ic_task == IC_TASK_ENCODER) { + address = 0x2d1 << 3; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + address = 0x87c << 3; + } else { + BUG(); + } + } + + if ((in_format == YCbCr) && (out_format == RGB)) { + /* Init CSC1 (YCbCr->RGB) */ + param[0] = + (ycbcr2rgb_coeff[3][0] << 27) | (ycbcr2rgb_coeff[0][0] << + 18) | + (ycbcr2rgb_coeff[1][1] << 9) | ycbcr2rgb_coeff[2][2]; + /* scale = 2, sat = 0 */ + param[1] = (ycbcr2rgb_coeff[3][0] >> 5) | (2L << (40 - 32)); + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (ycbcr2rgb_coeff[3][1] << 27) | (ycbcr2rgb_coeff[0][1] << + 18) | + (ycbcr2rgb_coeff[1][0] << 9) | ycbcr2rgb_coeff[2][0]; + param[1] = (ycbcr2rgb_coeff[3][1] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (ycbcr2rgb_coeff[3][2] << 27) | (ycbcr2rgb_coeff[0][2] << + 18) | + (ycbcr2rgb_coeff[1][2] << 9) | ycbcr2rgb_coeff[2][1]; + param[1] = (ycbcr2rgb_coeff[3][2] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else if ((in_format == RGB) && (out_format == YCbCr)) { + /* Init CSC1 (RGB->YCbCr) */ + param[0] = + (rgb2ycbcr_coeff[3][0] << 27) | (rgb2ycbcr_coeff[0][0] << + 18) | + (rgb2ycbcr_coeff[1][1] << 9) | rgb2ycbcr_coeff[2][2]; + /* scale = 1, sat = 0 */ + param[1] = (rgb2ycbcr_coeff[3][0] >> 5) | (1UL << 8); + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2ycbcr_coeff[3][1] << 27) | (rgb2ycbcr_coeff[0][1] << + 18) | + (rgb2ycbcr_coeff[1][0] << 9) | rgb2ycbcr_coeff[2][0]; + param[1] = (rgb2ycbcr_coeff[3][1] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2ycbcr_coeff[3][2] << 27) | (rgb2ycbcr_coeff[0][2] << + 18) | + (rgb2ycbcr_coeff[1][2] << 9) | rgb2ycbcr_coeff[2][1]; + param[1] = (rgb2ycbcr_coeff[3][2] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else if ((in_format == RGB) && (out_format == RGB)) { + /* Init CSC1 */ + param[0] = + (rgb2rgb_coeff[3][0] << 27) | (rgb2rgb_coeff[0][0] << 18) | + (rgb2rgb_coeff[1][1] << 9) | rgb2rgb_coeff[2][2]; + /* scale = 2, sat = 0 */ + param[1] = (rgb2rgb_coeff[3][0] >> 5) | (2UL << 8); + + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2rgb_coeff[3][1] << 27) | (rgb2rgb_coeff[0][1] << 18) | + (rgb2rgb_coeff[1][0] << 9) | rgb2rgb_coeff[2][0]; + param[1] = (rgb2rgb_coeff[3][1] >> 5); + + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2rgb_coeff[3][2] << 27) | (rgb2rgb_coeff[0][2] << 18) | + (rgb2rgb_coeff[1][2] << 9) | rgb2rgb_coeff[2][1]; + param[1] = (rgb2rgb_coeff[3][2] >> 5); + + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else { + dev_err(g_ipu_dev, "Unsupported color space conversion\n"); + } +} + +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t *resizeCoeff, + uint32_t *downsizeCoeff) +{ + uint32_t tempSize; + uint32_t tempDownsize; + + /* Cannot downsize more than 8:1 */ + if ((outSize << 3) < inSize) { + return false; + } + /* compute downsizing coefficient */ + tempDownsize = 0; + tempSize = inSize; + while ((tempSize >= outSize * 2) && (tempDownsize < 2)) { + tempSize >>= 1; + tempDownsize++; + } + *downsizeCoeff = tempDownsize; + + /* compute resizing coefficient using the following equation: + resizeCoeff = M*(SI -1)/(SO - 1) + where M = 2^13, SI - input size, SO - output size */ + *resizeCoeff = (8192L * (tempSize - 1)) / (outSize - 1); + if (*resizeCoeff >= 16384L) { + dev_err(g_ipu_dev, "Warning! Overflow on resize coeff.\n"); + *resizeCoeff = 0x3FFF; + } + + dev_dbg(g_ipu_dev, "resizing from %u -> %u pixels, " + "downsize=%u, resize=%u.%lu (reg=%u)\n", inSize, outSize, + *downsizeCoeff, (*resizeCoeff >= 8192L) ? 1 : 0, + ((*resizeCoeff & 0x1FFF) * 10000L) / 8192L, *resizeCoeff); + + return true; +} diff --git a/drivers/mxc/ipu/ipu_param_mem.h b/drivers/mxc/ipu/ipu_param_mem.h new file mode 100644 index 000000000000..9d6e287643a9 --- /dev/null +++ b/drivers/mxc/ipu/ipu_param_mem.h @@ -0,0 +1,176 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 + +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..d65e3be6834b --- /dev/null +++ b/drivers/mxc/ipu/ipu_prv.h @@ -0,0 +1,59 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include + +/* 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..dce8ae3e1669 --- /dev/null +++ b/drivers/mxc/ipu/ipu_regs.h @@ -0,0 +1,396 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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..3a8c4110cb98 --- /dev/null +++ b/drivers/mxc/ipu/ipu_sdc.c @@ -0,0 +1,357 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +static uint32_t g_h_start_width; +static uint32_t g_v_start_width; + +static const uint32_t di_mappings[] = { + 0x1600AAAA, 0x00E05555, 0x00070000, 3, /* RGB888 */ + 0x0005000F, 0x000B000F, 0x0011000F, 1, /* RGB666 */ + 0x0011000F, 0x000B000F, 0x0005000F, 1, /* BGR666 */ + 0x0004003F, 0x000A000F, 0x000F003F, 1 /* RGB565 */ +}; + +/*! + * This function is called to initialize a synchronous LCD panel. + * + * @param panel The type of panel. + * + * @param pixel_clk Desired pixel clock frequency in Hz. + * + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel + * format is a FOURCC ASCII code. + * + * @param width The width of panel in pixels. + * + * @param height The height of panel in pixels. + * + * @param hStartWidth The number of pixel clocks between the HSYNC + * signal pulse and the start of valid data. + * + * @param hSyncWidth The width of the HSYNC signal in units of pixel + * clocks. + * + * @param hEndWidth The number of pixel clocks between the end of + * valid data and the HSYNC signal for next line. + * + * @param vStartWidth The number of lines between the VSYNC + * signal pulse and the start of valid data. + * + * @param vSyncWidth The width of the VSYNC signal in units of lines + * + * @param vEndWidth The number of lines between the end of valid + * data and the VSYNC signal for next frame. + * + * @param sig Bitfield of signal polarities for LCD interface. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_sdc_init_panel(ipu_panel_t panel, + uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t v_sync_width, uint16_t v_end_width, + ipu_di_signal_cfg_t sig) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t old_conf; + uint32_t div; + + dev_dbg(g_ipu_dev, "panel size = %d x %d\n", width, height); + + if ((v_sync_width == 0) || (h_sync_width == 0)) + return EINVAL; + + /* Init panel size and blanking periods */ + reg = + ((uint32_t) (h_sync_width - 1) << 26) | + ((uint32_t) (width + h_sync_width + h_start_width + h_end_width - 1) + << 16); + __raw_writel(reg, SDC_HOR_CONF); + + reg = ((uint32_t) (v_sync_width - 1) << 26) | SDC_V_SYNC_WIDTH_L | + ((uint32_t) + (height + v_sync_width + v_start_width + v_end_width - 1) << 16); + __raw_writel(reg, SDC_VER_CONF); + + g_h_start_width = h_start_width + h_sync_width; + g_v_start_width = v_start_width + v_sync_width; + + switch (panel) { + case IPU_PANEL_SHARP_TFT: + __raw_writel(0x00FD0102L, SDC_SHARP_CONF_1); + __raw_writel(0x00F500F4L, SDC_SHARP_CONF_2); + __raw_writel(SDC_COM_SHARP | SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + case IPU_PANEL_TFT: + __raw_writel(SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + default: + return EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Init clocking */ + + /* Calculate divider */ + /* fractional part is 4 bits so simply multiple by 2^4 to get fractional part */ + dev_dbg(g_ipu_dev, "pixel clk = %d\n", pixel_clk); + div = (clk_get_rate(g_ipu_clk) * 16) / pixel_clk; + if (div < 0x40) { /* Divider less than 4 */ + dev_dbg(g_ipu_dev, + "InitPanel() - Pixel clock divider less than 1\n"); + div = 0x40; + } + /* DISP3_IF_CLK_DOWN_WR is half the divider value and 2 less fraction bits */ + /* Subtract 1 extra from DISP3_IF_CLK_DOWN_WR based on timing debug */ + /* DISP3_IF_CLK_UP_WR is 0 */ + __raw_writel((((div / 8) - 1) << 22) | div, DI_DISP3_TIME_CONF); + + /* DI settings */ + old_conf = __raw_readl(DI_DISP_IF_CONF) & 0x78FFFFFF; + old_conf |= sig.datamask_en << DI_D3_DATAMSK_SHIFT | + sig.clksel_en << DI_D3_CLK_SEL_SHIFT | + sig.clkidle_en << DI_D3_CLK_IDLE_SHIFT; + __raw_writel(old_conf, DI_DISP_IF_CONF); + + old_conf = __raw_readl(DI_DISP_SIG_POL) & 0xE0FFFFFF; + old_conf |= sig.data_pol << DI_D3_DATA_POL_SHIFT | + sig.clk_pol << DI_D3_CLK_POL_SHIFT | + sig.enable_pol << DI_D3_DRDY_SHARP_POL_SHIFT | + sig.Hsync_pol << DI_D3_HSYNC_POL_SHIFT | + sig.Vsync_pol << DI_D3_VSYNC_POL_SHIFT; + __raw_writel(old_conf, DI_DISP_SIG_POL); + + switch (pixel_fmt) { + case IPU_PIX_FMT_RGB24: + __raw_writel(di_mappings[0], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[1], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[2], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[3] - 1) << 12), DI_DISP_ACC_CC); + break; + case IPU_PIX_FMT_RGB666: + __raw_writel(di_mappings[4], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[5], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[6], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[7] - 1) << 12), DI_DISP_ACC_CC); + break; + case IPU_PIX_FMT_BGR666: + __raw_writel(di_mappings[8], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[9], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[10], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[11] - 1) << 12), DI_DISP_ACC_CC); + break; + default: + __raw_writel(di_mappings[12], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[13], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[14], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[15] - 1) << 12), DI_DISP_ACC_CC); + break; + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + dev_dbg(g_ipu_dev, "DI_DISP_IF_CONF = 0x%08X\n", + __raw_readl(DI_DISP_IF_CONF)); + dev_dbg(g_ipu_dev, "DI_DISP_SIG_POL = 0x%08X\n", + __raw_readl(DI_DISP_SIG_POL)); + dev_dbg(g_ipu_dev, "DI_DISP3_TIME_CONF = 0x%08X\n", + __raw_readl(DI_DISP3_TIME_CONF)); + + return 0; +} + +/*! + * This function sets the foreground and background plane global alpha blending + * modes. + * + * @param enable Boolean to enable or disable global alpha + * blending. If disabled, per pixel blending is used. + * + * @param alpha Global alpha value. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_global_alpha(bool enable, uint8_t alpha) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (enable) { + reg = __raw_readl(SDC_GW_CTRL) & 0x00FFFFFFL; + __raw_writel(reg | ((uint32_t) alpha << 24), SDC_GW_CTRL); + + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_GLB_A, SDC_COM_CONF); + } else { + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_GLB_A, SDC_COM_CONF); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function sets the transparent color key for SDC graphic plane. + * + * @param channel Input parameter for the logical channel ID. + * + * @param enable Boolean to enable or disable color key + * + * @param colorKey 24-bit RGB color to use as transparent color key. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_color_key(ipu_channel_t channel, bool enable, + uint32_t color_key) +{ + uint32_t reg, sdc_conf; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + sdc_conf = __raw_readl(SDC_COM_CONF); + if (channel == MEM_SDC_BG) { + sdc_conf &= ~SDC_COM_GWSEL; + } else { + sdc_conf |= SDC_COM_GWSEL; + } + + if (enable) { + reg = __raw_readl(SDC_GW_CTRL) & 0xFF000000L; + __raw_writel(reg | (color_key & 0x00FFFFFFL), SDC_GW_CTRL); + + sdc_conf |= SDC_COM_KEY_COLOR_G; + } else { + sdc_conf &= ~SDC_COM_KEY_COLOR_G; + } + __raw_writel(sdc_conf, SDC_COM_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_sdc_set_brightness(uint8_t value) +{ + __raw_writel(0x03000000UL | value << 16, SDC_PWM_CTRL); + return 0; +} + +/*! + * This function sets the window position of the foreground or background plane. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param x_pos The X coordinate position to place window at. + * The position is relative to the top left corner. + * + * @param y_pos The Y coordinate position to place window at. + * The position is relative to the top left corner. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_window_pos(ipu_channel_t channel, int16_t x_pos, + int16_t y_pos) +{ + x_pos += g_h_start_width; + y_pos += g_v_start_width; + + if (channel == MEM_SDC_BG) { + __raw_writel((x_pos << 16) | y_pos, SDC_BG_POS); + } else if (channel == MEM_SDC_FG) { + __raw_writel((x_pos << 16) | y_pos, SDC_FG_POS); + } else { + return EINVAL; + } + return 0; +} + +void _ipu_sdc_fg_init(ipu_channel_params_t *params) +{ + uint32_t reg; + (void)params; + + /* Enable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_FG_EN | SDC_COM_BG_EN, SDC_COM_CONF); +} + +uint32_t _ipu_sdc_fg_uninit(void) +{ + uint32_t reg; + + /* Disable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_FG_EN, SDC_COM_CONF); + + return reg & SDC_COM_FG_EN; +} + +void _ipu_sdc_bg_init(ipu_channel_params_t *params) +{ + uint32_t reg; + (void)params; + + /* Enable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_BG_EN, SDC_COM_CONF); +} + +uint32_t _ipu_sdc_bg_uninit(void) +{ + uint32_t reg; + + /* Disable BG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_BG_EN, SDC_COM_CONF); + + return reg & SDC_COM_BG_EN; +} + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(ipu_sdc_init_panel); +EXPORT_SYMBOL(ipu_sdc_set_global_alpha); +EXPORT_SYMBOL(ipu_sdc_set_color_key); +EXPORT_SYMBOL(ipu_sdc_set_brightness); +EXPORT_SYMBOL(ipu_disp_set_window_pos); diff --git a/drivers/mxc/ipu/pf/Kconfig b/drivers/mxc/ipu/pf/Kconfig new file mode 100644 index 000000000000..fa5a777cf727 --- /dev/null +++ b/drivers/mxc/ipu/pf/Kconfig @@ -0,0 +1,7 @@ +config MXC_IPU_PF + tristate "MXC MPEG4/H.264 Post Filter Driver" + depends on MXC_IPU_V1 + default y + help + Driver for MPEG4 dering and deblock and H.264 deblock + using MXC IPU h/w diff --git a/drivers/mxc/ipu/pf/Makefile b/drivers/mxc/ipu/pf/Makefile new file mode 100644 index 000000000000..641adf4be4bd --- /dev/null +++ b/drivers/mxc/ipu/pf/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXC_IPU_PF) += mxc_pf.o diff --git a/drivers/mxc/ipu/pf/mxc_pf.c b/drivers/mxc/ipu/pf/mxc_pf.c new file mode 100644 index 000000000000..dca52c7172ac --- /dev/null +++ b/drivers/mxc/ipu/pf/mxc_pf.c @@ -0,0 +1,991 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mxc_pf_data { + pf_operation_t mode; + u32 pf_enabled; + u32 width; + u32 height; + u32 stride; + uint32_t qp_size; + dma_addr_t qp_paddr; + void *qp_vaddr; + pf_buf buf[PF_MAX_BUFFER_CNT]; + void *buf_vaddr[PF_MAX_BUFFER_CNT]; + wait_queue_head_t pf_wait; + volatile int done_mask; + volatile int wait_mask; + volatile int busy_flag; + struct semaphore busy_lock; +}; + +static struct mxc_pf_data pf_data; +static u8 open_count; +static struct class *mxc_pf_class; + +/* + * Function definitions + */ + +static irqreturn_t mxc_pf_irq_handler(int irq, void *dev_id) +{ + struct mxc_pf_data *pf = dev_id; + + if (irq == IPU_IRQ_PF_Y_OUT_EOF) { + pf->done_mask |= PF_WAIT_Y; + } else if (irq == IPU_IRQ_PF_U_OUT_EOF) { + pf->done_mask |= PF_WAIT_U; + } else if (irq == IPU_IRQ_PF_V_OUT_EOF) { + pf->done_mask |= PF_WAIT_V; + } else { + return IRQ_NONE; + } + + if (pf->wait_mask && ((pf->done_mask & pf->wait_mask) == pf->wait_mask)) { + wake_up_interruptible(&pf->pf_wait); + } + return IRQ_HANDLED; +} + +/*! + * This function handles PF_IOCTL_INIT calls. It initializes the PF channels, + * interrupt handlers, and channel buffers. + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_init(pf_init_params *pf_init) +{ + int err; + ipu_channel_params_t params; + u32 w; + u32 stride; + u32 h; + u32 qp_size = 0; + u32 qp_stride; + + if ((pf_init->pf_mode > 4) || (pf_init->width > 1024) || + (pf_init->height > 1024) || (pf_init->stride < pf_init->width)) { + return -EINVAL; + } + + pf_data.mode = pf_init->pf_mode; + w = pf_data.width = pf_init->width; + h = pf_data.height = pf_init->height; + stride = pf_data.stride = pf_init->stride; + pf_data.qp_size = pf_init->qp_size; + + memset(¶ms, 0, sizeof(params)); + params.mem_pf_mem.operation = pf_data.mode; + err = ipu_init_channel(MEM_PF_Y_MEM, ¶ms); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing channel\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing Y input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing Y output buffer\n"); + goto err0; + } + + w = w / 2; + h = h / 2; + stride = stride / 2; + + if (pf_data.mode != PF_MPEG4_DERING) { + err = ipu_init_channel_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing U input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing U output buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing V input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing V output buffer\n"); + goto err0; + } + } + + /*Setup Channel QF and BSC Params */ + if (pf_data.mode == PF_H264_DEBLOCK) { + w = ((pf_data.width + 15) / 16); + h = (pf_data.height + 15) / 16; + qp_stride = w; + qp_size = 4 * qp_stride * h; + pr_debug("H264 QP width = %d, height = %d\n", w, h); + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC_32, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing H264 QP buffer\n"); + goto err0; + } +/* w = (pf_data.width + 3) / 4; */ + w *= 4; + h *= 4; + qp_stride = w; + err = ipu_init_channel_buffer(MEM_PF_U_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing H264 BSB buffer\n"); + goto err0; + } + qp_size += qp_stride * h; + } else { /* MPEG4 mode */ + + w = (pf_data.width + 15) / 16; + h = (pf_data.height + 15) / 16; + qp_stride = (w + 3) & ~0x3UL; + pr_debug("MPEG4 QP width = %d, height = %d, stride = %d\n", + w, h, qp_stride); + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing MPEG4 QP buffer\n"); + goto err0; + } + qp_size = qp_stride * h; + } + + /* Support 2 QP buffers */ + qp_size *= 2; + + if (pf_data.qp_size > qp_size) + qp_size = pf_data.qp_size; + else + pf_data.qp_size = qp_size; + + pf_data.qp_vaddr = dma_alloc_coherent(NULL, pf_data.qp_size, + &pf_data.qp_paddr, + GFP_KERNEL | GFP_DMA); + if (!pf_data.qp_vaddr) + return -ENOMEM; + + pf_init->qp_paddr = pf_data.qp_paddr; + pf_init->qp_size = pf_data.qp_size; + + return 0; + + err0: + return err; +} + +/*! + * This function handles PF_IOCTL_UNINIT calls. It uninitializes the PF + * channels and interrupt handlers. + * + * @return This function returns 0 on success or negative error code + * on error. + */ +static int mxc_pf_uninit(void) +{ + pf_data.pf_enabled = 0; + ipu_disable_irq(IPU_IRQ_PF_Y_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_V_OUT_EOF); + + ipu_disable_channel(MEM_PF_Y_MEM, true); + ipu_disable_channel(MEM_PF_U_MEM, true); + ipu_disable_channel(MEM_PF_V_MEM, true); + ipu_uninit_channel(MEM_PF_Y_MEM); + ipu_uninit_channel(MEM_PF_U_MEM); + ipu_uninit_channel(MEM_PF_V_MEM); + + if (pf_data.qp_vaddr) { + dma_free_coherent(NULL, pf_data.qp_size, pf_data.qp_vaddr, + pf_data.qp_paddr); + pf_data.qp_vaddr = NULL; + } + + return 0; +} + +/*! + * This function handles PF_IOCTL_REQBUFS calls. It initializes the PF channels + * and channel buffers. + * + * @param reqbufs Input/Output Structure containing buffer mode, + * type, offset, and size. The offset and size of + * the buffer are returned for PF_MEMORY_MMAP mode. + * + * @return This function returns 0 on success or negative error code + * on error. + */ +static int mxc_pf_reqbufs(pf_reqbufs_params *reqbufs) +{ + int err; + uint32_t buf_size; + int i; + int alloc_cnt = 0; + pf_buf *buf = pf_data.buf; + if (reqbufs->count > PF_MAX_BUFFER_CNT) { + reqbufs->count = PF_MAX_BUFFER_CNT; + } + /* Deallocate mmapped buffers */ + if (reqbufs->count == 0) { + for (i = 0; i < PF_MAX_BUFFER_CNT; i++) { + if (buf[i].index != -1) { + dma_free_coherent(NULL, buf[i].size, + pf_data.buf_vaddr[i], + buf[i].offset); + pf_data.buf_vaddr[i] = NULL; + buf[i].index = -1; + buf[i].size = 0; + } + } + return 0; + } + + buf_size = (pf_data.stride * pf_data.height * 3) / 2; + if (reqbufs->req_size > buf_size) { + buf_size = reqbufs->req_size; + pr_debug("using requested buffer size of %d\n", buf_size); + } else { + reqbufs->req_size = buf_size; + pr_debug("using default buffer size of %d\n", buf_size); + } + + for (i = 0; alloc_cnt < reqbufs->count; i++) { + buf[i].index = i; + buf[i].size = buf_size; + pf_data.buf_vaddr[i] = dma_alloc_coherent(NULL, buf[i].size, + &buf[i].offset, + GFP_KERNEL | GFP_DMA); + if (!pf_data.buf_vaddr[i] || !buf[i].offset) { + printk(KERN_ERR + "mxc_pf: unable to allocate IPU buffers.\n"); + err = -ENOMEM; + goto err0; + } + pr_debug("Allocated buffer %d at paddr 0x%08X, vaddr %p\n", + i, buf[i].offset, pf_data.buf_vaddr[i]); + + alloc_cnt++; + } + + return 0; + err0: + for (i = 0; i < alloc_cnt; i++) { + dma_free_coherent(NULL, buf[i].size, pf_data.buf_vaddr[i], + buf[i].offset); + pf_data.buf_vaddr[i] = NULL; + buf[i].index = -1; + buf[i].size = 0; + } + return err; +} + +/*! + * This function handles PF_IOCTL_START calls. It sets the PF channel buffers + * addresses and starts the channels + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_start(pf_buf *in, pf_buf *out, int qp_buf) +{ + int err; + dma_addr_t y_in_paddr; + dma_addr_t u_in_paddr; + dma_addr_t v_in_paddr; + dma_addr_t p1_in_paddr; + dma_addr_t p2_in_paddr; + dma_addr_t y_out_paddr; + dma_addr_t u_out_paddr; + dma_addr_t v_out_paddr; + + /* H.264 requires output buffer equal to input */ + if (pf_data.mode == PF_H264_DEBLOCK) + out = in; + + y_in_paddr = in->offset + in->y_offset; + if (in->u_offset) + u_in_paddr = in->offset + in->u_offset; + else + u_in_paddr = y_in_paddr + (pf_data.stride * pf_data.height); + if (in->v_offset) + v_in_paddr = in->offset + in->v_offset; + else + v_in_paddr = u_in_paddr + (pf_data.stride * pf_data.height) / 4; + p1_in_paddr = pf_data.qp_paddr; + if (qp_buf) + p1_in_paddr += pf_data.qp_size / 2; + + if (pf_data.mode == PF_H264_DEBLOCK) { + p2_in_paddr = p1_in_paddr + + ((pf_data.width + 15) / 16) * + ((pf_data.height + 15) / 16) * 4; + } else { + p2_in_paddr = 0; + } + + pr_debug("y_in_paddr = 0x%08X\nu_in_paddr = 0x%08X\n" + "v_in_paddr = 0x%08X\n" + "qp_paddr = 0x%08X\nbsb_paddr = 0x%08X\n", + y_in_paddr, u_in_paddr, v_in_paddr, p1_in_paddr, p2_in_paddr); + + y_out_paddr = out->offset + out->y_offset; + if (out->u_offset) + u_out_paddr = out->offset + out->u_offset; + else + u_out_paddr = y_out_paddr + (pf_data.stride * pf_data.height); + if (out->v_offset) + v_out_paddr = out->offset + out->v_offset; + else + v_out_paddr = + u_out_paddr + (pf_data.stride * pf_data.height) / 4; + + pr_debug("y_out_paddr = 0x%08X\nu_out_paddr = 0x%08X\n" + "v_out_paddr = 0x%08X\n", + y_out_paddr, u_out_paddr, v_out_paddr); + + pf_data.done_mask = 0; + + ipu_enable_irq(IPU_IRQ_PF_Y_OUT_EOF); + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_enable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_enable_irq(IPU_IRQ_PF_V_OUT_EOF); + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, 0, + y_in_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting Y input buffer\n"); + goto err0; + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, 0, + y_out_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting Y output buffer\n"); + goto err0; + } + + if (pf_data.mode != PF_MPEG4_DERING) { + err = + ipu_update_channel_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, 0, + u_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting U input buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, + 0, u_out_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting U output buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, 0, + v_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting V input buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, + 0, v_out_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting V output buffer\n"); + goto err0; + } + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_SEC_INPUT_BUFFER, 0, + p1_in_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting QP buffer\n"); + goto err0; + } + + if (pf_data.mode == PF_H264_DEBLOCK) { + + err = ipu_update_channel_buffer(MEM_PF_U_MEM, + IPU_SEC_INPUT_BUFFER, 0, + p2_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting H264 BSB buffer\n"); + goto err0; + } + ipu_select_buffer(MEM_PF_U_MEM, IPU_SEC_INPUT_BUFFER, 0); + } + + ipu_select_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_Y_MEM, IPU_SEC_INPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, 0); + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_select_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, 0); + } + + if (!pf_data.pf_enabled) { + pf_data.pf_enabled = 1; + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_enable_channel(MEM_PF_V_MEM); + ipu_enable_channel(MEM_PF_U_MEM); + } + ipu_enable_channel(MEM_PF_Y_MEM); + } + + return 0; + err0: + return err; +} + +/*! + * Post Filter driver open function. This function implements the Linux + * file_operations.open() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_open(struct inode *inode, struct file *filp) +{ + int i; + + if (open_count) { + return -EBUSY; + } + + open_count++; + + memset(&pf_data, 0, sizeof(pf_data)); + for (i = 0; i < PF_MAX_BUFFER_CNT; i++) { + pf_data.buf[i].index = -1; + } + init_waitqueue_head(&pf_data.pf_wait); + init_MUTEX(&pf_data.busy_lock); + + pf_data.busy_flag = 1; + + ipu_request_irq(IPU_IRQ_PF_Y_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_request_irq(IPU_IRQ_PF_U_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_request_irq(IPU_IRQ_PF_V_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_disable_irq(IPU_IRQ_PF_Y_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_V_OUT_EOF); + + return 0; +} + +/*! + * Post Filter driver release function. This function implements the Linux + * file_operations.release() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_release(struct inode *inode, struct file *filp) +{ + pf_reqbufs_params req_buf; + + if (open_count) { + mxc_pf_uninit(); + + /* Free any allocated buffers */ + req_buf.count = 0; + mxc_pf_reqbufs(&req_buf); + + ipu_free_irq(IPU_IRQ_PF_V_OUT_EOF, &pf_data); + ipu_free_irq(IPU_IRQ_PF_U_OUT_EOF, &pf_data); + ipu_free_irq(IPU_IRQ_PF_Y_OUT_EOF, &pf_data); + open_count--; + } + return 0; +} + +/*! + * Post Filter driver ioctl function. This function implements the Linux + * file_operations.ioctl() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @param cmd IOCTL command to handle + * + * @param arg Pointer to arguments for IOCTL + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + + switch (cmd) { + case PF_IOCTL_INIT: + { + pf_init_params pf_init; + + pr_debug("PF_IOCTL_INIT\n"); + if (copy_from_user(&pf_init, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + retval = mxc_pf_init(&pf_init); + if (retval < 0) + break; + pf_init.qp_paddr = pf_data.qp_paddr; + pf_init.qp_size = pf_data.qp_size; + + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &pf_init, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + pf_data.busy_flag = 0; + break; + } + case PF_IOCTL_UNINIT: + pr_debug("PF_IOCTL_UNINIT\n"); + retval = mxc_pf_uninit(); + break; + case PF_IOCTL_REQBUFS: + { + pf_reqbufs_params reqbufs; + pr_debug("PF_IOCTL_REQBUFS\n"); + + if (copy_from_user + (&reqbufs, (void *)arg, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + retval = mxc_pf_reqbufs(&reqbufs); + + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &reqbufs, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + break; + } + case PF_IOCTL_QUERYBUF: + { + pf_buf buf; + pr_debug("PF_IOCTL_QUERYBUF\n"); + + if (copy_from_user(&buf, (void *)arg, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + if ((buf.index < 0) || + (buf.index >= PF_MAX_BUFFER_CNT) || + (pf_data.buf[buf.index].index != buf.index)) { + retval = -EINVAL; + break; + } + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &pf_data.buf[buf.index], + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + break; + } + case PF_IOCTL_START: + { + int index; + pf_start_params start_params; + pr_debug("PF_IOCTL_START\n"); + + if (pf_data.busy_flag) { + retval = -EBUSY; + break; + } + + if (copy_from_user(&start_params, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + if (start_params.h264_pause_row >= + ((pf_data.height + 15) / 16)) { + retval = -EINVAL; + break; + } + + pf_data.busy_flag = 1; + + index = start_params.in.index; + if ((index >= 0) && (index < PF_MAX_BUFFER_CNT)) { + if (pf_data.buf[index].offset != + start_params.in.offset) { + retval = -EINVAL; + break; + } + } + + index = start_params.out.index; + if ((index >= 0) && (index < PF_MAX_BUFFER_CNT)) { + if (pf_data.buf[index].offset != + start_params.out.offset) { + retval = -EINVAL; + break; + } + } + + ipu_pf_set_pause_row(start_params.h264_pause_row); + + retval = mxc_pf_start(&start_params.in, &start_params.out, + start_params.qp_buf); + /*Update y, u, v buffers in DMA Channels */ + if ((retval < 0) + break; + + pr_debug("PF_IOCTL_START - processing started\n"); + + if (!start_params.wait) { + break; + } + + pr_debug("PF_IOCTL_START - waiting for completion\n"); + + pf_data.wait_mask = PF_WAIT_ALL; + /* Fall thru to wait */ + } + case PF_IOCTL_WAIT: + { + if (!pf_data.wait_mask) + pf_data.wait_mask = (u32) arg; + + if (pf_data.mode == PF_MPEG4_DERING) + pf_data.wait_mask &= PF_WAIT_Y; + + if (!pf_data.wait_mask) { + retval = -EINVAL; + break; + } + + if (!wait_event_interruptible_timeout(pf_data.pf_wait, + ((pf_data. + done_mask & + pf_data. + wait_mask) == + pf_data. + wait_mask), + 1 * HZ)) { + pr_debug + ("PF_IOCTL_WAIT: timeout, done_mask = %d\n", + pf_data.done_mask); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + pr_debug("PF_IOCTL_WAIT: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + pf_data.busy_flag = 0; + pf_data.wait_mask = 0; + + pr_debug("PF_IOCTL_WAIT - finished\n"); + break; + } + case PF_IOCTL_RESUME: + { + int pause_row; + pr_debug("PF_IOCTL_RESUME\n"); + + if (pf_data.busy_flag == 0) { + retval = -EFAULT; + break; + } + + if (copy_from_user(&pause_row, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + if (pause_row >= ((pf_data.height + 15) / 16)) { + retval = -EINVAL; + break; + } + + ipu_pf_set_pause_row(pause_row); + break; + } + + default: + printk(KERN_ERR "ipu_pf_ioctl not supported ioctl\n"); + retval = -1; + } + + if (retval < 0) + pr_debug("return = %d\n", retval); + return retval; +} + +/*! + * Post Filter driver mmap function. This function implements the Linux + * file_operations.mmap() API function for mapping driver buffers to user space. + * + * @param file struct file * + * + * @param vma structure vm_area_struct * + * + * @return 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error. + */ +static int mxc_pf_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + + pr_debug("pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&pf_data.busy_lock)) + return -EINTR; + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + printk(KERN_ERR "mxc_pf: remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mmap_exit: + up(&pf_data.busy_lock); + return res; +} + +/*! + * Post Filter driver fsync function. This function implements the Linux + * file_operations.fsync() API function. + * + * The user must call fsync() before reading an output buffer. This + * call flushes the L1 and L2 caches + * + * @param filp structure file * + * + * @param dentry struct dentry * + * + * @param datasync unused + * + * @return status POLLIN | POLLRDNORM + */ +int mxc_pf_fsync(struct file *filp, struct dentry *dentry, int datasync) +{ + flush_cache_all(); + outer_flush_all(); + return 0; +} + +/*! + * Post Filter driver poll function. This function implements the Linux + * file_operations.poll() API function. + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_pf_poll(struct file *file, poll_table * wait) +{ + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&pf_data.busy_lock)) + return -EINTR; + + queue = &pf_data.pf_wait; + poll_wait(file, queue, wait); + + up(&pf_data.busy_lock); + + return res; +} + +/*! + * File operation structure functions pointers. + */ +static struct file_operations mxc_pf_fops = { + .owner = THIS_MODULE, + .open = mxc_pf_open, + .release = mxc_pf_release, + .ioctl = mxc_pf_ioctl, + .poll = mxc_pf_poll, + .mmap = mxc_pf_mmap, + .fsync = mxc_pf_fsync, +}; + +static int mxc_pf_major; + +/*! + * Post Filter driver module initialization function. + */ +int mxc_pf_dev_init(void) +{ + int ret = 0; + struct device *temp_class; + + mxc_pf_major = register_chrdev(0, "mxc_ipu_pf", &mxc_pf_fops); + + if (mxc_pf_major < 0) { + printk(KERN_INFO "Unable to get a major for mxc_ipu_pf"); + return mxc_pf_major; + } + + mxc_pf_class = class_create(THIS_MODULE, "mxc_ipu_pf"); + if (IS_ERR(mxc_pf_class)) { + printk(KERN_ERR "Error creating mxc_ipu_pf class.\n"); + ret = PTR_ERR(mxc_pf_class); + goto err_out1; + } + + temp_class = device_create(mxc_pf_class, NULL, MKDEV(mxc_pf_major, 0), NULL, + "mxc_ipu_pf"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating mxc_ipu_pf class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + printk(KERN_INFO "IPU Post-filter loading\n"); + + return 0; + + err_out2: + class_destroy(mxc_pf_class); + err_out1: + unregister_chrdev(mxc_pf_major, "mxc_ipu_pf"); + return ret; +} + +/*! + * Post Filter driver module exit function. + */ +static void mxc_pf_exit(void) +{ + if (mxc_pf_major > 0) { + device_destroy(mxc_pf_class, MKDEV(mxc_pf_major, 0)); + class_destroy(mxc_pf_class); + unregister_chrdev(mxc_pf_major, "mxc_ipu_pf"); + } +} + +module_init(mxc_pf_dev_init); +module_exit(mxc_pf_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC MPEG4/H.264 Postfilter Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ipu3/Kconfig b/drivers/mxc/ipu3/Kconfig new file mode 100644 index 000000000000..0ae0ffa9e19d --- /dev/null +++ b/drivers/mxc/ipu3/Kconfig @@ -0,0 +1,5 @@ +config MXC_IPU_V3 + bool + +config MXC_IPU_V3D + bool diff --git a/drivers/mxc/ipu3/Makefile b/drivers/mxc/ipu3/Makefile new file mode 100644 index 000000000000..aa3e7b1bb501 --- /dev/null +++ b/drivers/mxc/ipu3/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_MXC_IPU_V3) = mxc_ipu.o + +mxc_ipu-objs := ipu_common.o ipu_ic.o ipu_disp.o ipu_capture.o ipu_device.o ipu_calc_stripes_sizes.o + diff --git a/drivers/mxc/ipu3/ipu_calc_stripes_sizes.c b/drivers/mxc/ipu3/ipu_calc_stripes_sizes.c new file mode 100644 index 000000000000..473772a924ca --- /dev/null +++ b/drivers/mxc/ipu3/ipu_calc_stripes_sizes.c @@ -0,0 +1,375 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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_calc_stripes_sizes.c + * + * @brief IPU IC functions + * + * @ingroup IPU + */ + +#include +#include +#include + +#define BPP_32 0 +#define BPP_16 3 +#define BPP_8 5 +#define BPP_24 1 +#define BPP_12 4 +#define BPP_18 2 + +static u64 _do_div(u64 a, u32 b) +{ + u64 div; + div = a; + do_div(div, b); + return div; +} + +static u32 truncate(u32 up, /* 0: down; else: up */ + u64 a, /* must be non-negative */ + u32 b) +{ + u32 d; + u64 div; + div = _do_div(a, b); + d = b * (div >> 32); + if (up && (a > (((u64)d) << 32))) + return d+b; + else + return d; +} + +static unsigned int f_calc(unsigned int pfs, unsigned int bpp, unsigned int *write) +{/* return input_f */ + unsigned int f_calculated = 0; + switch (pfs) { + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YVU420P: + f_calculated = 16; + break; + + case IPU_PIX_FMT_NV12: + f_calculated = 8; + break; + + default: + f_calculated = 0; + break; + + } + if (!f_calculated) { + switch (bpp) { + case BPP_32: + f_calculated = 2; + break; + + case BPP_16: + f_calculated = 4; + break; + + case BPP_8: + case BPP_24: + f_calculated = 8; + break; + + case BPP_12: + f_calculated = 16; + break; + + case BPP_18: + f_calculated = 32; + break; + + default: + f_calculated = 0; + break; + } + } + return f_calculated; +} + + +static unsigned int m_calc(unsigned int pfs) +{ + unsigned int m_calculated = 0; + switch (pfs) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + m_calculated = 8; + break; + + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + m_calculated = 2; + break; + + default: + m_calculated = 1; + break; + + } + return m_calculated; +} + + +/* Stripe parameters calculator */ +/************************************************************************** +Notes: +MSW = the maximal width allowed for a stripe + i.MX31: 720, i.MX35: 800, i.MX37/51/53: 1024 +cirr = the maximal inverse resizing ratio for which overlap in the input + is requested; typically cirr~2 +equal_stripes: + 0: each stripe is allowed to have independent parameters + for maximal image quality + 1: the stripes are requested to have identical parameters + (except the base address), for maximal performance +If performance is the top priority (above image quality) + Avoid overlap, by setting CIRR = 0 + This will also force effectively identical_stripes = 1 + Choose IF & OF that corresponds to the same IOX/SX for both stripes + Choose IFW & OFW such that + IFW/IM, IFW/IF, OFW/OM, OFW/OF are even integers + The function returns an error status: + 0: no error + 1: invalid input parameters -> aborted without result + Valid parameters should satisfy the following conditions + IFW <= OFW, otherwise downsizing is required + - which is not supported yet + 4 <= IFW,OFW, so some interpolation may be needed even without overlap + IM, OM, IF, OF should not vanish + 2*IF <= IFW + so the frame can be split to two equal stripes, even without overlap + 2*(OF+IF/irr_opt) <= OFW + so a valid positive INW exists even for equal stripes + OF <= MSW, otherwise, the left stripe cannot be sufficiently large + MSW < OFW, so splitting to stripes is required + OFW <= 2*MSW, so two stripes are sufficient + (this also implies that 2<=MSW) + 2: OF is not a multiple of OM - not fully-supported yet + Output is produced but OW is not guaranited to be a multiple of OM + 4: OFW reduced to be a multiple of OM + 8: CIRR > 1: truncated to 1 + Overlap is not supported (and not needed) y for upsizing) +**************************************************************************/ +int ipu_calc_stripes_sizes(const unsigned int input_frame_width, + /* input frame width;>1 */ + unsigned int output_frame_width, /* output frame width; >1 */ + const unsigned int maximal_stripe_width, + /* the maximal width allowed for a stripe */ + const unsigned long long cirr, /* see above */ + const unsigned int equal_stripes, /* see above */ + u32 input_pixelformat,/* pixel format after of read channel*/ + u32 output_pixelformat,/* pixel format after of write channel*/ + struct stripe_param *left, + struct stripe_param *right) +{ + const unsigned int irr_frac_bits = 13; + const unsigned long irr_steps = 1 << irr_frac_bits; + const u64 dirr = ((u64)1) << (32 - 2); + /* The maximum relative difference allowed between the irrs */ + const u64 cr = ((u64)4) << 32; + /* The importance ratio between the two terms in the cost function below */ + + unsigned int status; + unsigned int temp; + unsigned int onw_min; + unsigned int inw, onw, inw_best = 0; + /* number of pixels in the left stripe NOT hidden by the right stripe */ + u64 irr_opt; /* the optimal inverse resizing ratio */ + u64 rr_opt; /* the optimal resizing ratio = 1/irr_opt*/ + u64 dinw; /* the misalignment between the stripes */ + /* (measured in units of input columns) */ + u64 difwl, difwr; + /* The number of input columns not reflected in the output */ + /* the resizing ratio used for the right stripe is */ + /* left->irr and right->irr respectively */ + u64 cost, cost_min; + u64 div; /* result of division */ + + unsigned int input_m, input_f, output_m, output_f; /* parameters for upsizing by stripes */ + + status = 0; + + /* M, F calculations */ + /* read back pfs from params */ + + input_f = f_calc(input_pixelformat, 0, NULL); + input_m = 16; + /* BPP should be used in the out_F calc */ + /* Temporarily not used */ + /* out_F = F_calc(idmac->pfs, idmac->bpp, NULL); */ + + output_f = 16; + output_m = m_calc(output_pixelformat); + + + if ((input_frame_width < 4) || (output_frame_width < 4)) + return 1; + + irr_opt = _do_div((((u64)(input_frame_width - 1)) << 32), + (output_frame_width - 1)); + rr_opt = _do_div((((u64)(output_frame_width - 1)) << 32), + (input_frame_width - 1)); + + if ((input_m == 0) || (output_m == 0) || (input_f == 0) || (output_f == 0) + || (input_frame_width < (2 * input_f)) + || ((((u64)output_frame_width) << 32) < + (2 * ((((u64)output_f) << 32) + (input_f * rr_opt)))) + || (maximal_stripe_width < output_f) + || (output_frame_width <= maximal_stripe_width) + || ((2 * maximal_stripe_width) < output_frame_width)) + return 1; + + if (output_f % output_m) + status += 2; + + temp = truncate(0, (((u64)output_frame_width) << 32), output_m); + if (temp < output_frame_width) { + output_frame_width = temp; + status += 4; + } + + if (equal_stripes) { + if ((irr_opt > cirr) /* overlap in the input is not requested */ + && ((input_frame_width % (input_m << 1)) == 0) + && ((input_frame_width % (input_f << 1)) == 0) + && ((output_frame_width % (output_m << 1)) == 0) + && ((output_frame_width % (output_f << 1)) == 0)) { + /* without overlap */ + left->input_width = right->input_width = right->input_column = + input_frame_width >> 1; + left->output_width = right->output_width = right->output_column = + output_frame_width >> 1; + left->input_column = 0; + left->output_column = 0; + div = _do_div(((((u64)irr_steps) << 32) * + (right->input_width - 1)), (right->output_width - 1)); + left->irr = right->irr = truncate(0, div, 1); + } else { /* with overlap */ + onw = truncate(0, (((u64)output_frame_width - 1) << 32) >> 1, + output_f); + inw = truncate(0, onw * irr_opt, input_f); + /* this is the maximal inw which allows the same resizing ratio */ + /* in both stripes */ + onw = truncate(1, (inw * rr_opt), output_f); + div = _do_div((((u64)(irr_steps * inw)) << + 32), onw); + left->irr = right->irr = truncate(0, div, 1); + left->output_width = right->output_width = + output_frame_width - onw; + /* These are valid assignments for output_width, */ + /* assuming output_f is a multiple of output_m */ + div = (((u64)(left->output_width-1) * (left->irr)) << 32); + div = (((u64)1) << 32) + _do_div(div, irr_steps); + + left->input_width = right->input_width = truncate(1, div, input_m); + + div = _do_div((((u64)((right->output_width - 1) * right->irr)) << + 32), irr_steps); + difwr = (((u64)(input_frame_width - 1 - inw)) << 32) - div; + div = _do_div((difwr + (((u64)input_f) << 32)), 2); + left->input_column = truncate(0, div, input_f); + + + /* This splits the truncated input columns evenly */ + /* between the left and right margins */ + right->input_column = left->input_column + inw; + left->output_column = 0; + right->output_column = onw; + } + } else { /* independent stripes */ + onw_min = output_frame_width - maximal_stripe_width; + /* onw is a multiple of output_f, in the range */ + /* [max(output_f,output_frame_width-maximal_stripe_width),*/ + /*min(output_frame_width-2,maximal_stripe_width)] */ + /* definitely beyond the cost of any valid setting */ + cost_min = (((u64)input_frame_width) << 32) + cr; + onw = truncate(0, ((u64)maximal_stripe_width), output_f); + if (output_frame_width - onw == 1) + onw -= output_f; /* => onw and output_frame_width-1-onw are positive */ + inw = truncate(0, onw * irr_opt, input_f); + /* this is the maximal inw which allows the same resizing ratio */ + /* in both stripes */ + onw = truncate(1, inw * rr_opt, output_f); + do { + div = _do_div((((u64)(irr_steps * inw)) << 32), onw); + left->irr = truncate(0, div, 1); + div = _do_div((((u64)(onw * left->irr)) << 32), + irr_steps); + dinw = (((u64)inw) << 32) - div; + + div = _do_div((((u64)((output_frame_width - 1 - onw) * left->irr)) << + 32), irr_steps); + + difwl = (((u64)(input_frame_width - 1 - inw)) << 32) - div; + + cost = difwl + (((u64)(cr * dinw)) >> 32); + + if (cost < cost_min) { + inw_best = inw; + cost_min = cost; + } + + inw -= input_f; + onw = truncate(1, inw * rr_opt, output_f); + /* This is the minimal onw which allows the same resizing ratio */ + /* in both stripes */ + } while (onw >= onw_min); + + inw = inw_best; + onw = truncate(1, inw * rr_opt, output_f); + div = _do_div((((u64)(irr_steps * inw)) << 32), onw); + left->irr = truncate(0, div, 1); + + left->output_width = onw; + right->output_width = output_frame_width - onw; + /* These are valid assignments for output_width, */ + /* assuming output_f is a multiple of output_m */ + left->input_width = truncate(1, ((u64)(inw + 1)) << 32, input_m); + right->input_width = truncate(1, ((u64)(input_frame_width - inw)) << + 32, input_m); + + div = _do_div((((u64)(irr_steps * (input_frame_width - 1 - inw))) << + 32), (right->output_width - 1)); + right->irr = truncate(0, div, 1); + temp = truncate(0, ((u64)left->irr) * ((((u64)1) << 32) + dirr), 1); + if (temp < right->irr) + right->irr = temp; + div = _do_div(((u64)((right->output_width - 1) * right->irr) << + 32), irr_steps); + difwr = (u64)(input_frame_width - 1 - inw) - div; + + + div = _do_div((difwr + (((u64)input_f) << 32)), 2); + left->input_column = truncate(0, div, input_f); + + /* This splits the truncated input columns evenly */ + /* between the left and right margins */ + right->input_column = left->input_column + inw; + left->output_column = 0; + right->output_column = onw; + } + return status; +} +EXPORT_SYMBOL(ipu_calc_stripes_sizes); diff --git a/drivers/mxc/ipu3/ipu_capture.c b/drivers/mxc/ipu3/ipu_capture.c new file mode 100644 index 000000000000..32d3522cb859 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_capture.c @@ -0,0 +1,835 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_capture.c + * + * @brief IPU capture dase functions + * + * @ingroup IPU + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu_prv.h" +#include "ipu_regs.h" + +/*! + * ipu_csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param cfg_param ipu_csi_signal_cfg_t structure + * @param csi csi 0 or csi 1 + * + * @return 0 for success, -EINVAL for error + */ +int32_t +ipu_csi_init_interface(uint16_t width, uint16_t height, uint32_t pixel_fmt, + ipu_csi_signal_cfg_t cfg_param) +{ + uint32_t data = 0; + uint32_t csi = cfg_param.csi; + unsigned long lock_flags; + + /* Set SENS_DATA_FORMAT bits (8, 9 and 10) + RGB or YUV444 is 0 which is current value in data so not set + explicitly + This is also the default value if attempts are made to set it to + something invalid. */ + switch (pixel_fmt) { + case IPU_PIX_FMT_YUYV: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV; + break; + case IPU_PIX_FMT_UYVY: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY; + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR24: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; + break; + case IPU_PIX_FMT_GENERIC: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; + break; + case IPU_PIX_FMT_RGB565: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565; + break; + case IPU_PIX_FMT_RGB555: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB555; + break; + default: + return -EINVAL; + } + + /* Set the CSI_SENS_CONF register remaining fields */ + data |= cfg_param.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | + cfg_param.data_fmt << CSI_SENS_CONF_DATA_FMT_SHIFT | + cfg_param.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | + cfg_param.Vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | + cfg_param.Hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | + cfg_param.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | + cfg_param.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | + cfg_param.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | + cfg_param.pack_tight << CSI_SENS_CONF_PACK_TIGHT_SHIFT | + cfg_param.force_eof << CSI_SENS_CONF_FORCE_EOF_SHIFT | + cfg_param.data_en_pol << CSI_SENS_CONF_DATA_EN_POL_SHIFT; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel(data, CSI_SENS_CONF(csi)); + + /* Setup sensor frame size */ + __raw_writel((width - 1) | (height - 1) << 16, CSI_SENS_FRM_SIZE(csi)); + + /* Set CCIR registers */ + if (cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) { + __raw_writel(0x40030, CSI_CCIR_CODE_1(csi)); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3(csi)); + } else if (cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED) { + if (width == 720 && height == 625) { + /* PAL case */ + /* + * Field0BlankEnd = 0x6, Field0BlankStart = 0x2, + * Field0ActiveEnd = 0x4, Field0ActiveStart = 0 + */ + __raw_writel(0x40596, CSI_CCIR_CODE_1(csi)); + /* + * Field1BlankEnd = 0x7, Field1BlankStart = 0x3, + * Field1ActiveEnd = 0x5, Field1ActiveStart = 0x1 + */ + __raw_writel(0xD07DF, CSI_CCIR_CODE_2(csi)); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3(csi)); + } else if (width == 720 && height == 525) { + /* NTSC case */ + /* + * Field0BlankEnd = 0x7, Field0BlankStart = 0x3, + * Field0ActiveEnd = 0x5, Field0ActiveStart = 0x1 + */ + __raw_writel(0xD07DF, CSI_CCIR_CODE_1(csi)); + /* + * Field1BlankEnd = 0x6, Field1BlankStart = 0x2, + * Field1ActiveEnd = 0x4, Field1ActiveStart = 0 + */ + __raw_writel(0x40596, CSI_CCIR_CODE_2(csi)); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3(csi)); + } else { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + dev_err(g_ipu_dev, "Unsupported CCIR656 interlaced " + "video mode\n"); + return -EINVAL; + } + _ipu_csi_ccir_err_detection_enable(csi); + } else if ((cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR)) { + __raw_writel(0x40030, CSI_CCIR_CODE_1(csi)); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3(csi)); + _ipu_csi_ccir_err_detection_enable(csi); + } else if ((cfg_param.clk_mode == IPU_CSI_CLK_MODE_GATED_CLK) || + (cfg_param.clk_mode == IPU_CSI_CLK_MODE_NONGATED_CLK)) { + _ipu_csi_ccir_err_detection_disable(csi); + } + + dev_dbg(g_ipu_dev, "CSI_SENS_CONF = 0x%08X\n", + __raw_readl(CSI_SENS_CONF(csi))); + dev_dbg(g_ipu_dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", + __raw_readl(CSI_ACT_FRM_SIZE(csi))); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_csi_init_interface); + +/*! + * ipu_csi_get_sensor_protocol + * + * @param csi csi 0 or csi 1 + * + * @return Returns sensor protocol + */ +int32_t ipu_csi_get_sensor_protocol(uint32_t csi) +{ + return (__raw_readl(CSI_SENS_CONF(csi)) & + CSI_SENS_CONF_SENS_PRTCL_MASK) >> + CSI_SENS_CONF_SENS_PRTCL_SHIFT; +} +EXPORT_SYMBOL(ipu_csi_get_sensor_protocol); + +/*! + * _ipu_csi_mclk_set + * + * @param pixel_clk desired pixel clock frequency in Hz + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_mclk_set(uint32_t pixel_clk, uint32_t csi) +{ + uint32_t temp; + uint32_t div_ratio; + + div_ratio = (clk_get_rate(g_ipu_clk) / pixel_clk) - 1; + + if (div_ratio > 0xFF || div_ratio < 0) { + dev_dbg(g_ipu_dev, "The value of pixel_clk extends normal range\n"); + return -EINVAL; + } + + temp = __raw_readl(CSI_SENS_CONF(csi)); + temp &= ~CSI_SENS_CONF_DIVRATIO_MASK; + __raw_writel(temp | (div_ratio << CSI_SENS_CONF_DIVRATIO_SHIFT), + CSI_SENS_CONF(csi)); + + return 0; +} + +/*! + * ipu_csi_enable_mclk + * + * @param csi csi 0 or csi 1 + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return Returns 0 on success + */ +int ipu_csi_enable_mclk(int csi, bool flag, bool wait) +{ + if (flag) { + clk_enable(g_csi_clk[csi]); + if (wait == true) + msleep(10); + } else { + clk_disable(g_csi_clk[csi]); + } + + return 0; +} +EXPORT_SYMBOL(ipu_csi_enable_mclk); + +/*! + * ipu_csi_get_window_size + * + * @param width pointer to window width + * @param height pointer to window height + * @param csi csi 0 or csi 1 + */ +void ipu_csi_get_window_size(uint32_t *width, uint32_t *height, uint32_t csi) +{ + uint32_t reg; + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(CSI_ACT_FRM_SIZE(csi)); + *width = (reg & 0xFFFF) + 1; + *height = (reg >> 16 & 0xFFFF) + 1; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_get_window_size); + +/*! + * ipu_csi_set_window_size + * + * @param width window width + * @param height window height + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_window_size(uint32_t width, uint32_t height, uint32_t csi) +{ + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel((width - 1) | (height - 1) << 16, CSI_ACT_FRM_SIZE(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_window_size); + +/*! + * ipu_csi_set_window_pos + * + * @param left uint32 window x start + * @param top uint32 window y start + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_window_pos(uint32_t left, uint32_t top, uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~(CSI_HSC_MASK | CSI_VSC_MASK); + temp |= ((top << CSI_VSC_SHIFT) | (left << CSI_HSC_SHIFT)); + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_window_pos); + +/*! + * _ipu_csi_horizontal_downsize_enable + * Enable horizontal downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_horizontal_downsize_enable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp |= CSI_HORI_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_horizontal_downsize_disable + * Disable horizontal downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_horizontal_downsize_disable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~CSI_HORI_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_vertical_downsize_enable + * Enable vertical downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_vertical_downsize_enable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp |= CSI_VERT_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_vertical_downsize_disable + * Disable vertical downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_vertical_downsize_disable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~CSI_VERT_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * ipu_csi_set_test_generator + * + * @param active 1 for active and 0 for inactive + * @param r_value red value for the generated pattern of even pixel + * @param g_value green value for the generated pattern of even + * pixel + * @param b_value blue value for the generated pattern of even pixel + * @param pixel_clk desired pixel clock frequency in Hz + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_test_generator(bool active, uint32_t r_value, + uint32_t g_value, uint32_t b_value, uint32_t pix_clk, uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_TST_CTRL(csi)); + + if (active == false) { + temp &= ~CSI_TEST_GEN_MODE_EN; + __raw_writel(temp, CSI_TST_CTRL(csi)); + } else { + /* Set sensb_mclk div_ratio*/ + _ipu_csi_mclk_set(pix_clk, csi); + + temp &= ~(CSI_TEST_GEN_R_MASK | CSI_TEST_GEN_G_MASK | + CSI_TEST_GEN_B_MASK); + temp |= CSI_TEST_GEN_MODE_EN; + temp |= (r_value << CSI_TEST_GEN_R_SHIFT) | + (g_value << CSI_TEST_GEN_G_SHIFT) | + (b_value << CSI_TEST_GEN_B_SHIFT); + __raw_writel(temp, CSI_TST_CTRL(csi)); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_test_generator); + +/*! + * _ipu_csi_ccir_err_detection_en + * Enable error detection and correction for + * CCIR interlaced mode with protection bit. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_ccir_err_detection_enable(uint32_t csi) +{ + uint32_t temp; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + temp = __raw_readl(CSI_CCIR_CODE_1(csi)); + temp |= CSI_CCIR_ERR_DET_EN; + __raw_writel(temp, CSI_CCIR_CODE_1(csi)); +} + +/*! + * _ipu_csi_ccir_err_detection_disable + * Disable error detection and correction for + * CCIR interlaced mode with protection bit. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_ccir_err_detection_disable(uint32_t csi) +{ + uint32_t temp; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + temp = __raw_readl(CSI_CCIR_CODE_1(csi)); + temp &= ~CSI_CCIR_ERR_DET_EN; + __raw_writel(temp, CSI_CCIR_CODE_1(csi)); +} + +/*! + * _ipu_csi_set_mipi_di + * + * @param num MIPI data identifier 0-3 handled by CSI + * @param di_val data identifier value + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_mipi_di(uint32_t num, uint32_t di_val, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (di_val > 0xFFL) { + retval = -EINVAL; + goto err; + } + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_MIPI_DI(csi)); + + switch (num) { + case IPU_CSI_MIPI_DI0: + temp &= ~CSI_MIPI_DI0_MASK; + temp |= (di_val << CSI_MIPI_DI0_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI1: + temp &= ~CSI_MIPI_DI1_MASK; + temp |= (di_val << CSI_MIPI_DI1_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI2: + temp &= ~CSI_MIPI_DI2_MASK; + temp |= (di_val << CSI_MIPI_DI2_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI3: + temp &= ~CSI_MIPI_DI3_MASK; + temp |= (di_val << CSI_MIPI_DI3_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + default: + retval = -EINVAL; + goto err; + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_csi_set_skip_isp + * + * @param skip select frames to be skipped and set the + * correspond bits to 1 + * @param max_ratio number of frames in a skipping set and the + * maximum value of max_ratio is 5 + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_skip_isp(uint32_t skip, uint32_t max_ratio, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (max_ratio > 5) { + retval = -EINVAL; + goto err; + } + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_SKIP(csi)); + temp &= ~(CSI_MAX_RATIO_SKIP_ISP_MASK | CSI_SKIP_ISP_MASK); + temp |= (max_ratio << CSI_MAX_RATIO_SKIP_ISP_SHIFT) | + (skip << CSI_SKIP_ISP_SHIFT); + __raw_writel(temp, CSI_SKIP(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_csi_set_skip_smfc + * + * @param skip select frames to be skipped and set the + * correspond bits to 1 + * @param max_ratio number of frames in a skipping set and the + * maximum value of max_ratio is 5 + * @param id csi to smfc skipping id + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_skip_smfc(uint32_t skip, uint32_t max_ratio, + uint32_t id, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (max_ratio > 5 || id > 3) { + retval = -EINVAL; + goto err; + } + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_SKIP(csi)); + temp &= ~(CSI_MAX_RATIO_SKIP_SMFC_MASK | CSI_ID_2_SKIP_MASK | + CSI_SKIP_SMFC_MASK); + temp |= (max_ratio << CSI_MAX_RATIO_SKIP_SMFC_SHIFT) | + (id << CSI_ID_2_SKIP_SHIFT) | + (skip << CSI_SKIP_SMFC_SHIFT); + __raw_writel(temp, CSI_SKIP(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_smfc_init + * Map CSI frames to IDMAC channels. + * + * @param channel IDMAC channel 0-3 + * @param mipi_id mipi id number 0-3 + * @param csi csi0 or csi1 + */ +void _ipu_smfc_init(ipu_channel_t channel, uint32_t mipi_id, uint32_t csi) +{ + uint32_t temp; + + temp = __raw_readl(SMFC_MAP); + + switch (channel) { + case CSI_MEM0: + temp &= ~SMFC_MAP_CH0_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH0_SHIFT; + break; + case CSI_MEM1: + temp &= ~SMFC_MAP_CH1_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH1_SHIFT; + break; + case CSI_MEM2: + temp &= ~SMFC_MAP_CH2_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH2_SHIFT; + break; + case CSI_MEM3: + temp &= ~SMFC_MAP_CH3_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH3_SHIFT; + break; + default: + return; + } + + __raw_writel(temp, SMFC_MAP); +} + +/*! + * _ipu_smfc_set_wmc + * Caution: The number of required channels, the enabled channels + * and the FIFO size per channel are configured restrictedly. + * + * @param channel IDMAC channel 0-3 + * @param set set 1 or clear 0 + * @param level water mark level when FIFO is on the + * relative size + */ +void _ipu_smfc_set_wmc(ipu_channel_t channel, bool set, uint32_t level) +{ + uint32_t temp; + unsigned long lock_flags; + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(SMFC_WMC); + + switch (channel) { + case CSI_MEM0: + if (set == true) { + temp &= ~SMFC_WM0_SET_MASK; + temp |= level << SMFC_WM0_SET_SHIFT; + } else { + temp &= ~SMFC_WM0_CLR_MASK; + temp |= level << SMFC_WM0_CLR_SHIFT; + } + break; + case CSI_MEM1: + if (set == true) { + temp &= ~SMFC_WM1_SET_MASK; + temp |= level << SMFC_WM1_SET_SHIFT; + } else { + temp &= ~SMFC_WM1_CLR_MASK; + temp |= level << SMFC_WM1_CLR_SHIFT; + } + break; + case CSI_MEM2: + if (set == true) { + temp &= ~SMFC_WM2_SET_MASK; + temp |= level << SMFC_WM2_SET_SHIFT; + } else { + temp &= ~SMFC_WM2_CLR_MASK; + temp |= level << SMFC_WM2_CLR_SHIFT; + } + break; + case CSI_MEM3: + if (set == true) { + temp &= ~SMFC_WM3_SET_MASK; + temp |= level << SMFC_WM3_SET_SHIFT; + } else { + temp &= ~SMFC_WM3_CLR_MASK; + temp |= level << SMFC_WM3_CLR_SHIFT; + } + break; + default: + return; + } + + __raw_writel(temp, SMFC_WMC); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_smfc_set_burst_size + * + * @param channel IDMAC channel 0-3 + * @param bs burst size of IDMAC channel, + * the value programmed here shoud be BURST_SIZE-1 + */ +void _ipu_smfc_set_burst_size(ipu_channel_t channel, uint32_t bs) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(SMFC_BS); + + switch (channel) { + case CSI_MEM0: + temp &= ~SMFC_BS0_MASK; + temp |= bs << SMFC_BS0_SHIFT; + break; + case CSI_MEM1: + temp &= ~SMFC_BS1_MASK; + temp |= bs << SMFC_BS1_SHIFT; + break; + case CSI_MEM2: + temp &= ~SMFC_BS2_MASK; + temp |= bs << SMFC_BS2_SHIFT; + break; + case CSI_MEM3: + temp &= ~SMFC_BS3_MASK; + temp |= bs << SMFC_BS3_SHIFT; + break; + default: + return; + } + + __raw_writel(temp, SMFC_BS); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_init + * + * @param channel IDMAC channel + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_init(ipu_channel_t channel, uint32_t csi) +{ + uint32_t csi_sens_conf, csi_dest; + int retval = 0; + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + csi_dest = CSI_DATA_DEST_IDMAC; + break; + case CSI_PRP_ENC_MEM: + case CSI_PRP_VF_MEM: + csi_dest = CSI_DATA_DEST_IC; + break; + default: + retval = -EINVAL; + goto err; + } + + csi_sens_conf = __raw_readl(CSI_SENS_CONF(csi)); + csi_sens_conf &= ~CSI_SENS_CONF_DATA_DEST_MASK; + __raw_writel(csi_sens_conf | (csi_dest << + CSI_SENS_CONF_DATA_DEST_SHIFT), CSI_SENS_CONF(csi)); +err: + return retval; +} diff --git a/drivers/mxc/ipu3/ipu_common.c b/drivers/mxc/ipu3/ipu_common.c new file mode 100644 index 000000000000..bf09143e4702 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_common.c @@ -0,0 +1,2716 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 + */ +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +struct ipu_irq_node { + irqreturn_t(*handler) (int, void *); /*!< the ISR */ + const char *name; /*!< device associated with the interrupt */ + void *dev_id; /*!< some unique information for the ISR */ + __u32 flags; /*!< not used */ +}; + +/* Globals */ +struct clk *g_ipu_clk; +bool g_ipu_clk_enabled; +struct clk *g_di_clk[2]; +struct clk *g_pixel_clk[2]; +struct clk *g_csi_clk[2]; +unsigned char g_dc_di_assignment[10]; +ipu_channel_t g_ipu_csi_channel[2]; +int g_ipu_irq[2]; +int g_ipu_hw_rev; +bool g_sec_chan_en[24]; +bool g_thrd_chan_en[24]; +bool g_chan_is_interlaced[52]; +uint32_t g_channel_init_mask; +uint32_t g_channel_enable_mask; +DEFINE_SPINLOCK(ipu_lock); +struct device *g_ipu_dev; + +static struct ipu_irq_node ipu_irq_list[IPU_IRQ_COUNT]; + +static int ipu_dc_use_count; +static int ipu_dp_use_count; +static int ipu_dmfc_use_count; +static int ipu_smfc_use_count; +static int ipu_ic_use_count; +static int ipu_rot_use_count; +static int ipu_vdi_use_count; +static int ipu_di_use_count[2]; +static int ipu_csi_use_count[2]; +/* Set to the follow using IC direct channel, default non */ +static ipu_channel_t using_ic_dirct_ch; + +/* for power gating */ +static uint32_t ipu_conf_reg; +static uint32_t ic_conf_reg; +static uint32_t ipu_cha_db_mode_reg[4]; +static uint32_t ipu_cha_cur_buf_reg[4]; +static uint32_t idma_enable_reg[2]; +static uint32_t buf_ready_reg[8]; + +u32 *ipu_cm_reg; +u32 *ipu_idmac_reg; +u32 *ipu_dp_reg; +u32 *ipu_ic_reg; +u32 *ipu_dc_reg; +u32 *ipu_dc_tmpl_reg; +u32 *ipu_dmfc_reg; +u32 *ipu_di_reg[2]; +u32 *ipu_smfc_reg; +u32 *ipu_csi_reg[2]; +u32 *ipu_cpmem_base; +u32 *ipu_tpmem_base; +u32 *ipu_disp_base[2]; +u32 *ipu_vdi_reg; + +/* Static functions */ +static irqreturn_t ipu_irq_handler(int irq, void *desc); + +static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((uint32_t) ch >> (6 * type)) & 0x3F; +}; + +static inline int _ipu_is_ic_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 11) && (dma_chan <= 22) && (dma_chan != 17) && (dma_chan != 18)); +} + +static inline int _ipu_is_ic_graphic_chan(uint32_t dma_chan) +{ + return (dma_chan == 14 || dma_chan == 15); +} + +/* Either DP BG or DP FG can be graphic window */ +static inline int _ipu_is_dp_graphic_chan(uint32_t dma_chan) +{ + return (dma_chan == 23 || dma_chan == 27); +} + +static inline int _ipu_is_irt_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 45) && (dma_chan <= 50)); +} + +static inline int _ipu_is_dmfc_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 23) && (dma_chan <= 29)); +} + +static inline int _ipu_is_smfc_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 0) && (dma_chan <= 3)); +} + +#define idma_is_valid(ch) (ch != NO_DMA) +#define idma_mask(ch) (idma_is_valid(ch) ? (1UL << (ch & 0x1F)) : 0) +#define idma_is_set(reg, dma) (__raw_readl(reg(dma)) & idma_mask(dma)) + +static unsigned long _ipu_pixel_clk_get_rate(struct clk *clk) +{ + u32 div = __raw_readl(DI_BS_CLKGEN0(clk->id)); + if (div == 0) + return 0; + return (clk_get_rate(clk->parent) * 16) / div; +} + +static unsigned long _ipu_pixel_clk_round_rate(struct clk *clk, unsigned long rate) +{ + u32 div, div1; + u32 parent_rate = clk_get_rate(clk->parent) * 16; + /* + * Calculate divider + * Fractional part is 4 bits, + * so simply multiply by 2^4 to get fractional part. + */ + div = parent_rate / rate; + + if (div < 0x10) /* Min DI disp clock divider is 1 */ + div = 0x10; + if (div & ~0xFEF) + div &= 0xFF8; + else { + div1 = div & 0xFE0; + if ((parent_rate / div1 - parent_rate / div) < rate / 4) + div = div1; + else + div &= 0xFF8; + } + return parent_rate / div; +} + +static int _ipu_pixel_clk_set_rate(struct clk *clk, unsigned long rate) +{ + u32 div = (clk_get_rate(clk->parent) * 16) / rate; + + __raw_writel(div, DI_BS_CLKGEN0(clk->id)); + + /* Setup pixel clock timing */ + /* FIXME: needs to be more flexible */ + /* Down time is half of period */ + __raw_writel((div / 16) << 16, DI_BS_CLKGEN1(clk->id)); + + return 0; +} + +static int _ipu_pixel_clk_enable(struct clk *clk) +{ + u32 disp_gen = __raw_readl(IPU_DISP_GEN); + disp_gen |= clk->id ? DI1_COUNTER_RELEASE : DI0_COUNTER_RELEASE; + __raw_writel(disp_gen, IPU_DISP_GEN); + + return 0; +} + +static void _ipu_pixel_clk_disable(struct clk *clk) +{ + u32 disp_gen = __raw_readl(IPU_DISP_GEN); + disp_gen &= clk->id ? ~DI1_COUNTER_RELEASE : ~DI0_COUNTER_RELEASE; + __raw_writel(disp_gen, IPU_DISP_GEN); +} + +static int _ipu_pixel_clk_set_parent(struct clk *clk, struct clk *parent) +{ + u32 di_gen = __raw_readl(DI_GENERAL(clk->id)); + + if (parent == g_ipu_clk) + di_gen &= ~DI_GEN_DI_CLK_EXT; + else if (!IS_ERR(g_di_clk[clk->id]) && parent == g_di_clk[clk->id]) + di_gen |= DI_GEN_DI_CLK_EXT; + else + return -EINVAL; + + __raw_writel(di_gen, DI_GENERAL(clk->id)); + return 0; +} + +static struct clk pixel_clk[] = { + { + .id = 0, + .get_rate = _ipu_pixel_clk_get_rate, + .set_rate = _ipu_pixel_clk_set_rate, + .round_rate = _ipu_pixel_clk_round_rate, + .set_parent = _ipu_pixel_clk_set_parent, + .enable = _ipu_pixel_clk_enable, + .disable = _ipu_pixel_clk_disable, + }, + { + .id = 1, + .get_rate = _ipu_pixel_clk_get_rate, + .set_rate = _ipu_pixel_clk_set_rate, + .round_rate = _ipu_pixel_clk_round_rate, + .set_parent = _ipu_pixel_clk_set_parent, + .enable = _ipu_pixel_clk_enable, + .disable = _ipu_pixel_clk_disable, + }, +}; + +int __initdata primary_di = { 0 }; +static int __init di_setup(char *__unused) +{ + primary_di = 1; + return 1; +} +__setup("di1_primary", di_setup); + +struct platform_device *__init imx_add_ipuv3_fb( + const struct ipuv3_fb_platform_data *pdata, int id) +{ + if (pdata) { + if (pdata->res_size > 0) { + struct resource res[] = { + { + .start = pdata->res_base, + .end = pdata->res_base + pdata->res_size - 1, + .flags = IORESOURCE_MEM, + }, + }; + + return imx_add_platform_device_dmamask("mxc_sdc_fb", + id, res, ARRAY_SIZE(res), pdata, + sizeof(*pdata), DMA_BIT_MASK(32)); + } else + return imx_add_platform_device_dmamask("mxc_sdc_fb", id, + NULL, 0, pdata, sizeof(*pdata), + DMA_BIT_MASK(32)); + } else + return imx_add_platform_device_dmamask("mxc_sdc_fb", id, + NULL, 0, NULL, 0, DMA_BIT_MASK(32)); +} + +static int __init register_fb_device(struct platform_device *pdev) +{ + struct imx_ipuv3_platform_data *plat_data = pdev->dev.platform_data; + + if (primary_di) { + dev_info(g_ipu_dev, "DI1 is primary\n"); + /* DI1 -> DP-BG channel: */ + imx_add_ipuv3_fb(plat_data->fb_head1_platform_data, 1); + /* DI0 -> DC channel: */ + imx_add_ipuv3_fb(plat_data->fb_head0_platform_data, 0); + } else { + dev_info(g_ipu_dev, "DI0 is primary\n"); + /* DI0 -> DP-BG channel: */ + imx_add_ipuv3_fb(plat_data->fb_head0_platform_data, 0); + /* DI1 -> DC channel: */ + imx_add_ipuv3_fb(plat_data->fb_head1_platform_data, 1); + } + + /* + * DI0/1 DP-FG channel: + */ + imx_add_ipuv3_fb(NULL, 2); + + return 0; +} + +/*! + * This function is called by the driver framework to initialize the IPU + * hardware. + * + * @param dev The device structure for the IPU passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int ipu_probe(struct platform_device *pdev) +{ + struct resource *res; + struct imx_ipuv3_platform_data *plat_data = pdev->dev.platform_data; + unsigned long ipu_base; + + spin_lock_init(&ipu_lock); + + g_ipu_hw_rev = plat_data->rev; + + g_ipu_dev = &pdev->dev; + + /* Register IPU interrupts */ + g_ipu_irq[0] = platform_get_irq(pdev, 0); + if (g_ipu_irq[0] < 0) + return -EINVAL; + + if (request_irq(g_ipu_irq[0], ipu_irq_handler, 0, pdev->name, 0) != 0) { + dev_err(g_ipu_dev, "request SYNC interrupt failed\n"); + return -EBUSY; + } + /* Some platforms have 2 IPU interrupts */ + g_ipu_irq[1] = platform_get_irq(pdev, 1); + if (g_ipu_irq[1] >= 0) { + if (request_irq + (g_ipu_irq[1], ipu_irq_handler, 0, pdev->name, 0) != 0) { + dev_err(g_ipu_dev, "request ERR interrupt failed\n"); + return -EBUSY; + } + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) + return -ENODEV; + + ipu_base = res->start; + if (g_ipu_hw_rev == 3) /* IPUv3M */ + ipu_base += IPUV3M_REG_BASE; + else /* IPUv3D, v3E, v3EX */ + ipu_base += IPU_REG_BASE; + + ipu_cm_reg = ioremap(ipu_base + IPU_CM_REG_BASE, PAGE_SIZE); + ipu_ic_reg = ioremap(ipu_base + IPU_IC_REG_BASE, PAGE_SIZE); + ipu_idmac_reg = ioremap(ipu_base + IPU_IDMAC_REG_BASE, PAGE_SIZE); + /* DP Registers are accessed thru the SRM */ + ipu_dp_reg = ioremap(ipu_base + IPU_SRM_REG_BASE, PAGE_SIZE); + ipu_dc_reg = ioremap(ipu_base + IPU_DC_REG_BASE, PAGE_SIZE); + ipu_dmfc_reg = ioremap(ipu_base + IPU_DMFC_REG_BASE, PAGE_SIZE); + ipu_di_reg[0] = ioremap(ipu_base + IPU_DI0_REG_BASE, PAGE_SIZE); + ipu_di_reg[1] = ioremap(ipu_base + IPU_DI1_REG_BASE, PAGE_SIZE); + ipu_smfc_reg = ioremap(ipu_base + IPU_SMFC_REG_BASE, PAGE_SIZE); + ipu_csi_reg[0] = ioremap(ipu_base + IPU_CSI0_REG_BASE, PAGE_SIZE); + ipu_csi_reg[1] = ioremap(ipu_base + IPU_CSI1_REG_BASE, PAGE_SIZE); + ipu_cpmem_base = ioremap(ipu_base + IPU_CPMEM_REG_BASE, PAGE_SIZE); + ipu_tpmem_base = ioremap(ipu_base + IPU_TPM_REG_BASE, SZ_64K); + ipu_dc_tmpl_reg = ioremap(ipu_base + IPU_DC_TMPL_REG_BASE, SZ_128K); + ipu_disp_base[1] = ioremap(ipu_base + IPU_DISP1_BASE, SZ_4K); + ipu_vdi_reg = ioremap(ipu_base + IPU_VDI_REG_BASE, PAGE_SIZE); + + dev_dbg(g_ipu_dev, "IPU VDI Regs = %p\n", ipu_vdi_reg); + dev_dbg(g_ipu_dev, "IPU CM Regs = %p\n", ipu_cm_reg); + dev_dbg(g_ipu_dev, "IPU IC Regs = %p\n", ipu_ic_reg); + dev_dbg(g_ipu_dev, "IPU IDMAC Regs = %p\n", ipu_idmac_reg); + dev_dbg(g_ipu_dev, "IPU DP Regs = %p\n", ipu_dp_reg); + dev_dbg(g_ipu_dev, "IPU DC Regs = %p\n", ipu_dc_reg); + dev_dbg(g_ipu_dev, "IPU DMFC Regs = %p\n", ipu_dmfc_reg); + dev_dbg(g_ipu_dev, "IPU DI0 Regs = %p\n", ipu_di_reg[0]); + dev_dbg(g_ipu_dev, "IPU DI1 Regs = %p\n", ipu_di_reg[1]); + dev_dbg(g_ipu_dev, "IPU SMFC Regs = %p\n", ipu_smfc_reg); + dev_dbg(g_ipu_dev, "IPU CSI0 Regs = %p\n", ipu_csi_reg[0]); + dev_dbg(g_ipu_dev, "IPU CSI1 Regs = %p\n", ipu_csi_reg[1]); + dev_dbg(g_ipu_dev, "IPU CPMem = %p\n", ipu_cpmem_base); + dev_dbg(g_ipu_dev, "IPU TPMem = %p\n", ipu_tpmem_base); + dev_dbg(g_ipu_dev, "IPU DC Template Mem = %p\n", ipu_dc_tmpl_reg); + dev_dbg(g_ipu_dev, "IPU Display Region 1 Mem = %p\n", ipu_disp_base[1]); + + g_pixel_clk[0] = &pixel_clk[0]; + g_pixel_clk[1] = &pixel_clk[1]; + + /* Enable IPU and CSI clocks */ + /* Get IPU clock freq */ + g_ipu_clk = clk_get(&pdev->dev, "ipu_clk"); + dev_dbg(g_ipu_dev, "ipu_clk = %lu\n", clk_get_rate(g_ipu_clk)); + + if (plat_data->init) + plat_data->init(); + + clk_set_parent(g_pixel_clk[0], g_ipu_clk); + clk_set_parent(g_pixel_clk[1], g_ipu_clk); + clk_enable(g_ipu_clk); + + g_di_clk[0] = clk_get(&pdev->dev, "ipu_di0_clk"); + g_di_clk[1] = clk_get(&pdev->dev, "ipu_di1_clk"); + + /*g_csi_clk[0] = plat_data->csi_clk[0]; + g_csi_clk[1] = plat_data->csi_clk[1];*/ + + __raw_writel(0x807FFFFF, IPU_MEM_RST); + while (__raw_readl(IPU_MEM_RST) & 0x80000000) + ; + + _ipu_init_dc_mappings(); + + /* Enable error interrupts by default */ + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(5)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(6)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(9)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(10)); + + /* DMFC Init */ + _ipu_dmfc_init(DMFC_NORMAL, 1); + + /* Set sync refresh channels and CSI->mem channel as high priority */ + __raw_writel(0x18800001L, IDMAC_CHA_PRI(0)); + + /* Set MCU_T to divide MCU access window into 2 */ + __raw_writel(0x00400000L | (IPU_MCU_T_DEFAULT << 18), IPU_DISP_GEN); + + clk_disable(g_ipu_clk); + + register_fb_device(pdev); + + register_ipu_device(); + + return 0; +} + +int ipu_remove(struct platform_device *pdev) +{ + if (g_ipu_irq[0]) + free_irq(g_ipu_irq[0], 0); + if (g_ipu_irq[1]) + free_irq(g_ipu_irq[1], 0); + + clk_put(g_ipu_clk); + + iounmap(ipu_cm_reg); + iounmap(ipu_ic_reg); + iounmap(ipu_idmac_reg); + iounmap(ipu_dc_reg); + iounmap(ipu_dp_reg); + iounmap(ipu_dmfc_reg); + iounmap(ipu_di_reg[0]); + iounmap(ipu_di_reg[1]); + iounmap(ipu_smfc_reg); + iounmap(ipu_csi_reg[0]); + iounmap(ipu_csi_reg[1]); + iounmap(ipu_cpmem_base); + iounmap(ipu_tpmem_base); + iounmap(ipu_dc_tmpl_reg); + iounmap(ipu_disp_base[1]); + iounmap(ipu_vdi_reg); + + return 0; +} + +void ipu_dump_registers(void) +{ + printk(KERN_DEBUG "IPU_CONF = \t0x%08X\n", __raw_readl(IPU_CONF)); + printk(KERN_DEBUG "IDMAC_CONF = \t0x%08X\n", __raw_readl(IDMAC_CONF)); + printk(KERN_DEBUG "IDMAC_CHA_EN1 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_EN(0))); + printk(KERN_DEBUG "IDMAC_CHA_EN2 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_EN(32))); + printk(KERN_DEBUG "IDMAC_CHA_PRI1 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_PRI(0))); + printk(KERN_DEBUG "IDMAC_CHA_PRI2 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_PRI(32))); + printk(KERN_DEBUG "IDMAC_BAND_EN1 = \t0x%08X\n", + __raw_readl(IDMAC_BAND_EN(0))); + printk(KERN_DEBUG "IDMAC_BAND_EN2 = \t0x%08X\n", + __raw_readl(IDMAC_BAND_EN(32))); + printk(KERN_DEBUG "IPU_CHA_DB_MODE_SEL0 = \t0x%08X\n", + __raw_readl(IPU_CHA_DB_MODE_SEL(0))); + printk(KERN_DEBUG "IPU_CHA_DB_MODE_SEL1 = \t0x%08X\n", + __raw_readl(IPU_CHA_DB_MODE_SEL(32))); + printk(KERN_DEBUG "DMFC_WR_CHAN = \t0x%08X\n", + __raw_readl(DMFC_WR_CHAN)); + printk(KERN_DEBUG "DMFC_WR_CHAN_DEF = \t0x%08X\n", + __raw_readl(DMFC_WR_CHAN_DEF)); + printk(KERN_DEBUG "DMFC_DP_CHAN = \t0x%08X\n", + __raw_readl(DMFC_DP_CHAN)); + printk(KERN_DEBUG "DMFC_DP_CHAN_DEF = \t0x%08X\n", + __raw_readl(DMFC_DP_CHAN_DEF)); + printk(KERN_DEBUG "DMFC_IC_CTRL = \t0x%08X\n", + __raw_readl(DMFC_IC_CTRL)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW1 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW1)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW2 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW2)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW3 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW3)); + printk(KERN_DEBUG "IPU_FS_DISP_FLOW1 = \t0x%08X\n", + __raw_readl(IPU_FS_DISP_FLOW1)); +} + +/*! + * This function is called to initialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to init. + * + * @param params Input parameter containing union of channel + * initialization parameters. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel(ipu_channel_t channel, ipu_channel_params_t *params) +{ + int ret = 0; + uint32_t ipu_conf; + uint32_t reg; + unsigned long lock_flags; + + dev_dbg(g_ipu_dev, "init channel = %d\n", IPU_CHAN_ID(channel)); + + /* re-enable error interrupts every time a channel is initialized */ + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(5)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(6)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(9)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(10)); + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already initialized %d\n", + IPU_CHAN_ID(channel)); + } + + ipu_conf = __raw_readl(IPU_CONF); + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + if (params->csi_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + + if (params->csi_mem.interlaced) + g_chan_is_interlaced[channel_2_dma(channel, + IPU_OUTPUT_BUFFER)] = true; + else + g_chan_is_interlaced[channel_2_dma(channel, + IPU_OUTPUT_BUFFER)] = false; + + ipu_smfc_use_count++; + g_ipu_csi_channel[params->csi_mem.csi] = channel; + + /*SMFC setting*/ + if (params->csi_mem.mipi_en) { + ipu_conf |= (1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_mem.csi)); + _ipu_smfc_init(channel, params->csi_mem.mipi_id, + params->csi_mem.csi); + } else { + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_mem.csi)); + _ipu_smfc_init(channel, 0, params->csi_mem.csi); + } + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_mem.csi); + break; + case CSI_PRP_ENC_MEM: + if (params->csi_prp_enc_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + if (using_ic_dirct_ch == MEM_VDI_PRP_VF_MEM) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = CSI_PRP_ENC_MEM; + + ipu_ic_use_count++; + g_ipu_csi_channel[params->csi_prp_enc_mem.csi] = channel; + + /*Without SMFC, CSI only support parallel data source*/ + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_prp_enc_mem.csi)); + + /*CSI0/1 feed into IC*/ + ipu_conf &= ~IPU_CONF_IC_INPUT; + if (params->csi_prp_enc_mem.csi) + ipu_conf |= IPU_CONF_CSI_SEL; + else + ipu_conf &= ~IPU_CONF_CSI_SEL; + + /*PRP skip buffer in memory, only valid when RWS_EN is true*/ + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_prp_enc_mem.csi); + _ipu_ic_init_prpenc(params, true); + break; + case CSI_PRP_VF_MEM: + if (params->csi_prp_vf_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + if (using_ic_dirct_ch == MEM_VDI_PRP_VF_MEM) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = CSI_PRP_VF_MEM; + + ipu_ic_use_count++; + g_ipu_csi_channel[params->csi_prp_vf_mem.csi] = channel; + + /*Without SMFC, CSI only support parallel data source*/ + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_prp_vf_mem.csi)); + + /*CSI0/1 feed into IC*/ + ipu_conf &= ~IPU_CONF_IC_INPUT; + if (params->csi_prp_vf_mem.csi) + ipu_conf |= IPU_CONF_CSI_SEL; + else + ipu_conf &= ~IPU_CONF_CSI_SEL; + + /*PRP skip buffer in memory, only valid when RWS_EN is true*/ + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_prp_vf_mem.csi); + _ipu_ic_init_prpvf(params, true); + break; + case MEM_PRP_VF_MEM: + ipu_ic_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg | FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + if (params->mem_prp_vf_mem.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, false); + break; + case MEM_VDI_PRP_VF_MEM: + if ((using_ic_dirct_ch == CSI_PRP_VF_MEM) || + (using_ic_dirct_ch == CSI_PRP_ENC_MEM)) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = MEM_VDI_PRP_VF_MEM; + ipu_ic_use_count++; + ipu_vdi_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + reg &= ~FS_VDI_SRC_SEL_MASK; + __raw_writel(reg , IPU_FS_PROC_FLOW1); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + _ipu_ic_init_prpvf(params, false); + _ipu_vdi_init(channel, params); + break; + case MEM_VDI_PRP_VF_MEM_P: + _ipu_vdi_init(channel, params); + break; + case MEM_VDI_PRP_VF_MEM_N: + _ipu_vdi_init(channel, params); + break; + case MEM_ROT_VF_MEM: + ipu_ic_use_count++; + ipu_rot_use_count++; + _ipu_ic_init_rotate_vf(params); + break; + case MEM_PRP_ENC_MEM: + ipu_ic_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg | FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + _ipu_ic_init_prpenc(params, false); + break; + case MEM_ROT_ENC_MEM: + ipu_ic_use_count++; + ipu_rot_use_count++; + _ipu_ic_init_rotate_enc(params); + break; + case MEM_PP_MEM: + if (params->mem_pp_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + if (params->mem_pp_mem.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + _ipu_ic_init_pp(params); + ipu_ic_use_count++; + break; + case MEM_ROT_PP_MEM: + _ipu_ic_init_rotate_pp(params); + ipu_ic_use_count++; + ipu_rot_use_count++; + break; + case MEM_DC_SYNC: + if (params->mem_dc_sync.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[1] = params->mem_dc_sync.di; + _ipu_dc_init(1, params->mem_dc_sync.di, + params->mem_dc_sync.interlaced, + params->mem_dc_sync.out_pixel_fmt); + ipu_di_use_count[params->mem_dc_sync.di]++; + ipu_dc_use_count++; + ipu_dmfc_use_count++; + break; + case MEM_BG_SYNC: + if (params->mem_dp_bg_sync.di > 1) { + ret = -EINVAL; + goto err; + } + + if (params->mem_dp_bg_sync.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + g_dc_di_assignment[5] = params->mem_dp_bg_sync.di; + _ipu_dp_init(channel, params->mem_dp_bg_sync.in_pixel_fmt, + params->mem_dp_bg_sync.out_pixel_fmt); + _ipu_dc_init(5, params->mem_dp_bg_sync.di, + params->mem_dp_bg_sync.interlaced, + params->mem_dp_bg_sync.out_pixel_fmt); + ipu_di_use_count[params->mem_dp_bg_sync.di]++; + ipu_dc_use_count++; + ipu_dp_use_count++; + ipu_dmfc_use_count++; + break; + case MEM_FG_SYNC: + _ipu_dp_init(channel, params->mem_dp_fg_sync.in_pixel_fmt, + params->mem_dp_fg_sync.out_pixel_fmt); + + if (params->mem_dp_fg_sync.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + ipu_dc_use_count++; + ipu_dp_use_count++; + ipu_dmfc_use_count++; + break; + case DIRECT_ASYNC0: + if (params->direct_async.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[8] = params->direct_async.di; + _ipu_dc_init(8, params->direct_async.di, false, IPU_PIX_FMT_GENERIC); + ipu_di_use_count[params->direct_async.di]++; + ipu_dc_use_count++; + break; + case DIRECT_ASYNC1: + if (params->direct_async.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[9] = params->direct_async.di; + _ipu_dc_init(9, params->direct_async.di, false, IPU_PIX_FMT_GENERIC); + ipu_di_use_count[params->direct_async.di]++; + ipu_dc_use_count++; + break; + default: + dev_err(g_ipu_dev, "Missing channel initialization\n"); + break; + } + + /* Enable IPU sub module */ + g_channel_init_mask |= 1L << IPU_CHAN_ID(channel); + + __raw_writel(ipu_conf, IPU_CONF); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return ret; +} +EXPORT_SYMBOL(ipu_init_channel); + +/*! + * This function is called to uninitialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to uninit. + */ +void ipu_uninit_channel(ipu_channel_t channel) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t in_dma, out_dma = 0; + uint32_t ipu_conf; + + if ((g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already uninitialized %d\n", + IPU_CHAN_ID(channel)); + return; + } + + /* Make sure channel is disabled */ + /* Get input and output dma channels */ + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + + if (idma_is_set(IDMAC_CHA_EN, in_dma) || + idma_is_set(IDMAC_CHA_EN, out_dma)) { + dev_err(g_ipu_dev, + "Channel %d is not disabled, disable first\n", + IPU_CHAN_ID(channel)); + return; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = __raw_readl(IPU_CONF); + + /* Reset the double buffer */ + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(in_dma)); + __raw_writel(reg & ~idma_mask(in_dma), IPU_CHA_DB_MODE_SEL(in_dma)); + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(out_dma)); + __raw_writel(reg & ~idma_mask(out_dma), IPU_CHA_DB_MODE_SEL(out_dma)); + + if (_ipu_is_ic_chan(in_dma) || _ipu_is_dp_graphic_chan(in_dma)) { + g_sec_chan_en[IPU_CHAN_ID(channel)] = false; + g_thrd_chan_en[IPU_CHAN_ID(channel)] = false; + } + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + ipu_smfc_use_count--; + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + } + break; + case CSI_PRP_ENC_MEM: + ipu_ic_use_count--; + if (using_ic_dirct_ch == CSI_PRP_ENC_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpenc(); + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + } + break; + case CSI_PRP_VF_MEM: + ipu_ic_use_count--; + if (using_ic_dirct_ch == CSI_PRP_VF_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpvf(); + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + } + break; + case MEM_PRP_VF_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_prpvf(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_VDI_PRP_VF_MEM: + ipu_ic_use_count--; + ipu_vdi_use_count--; + if (using_ic_dirct_ch == MEM_VDI_PRP_VF_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpvf(); + _ipu_vdi_uninit(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_VDI_PRP_VF_MEM_P: + case MEM_VDI_PRP_VF_MEM_N: + break; + case MEM_ROT_VF_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_vf(); + break; + case MEM_PRP_ENC_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_prpenc(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_ROT_ENC_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_enc(); + break; + case MEM_PP_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_pp(); + break; + case MEM_ROT_PP_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_pp(); + break; + case MEM_DC_SYNC: + _ipu_dc_uninit(1); + ipu_di_use_count[g_dc_di_assignment[1]]--; + ipu_dc_use_count--; + ipu_dmfc_use_count--; + break; + case MEM_BG_SYNC: + _ipu_dp_uninit(channel); + _ipu_dc_uninit(5); + ipu_di_use_count[g_dc_di_assignment[5]]--; + ipu_dc_use_count--; + ipu_dp_use_count--; + ipu_dmfc_use_count--; + break; + case MEM_FG_SYNC: + _ipu_dp_uninit(channel); + ipu_dc_use_count--; + ipu_dp_use_count--; + ipu_dmfc_use_count--; + break; + case DIRECT_ASYNC0: + _ipu_dc_uninit(8); + ipu_di_use_count[g_dc_di_assignment[8]]--; + ipu_dc_use_count--; + break; + case DIRECT_ASYNC1: + _ipu_dc_uninit(9); + ipu_di_use_count[g_dc_di_assignment[9]]--; + ipu_dc_use_count--; + break; + default: + break; + } + + g_channel_init_mask &= ~(1L << IPU_CHAN_ID(channel)); + + if (ipu_ic_use_count == 0) + ipu_conf &= ~IPU_CONF_IC_EN; + if (ipu_vdi_use_count == 0) { + ipu_conf &= ~IPU_CONF_ISP_EN; + ipu_conf &= ~IPU_CONF_VDI_EN; + ipu_conf &= ~IPU_CONF_IC_INPUT; + } + if (ipu_rot_use_count == 0) + ipu_conf &= ~IPU_CONF_ROT_EN; + if (ipu_dc_use_count == 0) + ipu_conf &= ~IPU_CONF_DC_EN; + if (ipu_dp_use_count == 0) + ipu_conf &= ~IPU_CONF_DP_EN; + if (ipu_dmfc_use_count == 0) + ipu_conf &= ~IPU_CONF_DMFC_EN; + if (ipu_di_use_count[0] == 0) { + ipu_conf &= ~IPU_CONF_DI0_EN; + } + if (ipu_di_use_count[1] == 0) { + ipu_conf &= ~IPU_CONF_DI1_EN; + } + if (ipu_smfc_use_count == 0) + ipu_conf &= ~IPU_CONF_SMFC_EN; + + __raw_writel(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (ipu_conf == 0) { + clk_disable(g_ipu_clk); + g_ipu_clk_enabled = false; + } + + WARN_ON(ipu_ic_use_count < 0); + WARN_ON(ipu_vdi_use_count < 0); + WARN_ON(ipu_rot_use_count < 0); + WARN_ON(ipu_dc_use_count < 0); + WARN_ON(ipu_dp_use_count < 0); + WARN_ON(ipu_dmfc_use_count < 0); + WARN_ON(ipu_smfc_use_count < 0); +} +EXPORT_SYMBOL(ipu_uninit_channel); + +/*! + * This function is called to initialize a buffer for logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param pixel_fmt Input parameter for pixel format of buffer. + * Pixel format is a FOURCC ASCII code. + * + * @param width Input parameter for width of buffer in pixels. + * + * @param height Input parameter for height of buffer in pixels. + * + * @param stride Input parameter for stride length of buffer + * in pixels. + * + * @param rot_mode Input parameter for rotation setting of buffer. + * A rotation setting other than + * IPU_ROTATE_VERT_FLIP + * should only be used for input buffers of + * rotation channels. + * + * @param phyaddr_0 Input parameter buffer 0 physical address. + * + * @param phyaddr_1 Input parameter buffer 1 physical address. + * Setting this to a value other than NULL enables + * double buffering mode. + * + * @param u private u offset for additional cropping, + * zero if not used. + * + * @param v private v offset for additional cropping, + * zero if not used. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u, uint32_t v) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t dma_chan; + uint32_t burst_size; + + dma_chan = channel_2_dma(channel, type); + if (!idma_is_valid(dma_chan)) + return -EINVAL; + + if (stride < width * bytes_per_pixel(pixel_fmt)) + stride = width * bytes_per_pixel(pixel_fmt); + + if (stride % 4) { + dev_err(g_ipu_dev, + "Stride not 32-bit aligned, stride = %d\n", stride); + return -EINVAL; + } + /* IC & IRT channels' width must be multiple of 8 pixels */ + if ((_ipu_is_ic_chan(dma_chan) || _ipu_is_irt_chan(dma_chan)) + && (width % 8)) { + dev_err(g_ipu_dev, "Width must be 8 pixel multiple\n"); + return -EINVAL; + } + + /* Build parameter memory data for DMA channel */ + _ipu_ch_param_init(dma_chan, pixel_fmt, width, height, stride, u, v, 0, + phyaddr_0, phyaddr_1); + + /* Set correlative channel parameter of local alpha channel */ + if ((_ipu_is_ic_graphic_chan(dma_chan) || + _ipu_is_dp_graphic_chan(dma_chan)) && + (g_thrd_chan_en[IPU_CHAN_ID(channel)] == true)) { + _ipu_ch_param_set_alpha_use_separate_channel(dma_chan, true); + _ipu_ch_param_set_alpha_buffer_memory(dma_chan); + _ipu_ch_param_set_alpha_condition_read(dma_chan); + /* fix alpha width as 8 and burst size as 16*/ + _ipu_ch_params_set_alpha_width(dma_chan, 8); + _ipu_ch_param_set_burst_size(dma_chan, 16); + } else if (_ipu_is_ic_graphic_chan(dma_chan) && + ipu_pixel_format_has_alpha(pixel_fmt)) + _ipu_ch_param_set_alpha_use_separate_channel(dma_chan, false); + + if (rot_mode) + _ipu_ch_param_set_rotation(dma_chan, rot_mode); + + /* IC and ROT channels have restriction of 8 or 16 pix burst length */ + if (_ipu_is_ic_chan(dma_chan)) { + if ((width % 16) == 0) + _ipu_ch_param_set_burst_size(dma_chan, 16); + else + _ipu_ch_param_set_burst_size(dma_chan, 8); + } else if (_ipu_is_irt_chan(dma_chan)) { + _ipu_ch_param_set_burst_size(dma_chan, 8); + _ipu_ch_param_set_block_mode(dma_chan); + } else if (_ipu_is_dmfc_chan(dma_chan)) { + u32 dmfc_dp_chan, dmfc_wr_chan; + /* + * non-interleaving format need enlarge burst size + * to work-around black flash issue. + */ + if (((dma_chan == 23) || (dma_chan == 27) || (dma_chan == 28)) + && ((pixel_fmt == IPU_PIX_FMT_YUV420P) || + (pixel_fmt == IPU_PIX_FMT_YVU420P) || + (pixel_fmt == IPU_PIX_FMT_YUV420P2) || + (pixel_fmt == IPU_PIX_FMT_YVU422P) || + (pixel_fmt == IPU_PIX_FMT_YUV422P) || + (pixel_fmt == IPU_PIX_FMT_NV12))) { + if (dma_chan == 23) { + dmfc_dp_chan = __raw_readl(DMFC_DP_CHAN); + dmfc_dp_chan &= ~(0xc0); + dmfc_dp_chan |= 0x40; + __raw_writel(dmfc_dp_chan, DMFC_DP_CHAN); + } else if (dma_chan == 27) { + dmfc_dp_chan = __raw_readl(DMFC_DP_CHAN); + dmfc_dp_chan &= ~(0xc000); + dmfc_dp_chan |= 0x4000; + __raw_writel(dmfc_dp_chan, DMFC_DP_CHAN); + } else if (dma_chan == 28) { + dmfc_wr_chan = __raw_readl(DMFC_WR_CHAN); + dmfc_wr_chan &= ~(0xc0); + dmfc_wr_chan |= 0x40; + __raw_writel(dmfc_wr_chan, DMFC_WR_CHAN); + } + _ipu_ch_param_set_burst_size(dma_chan, 64); + } else { + if (dma_chan == 23) { + dmfc_dp_chan = __raw_readl(DMFC_DP_CHAN); + dmfc_dp_chan &= ~(0xc0); + dmfc_dp_chan |= 0x80; + __raw_writel(dmfc_dp_chan, DMFC_DP_CHAN); + } else if (dma_chan == 27) { + dmfc_dp_chan = __raw_readl(DMFC_DP_CHAN); + dmfc_dp_chan &= ~(0xc000); + dmfc_dp_chan |= 0x8000; + __raw_writel(dmfc_dp_chan, DMFC_DP_CHAN); + } else { + dmfc_wr_chan = __raw_readl(DMFC_WR_CHAN); + dmfc_wr_chan &= ~(0xc0); + dmfc_wr_chan |= 0x80; + __raw_writel(dmfc_wr_chan, DMFC_WR_CHAN); + } + } + spin_lock_irqsave(&ipu_lock, lock_flags); + _ipu_dmfc_set_wait4eot(dma_chan, width); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } + + if (_ipu_disp_chan_is_interlaced(channel) || + g_chan_is_interlaced[dma_chan]) + _ipu_ch_param_set_interlaced_scan(dma_chan); + + if (_ipu_is_ic_chan(dma_chan) || _ipu_is_irt_chan(dma_chan)) { + burst_size = _ipu_ch_param_get_burst_size(dma_chan); + _ipu_ic_idma_init(dma_chan, width, height, burst_size, + rot_mode); + } else if (_ipu_is_smfc_chan(dma_chan)) { + burst_size = _ipu_ch_param_get_burst_size(dma_chan); + if ((pixel_fmt == IPU_PIX_FMT_GENERIC) && + ((_ipu_ch_param_get_bpp(dma_chan) == 5) || + (_ipu_ch_param_get_bpp(dma_chan) == 3))) + burst_size = burst_size >> 4; + else + burst_size = burst_size >> 2; + _ipu_smfc_set_burst_size(channel, burst_size-1); + } + + if (idma_is_set(IDMAC_CHA_PRI, dma_chan) && !cpu_is_mx53()) + _ipu_ch_param_set_high_priority(dma_chan); + + _ipu_ch_param_dump(dma_chan); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(dma_chan)); + if (phyaddr_1) + reg |= idma_mask(dma_chan); + else + reg &= ~idma_mask(dma_chan); + __raw_writel(reg, IPU_CHA_DB_MODE_SEL(dma_chan)); + + /* Reset to buffer 0 */ + __raw_writel(idma_mask(dma_chan), IPU_CHA_CUR_BUF(dma_chan)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_init_channel_buffer); + +/*! + * This function is called to update the physical address of a buffer for + * a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for buffer number to update. + * 0 or 1 are the only valid values. + * + * @param phyaddr Input parameter buffer physical address. + * + * @return This function returns 0 on success or negative error code on + * fail. This function will fail if the buffer is set to ready. + */ +int32_t ipu_update_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum, dma_addr_t phyaddr) +{ + uint32_t reg; + int ret = 0; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bufNum == 0) + reg = __raw_readl(IPU_CHA_BUF0_RDY(dma_chan)); + else + reg = __raw_readl(IPU_CHA_BUF1_RDY(dma_chan)); + + if ((reg & idma_mask(dma_chan)) == 0) + _ipu_ch_param_set_buffer(dma_chan, bufNum, phyaddr); + else + ret = -EACCES; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return ret; +} +EXPORT_SYMBOL(ipu_update_channel_buffer); + + +/*! + * This function is called to 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 u predefined private u offset for additional cropping, + * zero if not used. + * + * @param v predefined private v offset for additional cropping, + * zero if not used. + * + * @param vertical_offset vertical offset for Y coordinate + * in the existed frame + * + * + * @param horizontal_offset horizontal offset for X coordinate + * in the existed frame + * + * + * @return Returns 0 on success or negative error code on fail + * This function will fail if any buffer is set to ready. + */ + +int32_t ipu_update_channel_offset(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + uint32_t u, uint32_t v, + uint32_t vertical_offset, uint32_t horizontal_offset) +{ + int ret = 0; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if ((__raw_readl(IPU_CHA_BUF0_RDY(dma_chan)) & idma_mask(dma_chan)) || + (__raw_readl(IPU_CHA_BUF0_RDY(dma_chan)) & idma_mask(dma_chan))) + ret = -EACCES; + else + _ipu_ch_offset_update(dma_chan, pixel_fmt, width, height, stride, + u, v, 0, vertical_offset, horizontal_offset); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return ret; +} +EXPORT_SYMBOL(ipu_update_channel_offset); + + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_select_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + uint32_t dma_chan = channel_2_dma(channel, type); + uint32_t reg; + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + reg = __raw_readl(IPU_CHA_BUF0_RDY(dma_chan)); + __raw_writel(idma_mask(dma_chan) | reg, + IPU_CHA_BUF0_RDY(dma_chan)); + } else { + /*Mark buffer 1 as ready. */ + reg = __raw_readl(IPU_CHA_BUF1_RDY(dma_chan)); + __raw_writel(idma_mask(dma_chan) | reg, + IPU_CHA_BUF1_RDY(dma_chan)); + } + return 0; +} +EXPORT_SYMBOL(ipu_select_buffer); + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_select_multi_vdi_buffer(uint32_t bufNum) +{ + + uint32_t dma_chan = channel_2_dma(MEM_VDI_PRP_VF_MEM, IPU_INPUT_BUFFER); + uint32_t mask_bit = + idma_mask(channel_2_dma(MEM_VDI_PRP_VF_MEM_P, IPU_INPUT_BUFFER))| + idma_mask(dma_chan)| + idma_mask(channel_2_dma(MEM_VDI_PRP_VF_MEM_N, IPU_INPUT_BUFFER)); + uint32_t reg; + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + reg = __raw_readl(IPU_CHA_BUF0_RDY(dma_chan)); + __raw_writel(mask_bit | reg, IPU_CHA_BUF0_RDY(dma_chan)); + } else { + /*Mark buffer 1 as ready. */ + reg = __raw_readl(IPU_CHA_BUF1_RDY(dma_chan)); + __raw_writel(mask_bit | reg, IPU_CHA_BUF1_RDY(dma_chan)); + } + return 0; +} +EXPORT_SYMBOL(ipu_select_multi_vdi_buffer); + +#define NA -1 +static int proc_dest_sel[] = { + 0, 1, 1, 3, 5, 5, 4, 7, 8, 9, 10, 11, 12, 14, 15, 16, + 0, 1, 1, 5, 5, 5, 5, 5, 7, 8, 9, 10, 11, 12, 14, 31 }; +static int proc_src_sel[] = { 0, 6, 7, 6, 7, 8, 5, NA, NA, NA, + NA, NA, NA, NA, NA, 1, 2, 3, 4, 7, 8, NA, 8, NA }; +static int disp_src_sel[] = { 0, 6, 7, 8, 3, 4, 5, NA, NA, NA, + NA, NA, NA, NA, NA, 1, NA, 2, NA, 3, 4, 4, 4, 4 }; + + +/*! + * This function links 2 channels together for automatic frame + * synchronization. The output of the source channel is linked to the input of + * the destination channel. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_link_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + int retval = 0; + unsigned long lock_flags; + uint32_t fs_proc_flow1; + uint32_t fs_proc_flow2; + uint32_t fs_proc_flow3; + uint32_t fs_disp_flow1; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow1 = __raw_readl(IPU_FS_PROC_FLOW1); + fs_proc_flow2 = __raw_readl(IPU_FS_PROC_FLOW2); + fs_proc_flow3 = __raw_readl(IPU_FS_PROC_FLOW3); + fs_disp_flow1 = __raw_readl(IPU_FS_DISP_FLOW1); + + switch (src_ch) { + case CSI_MEM0: + fs_proc_flow3 &= ~FS_SMFC0_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC0_DEST_SEL_OFFSET; + break; + case CSI_MEM1: + fs_proc_flow3 &= ~FS_SMFC1_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC1_DEST_SEL_OFFSET; + break; + case CSI_MEM2: + fs_proc_flow3 &= ~FS_SMFC2_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC2_DEST_SEL_OFFSET; + break; + case CSI_MEM3: + fs_proc_flow3 &= ~FS_SMFC3_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC3_DEST_SEL_OFFSET; + break; + case CSI_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_DEST_SEL_OFFSET; + break; + case CSI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_PP_MEM: + fs_proc_flow2 &= ~FS_PP_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PP_DEST_SEL_OFFSET; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow2 &= ~FS_PP_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PP_ROT_DEST_SEL_OFFSET; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_DEST_SEL_OFFSET; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_ROT_DEST_SEL_OFFSET; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_ROT_DEST_SEL_OFFSET; + break; + default: + retval = -EINVAL; + goto err; + } + + switch (dest_ch) { + case MEM_PP_MEM: + fs_proc_flow1 &= ~FS_PP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PP_SRC_SEL_OFFSET; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow1 &= ~FS_PP_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PP_ROT_SRC_SEL_OFFSET; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow1 &= ~FS_PRPENC_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PRPENC_ROT_SRC_SEL_OFFSET; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow1 &= ~FS_PRPVF_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PRPVF_ROT_SRC_SEL_OFFSET; + break; + case MEM_DC_SYNC: + fs_disp_flow1 &= ~FS_DC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << FS_DC1_SRC_SEL_OFFSET; + break; + case MEM_BG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC0_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_SYNC0_SRC_SEL_OFFSET; + break; + case MEM_FG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_SYNC1_SRC_SEL_OFFSET; + break; + case MEM_DC_ASYNC: + fs_disp_flow1 &= ~FS_DC2_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << FS_DC2_SRC_SEL_OFFSET; + break; + case MEM_BG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC0_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_ASYNC0_SRC_SEL_OFFSET; + break; + case MEM_FG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_ASYNC1_SRC_SEL_OFFSET; + break; + default: + retval = -EINVAL; + goto err; + } + + __raw_writel(fs_proc_flow1, IPU_FS_PROC_FLOW1); + __raw_writel(fs_proc_flow2, IPU_FS_PROC_FLOW2); + __raw_writel(fs_proc_flow3, IPU_FS_PROC_FLOW3); + __raw_writel(fs_disp_flow1, IPU_FS_DISP_FLOW1); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return retval; +} +EXPORT_SYMBOL(ipu_link_channels); + +/*! + * This function unlinks 2 channels and disables automatic frame + * synchronization. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_unlink_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + int retval = 0; + unsigned long lock_flags; + uint32_t fs_proc_flow1; + uint32_t fs_proc_flow2; + uint32_t fs_proc_flow3; + uint32_t fs_disp_flow1; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow1 = __raw_readl(IPU_FS_PROC_FLOW1); + fs_proc_flow2 = __raw_readl(IPU_FS_PROC_FLOW2); + fs_proc_flow3 = __raw_readl(IPU_FS_PROC_FLOW3); + fs_disp_flow1 = __raw_readl(IPU_FS_DISP_FLOW1); + + switch (src_ch) { + case CSI_MEM0: + fs_proc_flow3 &= ~FS_SMFC0_DEST_SEL_MASK; + break; + case CSI_MEM1: + fs_proc_flow3 &= ~FS_SMFC1_DEST_SEL_MASK; + break; + case CSI_MEM2: + fs_proc_flow3 &= ~FS_SMFC2_DEST_SEL_MASK; + break; + case CSI_MEM3: + fs_proc_flow3 &= ~FS_SMFC3_DEST_SEL_MASK; + break; + case CSI_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + break; + case CSI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_PP_MEM: + fs_proc_flow2 &= ~FS_PP_DEST_SEL_MASK; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow2 &= ~FS_PP_ROT_DEST_SEL_MASK; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_ROT_DEST_SEL_MASK; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + break; + default: + retval = -EINVAL; + goto err; + } + + switch (dest_ch) { + case MEM_PP_MEM: + fs_proc_flow1 &= ~FS_PP_SRC_SEL_MASK; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow1 &= ~FS_PP_ROT_SRC_SEL_MASK; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow1 &= ~FS_PRPENC_ROT_SRC_SEL_MASK; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow1 &= ~FS_PRPVF_ROT_SRC_SEL_MASK; + break; + case MEM_DC_SYNC: + fs_disp_flow1 &= ~FS_DC1_SRC_SEL_MASK; + break; + case MEM_BG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC0_SRC_SEL_MASK; + break; + case MEM_FG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC1_SRC_SEL_MASK; + break; + case MEM_DC_ASYNC: + fs_disp_flow1 &= ~FS_DC2_SRC_SEL_MASK; + break; + case MEM_BG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC0_SRC_SEL_MASK; + break; + case MEM_FG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC1_SRC_SEL_MASK; + break; + default: + retval = -EINVAL; + goto err; + } + + __raw_writel(fs_proc_flow1, IPU_FS_PROC_FLOW1); + __raw_writel(fs_proc_flow2, IPU_FS_PROC_FLOW2); + __raw_writel(fs_proc_flow3, IPU_FS_PROC_FLOW3); + __raw_writel(fs_disp_flow1, IPU_FS_DISP_FLOW1); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return retval; +} +EXPORT_SYMBOL(ipu_unlink_channels); + +/*! + * This function check whether a logical channel was enabled. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 1 while request channel is enabled or + * 0 for not enabled. + */ +int32_t ipu_is_channel_busy(ipu_channel_t channel) +{ + uint32_t reg; + uint32_t in_dma; + uint32_t out_dma; + + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + if (reg & idma_mask(in_dma)) + return 1; + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + if (reg & idma_mask(out_dma)) + return 1; + return 0; +} +EXPORT_SYMBOL(ipu_is_channel_busy); + +/*! + * This function enables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_enable_channel(ipu_channel_t channel) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t ipu_conf; + uint32_t in_dma; + uint32_t out_dma; + uint32_t sec_dma; + uint32_t thrd_dma; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (g_channel_enable_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already enabled %d\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + ipu_conf = __raw_readl(IPU_CONF); + if (ipu_di_use_count[0] > 0) { + ipu_conf |= IPU_CONF_DI0_EN; + } + if (ipu_di_use_count[1] > 0) { + ipu_conf |= IPU_CONF_DI1_EN; + } + if (ipu_dp_use_count > 0) + ipu_conf |= IPU_CONF_DP_EN; + if (ipu_dc_use_count > 0) + ipu_conf |= IPU_CONF_DC_EN; + if (ipu_dmfc_use_count > 0) + ipu_conf |= IPU_CONF_DMFC_EN; + if (ipu_ic_use_count > 0) + ipu_conf |= IPU_CONF_IC_EN; + if (ipu_vdi_use_count > 0) { + ipu_conf |= IPU_CONF_ISP_EN; + ipu_conf |= IPU_CONF_VDI_EN; + ipu_conf |= IPU_CONF_IC_INPUT; + } + if (ipu_rot_use_count > 0) + ipu_conf |= IPU_CONF_ROT_EN; + if (ipu_smfc_use_count > 0) + ipu_conf |= IPU_CONF_SMFC_EN; + __raw_writel(ipu_conf, IPU_CONF); + + if (idma_is_valid(in_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + __raw_writel(reg | idma_mask(in_dma), IDMAC_CHA_EN(in_dma)); + } + if (idma_is_valid(out_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + __raw_writel(reg | idma_mask(out_dma), IDMAC_CHA_EN(out_dma)); + } + + if ((g_sec_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_PP_MEM) || (channel == MEM_PRP_VF_MEM) || + (channel == MEM_VDI_PRP_VF_MEM))) { + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(sec_dma)); + __raw_writel(reg | idma_mask(sec_dma), IDMAC_CHA_EN(sec_dma)); + } + if ((g_thrd_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_PP_MEM) || (channel == MEM_PRP_VF_MEM))) { + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg | idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg | idma_mask(sec_dma), IDMAC_SEP_ALPHA); + } else if ((g_thrd_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC))) { + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg | idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg | idma_mask(in_dma), IDMAC_SEP_ALPHA); + } + + if ((channel == MEM_DC_SYNC) || (channel == MEM_BG_SYNC) || + (channel == MEM_FG_SYNC)) { + reg = __raw_readl(IDMAC_WM_EN(in_dma)); + __raw_writel(reg | idma_mask(in_dma), IDMAC_WM_EN(in_dma)); + + _ipu_dp_dc_enable(channel); + } + + if (_ipu_is_ic_chan(in_dma) || _ipu_is_ic_chan(out_dma) || + _ipu_is_irt_chan(in_dma) || _ipu_is_irt_chan(out_dma)) + _ipu_ic_enable_task(channel); + + g_channel_enable_mask |= 1L << IPU_CHAN_ID(channel); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_enable_channel); + +/*! + * This function check buffer ready for a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to clear. + * + * @param bufNum Input parameter for which buffer number clear + * ready state. + * + */ +int32_t ipu_check_buffer_ready(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + uint32_t dma_chan = channel_2_dma(channel, type); + uint32_t reg; + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (bufNum == 0) + reg = __raw_readl(IPU_CHA_BUF0_RDY(dma_chan)); + else + reg = __raw_readl(IPU_CHA_BUF1_RDY(dma_chan)); + + if (reg & idma_mask(dma_chan)) + return 1; + else + return 0; +} +EXPORT_SYMBOL(ipu_check_buffer_ready); + +/*! + * This function clear buffer ready for a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to clear. + * + * @param bufNum Input parameter for which buffer number clear + * ready state. + * + */ +void ipu_clear_buffer_ready(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + unsigned long lock_flags; + uint32_t dma_ch = channel_2_dma(channel, type); + + if (!idma_is_valid(dma_ch)) + return; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel(0xF0000000, IPU_GPR); /* write one to clear */ + if (bufNum == 0) { + if (idma_is_set(IPU_CHA_BUF0_RDY, dma_ch)) { + __raw_writel(idma_mask(dma_ch), + IPU_CHA_BUF0_RDY(dma_ch)); + } + } else { + if (idma_is_set(IPU_CHA_BUF1_RDY, dma_ch)) { + __raw_writel(idma_mask(dma_ch), + IPU_CHA_BUF1_RDY(dma_ch)); + } + } + __raw_writel(0x0, IPU_GPR); /* write one to set */ + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_clear_buffer_ready); + +static irqreturn_t disable_chan_irq_handler(int irq, void *dev_id) +{ + struct completion *comp = dev_id; + + complete(comp); + return IRQ_HANDLED; +} + +/*! + * This function disables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param wait_for_stop Flag to set whether to wait for channel end + * of frame or return immediately. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_disable_channel(ipu_channel_t channel, bool wait_for_stop) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t out_dma; + uint32_t sec_dma = NO_DMA; + uint32_t thrd_dma = NO_DMA; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if ((g_channel_enable_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already disabled %d\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + if ((idma_is_valid(in_dma) && + !idma_is_set(IDMAC_CHA_EN, in_dma)) + && (idma_is_valid(out_dma) && + !idma_is_set(IDMAC_CHA_EN, out_dma))) + return -EINVAL; + + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + if (g_thrd_chan_en[IPU_CHAN_ID(channel)]) { + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + } + + if ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC) || + (channel == MEM_DC_SYNC)) { + if (channel == MEM_FG_SYNC) + ipu_disp_set_window_pos(channel, 0, 0); + + _ipu_dp_dc_disable(channel, false); + + /* + * wait for BG channel EOF then disable FG-IDMAC, + * it avoid FG NFB4EOF error. + */ + if (channel == MEM_FG_SYNC) { + int timeout = 50; + + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_BG_SYNC_EOF), + IPUIRQ_2_STATREG(IPU_IRQ_BG_SYNC_EOF)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_BG_SYNC_EOF)) & + IPUIRQ_2_MASK(IPU_IRQ_BG_SYNC_EOF)) == 0) { + msleep(10); + timeout -= 10; + if (timeout <= 0) { + dev_err(g_ipu_dev, "warning: wait for bg sync eof timeout\n"); + break; + } + } + } + } else if (wait_for_stop) { + while (idma_is_set(IDMAC_CHA_BUSY, in_dma) || + idma_is_set(IDMAC_CHA_BUSY, out_dma) || + (g_sec_chan_en[IPU_CHAN_ID(channel)] && + idma_is_set(IDMAC_CHA_BUSY, sec_dma)) || + (g_thrd_chan_en[IPU_CHAN_ID(channel)] && + idma_is_set(IDMAC_CHA_BUSY, thrd_dma))) { + uint32_t ret, irq = 0xffffffff; + DECLARE_COMPLETION_ONSTACK(disable_comp); + + if (idma_is_set(IDMAC_CHA_BUSY, out_dma)) + irq = out_dma; + if (g_sec_chan_en[IPU_CHAN_ID(channel)] && + idma_is_set(IDMAC_CHA_BUSY, sec_dma)) + irq = sec_dma; + if (g_thrd_chan_en[IPU_CHAN_ID(channel)] && + idma_is_set(IDMAC_CHA_BUSY, thrd_dma)) + irq = thrd_dma; + if (idma_is_set(IDMAC_CHA_BUSY, in_dma)) + irq = in_dma; + + if (irq == 0xffffffff) { + dev_err(g_ipu_dev, "warning: no channel busy, break\n"); + break; + } + ret = ipu_request_irq(irq, disable_chan_irq_handler, 0, NULL, &disable_comp); + if (ret < 0) { + dev_err(g_ipu_dev, "irq %d in use\n", irq); + break; + } else { + ret = wait_for_completion_timeout(&disable_comp, msecs_to_jiffies(200)); + ipu_free_irq(irq, &disable_comp); + if (ret == 0) { + ipu_dump_registers(); + dev_err(g_ipu_dev, "warning: disable ipu dma channel %d during its busy state\n", irq); + break; + } + } + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC) || + (channel == MEM_DC_SYNC)) { + reg = __raw_readl(IDMAC_WM_EN(in_dma)); + __raw_writel(reg & ~idma_mask(in_dma), IDMAC_WM_EN(in_dma)); + } + + /* Disable IC task */ + if (_ipu_is_ic_chan(in_dma) || _ipu_is_ic_chan(out_dma) || + _ipu_is_irt_chan(in_dma) || _ipu_is_irt_chan(out_dma)) + _ipu_ic_disable_task(channel); + + /* Disable DMA channel(s) */ + if (idma_is_valid(in_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + __raw_writel(reg & ~idma_mask(in_dma), IDMAC_CHA_EN(in_dma)); + __raw_writel(idma_mask(in_dma), IPU_CHA_CUR_BUF(in_dma)); + } + if (idma_is_valid(out_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + __raw_writel(reg & ~idma_mask(out_dma), IDMAC_CHA_EN(out_dma)); + __raw_writel(idma_mask(out_dma), IPU_CHA_CUR_BUF(out_dma)); + } + if (g_sec_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(sec_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(sec_dma)); + __raw_writel(reg & ~idma_mask(sec_dma), IDMAC_CHA_EN(sec_dma)); + __raw_writel(idma_mask(sec_dma), IPU_CHA_CUR_BUF(sec_dma)); + } + if (g_thrd_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(thrd_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg & ~idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) { + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg & ~idma_mask(in_dma), IDMAC_SEP_ALPHA); + } else { + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg & ~idma_mask(sec_dma), IDMAC_SEP_ALPHA); + } + __raw_writel(idma_mask(thrd_dma), IPU_CHA_CUR_BUF(thrd_dma)); + } + + g_channel_enable_mask &= ~(1L << IPU_CHAN_ID(channel)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + /* Set channel buffers NOT to be ready */ + if (idma_is_valid(in_dma)) { + ipu_clear_buffer_ready(channel, IPU_VIDEO_IN_BUFFER, 0); + ipu_clear_buffer_ready(channel, IPU_VIDEO_IN_BUFFER, 1); + } + if (idma_is_valid(out_dma)) { + ipu_clear_buffer_ready(channel, IPU_OUTPUT_BUFFER, 0); + ipu_clear_buffer_ready(channel, IPU_OUTPUT_BUFFER, 1); + } + if (g_sec_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(sec_dma)) { + ipu_clear_buffer_ready(channel, IPU_GRAPH_IN_BUFFER, 0); + ipu_clear_buffer_ready(channel, IPU_GRAPH_IN_BUFFER, 1); + } + if (g_thrd_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(thrd_dma)) { + ipu_clear_buffer_ready(channel, IPU_ALPHA_IN_BUFFER, 0); + ipu_clear_buffer_ready(channel, IPU_ALPHA_IN_BUFFER, 1); + } + + return 0; +} +EXPORT_SYMBOL(ipu_disable_channel); + +/*! + * This function enables CSI. + * + * @param csi csi num 0 or 1 + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_enable_csi(uint32_t csi) +{ + uint32_t reg; + unsigned long lock_flags; + + if (csi > 1) { + dev_err(g_ipu_dev, "Wrong csi num_%d\n", csi); + return -EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + ipu_csi_use_count[csi]++; + + if (ipu_csi_use_count[csi] == 1) { + reg = __raw_readl(IPU_CONF); + if (csi == 0) + __raw_writel(reg | IPU_CONF_CSI0_EN, IPU_CONF); + else + __raw_writel(reg | IPU_CONF_CSI1_EN, IPU_CONF); + } + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} +EXPORT_SYMBOL(ipu_enable_csi); + +/*! + * This function disables CSI. + * + * @param csi csi num 0 or 1 + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_disable_csi(uint32_t csi) +{ + uint32_t reg; + unsigned long lock_flags; + + if (csi > 1) { + dev_err(g_ipu_dev, "Wrong csi num_%d\n", csi); + return -EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + ipu_csi_use_count[csi]--; + + if (ipu_csi_use_count[csi] == 0) { + reg = __raw_readl(IPU_CONF); + if (csi == 0) + __raw_writel(reg & ~IPU_CONF_CSI0_EN, IPU_CONF); + else + __raw_writel(reg & ~IPU_CONF_CSI1_EN, IPU_CONF); + } + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} +EXPORT_SYMBOL(ipu_disable_csi); + +static irqreturn_t ipu_irq_handler(int irq, void *desc) +{ + int i; + uint32_t line; + irqreturn_t result = IRQ_NONE; + uint32_t int_stat; + const int err_reg[] = { 5, 6, 9, 10, 0 }; + const int int_reg[] = { 1, 2, 3, 4, 11, 12, 13, 14, 15, 0 }; + + for (i = 0;; i++) { + if (err_reg[i] == 0) + break; + int_stat = __raw_readl(IPU_INT_STAT(err_reg[i])); + int_stat &= __raw_readl(IPU_INT_CTRL(err_reg[i])); + if (int_stat) { + __raw_writel(int_stat, IPU_INT_STAT(err_reg[i])); + dev_err(g_ipu_dev, + "IPU Error - IPU_INT_STAT_%d = 0x%08X\n", + err_reg[i], int_stat); + /* Disable interrupts so we only get error once */ + int_stat = + __raw_readl(IPU_INT_CTRL(err_reg[i])) & ~int_stat; + __raw_writel(int_stat, IPU_INT_CTRL(err_reg[i])); + } + } + + for (i = 0;; i++) { + if (int_reg[i] == 0) + break; + int_stat = __raw_readl(IPU_INT_STAT(int_reg[i])); + int_stat &= __raw_readl(IPU_INT_CTRL(int_reg[i])); + __raw_writel(int_stat, IPU_INT_STAT(int_reg[i])); + while ((line = ffs(int_stat)) != 0) { + line--; + int_stat &= ~(1UL << line); + line += (int_reg[i] - 1) * 32; + result |= + ipu_irq_list[line].handler(line, + ipu_irq_list[line]. + dev_id); + } + } + + return result; +} + +/*! + * This function enables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to enable interrupt for. + * + */ +void ipu_enable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg |= IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_enable_irq); + +/*! + * This function disables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to disable interrupt for. + * + */ +void ipu_disable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg &= ~IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_disable_irq); + +/*! + * This function clears the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to clear interrupt for. + * + */ +void ipu_clear_irq(uint32_t irq) +{ + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + __raw_writel(IPUIRQ_2_MASK(irq), IPUIRQ_2_STATREG(irq)); + + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_clear_irq); + +/*! + * This function returns the current interrupt status for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @return Returns true if the interrupt is pending/asserted or false if + * the interrupt is not pending. + */ +bool ipu_get_irq_status(uint32_t irq) +{ + uint32_t reg; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + reg = __raw_readl(IPUIRQ_2_STATREG(irq)); + + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + if (reg & IPUIRQ_2_MASK(irq)) + return true; + else + return false; +} +EXPORT_SYMBOL(ipu_get_irq_status); + +/*! + * This function registers an interrupt handler function for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param handler Input parameter for address of the handler + * function. + * + * @param irq_flags Flags for interrupt mode. Currently not used. + * + * @param devname Input parameter for string name of driver + * registering the handler. + * + * @param dev_id Input parameter for pointer of data to be + * passed to the handler. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int ipu_request_irq(uint32_t irq, + irqreturn_t(*handler) (int, void *), + uint32_t irq_flags, const char *devname, void *dev_id) +{ + unsigned long lock_flags; + + BUG_ON(irq >= IPU_IRQ_COUNT); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (ipu_irq_list[irq].handler != NULL) { + dev_err(g_ipu_dev, + "handler already installed on irq %d\n", irq); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + ipu_irq_list[irq].handler = handler; + ipu_irq_list[irq].flags = irq_flags; + ipu_irq_list[irq].dev_id = dev_id; + ipu_irq_list[irq].name = devname; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + ipu_enable_irq(irq); /* enable the interrupt */ + + return 0; +} +EXPORT_SYMBOL(ipu_request_irq); + +/*! + * This function unregisters an interrupt handler for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. This must match value passed to + * ipu_request_irq(). + * + */ +void ipu_free_irq(uint32_t irq, void *dev_id) +{ + ipu_disable_irq(irq); /* disable the interrupt */ + + if (ipu_irq_list[irq].dev_id == dev_id) + ipu_irq_list[irq].handler = NULL; +} +EXPORT_SYMBOL(ipu_free_irq); + +uint32_t ipu_get_cur_buffer_idx(ipu_channel_t channel, ipu_buffer_t type) +{ + uint32_t reg, dma_chan; + + dma_chan = channel_2_dma(channel, type); + if (!idma_is_valid(dma_chan)) + return -EINVAL; + + reg = __raw_readl(IPU_CHA_CUR_BUF(dma_chan/32)); + if (reg & idma_mask(dma_chan)) + return 1; + else + return 0; +} +EXPORT_SYMBOL(ipu_get_cur_buffer_idx); + +uint32_t _ipu_channel_status(ipu_channel_t channel) +{ + uint32_t stat = 0; + uint32_t task_stat_reg = __raw_readl(IPU_PROC_TASK_STAT); + + switch (channel) { + case MEM_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_ROT_VF_MEM: + stat = + (task_stat_reg & TSTAT_VF_ROT_MASK) >> TSTAT_VF_ROT_OFFSET; + break; + case MEM_PRP_ENC_MEM: + stat = (task_stat_reg & TSTAT_ENC_MASK) >> TSTAT_ENC_OFFSET; + break; + case MEM_ROT_ENC_MEM: + stat = + (task_stat_reg & TSTAT_ENC_ROT_MASK) >> + TSTAT_ENC_ROT_OFFSET; + break; + case MEM_PP_MEM: + stat = (task_stat_reg & TSTAT_PP_MASK) >> TSTAT_PP_OFFSET; + break; + case MEM_ROT_PP_MEM: + stat = + (task_stat_reg & TSTAT_PP_ROT_MASK) >> TSTAT_PP_ROT_OFFSET; + break; + + default: + stat = TASK_STAT_IDLE; + break; + } + return stat; +} + +int32_t ipu_swap_channel(ipu_channel_t from_ch, ipu_channel_t to_ch) +{ + uint32_t reg; + unsigned long lock_flags; + + int from_dma = channel_2_dma(from_ch, IPU_INPUT_BUFFER); + int to_dma = channel_2_dma(to_ch, IPU_INPUT_BUFFER); + + /* enable target channel */ + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN(to_dma)); + __raw_writel(reg | idma_mask(to_dma), IDMAC_CHA_EN(to_dma)); + + g_channel_enable_mask |= 1L << IPU_CHAN_ID(to_ch); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + /* switch dp dc */ + _ipu_dp_dc_disable(from_ch, true); + + /* disable source channel */ + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN(from_dma)); + __raw_writel(reg & ~idma_mask(from_dma), IDMAC_CHA_EN(from_dma)); + __raw_writel(idma_mask(from_dma), IPU_CHA_CUR_BUF(from_dma)); + + g_channel_enable_mask &= ~(1L << IPU_CHAN_ID(from_ch)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_swap_channel); + +uint32_t bytes_per_pixel(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: /*generic data */ + case IPU_PIX_FMT_RGB332: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_YUV422P: + return 1; + break; + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + return 2; + break; + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 3; + break; + case IPU_PIX_FMT_GENERIC_32: /*generic data */ + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_ABGR32: + return 4; + break; + default: + return 1; + break; + } + return 0; +} +EXPORT_SYMBOL(bytes_per_pixel); + +ipu_color_space_t format_to_colorspace(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGB666: + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_GBR24: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_ABGR32: + case IPU_PIX_FMT_LVDS666: + case IPU_PIX_FMT_LVDS888: + return RGB; + break; + + default: + return YCbCr; + break; + } + return RGB; +} + +bool ipu_pixel_format_has_alpha(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_ABGR32: + return true; + break; + default: + return false; + break; + } + return false; +} + +void ipu_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + _ipu_dp_set_csc_coefficients(channel, param); +} +EXPORT_SYMBOL(ipu_set_csc_coefficients); + +static int ipu_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct imx_ipuv3_platform_data *plat_data = pdev->dev.platform_data; + + if (g_ipu_clk_enabled) { + /* save and disable enabled channels*/ + idma_enable_reg[0] = __raw_readl(IDMAC_CHA_EN(0)); + idma_enable_reg[1] = __raw_readl(IDMAC_CHA_EN(32)); + while ((__raw_readl(IDMAC_CHA_BUSY(0)) & idma_enable_reg[0]) + || (__raw_readl(IDMAC_CHA_BUSY(32)) & + idma_enable_reg[1])) { + /* disable channel not busy already */ + uint32_t chan_should_disable, timeout = 1000, time = 0; + + chan_should_disable = + __raw_readl(IDMAC_CHA_BUSY(0)) + ^ idma_enable_reg[0]; + __raw_writel((~chan_should_disable) & + idma_enable_reg[0], IDMAC_CHA_EN(0)); + chan_should_disable = + __raw_readl(IDMAC_CHA_BUSY(1)) + ^ idma_enable_reg[1]; + __raw_writel((~chan_should_disable) & + idma_enable_reg[1], IDMAC_CHA_EN(32)); + msleep(2); + time += 2; + if (time >= timeout) + return -1; + } + __raw_writel(0, IDMAC_CHA_EN(0)); + __raw_writel(0, IDMAC_CHA_EN(32)); + + /* save double buffer select regs */ + ipu_cha_db_mode_reg[0] = __raw_readl(IPU_CHA_DB_MODE_SEL(0)); + ipu_cha_db_mode_reg[1] = __raw_readl(IPU_CHA_DB_MODE_SEL(32)); + ipu_cha_db_mode_reg[2] = + __raw_readl(IPU_ALT_CHA_DB_MODE_SEL(0)); + ipu_cha_db_mode_reg[3] = + __raw_readl(IPU_ALT_CHA_DB_MODE_SEL(32)); + + /* save current buffer regs */ + ipu_cha_cur_buf_reg[0] = __raw_readl(IPU_CHA_CUR_BUF(0)); + ipu_cha_cur_buf_reg[1] = __raw_readl(IPU_CHA_CUR_BUF(32)); + ipu_cha_cur_buf_reg[2] = __raw_readl(IPU_ALT_CUR_BUF0); + ipu_cha_cur_buf_reg[3] = __raw_readl(IPU_ALT_CUR_BUF1); + + /* save sub-modules status and disable all */ + ic_conf_reg = __raw_readl(IC_CONF); + __raw_writel(0, IC_CONF); + ipu_conf_reg = __raw_readl(IPU_CONF); + __raw_writel(0, IPU_CONF); + + /* save buf ready regs */ + buf_ready_reg[0] = __raw_readl(IPU_CHA_BUF0_RDY(0)); + buf_ready_reg[1] = __raw_readl(IPU_CHA_BUF0_RDY(32)); + buf_ready_reg[2] = __raw_readl(IPU_CHA_BUF1_RDY(0)); + buf_ready_reg[3] = __raw_readl(IPU_CHA_BUF1_RDY(32)); + buf_ready_reg[4] = __raw_readl(IPU_ALT_CHA_BUF0_RDY(0)); + buf_ready_reg[5] = __raw_readl(IPU_ALT_CHA_BUF0_RDY(32)); + buf_ready_reg[6] = __raw_readl(IPU_ALT_CHA_BUF1_RDY(0)); + buf_ready_reg[7] = __raw_readl(IPU_ALT_CHA_BUF1_RDY(32)); + } + + if (plat_data->pg) + plat_data->pg(1); + + return 0; +} + +static int ipu_resume(struct platform_device *pdev) +{ + struct imx_ipuv3_platform_data *plat_data = pdev->dev.platform_data; + + if (plat_data->pg) + plat_data->pg(0); + + if (g_ipu_clk_enabled) { + + /* restore buf ready regs */ + __raw_writel(buf_ready_reg[0], IPU_CHA_BUF0_RDY(0)); + __raw_writel(buf_ready_reg[1], IPU_CHA_BUF0_RDY(32)); + __raw_writel(buf_ready_reg[2], IPU_CHA_BUF1_RDY(0)); + __raw_writel(buf_ready_reg[3], IPU_CHA_BUF1_RDY(32)); + __raw_writel(buf_ready_reg[4], IPU_ALT_CHA_BUF0_RDY(0)); + __raw_writel(buf_ready_reg[5], IPU_ALT_CHA_BUF0_RDY(32)); + __raw_writel(buf_ready_reg[6], IPU_ALT_CHA_BUF1_RDY(0)); + __raw_writel(buf_ready_reg[7], IPU_ALT_CHA_BUF1_RDY(32)); + + /* re-enable sub-modules*/ + __raw_writel(ipu_conf_reg, IPU_CONF); + __raw_writel(ic_conf_reg, IC_CONF); + + /* restore double buffer select regs */ + __raw_writel(ipu_cha_db_mode_reg[0], IPU_CHA_DB_MODE_SEL(0)); + __raw_writel(ipu_cha_db_mode_reg[1], IPU_CHA_DB_MODE_SEL(32)); + __raw_writel(ipu_cha_db_mode_reg[2], + IPU_ALT_CHA_DB_MODE_SEL(0)); + __raw_writel(ipu_cha_db_mode_reg[3], + IPU_ALT_CHA_DB_MODE_SEL(32)); + + /* restore current buffer select regs */ + __raw_writel(~(ipu_cha_cur_buf_reg[0]), IPU_CHA_CUR_BUF(0)); + __raw_writel(~(ipu_cha_cur_buf_reg[1]), IPU_CHA_CUR_BUF(32)); + __raw_writel(~(ipu_cha_cur_buf_reg[2]), IPU_ALT_CUR_BUF0); + __raw_writel(~(ipu_cha_cur_buf_reg[3]), IPU_ALT_CUR_BUF1); + + /* restart idma channel*/ + __raw_writel(idma_enable_reg[0], IDMAC_CHA_EN(0)); + __raw_writel(idma_enable_reg[1], IDMAC_CHA_EN(32)); + } else { + clk_enable(g_ipu_clk); + _ipu_dmfc_init(dmfc_type_setup, 1); + _ipu_init_dc_mappings(); + + /* Set sync refresh channels as high priority */ + __raw_writel(0x18800000L, IDMAC_CHA_PRI(0)); + clk_disable(g_ipu_clk); + } + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcipu_driver = { + .driver = { + .name = "imx-ipuv3", + }, + .probe = ipu_probe, + .remove = ipu_remove, + .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) +{ + platform_driver_unregister(&mxcipu_driver); +} + +module_exit(ipu_gen_uninit); diff --git a/drivers/mxc/ipu3/ipu_device.c b/drivers/mxc/ipu3/ipu_device.c new file mode 100644 index 000000000000..057ddf6e447b --- /dev/null +++ b/drivers/mxc/ipu3/ipu_device.c @@ -0,0 +1,511 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_device.c + * + * @brief This file contains the IPUv3 driver device interface and fops functions. + * + * @ingroup IPU + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* Strucutures and variables for exporting MXC IPU as device*/ + +static int mxc_ipu_major; +static struct class *mxc_ipu_class; + +DEFINE_SPINLOCK(event_lock); + +struct ipu_dev_irq_info { + wait_queue_head_t waitq; + int irq_pending; +} irq_info[480]; + +int register_ipu_device(void); + +/* Static functions */ + +int get_events(ipu_event_info *p) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&event_lock, flags); + if (irq_info[p->irq].irq_pending > 0) + irq_info[p->irq].irq_pending--; + else + ret = -1; + spin_unlock_irqrestore(&event_lock, flags); + + return ret; +} + +static irqreturn_t mxc_ipu_generic_handler(int irq, void *dev_id) +{ + irq_info[irq].irq_pending++; + + /* Wakeup any blocking user context */ + wake_up_interruptible(&(irq_info[irq].waitq)); + return IRQ_HANDLED; +} + +static int mxc_ipu_open(struct inode *inode, struct file *file) +{ + int ret = 0; + return ret; +} + +static long mxc_ipu_ioctl(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_SELECT_MULTI_VDI_BUFFER: + { + uint32_t parm; + if (copy_from_user + (&parm, (uint32_t *) arg, + sizeof(uint32_t))) + return -EFAULT; + + ret = ipu_select_multi_vdi_buffer(parm); + } + break; + case IPU_LINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) + return -EFAULT; + + ret = ipu_link_channels(link.src_ch, + link.dest_ch); + + } + break; + case IPU_UNLINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) + return -EFAULT; + + ret = ipu_unlink_channels(link.src_ch, + link.dest_ch); + + } + break; + case IPU_ENABLE_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_enable_channel(ch); + } + break; + case IPU_DISABLE_CHANNEL: + { + ipu_channel_info info; + if (copy_from_user + (&info, (ipu_channel_info *) arg, + sizeof(ipu_channel_info))) + return -EFAULT; + + ret = ipu_disable_channel(info.channel, + info.stop); + } + break; + case IPU_ENABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_enable_irq(irq); + } + break; + case IPU_DISABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_disable_irq(irq); + } + break; + case IPU_CLEAR_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_clear_irq(irq); + } + break; + case IPU_FREE_IRQ: + { + ipu_irq_info info; + + if (copy_from_user + (&info, (ipu_irq_info *) arg, + sizeof(ipu_irq_info))) + return -EFAULT; + + ipu_free_irq(info.irq, info.dev_id); + irq_info[info.irq].irq_pending = 0; + } + break; + case IPU_REQUEST_IRQ_STATUS: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ret = ipu_get_irq_status(irq); + } + break; + case IPU_REGISTER_GENERIC_ISR: + { + ipu_event_info info; + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + ret = + ipu_request_irq(info.irq, + mxc_ipu_generic_handler, + 0, "video_sink", info.dev); + if (ret == 0) + init_waitqueue_head(&(irq_info[info.irq].waitq)); + } + break; + case IPU_GET_EVENT: + /* User will have to allocate event_type + structure and pass the pointer in arg */ + { + ipu_event_info info; + int r = -1; + + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + r = get_events(&info); + if (r == -1) { + if ((file->f_flags & O_NONBLOCK) && + (irq_info[info.irq].irq_pending == 0)) + return -EAGAIN; + wait_event_interruptible_timeout(irq_info[info.irq].waitq, + (irq_info[info.irq].irq_pending != 0), 2 * HZ); + r = get_events(&info); + } + ret = -1; + if (r == 0) { + if (!copy_to_user((ipu_event_info *) arg, + &info, sizeof(ipu_event_info))) + ret = 0; + } + } + break; + case IPU_ALOC_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + info.vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(info.size), + &info.paddr, + GFP_DMA | GFP_KERNEL); + if (info.vaddr == 0) { + printk(KERN_ERR "dma alloc failed!\n"); + return -ENOBUFS; + } + if (copy_to_user((ipu_mem_info *) arg, &info, + sizeof(ipu_mem_info)) > 0) + return -EFAULT; + } + break; + case IPU_FREE_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + if (info.vaddr) + dma_free_coherent(0, PAGE_ALIGN(info.size), + info.vaddr, info.paddr); + else + return -EFAULT; + } + break; + case IPU_IS_CHAN_BUSY: + { + ipu_channel_t chan; + if (copy_from_user + (&chan, (ipu_channel_t *)arg, + sizeof(ipu_channel_t))) + return -EFAULT; + + if (ipu_is_channel_busy(chan)) + ret = 1; + else + ret = 0; + } + break; + case IPU_CALC_STRIPES_SIZE: + { + ipu_stripe_parm stripe_parm; + + if (copy_from_user (&stripe_parm, (ipu_stripe_parm *)arg, + sizeof(ipu_stripe_parm))) + return -EFAULT; + ipu_calc_stripes_sizes(stripe_parm.input_width, + stripe_parm.output_width, + stripe_parm.maximal_stripe_width, + stripe_parm.cirr, + stripe_parm.equal_stripes, + stripe_parm.input_pixelformat, + stripe_parm.output_pixelformat, + &stripe_parm.left, + &stripe_parm.right); + if (copy_to_user((ipu_stripe_parm *) arg, &stripe_parm, + sizeof(ipu_stripe_parm)) > 0) + return -EFAULT; + } + break; + case IPU_UPDATE_BUF_OFFSET: + { + ipu_buf_offset_parm offset_parm; + + if (copy_from_user (&offset_parm, (ipu_buf_offset_parm *)arg, + sizeof(ipu_buf_offset_parm))) + return -EFAULT; + ret = ipu_update_channel_offset(offset_parm.channel, + offset_parm.type, + offset_parm.pixel_fmt, + offset_parm.width, + offset_parm.height, + offset_parm.stride, + offset_parm.u_offset, + offset_parm.v_offset, + offset_parm.vertical_offset, + offset_parm.horizontal_offset); + } + break; + case IPU_CSC_UPDATE: + { + int param[5][3]; + ipu_csc_update csc; + if (copy_from_user(&csc, (void *) arg, + sizeof(ipu_csc_update))) + return -EFAULT; + if (copy_from_user(¶m[0][0], (void *) csc.param, + sizeof(param))) + return -EFAULT; + ipu_set_csc_coefficients(csc.channel, param); + } + break; + default: + break; + } + return ret; +} + +static int mxc_ipu_mmap(struct file *file, struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + printk(KERN_ERR + "mmap failed!\n"); + return -ENOBUFS; + } + return 0; +} + +static int mxc_ipu_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations mxc_ipu_fops = { + .owner = THIS_MODULE, + .open = mxc_ipu_open, + .mmap = mxc_ipu_mmap, + .release = mxc_ipu_release, + .unlocked_ioctl = mxc_ipu_ioctl, +}; + +int register_ipu_device() +{ + int ret = 0; + struct device *temp; + mxc_ipu_major = register_chrdev(0, "mxc_ipu", &mxc_ipu_fops); + if (mxc_ipu_major < 0) { + printk(KERN_ERR + "Unable to register Mxc Ipu as a char device\n"); + return mxc_ipu_major; + } + + mxc_ipu_class = class_create(THIS_MODULE, "mxc_ipu"); + if (IS_ERR(mxc_ipu_class)) { + printk(KERN_ERR "Unable to create class for Mxc Ipu\n"); + ret = PTR_ERR(mxc_ipu_class); + goto err1; + } + + temp = device_create(mxc_ipu_class, NULL, MKDEV(mxc_ipu_major, 0), + NULL, "mxc_ipu"); + + if (IS_ERR(temp)) { + printk(KERN_ERR "Unable to create class device for Mxc Ipu\n"); + ret = PTR_ERR(temp); + goto err2; + } + spin_lock_init(&event_lock); + + return ret; + +err2: + class_destroy(mxc_ipu_class); +err1: + unregister_chrdev(mxc_ipu_major, "mxc_ipu"); + return ret; + +} diff --git a/drivers/mxc/ipu3/ipu_disp.c b/drivers/mxc/ipu3/ipu_disp.c new file mode 100644 index 000000000000..01dd6a6aef30 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_disp.c @@ -0,0 +1,1847 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_disp.c + * + * @brief IPU display submodule API functions + * + * @ingroup IPU + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum csc_type_t { + RGB2YUV = 0, + YUV2RGB, + RGB2RGB, + YUV2YUV, + CSC_NONE, + CSC_NUM +}; + +struct dp_csc_param_t { + int mode; + void *coeff; +}; + +#define SYNC_WAVE 0 +#define ASYNC_SER_WAVE 6 + +/* DC display ID assignments */ +#define DC_DISP_ID_SYNC(di) (di) +#define DC_DISP_ID_SERIAL 2 +#define DC_DISP_ID_ASYNC 3 + +int dmfc_type_setup; +static int dmfc_size_28, dmfc_size_29, dmfc_size_24, dmfc_size_27, dmfc_size_23; + +void _ipu_dmfc_init(int dmfc_type, int first) +{ + u32 dmfc_wr_chan, dmfc_dp_chan; + + if (first) { + if (dmfc_type_setup > dmfc_type) + dmfc_type = dmfc_type_setup; + else + dmfc_type_setup = dmfc_type; + + /* disable DMFC-IC channel*/ + __raw_writel(0x2, DMFC_IC_CTRL); + } else if (dmfc_type_setup >= DMFC_HIGH_RESOLUTION_DC) { + printk(KERN_DEBUG "DMFC high resolution has set, will not change\n"); + return; + } else + dmfc_type_setup = dmfc_type; + + if (dmfc_type == DMFC_HIGH_RESOLUTION_DC) { + /* 1 - segment 0~3; + * 5B - segement 4, 5; + * 5F - segement 6, 7; + * 1C, 2C and 6B, 6F unused; + */ + printk(KERN_INFO "IPU DMFC DC HIGH RESOLUTION: 1(0~3), 5B(4,5), 5F(6,7)\n"); + dmfc_wr_chan = 0x00000088; + dmfc_dp_chan = 0x00009694; + dmfc_size_28 = 256*4; + dmfc_size_29 = 0; + dmfc_size_24 = 0; + dmfc_size_27 = 128*4; + dmfc_size_23 = 128*4; + } else if (dmfc_type == DMFC_HIGH_RESOLUTION_DP) { + /* 1 - segment 0, 1; + * 5B - segement 2~5; + * 5F - segement 6,7; + * 1C, 2C and 6B, 6F unused; + */ + printk(KERN_INFO "IPU DMFC DP HIGH RESOLUTION: 1(0,1), 5B(2~5), 5F(6,7)\n"); + dmfc_wr_chan = 0x00000090; + dmfc_dp_chan = 0x0000968a; + dmfc_size_28 = 128*4; + dmfc_size_29 = 0; + dmfc_size_24 = 0; + dmfc_size_27 = 128*4; + dmfc_size_23 = 256*4; + } else if (dmfc_type == DMFC_HIGH_RESOLUTION_ONLY_DP) { + /* 5B - segement 0~3; + * 5F - segement 4~7; + * 1, 1C, 2C and 6B, 6F unused; + */ + printk(KERN_INFO "IPU DMFC ONLY-DP HIGH RESOLUTION: 5B(0~3), 5F(4~7)\n"); + dmfc_wr_chan = 0x00000000; + dmfc_dp_chan = 0x00008c88; + dmfc_size_28 = 0; + dmfc_size_29 = 0; + dmfc_size_24 = 0; + dmfc_size_27 = 256*4; + dmfc_size_23 = 256*4; + } else { + /* 1 - segment 0, 1; + * 5B - segement 4, 5; + * 5F - segement 6, 7; + * 1C, 2C and 6B, 6F unused; + */ + printk(KERN_INFO "IPU DMFC NORMAL mode: 1(0~1), 5B(4,5), 5F(6,7)\n"); + dmfc_wr_chan = 0x00000090; + dmfc_dp_chan = 0x00009694; + dmfc_size_28 = 128*4; + dmfc_size_29 = 0; + dmfc_size_24 = 0; + dmfc_size_27 = 128*4; + dmfc_size_23 = 128*4; + } + __raw_writel(dmfc_wr_chan, DMFC_WR_CHAN); + __raw_writel(0x202020F6, DMFC_WR_CHAN_DEF); + __raw_writel(dmfc_dp_chan, DMFC_DP_CHAN); + /* Enable chan 5 watermark set at 5 bursts and clear at 7 bursts */ + __raw_writel(0x2020F6F6, DMFC_DP_CHAN_DEF); +} + +static int __init dmfc_setup(char *options) +{ + get_option(&options, &dmfc_type_setup); + if (dmfc_type_setup > DMFC_HIGH_RESOLUTION_ONLY_DP) + dmfc_type_setup = DMFC_HIGH_RESOLUTION_ONLY_DP; + return 1; +} +__setup("dmfc=", dmfc_setup); + +void _ipu_dmfc_set_wait4eot(int dma_chan, int width) +{ + u32 dmfc_gen1 = __raw_readl(DMFC_GENERAL1); + + if (width >= HIGH_RESOLUTION_WIDTH) { + if (dma_chan == 23) + _ipu_dmfc_init(DMFC_HIGH_RESOLUTION_DP, 0); + else if (dma_chan == 28) + _ipu_dmfc_init(DMFC_HIGH_RESOLUTION_DC, 0); + } + + if (dma_chan == 23) { /*5B*/ + if (dmfc_size_23/width > 3) + dmfc_gen1 |= 1UL << 20; + else + dmfc_gen1 &= ~(1UL << 20); + } else if (dma_chan == 24) { /*6B*/ + if (dmfc_size_24/width > 1) + dmfc_gen1 |= 1UL << 22; + else + dmfc_gen1 &= ~(1UL << 22); + } else if (dma_chan == 27) { /*5F*/ + if (dmfc_size_27/width > 2) + dmfc_gen1 |= 1UL << 21; + else + dmfc_gen1 &= ~(1UL << 21); + } else if (dma_chan == 28) { /*1*/ + if (dmfc_size_28/width > 2) + dmfc_gen1 |= 1UL << 16; + else + dmfc_gen1 &= ~(1UL << 16); + } else if (dma_chan == 29) { /*6F*/ + if (dmfc_size_29/width > 1) + dmfc_gen1 |= 1UL << 23; + else + dmfc_gen1 &= ~(1UL << 23); + } + + __raw_writel(dmfc_gen1, DMFC_GENERAL1); +} + +static void _ipu_di_data_wave_config(int di, + int wave_gen, + int access_size, int component_size) +{ + u32 reg; + reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) | + (component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET); + __raw_writel(reg, DI_DW_GEN(di, wave_gen)); +} + +static void _ipu_di_data_pin_config(int di, int wave_gen, int di_pin, int set, + int up, int down) +{ + u32 reg; + + reg = __raw_readl(DI_DW_GEN(di, wave_gen)); + reg &= ~(0x3 << (di_pin * 2)); + reg |= set << (di_pin * 2); + __raw_writel(reg, DI_DW_GEN(di, wave_gen)); + + __raw_writel((down << 16) | up, DI_DW_SET(di, wave_gen, set)); +} + +static void _ipu_di_sync_config(int di, int wave_gen, + int run_count, int run_src, + int offset_count, int offset_src, + int repeat_count, int cnt_clr_src, + int cnt_polarity_gen_en, + int cnt_polarity_clr_src, + int cnt_polarity_trigger_src, + int cnt_up, int cnt_down) +{ + u32 reg; + + if ((run_count >= 0x1000) || (offset_count >= 0x1000) || (repeat_count >= 0x1000) || + (cnt_up >= 0x400) || (cnt_down >= 0x400)) { + dev_err(g_ipu_dev, "DI%d counters out of range.\n", di); + return; + } + + reg = (run_count << 19) | (++run_src << 16) | + (offset_count << 3) | ++offset_src; + __raw_writel(reg, DI_SW_GEN0(di, wave_gen)); + reg = (cnt_polarity_gen_en << 29) | (++cnt_clr_src << 25) | + (++cnt_polarity_trigger_src << 12) | (++cnt_polarity_clr_src << 9); + reg |= (cnt_down << 16) | cnt_up; + if (repeat_count == 0) { + /* Enable auto reload */ + reg |= 0x10000000; + } + __raw_writel(reg, DI_SW_GEN1(di, wave_gen)); + reg = __raw_readl(DI_STP_REP(di, wave_gen)); + reg &= ~(0xFFFF << (16 * ((wave_gen - 1) & 0x1))); + reg |= repeat_count << (16 * ((wave_gen - 1) & 0x1)); + __raw_writel(reg, DI_STP_REP(di, wave_gen)); +} + +static void _ipu_dc_map_link(int current_map, + int base_map_0, int buf_num_0, + int base_map_1, int buf_num_1, + int base_map_2, int buf_num_2) +{ + int ptr_0 = base_map_0 * 3 + buf_num_0; + int ptr_1 = base_map_1 * 3 + buf_num_1; + int ptr_2 = base_map_2 * 3 + buf_num_2; + int ptr; + u32 reg; + ptr = (ptr_2 << 10) + (ptr_1 << 5) + ptr_0; + + reg = __raw_readl(DC_MAP_CONF_PTR(current_map)); + reg &= ~(0x1F << ((16 * (current_map & 0x1)))); + reg |= ptr << ((16 * (current_map & 0x1))); + __raw_writel(reg, DC_MAP_CONF_PTR(current_map)); +} + +static void _ipu_dc_map_config(int map, int byte_num, int offset, int mask) +{ + int ptr = map * 3 + byte_num; + u32 reg; + + reg = __raw_readl(DC_MAP_CONF_VAL(ptr)); + reg &= ~(0xFFFF << (16 * (ptr & 0x1))); + reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1)); + __raw_writel(reg, DC_MAP_CONF_VAL(ptr)); + + reg = __raw_readl(DC_MAP_CONF_PTR(map)); + reg &= ~(0x1F << ((16 * (map & 0x1)) + (5 * byte_num))); + reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num)); + __raw_writel(reg, DC_MAP_CONF_PTR(map)); +} + +static void _ipu_dc_map_clear(int map) +{ + u32 reg = __raw_readl(DC_MAP_CONF_PTR(map)); + __raw_writel(reg & ~(0xFFFF << (16 * (map & 0x1))), + DC_MAP_CONF_PTR(map)); +} + +static void _ipu_dc_write_tmpl(int word, u32 opcode, u32 operand, int map, + int wave, int glue, int sync, int stop) +{ + u32 reg; + + if (opcode == WRG) { + reg = sync; + reg |= (glue << 4); + reg |= (++wave << 11); + reg |= ((operand & 0x1FFFF) << 15); + __raw_writel(reg, ipu_dc_tmpl_reg + word * 2); + + reg = (operand >> 17); + reg |= opcode << 7; + reg |= (stop << 9); + __raw_writel(reg, ipu_dc_tmpl_reg + word * 2 + 1); + } else { + reg = sync; + reg |= (glue << 4); + reg |= (++wave << 11); + reg |= (++map << 15); + reg |= (operand << 20) & 0xFFF00000; + __raw_writel(reg, ipu_dc_tmpl_reg + word * 2); + + reg = (operand >> 12); + reg |= opcode << 4; + reg |= (stop << 9); + __raw_writel(reg, ipu_dc_tmpl_reg + word * 2 + 1); + } +} + +static void _ipu_dc_link_event(int chan, int event, int addr, int priority) +{ + u32 reg; + u32 address_shift; + if (event < DC_EVEN_UGDE0) { + reg = __raw_readl(DC_RL_CH(chan, event)); + reg &= ~(0xFFFF << (16 * (event & 0x1))); + reg |= ((addr << 8) | priority) << (16 * (event & 0x1)); + __raw_writel(reg, DC_RL_CH(chan, event)); + } else { + reg = __raw_readl(DC_UGDE_0((event - DC_EVEN_UGDE0) / 2)); + if ((event - DC_EVEN_UGDE0) & 0x1) { + reg &= ~(0x2FF << 16); + reg |= (addr << 16); + reg |= priority ? (2 << 24) : 0x0; + } else { + reg &= ~0xFC00FFFF; + if (priority) + chan = (chan >> 1) + + ((((chan & 0x1) + ((chan & 0x2) >> 1))) | (chan >> 3)); + else + chan = 0x7; + address_shift = ((event - DC_EVEN_UGDE0) >> 1) ? 7 : 8; + reg |= (addr << address_shift) | (priority << 3) | chan; + } + __raw_writel(reg, DC_UGDE_0((event - DC_EVEN_UGDE0) / 2)); + } +} + +/* Y = R * 1.200 + G * 2.343 + B * .453 + 0.250; + U = R * -.672 + G * -1.328 + B * 2.000 + 512.250.; + V = R * 2.000 + G * -1.672 + B * -.328 + 512.250.;*/ +static const int rgb2ycbcr_coeff[5][3] = { + {0x4D, 0x96, 0x1D}, + {-0x2B, -0x55, 0x80}, + {0x80, -0x6B, -0x15}, + {0x0000, 0x0200, 0x0200}, /* B0, B1, B2 */ + {0x2, 0x2, 0x2}, /* S0, S1, S2 */ +}; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ +static const int ycbcr2rgb_coeff[5][3] = { + {0x095, 0x000, 0x0CC}, + {0x095, 0x3CE, 0x398}, + {0x095, 0x0FF, 0x000}, + {0x3E42, 0x010A, 0x3DD6}, /*B0,B1,B2 */ + {0x1, 0x1, 0x1}, /*S0,S1,S2 */ +}; + +#define mask_a(a) ((u32)(a) & 0x3FF) +#define mask_b(b) ((u32)(b) & 0x3FFF) + +/* Pls keep S0, S1 and S2 as 0x2 by using this convertion */ +static int _rgb_to_yuv(int n, int red, int green, int blue) +{ + int c; + c = red * rgb2ycbcr_coeff[n][0]; + c += green * rgb2ycbcr_coeff[n][1]; + c += blue * rgb2ycbcr_coeff[n][2]; + c /= 16; + c += rgb2ycbcr_coeff[3][n] * 4; + c += 8; + c /= 16; + if (c < 0) + c = 0; + if (c > 255) + c = 255; + return c; +} + +/* + * Row is for BG: RGB2YUV YUV2RGB RGB2RGB YUV2YUV CSC_NONE + * Column is for FG: RGB2YUV YUV2RGB RGB2RGB YUV2YUV CSC_NONE + */ +static struct dp_csc_param_t dp_csc_array[CSC_NUM][CSC_NUM] = { +{{DP_COM_CONF_CSC_DEF_BOTH, &rgb2ycbcr_coeff}, {0, 0}, {0, 0}, {DP_COM_CONF_CSC_DEF_BG, &rgb2ycbcr_coeff}, {DP_COM_CONF_CSC_DEF_BG, &rgb2ycbcr_coeff} }, +{{0, 0}, {DP_COM_CONF_CSC_DEF_BOTH, &ycbcr2rgb_coeff}, {DP_COM_CONF_CSC_DEF_BG, &ycbcr2rgb_coeff}, {0, 0}, {DP_COM_CONF_CSC_DEF_BG, &ycbcr2rgb_coeff} }, +{{0, 0}, {DP_COM_CONF_CSC_DEF_FG, &ycbcr2rgb_coeff}, {0, 0}, {0, 0}, {0, 0} }, +{{DP_COM_CONF_CSC_DEF_FG, &rgb2ycbcr_coeff}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }, +{{DP_COM_CONF_CSC_DEF_FG, &rgb2ycbcr_coeff}, {DP_COM_CONF_CSC_DEF_FG, &ycbcr2rgb_coeff}, {0, 0}, {0, 0}, {0, 0} } +}; + +static enum csc_type_t fg_csc_type = CSC_NONE, bg_csc_type = CSC_NONE; +static int color_key_4rgb = 1; + +void __ipu_dp_csc_setup(int dp, struct dp_csc_param_t dp_csc_param, + bool srm_mode_update) +{ + u32 reg; + const int (*coeff)[5][3]; + + if (dp_csc_param.mode >= 0) { + reg = __raw_readl(DP_COM_CONF(dp)); + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + reg |= dp_csc_param.mode; + __raw_writel(reg, DP_COM_CONF(dp)); + } + + coeff = dp_csc_param.coeff; + + if (coeff) { + __raw_writel(mask_a((*coeff)[0][0]) | + (mask_a((*coeff)[0][1]) << 16), DP_CSC_A_0(dp)); + __raw_writel(mask_a((*coeff)[0][2]) | + (mask_a((*coeff)[1][0]) << 16), DP_CSC_A_1(dp)); + __raw_writel(mask_a((*coeff)[1][1]) | + (mask_a((*coeff)[1][2]) << 16), DP_CSC_A_2(dp)); + __raw_writel(mask_a((*coeff)[2][0]) | + (mask_a((*coeff)[2][1]) << 16), DP_CSC_A_3(dp)); + __raw_writel(mask_a((*coeff)[2][2]) | + (mask_b((*coeff)[3][0]) << 16) | + ((*coeff)[4][0] << 30), DP_CSC_0(dp)); + __raw_writel(mask_b((*coeff)[3][1]) | ((*coeff)[4][1] << 14) | + (mask_b((*coeff)[3][2]) << 16) | + ((*coeff)[4][2] << 30), DP_CSC_1(dp)); + } + + if (srm_mode_update) { + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + } +} + +int _ipu_dp_init(ipu_channel_t channel, uint32_t in_pixel_fmt, + uint32_t out_pixel_fmt) +{ + int in_fmt, out_fmt; + int dp; + int partial = false; + uint32_t reg; + + if (channel == MEM_FG_SYNC) { + dp = DP_SYNC; + partial = true; + } else if (channel == MEM_BG_SYNC) { + dp = DP_SYNC; + partial = false; + } else if (channel == MEM_BG_ASYNC0) { + dp = DP_ASYNC0; + partial = false; + } else { + return -EINVAL; + } + + in_fmt = format_to_colorspace(in_pixel_fmt); + out_fmt = format_to_colorspace(out_pixel_fmt); + + if (partial) { + if (in_fmt == RGB) { + if (out_fmt == RGB) + fg_csc_type = RGB2RGB; + else + fg_csc_type = RGB2YUV; + } else { + if (out_fmt == RGB) + fg_csc_type = YUV2RGB; + else + fg_csc_type = YUV2YUV; + } + } else { + if (in_fmt == RGB) { + if (out_fmt == RGB) + bg_csc_type = RGB2RGB; + else + bg_csc_type = RGB2YUV; + } else { + if (out_fmt == RGB) + bg_csc_type = YUV2RGB; + else + bg_csc_type = YUV2YUV; + } + } + + /* Transform color key from rgb to yuv if CSC is enabled */ + reg = __raw_readl(DP_COM_CONF(dp)); + if (color_key_4rgb && (reg & DP_COM_CONF_GWCKE) && + (((fg_csc_type == RGB2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == RGB2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2RGB) && (bg_csc_type == YUV2RGB)))) { + int red, green, blue; + int y, u, v; + uint32_t color_key = __raw_readl(DP_GRAPH_WIND_CTRL(dp)) & 0xFFFFFFL; + + dev_dbg(g_ipu_dev, "_ipu_dp_init color key 0x%x need change to yuv fmt!\n", color_key); + + red = (color_key >> 16) & 0xFF; + green = (color_key >> 8) & 0xFF; + blue = color_key & 0xFF; + + y = _rgb_to_yuv(0, red, green, blue); + u = _rgb_to_yuv(1, red, green, blue); + v = _rgb_to_yuv(2, red, green, blue); + color_key = (y << 16) | (u << 8) | v; + + reg = __raw_readl(DP_GRAPH_WIND_CTRL(dp)) & 0xFF000000L; + __raw_writel(reg | color_key, DP_GRAPH_WIND_CTRL(dp)); + color_key_4rgb = 0; + + dev_dbg(g_ipu_dev, "_ipu_dp_init color key change to yuv fmt 0x%x!\n", color_key); + } + + __ipu_dp_csc_setup(dp, dp_csc_array[bg_csc_type][fg_csc_type], true); + + return 0; +} + +void _ipu_dp_uninit(ipu_channel_t channel) +{ + int dp; + int partial = false; + + if (channel == MEM_FG_SYNC) { + dp = DP_SYNC; + partial = true; + } else if (channel == MEM_BG_SYNC) { + dp = DP_SYNC; + partial = false; + } else if (channel == MEM_BG_ASYNC0) { + dp = DP_ASYNC0; + partial = false; + } else { + return; + } + + if (partial) + fg_csc_type = CSC_NONE; + else + bg_csc_type = CSC_NONE; + + __ipu_dp_csc_setup(dp, dp_csc_array[bg_csc_type][fg_csc_type], false); +} + +void _ipu_dc_init(int dc_chan, int di, bool interlaced, uint32_t pixel_fmt) +{ + u32 reg = 0; + + if ((dc_chan == 1) || (dc_chan == 5)) { + if (interlaced) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 0, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 0, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 0, 1); + } else { + if (di) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 2, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 3, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 4, 1); + if ((pixel_fmt == IPU_PIX_FMT_YUYV) || + (pixel_fmt == IPU_PIX_FMT_UYVY) || + (pixel_fmt == IPU_PIX_FMT_YVYU) || + (pixel_fmt == IPU_PIX_FMT_VYUY)) { + _ipu_dc_link_event(dc_chan, DC_ODD_UGDE1, 9, 5); + _ipu_dc_link_event(dc_chan, DC_EVEN_UGDE1, 8, 5); + } + } else { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 5, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 6, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 7, 1); + if ((pixel_fmt == IPU_PIX_FMT_YUYV) || + (pixel_fmt == IPU_PIX_FMT_UYVY) || + (pixel_fmt == IPU_PIX_FMT_YVYU) || + (pixel_fmt == IPU_PIX_FMT_VYUY)) { + _ipu_dc_link_event(dc_chan, DC_ODD_UGDE0, 10, 5); + _ipu_dc_link_event(dc_chan, DC_EVEN_UGDE0, 11, 5); + } + } + } + _ipu_dc_link_event(dc_chan, DC_EVT_NF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR, 0, 0); + + reg = 0x2; + reg |= DC_DISP_ID_SYNC(di) << DC_WR_CH_CONF_PROG_DISP_ID_OFFSET; + reg |= di << 2; + if (interlaced) + reg |= DC_WR_CH_CONF_FIELD_MODE; + } else if ((dc_chan == 8) || (dc_chan == 9)) { + /* async channels */ + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_0, 0x64, 1); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_1, 0x64, 1); + + reg = 0x3; + reg |= DC_DISP_ID_SERIAL << DC_WR_CH_CONF_PROG_DISP_ID_OFFSET; + } + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + __raw_writel(0x00000000, DC_WR_CH_ADDR(dc_chan)); + + __raw_writel(0x00000084, DC_GEN); +} + +void _ipu_dc_uninit(int dc_chan) +{ + if ((dc_chan == 1) || (dc_chan == 5)) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR, 0, 0); + _ipu_dc_link_event(dc_chan, DC_ODD_UGDE0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVEN_UGDE0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_ODD_UGDE1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVEN_UGDE1, 0, 0); + } else if ((dc_chan == 8) || (dc_chan == 9)) { + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_R_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_R_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_R_1, 0, 0); + } +} + +int _ipu_disp_chan_is_interlaced(ipu_channel_t channel) +{ + if (channel == MEM_DC_SYNC) + return !!(__raw_readl(DC_WR_CH_CONF_1) & + DC_WR_CH_CONF_FIELD_MODE); + else if ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC)) + return !!(__raw_readl(DC_WR_CH_CONF_5) & + DC_WR_CH_CONF_FIELD_MODE); + return 0; +} + +void _ipu_dp_dc_enable(ipu_channel_t channel) +{ + int di; + uint32_t reg; + uint32_t dc_chan; + int irq = 0; + + if (channel == MEM_FG_SYNC) + irq = IPU_IRQ_DP_SF_END; + else if (channel == MEM_DC_SYNC) + dc_chan = 1; + else if (channel == MEM_BG_SYNC) + dc_chan = 5; + else + return; + + if (channel == MEM_FG_SYNC) { + /* Enable FG channel */ + reg = __raw_readl(DP_COM_CONF(DP_SYNC)); + __raw_writel(reg | DP_COM_CONF_FG_EN, DP_COM_CONF(DP_SYNC)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + return; + } + + di = g_dc_di_assignment[dc_chan]; + + /* Make sure other DC sync channel is not assigned same DI */ + reg = __raw_readl(DC_WR_CH_CONF(6 - dc_chan)); + if ((di << 2) == (reg & DC_WR_CH_CONF_PROG_DI_ID)) { + reg &= ~DC_WR_CH_CONF_PROG_DI_ID; + reg |= di ? 0 : DC_WR_CH_CONF_PROG_DI_ID; + __raw_writel(reg, DC_WR_CH_CONF(6 - dc_chan)); + } + + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + reg |= 4 << DC_WR_CH_CONF_PROG_TYPE_OFFSET; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + clk_enable(g_pixel_clk[di]); +} + +static bool dc_swap; + +static irqreturn_t dc_irq_handler(int irq, void *dev_id) +{ + struct completion *comp = dev_id; + uint32_t reg; + uint32_t dc_chan; + + if (irq == IPU_IRQ_DC_FC_1) + dc_chan = 1; + else + dc_chan = 5; + + if (!dc_swap) { + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + reg &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + reg = __raw_readl(IPU_DISP_GEN); + if (g_dc_di_assignment[dc_chan]) + reg &= ~DI1_COUNTER_RELEASE; + else + reg &= ~DI0_COUNTER_RELEASE; + __raw_writel(reg, IPU_DISP_GEN); + } + + complete(comp); + return IRQ_HANDLED; +} + +void _ipu_dp_dc_disable(ipu_channel_t channel, bool swap) +{ + int ret; + unsigned long lock_flags; + uint32_t reg; + uint32_t csc; + uint32_t dc_chan; + int irq = 0; + int timeout = 50; + DECLARE_COMPLETION_ONSTACK(dc_comp); + + dc_swap = swap; + + if (channel == MEM_DC_SYNC) { + dc_chan = 1; + irq = IPU_IRQ_DC_FC_1; + } else if (channel == MEM_BG_SYNC) { + dc_chan = 5; + irq = IPU_IRQ_DP_SF_END; + } else if (channel == MEM_FG_SYNC) { + /* Disable FG channel */ + dc_chan = 5; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(DP_COM_CONF(DP_SYNC)); + csc = reg & DP_COM_CONF_CSC_DEF_MASK; + if (csc == DP_COM_CONF_CSC_DEF_FG) + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + + reg &= ~DP_COM_CONF_FG_EN; + __raw_writel(reg, DP_COM_CONF(DP_SYNC)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_DP_SF_END), + IPUIRQ_2_STATREG(IPU_IRQ_DP_SF_END)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_DP_SF_END)) & + IPUIRQ_2_MASK(IPU_IRQ_DP_SF_END)) == 0) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + return; + } else { + return; + } + + if (!dc_swap) + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan]), + IPUIRQ_2_STATREG(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])); + ipu_clear_irq(irq); + ret = ipu_request_irq(irq, dc_irq_handler, 0, NULL, &dc_comp); + if (ret < 0) { + dev_err(g_ipu_dev, "DC irq %d in use\n", irq); + return; + } + ret = wait_for_completion_timeout(&dc_comp, msecs_to_jiffies(50)); + + dev_dbg(g_ipu_dev, "DC stop timeout - %d * 10ms\n", 5 - ret); + ipu_free_irq(irq, &dc_comp); + + if (dc_swap) { + spin_lock_irqsave(&ipu_lock, lock_flags); + /* Swap DC channel 1 and 5 settings, and disable old dc chan */ + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + __raw_writel(reg, DC_WR_CH_CONF(6 - dc_chan)); + reg &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; + reg ^= DC_WR_CH_CONF_PROG_DI_ID; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } else { + /* Clock is already off because it must be done quickly, but + we need to fix the ref count */ + clk_disable(g_pixel_clk[g_dc_di_assignment[dc_chan]]); + + if (__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])) & + IPUIRQ_2_MASK(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])) + dev_dbg(g_ipu_dev, + "VSyncPre occurred before DI%d disable\n", + g_dc_di_assignment[dc_chan]); + } +} + +void _ipu_init_dc_mappings(void) +{ + /* IPU_PIX_FMT_RGB24 */ + _ipu_dc_map_clear(0); + _ipu_dc_map_config(0, 0, 7, 0xFF); + _ipu_dc_map_config(0, 1, 15, 0xFF); + _ipu_dc_map_config(0, 2, 23, 0xFF); + + /* IPU_PIX_FMT_RGB666 */ + _ipu_dc_map_clear(1); + _ipu_dc_map_config(1, 0, 5, 0xFC); + _ipu_dc_map_config(1, 1, 11, 0xFC); + _ipu_dc_map_config(1, 2, 17, 0xFC); + + /* IPU_PIX_FMT_YUV444 */ + _ipu_dc_map_clear(2); + _ipu_dc_map_config(2, 0, 15, 0xFF); + _ipu_dc_map_config(2, 1, 23, 0xFF); + _ipu_dc_map_config(2, 2, 7, 0xFF); + + /* IPU_PIX_FMT_RGB565 */ + _ipu_dc_map_clear(3); + _ipu_dc_map_config(3, 0, 4, 0xF8); + _ipu_dc_map_config(3, 1, 10, 0xFC); + _ipu_dc_map_config(3, 2, 15, 0xF8); + + /* IPU_PIX_FMT_LVDS666 */ + _ipu_dc_map_clear(4); + _ipu_dc_map_config(4, 0, 5, 0xFC); + _ipu_dc_map_config(4, 1, 13, 0xFC); + _ipu_dc_map_config(4, 2, 21, 0xFC); + + /* IPU_PIX_FMT_VYUY 16bit width */ + _ipu_dc_map_clear(5); + _ipu_dc_map_config(5, 0, 7, 0xFF); + _ipu_dc_map_config(5, 1, 0, 0x0); + _ipu_dc_map_config(5, 2, 15, 0xFF); + _ipu_dc_map_clear(6); + _ipu_dc_map_config(6, 0, 0, 0x0); + _ipu_dc_map_config(6, 1, 7, 0xFF); + _ipu_dc_map_config(6, 2, 15, 0xFF); + + /* IPU_PIX_FMT_UYUV 16bit width */ + _ipu_dc_map_clear(7); + _ipu_dc_map_link(7, 6, 0, 6, 1, 6, 2); + _ipu_dc_map_clear(8); + _ipu_dc_map_link(8, 5, 0, 5, 1, 5, 2); + + /* IPU_PIX_FMT_YUYV 16bit width */ + _ipu_dc_map_clear(9); + _ipu_dc_map_link(9, 5, 2, 5, 1, 5, 0); + _ipu_dc_map_clear(10); + _ipu_dc_map_link(10, 5, 1, 5, 2, 5, 0); + + /* IPU_PIX_FMT_YVYU 16bit width */ + _ipu_dc_map_clear(11); + _ipu_dc_map_link(11, 5, 1, 5, 2, 5, 0); + _ipu_dc_map_clear(12); + _ipu_dc_map_link(12, 5, 2, 5, 1, 5, 0); + + /* IPU_PIX_FMT_GBR24 */ + _ipu_dc_map_clear(13); + _ipu_dc_map_link(13, 0, 2, 0, 0, 0, 1); +} + +int _ipu_pixfmt_to_map(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: + case IPU_PIX_FMT_RGB24: + return 0; + case IPU_PIX_FMT_RGB666: + return 1; + case IPU_PIX_FMT_YUV444: + return 2; + case IPU_PIX_FMT_RGB565: + return 3; + case IPU_PIX_FMT_LVDS666: + return 4; + case IPU_PIX_FMT_VYUY: + return 6; + case IPU_PIX_FMT_UYVY: + return 8; + case IPU_PIX_FMT_YUYV: + return 10; + case IPU_PIX_FMT_YVYU: + return 12; + case IPU_PIX_FMT_GBR24: + return 13; + } + + return -1; +} + +/*! + * This function sets the colorspace for of dp. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param param If it's not NULL, update the csc table + * with this parameter. + * + * @return N/A + */ +void _ipu_dp_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + int dp; + struct dp_csc_param_t dp_csc_param; + + if (channel == MEM_FG_SYNC) + dp = DP_SYNC; + else if (channel == MEM_BG_SYNC) + dp = DP_SYNC; + else if (channel == MEM_BG_ASYNC0) + dp = DP_ASYNC0; + else + return; + + dp_csc_param.mode = -1; + dp_csc_param.coeff = param; + __ipu_dp_csc_setup(dp, dp_csc_param, true); +} + +/*! + * This function is called to adapt synchronous LCD panel to IPU restriction. + * + */ +void adapt_panel_to_ipu_restricitions(uint16_t *v_start_width, + uint16_t *v_sync_width, + uint16_t *v_end_width) +{ + if (*v_end_width < 2) { + uint16_t diff = 2 - *v_end_width; + if (*v_start_width >= diff) { + *v_end_width = 2; + *v_start_width = *v_start_width - diff; + } else if (*v_sync_width > diff) { + *v_end_width = 2; + *v_sync_width = *v_sync_width - diff; + } else + dev_err(g_ipu_dev, "WARNING: try to adapt timming, but failed\n"); + dev_err(g_ipu_dev, "WARNING: adapt panel end blank lines\n"); + } +} + +/*! + * This function is called to initialize a synchronous LCD panel. + * + * @param disp The DI the panel is attached to. + * + * @param pixel_clk Desired pixel clock frequency in Hz. + * + * @param pixel_fmt Input parameter for pixel format of buffer. + * Pixel format is a FOURCC ASCII code. + * + * @param width The width of panel in pixels. + * + * @param height The height of panel in pixels. + * + * @param hStartWidth The number of pixel clocks between the HSYNC + * signal pulse and the start of valid data. + * + * @param hSyncWidth The width of the HSYNC signal in units of pixel + * clocks. + * + * @param hEndWidth The number of pixel clocks between the end of + * valid data and the HSYNC signal for next line. + * + * @param vStartWidth The number of lines between the VSYNC + * signal pulse and the start of valid data. + * + * @param vSyncWidth The width of the VSYNC signal in units of lines + * + * @param vEndWidth The number of lines between the end of valid + * data and the VSYNC signal for next frame. + * + * @param sig Bitfield of signal polarities for LCD interface. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_init_sync_panel(int disp, uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t v_sync_width, uint16_t v_end_width, + uint32_t v_to_h_sync, ipu_di_signal_cfg_t sig) +{ + unsigned long lock_flags; + uint32_t field0_offset = 0; + uint32_t field1_offset; + uint32_t reg; + uint32_t di_gen, vsync_cnt; + uint32_t div, rounded_pixel_clk; + uint32_t h_total, v_total; + int map; + struct clk *di_parent; + + dev_dbg(g_ipu_dev, "panel size = %d x %d\n", width, height); + + if ((v_sync_width == 0) || (h_sync_width == 0)) + return EINVAL; + + adapt_panel_to_ipu_restricitions(&v_start_width, &v_sync_width, &v_end_width); + h_total = width + h_sync_width + h_start_width + h_end_width; + v_total = height + v_sync_width + v_start_width + v_end_width; + + /* Init clocking */ + dev_dbg(g_ipu_dev, "pixel clk = %d\n", pixel_clk); + + /*clear DI*/ + __raw_writel((1 << 21), DI_GENERAL(disp)); + + di_parent = clk_get_parent(g_di_clk[disp]); + if (clk_get(NULL, "tve_clk") == di_parent || + clk_get(NULL, "ldb_di0_clk") == di_parent || + clk_get(NULL, "ldb_di1_clk") == di_parent) { + /* if di clk parent is tve/ldb, then keep it;*/ + dev_dbg(g_ipu_dev, "use special clk parent\n"); + clk_set_parent(g_pixel_clk[disp], g_di_clk[disp]); + } else { + /* try ipu clk first*/ + dev_dbg(g_ipu_dev, "try ipu internal clk\n"); + clk_set_parent(g_pixel_clk[disp], g_ipu_clk); + rounded_pixel_clk = clk_round_rate(g_pixel_clk[disp], pixel_clk); + /* + * we will only use 1/2 fraction for ipu clk, + * so if the clk rate is not fit, try ext clk. + */ + if (!sig.int_clk && + ((rounded_pixel_clk >= pixel_clk + pixel_clk/16) || + (rounded_pixel_clk <= pixel_clk - pixel_clk/16))) { + dev_dbg(g_ipu_dev, "try ipu ext di clk\n"); + rounded_pixel_clk = pixel_clk * 2; + while (rounded_pixel_clk < 150000000) + rounded_pixel_clk += pixel_clk * 2; + clk_set_rate(di_parent, rounded_pixel_clk); + rounded_pixel_clk = + clk_round_rate(g_di_clk[disp], pixel_clk); + clk_set_rate(g_di_clk[disp], rounded_pixel_clk); + clk_set_parent(g_pixel_clk[disp], g_di_clk[disp]); + } + } + rounded_pixel_clk = clk_round_rate(g_pixel_clk[disp], pixel_clk); + clk_set_rate(g_pixel_clk[disp], rounded_pixel_clk); + msleep(5); + /* Get integer portion of divider */ + div = clk_get_rate(clk_get_parent(g_pixel_clk[disp])) / rounded_pixel_clk; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + _ipu_di_data_wave_config(disp, SYNC_WAVE, div - 1, div - 1); + _ipu_di_data_pin_config(disp, SYNC_WAVE, DI_PIN15, 3, 0, div * 2); + + map = _ipu_pixfmt_to_map(pixel_fmt); + if (map < 0) { + dev_dbg(g_ipu_dev, "IPU_DISP: No MAP\n"); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + di_gen = __raw_readl(DI_GENERAL(disp)); + + if (sig.interlaced) { + if (g_ipu_hw_rev >= 2) { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 1, /* counter */ + h_total/2 - 1, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Field 1 VSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 2, /* counter */ + h_total - 1, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 2*div /* COUNT DOWN */ + ); + + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 3, /* counter */ + v_total*2 - 1, /* run count */ + DI_SYNC_INT_HSYNC, /* run_resolution */ + 1, /* offset */ + DI_SYNC_INT_HSYNC, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 2*div /* COUNT DOWN */ + ); + + /* Active Field ? */ + _ipu_di_sync_config( + disp, /* display */ + 4, /* counter */ + v_total/2 - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + v_start_width, /* offset */ + DI_SYNC_HSYNC, /* offset resolution */ + 2, /* repeat count */ + DI_SYNC_VSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Active Line */ + _ipu_di_sync_config( + disp, /* display */ + 5, /* counter */ + 0, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + height/2, /* repeat count */ + 4, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Field 0 VSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 6, /* counter */ + v_total - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* DC VSYNC waveform */ + vsync_cnt = 7; + _ipu_di_sync_config( + disp, /* display */ + 7, /* counter */ + v_total/2 - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 9, /* offset */ + DI_SYNC_HSYNC, /* offset resolution */ + 2, /* repeat count */ + DI_SYNC_VSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* active pixel waveform */ + _ipu_di_sync_config( + disp, /* display */ + 8, /* counter */ + 0, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + h_start_width, /* offset */ + DI_SYNC_CLK, /* offset resolution */ + width, /* repeat count */ + 5, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Second VSYNC */ + _ipu_di_sync_config( + disp, /* display */ + 9, /* counter */ + v_total - 1, /* run count */ + DI_SYNC_INT_HSYNC, /* run_resolution */ + v_total/2, /* offset */ + DI_SYNC_INT_HSYNC, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_HSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 2*div /* COUNT DOWN */ + ); + + /* set gentime select and tag sel */ + reg = __raw_readl(DI_SW_GEN1(disp, 9)); + reg &= 0x1FFFFFFF; + reg |= (3-1)<<29 | 0x00008000; + __raw_writel(reg, DI_SW_GEN1(disp, 9)); + + __raw_writel(v_total / 2 - 1, DI_SCR_CONF(disp)); + + /* set y_sel = 1 */ + di_gen |= 0x10000000; + di_gen |= DI_GEN_POLARITY_5; + di_gen |= DI_GEN_POLARITY_8; + } else { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 1, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + field1_offset = v_sync_width + v_start_width + height / 2 + + v_end_width; + if (sig.odd_field_first) { + field0_offset = field1_offset - 1; + field1_offset = 0; + } + v_total += v_start_width + v_end_width; + + /* Field 1 VSYNC waveform */ + _ipu_di_sync_config(disp, 2, v_total - 1, 1, + field0_offset, + field0_offset ? 1 : DI_SYNC_NONE, + 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, DI_SYNC_NONE, 0, 4); + + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 3, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, DI_SYNC_NONE, 0, 4); + + /* Active Field ? */ + _ipu_di_sync_config(disp, 4, + field0_offset ? + field0_offset : field1_offset - 2, + 1, v_start_width + v_sync_width, 1, 2, 2, + 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, 0); + + /* Active Line */ + _ipu_di_sync_config(disp, 5, 0, 1, + 0, DI_SYNC_NONE, + height / 2, 4, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* Field 0 VSYNC waveform */ + _ipu_di_sync_config(disp, 6, v_total - 1, 1, + 0, DI_SYNC_NONE, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* DC VSYNC waveform */ + vsync_cnt = 7; + _ipu_di_sync_config(disp, 7, 0, 1, + field1_offset, + field1_offset ? 1 : DI_SYNC_NONE, + 1, 2, 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, 0); + + /* active pixel waveform */ + _ipu_di_sync_config(disp, 8, 0, DI_SYNC_CLK, + h_sync_width + h_start_width, DI_SYNC_CLK, + width, 5, 0, DI_SYNC_NONE, DI_SYNC_NONE, + 0, 0); + + /* ??? */ + _ipu_di_sync_config(disp, 9, v_total - 1, 2, + 0, DI_SYNC_NONE, + 0, DI_SYNC_NONE, 6, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + reg = __raw_readl(DI_SW_GEN1(disp, 9)); + reg |= 0x8000; + __raw_writel(reg, DI_SW_GEN1(disp, 9)); + + __raw_writel(v_sync_width + v_start_width + + v_end_width + height / 2 - 1, DI_SCR_CONF(disp)); + } + + /* Init template microcode */ + _ipu_dc_write_tmpl(0, WROD(0), 0, map, SYNC_WAVE, 0, 8, 1); + + if (sig.Hsync_pol) + di_gen |= DI_GEN_POLARITY_3; + if (sig.Vsync_pol) + di_gen |= DI_GEN_POLARITY_2; + } else { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 1, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* Setup external (delayed) HSYNC waveform */ + _ipu_di_sync_config(disp, DI_SYNC_HSYNC, h_total - 1, + DI_SYNC_CLK, div * v_to_h_sync, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 1, DI_SYNC_NONE, + DI_SYNC_CLK, 0, h_sync_width * 2); + /* Setup VSYNC waveform */ + vsync_cnt = DI_SYNC_VSYNC; + _ipu_di_sync_config(disp, DI_SYNC_VSYNC, v_total - 1, + DI_SYNC_INT_HSYNC, 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, 1, DI_SYNC_NONE, + DI_SYNC_INT_HSYNC, 0, v_sync_width * 2); + __raw_writel(v_total - 1, DI_SCR_CONF(disp)); + + /* Setup active data waveform to sync with DC */ + _ipu_di_sync_config(disp, 4, 0, DI_SYNC_HSYNC, + v_sync_width + v_start_width, DI_SYNC_HSYNC, height, + DI_SYNC_VSYNC, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + _ipu_di_sync_config(disp, 5, 0, DI_SYNC_CLK, + h_sync_width + h_start_width, DI_SYNC_CLK, + width, 4, 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, + 0); + + /* set VGA delayed hsync/vsync no matter VGA enabled */ + if (disp) { + /* couter 7 for VGA delay HSYNC */ + _ipu_di_sync_config(disp, 7, + h_total - 1, DI_SYNC_CLK, + 18, DI_SYNC_CLK, + 0, DI_SYNC_NONE, + 1, DI_SYNC_NONE, DI_SYNC_CLK, + 0, h_sync_width * 2); + + /* couter 8 for VGA delay VSYNC */ + _ipu_di_sync_config(disp, 8, + v_total - 1, DI_SYNC_INT_HSYNC, + 1, DI_SYNC_INT_HSYNC, + 0, DI_SYNC_NONE, + 1, DI_SYNC_NONE, DI_SYNC_INT_HSYNC, + 0, v_sync_width * 2); + } + + /* reset all unused counters */ + __raw_writel(0, DI_SW_GEN0(disp, 6)); + __raw_writel(0, DI_SW_GEN1(disp, 6)); + if (!disp) { + __raw_writel(0, DI_SW_GEN0(disp, 7)); + __raw_writel(0, DI_SW_GEN1(disp, 7)); + __raw_writel(0, DI_STP_REP(disp, 7)); + __raw_writel(0, DI_SW_GEN0(disp, 8)); + __raw_writel(0, DI_SW_GEN1(disp, 8)); + __raw_writel(0, DI_STP_REP(disp, 8)); + } + __raw_writel(0, DI_SW_GEN0(disp, 9)); + __raw_writel(0, DI_SW_GEN1(disp, 9)); + __raw_writel(0, DI_STP_REP(disp, 9)); + + reg = __raw_readl(DI_STP_REP(disp, 6)); + reg &= 0x0000FFFF; + __raw_writel(reg, DI_STP_REP(disp, 6)); + + /* Init template microcode */ + if (disp) { + if ((pixel_fmt == IPU_PIX_FMT_YUYV) || + (pixel_fmt == IPU_PIX_FMT_UYVY) || + (pixel_fmt == IPU_PIX_FMT_YVYU) || + (pixel_fmt == IPU_PIX_FMT_VYUY)) { + _ipu_dc_write_tmpl(8, WROD(0), 0, (map - 1), SYNC_WAVE, 0, 5, 1); + _ipu_dc_write_tmpl(9, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1); + /* configure user events according to DISP NUM */ + __raw_writel((width - 1), DC_UGDE_3(disp)); + } + _ipu_dc_write_tmpl(2, WROD(0), 0, map, SYNC_WAVE, 8, 5, 1); + _ipu_dc_write_tmpl(3, WRG, 0, map, SYNC_WAVE, 4, 5, 1); + _ipu_dc_write_tmpl(4, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1); + } else { + if ((pixel_fmt == IPU_PIX_FMT_YUYV) || + (pixel_fmt == IPU_PIX_FMT_UYVY) || + (pixel_fmt == IPU_PIX_FMT_YVYU) || + (pixel_fmt == IPU_PIX_FMT_VYUY)) { + _ipu_dc_write_tmpl(10, WROD(0), 0, (map - 1), SYNC_WAVE, 0, 5, 1); + _ipu_dc_write_tmpl(11, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1); + /* configure user events according to DISP NUM */ + __raw_writel(width - 1, DC_UGDE_3(disp)); + } + _ipu_dc_write_tmpl(5, WROD(0), 0, map, SYNC_WAVE, 8, 5, 1); + _ipu_dc_write_tmpl(6, WRG, 0, map, SYNC_WAVE, 4, 5, 1); + _ipu_dc_write_tmpl(7, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1); + } + + if (sig.Hsync_pol) { + di_gen |= DI_GEN_POLARITY_2; + if (disp) + di_gen |= DI_GEN_POLARITY_7; + } + if (sig.Vsync_pol) { + di_gen |= DI_GEN_POLARITY_3; + if (disp) + di_gen |= DI_GEN_POLARITY_8; + } + } + /* changinc DISP_CLK polarity: it can be wrong for some applications */ + if ((pixel_fmt == IPU_PIX_FMT_YUYV) || + (pixel_fmt == IPU_PIX_FMT_UYVY) || + (pixel_fmt == IPU_PIX_FMT_YVYU) || + (pixel_fmt == IPU_PIX_FMT_VYUY)) + di_gen |= 0x00020000; + + if (!sig.clk_pol) + di_gen |= DI_GEN_POLARITY_DISP_CLK; + + __raw_writel(di_gen, DI_GENERAL(disp)); + + __raw_writel((--vsync_cnt << DI_VSYNC_SEL_OFFSET) | + 0x00000002, DI_SYNC_AS_GEN(disp)); + reg = __raw_readl(DI_POL(disp)); + reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15); + if (sig.enable_pol) + reg |= DI_POL_DRDY_POLARITY_15; + if (sig.data_pol) + reg |= DI_POL_DRDY_DATA_POLARITY; + __raw_writel(reg, DI_POL(disp)); + + __raw_writel(width, DC_DISP_CONF2(DC_DISP_ID_SYNC(disp))); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_init_sync_panel); + + +int ipu_init_async_panel(int disp, int type, uint32_t cycle_time, + uint32_t pixel_fmt, ipu_adc_sig_cfg_t sig) +{ + unsigned long lock_flags; + int map; + u32 ser_conf = 0; + u32 div; + u32 di_clk = clk_get_rate(g_ipu_clk); + + /* round up cycle_time, then calcalate the divider using scaled math */ + cycle_time += (1000000000UL / di_clk) - 1; + div = (cycle_time * (di_clk / 256UL)) / (1000000000UL / 256UL); + + map = _ipu_pixfmt_to_map(pixel_fmt); + if (map < 0) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (type == IPU_PANEL_SERIAL) { + __raw_writel((div << 24) | ((sig.ifc_width - 1) << 4), + DI_DW_GEN(disp, ASYNC_SER_WAVE)); + + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_CS, + 0, 0, (div * 2) + 1); + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_SER_CLK, + 1, div, div * 2); + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_SER_RS, + 2, 0, 0); + + _ipu_dc_write_tmpl(0x64, WROD(0), 0, map, ASYNC_SER_WAVE, 0, 0, 1); + + /* Configure DC for serial panel */ + __raw_writel(0x14, DC_DISP_CONF1(DC_DISP_ID_SERIAL)); + + if (sig.clk_pol) + ser_conf |= DI_SER_CONF_SERIAL_CLK_POL; + if (sig.data_pol) + ser_conf |= DI_SER_CONF_SERIAL_DATA_POL; + if (sig.rs_pol) + ser_conf |= DI_SER_CONF_SERIAL_RS_POL; + if (sig.cs_pol) + ser_conf |= DI_SER_CONF_SERIAL_CS_POL; + __raw_writel(ser_conf, DI_SER_CONF(disp)); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} +EXPORT_SYMBOL(ipu_init_async_panel); + +/*! + * This function sets the foreground and background plane global alpha blending + * modes. This function also sets the DP graphic plane according to the + * parameter of IPUv3 DP channel. + * + * @param channel IPUv3 DP channel + * + * @param enable Boolean to enable or disable global alpha + * blending. If disabled, local blending is used. + * + * @param alpha Global alpha value. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_global_alpha(ipu_channel_t channel, bool enable, + uint8_t alpha) +{ + uint32_t reg; + uint32_t flow; + unsigned long lock_flags; + bool bg_chan; + + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_BG_ASYNC0 || channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_BG_ASYNC1 || channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (channel == MEM_BG_SYNC || channel == MEM_BG_ASYNC0 || + channel == MEM_BG_ASYNC1) + bg_chan = true; + else + bg_chan = false; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bg_chan) { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWSEL, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWSEL, DP_COM_CONF(flow)); + } + + if (enable) { + reg = __raw_readl(DP_GRAPH_WIND_CTRL(flow)) & 0x00FFFFFFL; + __raw_writel(reg | ((uint32_t) alpha << 24), + DP_GRAPH_WIND_CTRL(flow)); + + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWAM, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWAM, DP_COM_CONF(flow)); + } + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_global_alpha); + +/*! + * This function sets the transparent color key for SDC graphic plane. + * + * @param channel Input parameter for the logical channel ID. + * + * @param enable Boolean to enable or disable color key + * + * @param colorKey 24-bit RGB color for transparent color key. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_color_key(ipu_channel_t channel, bool enable, + uint32_t color_key) +{ + uint32_t reg, flow; + int y, u, v; + int red, green, blue; + unsigned long lock_flags; + + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_BG_ASYNC0 || channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_BG_ASYNC1 || channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + color_key_4rgb = 1; + /* Transform color key from rgb to yuv if CSC is enabled */ + if (((fg_csc_type == RGB2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == RGB2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2RGB) && (bg_csc_type == YUV2RGB))) { + + dev_dbg(g_ipu_dev, "color key 0x%x need change to yuv fmt\n", color_key); + + red = (color_key >> 16) & 0xFF; + green = (color_key >> 8) & 0xFF; + blue = color_key & 0xFF; + + y = _rgb_to_yuv(0, red, green, blue); + u = _rgb_to_yuv(1, red, green, blue); + v = _rgb_to_yuv(2, red, green, blue); + color_key = (y << 16) | (u << 8) | v; + + color_key_4rgb = 0; + + dev_dbg(g_ipu_dev, "color key change to yuv fmt 0x%x\n", color_key); + } + + if (enable) { + reg = __raw_readl(DP_GRAPH_WIND_CTRL(flow)) & 0xFF000000L; + __raw_writel(reg | color_key, DP_GRAPH_WIND_CTRL(flow)); + + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWCKE, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWCKE, DP_COM_CONF(flow)); + } + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_color_key); + +/*! + * This function sets the gamma correction for DP output. + * + * @param channel Input parameter for the logical channel ID. + * + * @param enable Boolean to enable or disable gamma correction. + * + * @param constk Gamma piecewise linear approximation constk coeff. + * + * @param slopek Gamma piecewise linear approximation slopek coeff. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_gamma_correction(ipu_channel_t channel, bool enable, int constk[], int slopek[]) +{ + uint32_t reg, flow, i; + unsigned long lock_flags; + + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_BG_ASYNC0 || channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_BG_ASYNC1 || channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + spin_lock_irqsave(&ipu_lock, lock_flags); + + for (i = 0; i < 8; i++) + __raw_writel((constk[2*i] & 0x1ff) | ((constk[2*i+1] & 0x1ff) << 16), DP_GAMMA_C(flow, i)); + for (i = 0; i < 4; i++) + __raw_writel((slopek[4*i] & 0xff) | ((slopek[4*i+1] & 0xff) << 8) | + ((slopek[4*i+2] & 0xff) << 16) | ((slopek[4*i+3] & 0xff) << 24), DP_GAMMA_S(flow, i)); + + reg = __raw_readl(DP_COM_CONF(flow)); + if (enable) { + if ((bg_csc_type == RGB2YUV) || (bg_csc_type == YUV2YUV)) + reg |= DP_COM_CONF_GAMMA_YUV_EN; + else + reg &= ~DP_COM_CONF_GAMMA_YUV_EN; + __raw_writel(reg | DP_COM_CONF_GAMMA_EN, DP_COM_CONF(flow)); + } else + __raw_writel(reg & ~DP_COM_CONF_GAMMA_EN, DP_COM_CONF(flow)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_gamma_correction); + +/*! + * This function sets the window position of the foreground or background plane. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param x_pos The X coordinate position to place window at. + * The position is relative to the top left corner. + * + * @param y_pos The Y coordinate position to place window at. + * The position is relative to the top left corner. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_window_pos(ipu_channel_t channel, int16_t x_pos, + int16_t y_pos) +{ + u32 reg; + unsigned long lock_flags; + uint32_t flow = 0; + uint32_t dp_srm_shift; + + if (channel == MEM_FG_SYNC) { + flow = DP_SYNC; + dp_srm_shift = 3; + } else if (channel == MEM_FG_ASYNC0) { + flow = DP_ASYNC0; + dp_srm_shift = 5; + } else if (channel == MEM_FG_ASYNC1) { + flow = DP_ASYNC1; + dp_srm_shift = 7; + } else + return -EINVAL; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel((x_pos << 16) | y_pos, DP_FG_POS(flow)); + + if (ipu_is_channel_busy(channel)) { + /* controled by FSU if channel enabled */ + reg = __raw_readl(IPU_SRM_PRI2) & (~(0x3 << dp_srm_shift)); + reg |= (0x1 << dp_srm_shift); + __raw_writel(reg, IPU_SRM_PRI2); + } else { + /* disable auto swap, controled by MCU if channel disabled */ + reg = __raw_readl(IPU_SRM_PRI2) & (~(0x3 << dp_srm_shift)); + __raw_writel(reg, IPU_SRM_PRI2); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_window_pos); + +int32_t ipu_disp_get_window_pos(ipu_channel_t channel, int16_t *x_pos, + int16_t *y_pos) +{ + u32 reg; + unsigned long lock_flags; + uint32_t flow = 0; + + if (channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(DP_FG_POS(flow)); + + *x_pos = (reg >> 16) & 0x7FF; + *y_pos = reg & 0x7FF; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_get_window_pos); + +void ipu_disp_direct_write(ipu_channel_t channel, u32 value, u32 offset) +{ + if (channel == DIRECT_ASYNC0) + __raw_writel(value, ipu_disp_base[0] + offset); + else if (channel == DIRECT_ASYNC1) + __raw_writel(value, ipu_disp_base[1] + offset); +} +EXPORT_SYMBOL(ipu_disp_direct_write); + +void ipu_reset_disp_panel(void) +{ + uint32_t tmp; + + tmp = __raw_readl(DI_GENERAL(1)); + __raw_writel(tmp | 0x08, DI_GENERAL(1)); + msleep(10); /* tRES >= 100us */ + tmp = __raw_readl(DI_GENERAL(1)); + __raw_writel(tmp & ~0x08, DI_GENERAL(1)); + msleep(60); + + return; +} +EXPORT_SYMBOL(ipu_reset_disp_panel); diff --git a/drivers/mxc/ipu3/ipu_ic.c b/drivers/mxc/ipu3/ipu_ic.c new file mode 100644 index 000000000000..77a67bf8e9c8 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_ic.c @@ -0,0 +1,833 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum { + IC_TASK_VIEWFINDER, + IC_TASK_ENCODER, + IC_TASK_POST_PROCESSOR +}; + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format, int csc_index); +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t *resizeCoeff, + uint32_t *downsizeCoeff); + +void _ipu_vdi_set_top_field_man(bool top_field_0) +{ + uint32_t reg; + + reg = __raw_readl(VDI_C); + if (top_field_0) + reg &= ~VDI_C_TOP_FIELD_MAN_1; + else + reg |= VDI_C_TOP_FIELD_MAN_1; + __raw_writel(reg, VDI_C); +} + +void _ipu_vdi_set_motion(ipu_motion_sel motion_sel) +{ + uint32_t reg; + + reg = __raw_readl(VDI_C); + reg &= ~(VDI_C_MOT_SEL_FULL | VDI_C_MOT_SEL_MED | VDI_C_MOT_SEL_LOW); + if (motion_sel == HIGH_MOTION) + reg |= VDI_C_MOT_SEL_FULL; + else if (motion_sel == MED_MOTION) + reg |= VDI_C_MOT_SEL_MED; + else + reg |= VDI_C_MOT_SEL_LOW; + + __raw_writel(reg, VDI_C); +} + +void ic_dump_register(void) +{ + printk(KERN_DEBUG "IC_CONF = \t0x%08X\n", __raw_readl(IC_CONF)); + printk(KERN_DEBUG "IC_PRP_ENC_RSC = \t0x%08X\n", + __raw_readl(IC_PRP_ENC_RSC)); + printk(KERN_DEBUG "IC_PRP_VF_RSC = \t0x%08X\n", + __raw_readl(IC_PRP_VF_RSC)); + printk(KERN_DEBUG "IC_PP_RSC = \t0x%08X\n", __raw_readl(IC_PP_RSC)); + printk(KERN_DEBUG "IC_IDMAC_1 = \t0x%08X\n", __raw_readl(IC_IDMAC_1)); + printk(KERN_DEBUG "IC_IDMAC_2 = \t0x%08X\n", __raw_readl(IC_IDMAC_2)); + printk(KERN_DEBUG "IC_IDMAC_3 = \t0x%08X\n", __raw_readl(IC_IDMAC_3)); +} + +void _ipu_ic_enable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_VDI_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf |= IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_MEM: + ic_conf |= IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf |= IC_CONF_PP_ROT_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_disable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_VDI_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_MEM: + ic_conf &= ~IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf &= ~IC_CONF_PP_ROT_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_vdi_init(ipu_channel_t channel, ipu_channel_params_t *params) +{ + uint32_t reg; + uint32_t pixel_fmt; + + reg = ((params->mem_prp_vf_mem.in_height-1) << 16) | + (params->mem_prp_vf_mem.in_width-1); + __raw_writel(reg, VDI_FSIZE); + + /* Full motion, only vertical filter is used + Burst size is 4 accesses */ + if (params->mem_prp_vf_mem.in_pixel_fmt == + IPU_PIX_FMT_UYVY || + params->mem_prp_vf_mem.in_pixel_fmt == + IPU_PIX_FMT_YUYV) + pixel_fmt = VDI_C_CH_422; + else + pixel_fmt = VDI_C_CH_420; + + reg = __raw_readl(VDI_C); + reg |= pixel_fmt; + switch (channel) { + case MEM_VDI_PRP_VF_MEM: + reg |= VDI_C_BURST_SIZE2_4; + break; + case MEM_VDI_PRP_VF_MEM_P: + reg |= VDI_C_BURST_SIZE1_4 | VDI_C_VWM1_SET_1 | VDI_C_VWM1_CLR_2; + break; + case MEM_VDI_PRP_VF_MEM_N: + reg |= VDI_C_BURST_SIZE3_4 | VDI_C_VWM3_SET_1 | VDI_C_VWM3_CLR_2; + break; + default: + break; + } + __raw_writel(reg, VDI_C); + + if (params->mem_prp_vf_mem.field_fmt == V4L2_FIELD_INTERLACED_TB) + _ipu_vdi_set_top_field_man(false); + else if (params->mem_prp_vf_mem.field_fmt == V4L2_FIELD_INTERLACED_BT) + _ipu_vdi_set_top_field_man(true); + + _ipu_vdi_set_motion(params->mem_prp_vf_mem.motion_sel); + + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_RWS_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_vdi_uninit(void) +{ + __raw_writel(0, VDI_FSIZE); + __raw_writel(0, VDI_C); +} + +void _ipu_ic_init_prpvf(ipu_channel_params_t *params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_height, + params->mem_prp_vf_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + /* Upadeted for IC split case */ + if (!(params->mem_prp_vf_mem.outh_resize_ratio)) { + _calc_resize_coeffs(params->mem_prp_vf_mem.in_width, + params->mem_prp_vf_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + } else + reg |= params->mem_prp_vf_mem.outh_resize_ratio; + + __raw_writel(reg, IC_PRP_VF_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PRPVF_CMB; + + if (!(ic_conf & IC_CONF_PRPVF_CSC1)) { + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_VIEWFINDER, RGB, RGB, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->RGB CSC */ + } + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_g_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC2 */ + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt, 2); + ic_conf |= IC_CONF_PRPVF_CSC2; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC2 */ + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB, 2); + ic_conf |= IC_CONF_PRPVF_CSC2; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + reg = __raw_readl(IC_CMBP_1); + reg &= ~(0xff); + reg |= params->mem_prp_vf_mem.alpha; + __raw_writel(reg, IC_CMBP_1); + } else + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + + if (params->mem_prp_vf_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + __raw_writel(params->mem_prp_vf_mem.key_color, + IC_CMBP_2); + } else + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_PRPVF_CMB; + } + + if (src_is_csi) + ic_conf &= ~IC_CONF_RWS_EN; + else + ic_conf |= IC_CONF_RWS_EN; + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpvf(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPVF_EN | IC_CONF_PRPVF_CMB | + IC_CONF_PRPVF_CSC2 | IC_CONF_PRPVF_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_vf(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_vf(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PRPVF_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_prpenc(ipu_channel_params_t *params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_height, + params->mem_prp_enc_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + /* Upadeted for IC split case */ + if (!(params->mem_prp_enc_mem.outh_resize_ratio)) { + _calc_resize_coeffs(params->mem_prp_enc_mem.in_width, + params->mem_prp_enc_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + } else + reg |= params->mem_prp_enc_mem.outh_resize_ratio; + + __raw_writel(reg, IC_PRP_ENC_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_ENCODER, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PRPENC_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_ENCODER, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PRPENC_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (src_is_csi) + ic_conf &= ~IC_CONF_RWS_EN; + else + ic_conf |= IC_CONF_RWS_EN; + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpenc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_enc(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_enc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_ROT_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_pp(ipu_channel_params_t *params) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + if (!(params->mem_pp_mem.outv_resize_ratio)) { + _calc_resize_coeffs(params->mem_pp_mem.in_height, + params->mem_pp_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + } else { + reg = (params->mem_pp_mem.outv_resize_ratio) << 16; + } + + /* Setup horizontal resizing */ + /* Upadeted for IC split case */ + if (!(params->mem_pp_mem.outh_resize_ratio)) { + _calc_resize_coeffs(params->mem_pp_mem.in_width, + params->mem_pp_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + } else { + reg |= params->mem_pp_mem.outh_resize_ratio; + } + + __raw_writel(reg, IC_PP_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_pp_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PP_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PP_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PP_CMB; + + if (!(ic_conf & IC_CONF_PP_CSC1)) { + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB, 1); + ic_conf |= IC_CONF_PP_CSC1; /* Enable RGB->RGB CSC */ + } + + in_fmt = format_to_colorspace(params->mem_pp_mem.in_g_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC2 */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt, 2); + ic_conf |= IC_CONF_PP_CSC2; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC2 */ + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB, 2); + ic_conf |= IC_CONF_PP_CSC2; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + reg = __raw_readl(IC_CMBP_1); + reg &= ~(0xff00); + reg |= (params->mem_pp_mem.alpha << 8); + __raw_writel(reg, IC_CMBP_1); + } else + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + + if (params->mem_pp_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + __raw_writel(params->mem_pp_mem.key_color, + IC_CMBP_2); + } else + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_pp(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PP_EN | IC_CONF_PP_CSC1 | IC_CONF_PP_CSC2 | + IC_CONF_PP_CMB); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_pp(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_pp(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PP_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +int _ipu_ic_idma_init(int dma_chan, uint16_t width, uint16_t height, + int burst_size, ipu_rotate_mode_t rot) +{ + u32 ic_idmac_1, ic_idmac_2, ic_idmac_3; + u32 temp_rot = bitrev8(rot) >> 5; + bool need_hor_flip = false; + + if ((burst_size != 8) && (burst_size != 16)) { + dev_dbg(g_ipu_dev, "Illegal burst length for IC\n"); + return -EINVAL; + } + + width--; + height--; + + if (temp_rot & 0x2) /* Need horizontal flip */ + need_hor_flip = true; + + ic_idmac_1 = __raw_readl(IC_IDMAC_1); + ic_idmac_2 = __raw_readl(IC_IDMAC_2); + ic_idmac_3 = __raw_readl(IC_IDMAC_3); + if (dma_chan == 22) { /* PP output - CB2 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB2_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB2_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PP_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PP_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PP_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PP_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PP_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PP_WIDTH_OFFSET; + + } else if (dma_chan == 11) { /* PP Input - CB5 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB5_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB5_BURST_16; + } else if (dma_chan == 47) { /* PP Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PP_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PP_ROT_OFFSET; + } + + if (dma_chan == 12) { /* PRP Input - CB6 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB6_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB6_BURST_16; + } + + if (dma_chan == 20) { /* PRP ENC output - CB0 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB0_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB0_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PRPENC_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PRPENC_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PRPENC_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PRPENC_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PRPENC_WIDTH_OFFSET; + + } else if (dma_chan == 45) { /* PRP ENC Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPENC_ROT_OFFSET; + } + + if (dma_chan == 21) { /* PRP VF output - CB1 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB1_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB1_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PRPVF_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PRPVF_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PRPVF_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PRPVF_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PRPVF_WIDTH_OFFSET; + + } else if (dma_chan == 46) { /* PRP VF Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPVF_ROT_OFFSET; + } + + if (dma_chan == 14) { /* PRP VF graphics combining input - CB3 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB3_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB3_BURST_16; + } else if (dma_chan == 15) { /* PP graphics combining input - CB4 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB4_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB4_BURST_16; + } + + __raw_writel(ic_idmac_1, IC_IDMAC_1); + __raw_writel(ic_idmac_2, IC_IDMAC_2); + __raw_writel(ic_idmac_3, IC_IDMAC_3); + + return 0; +} + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format, int csc_index) +{ + +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ + static const uint32_t rgb2ycbcr_coeff[4][3] = { + {0x004D, 0x0096, 0x001D}, + {0x01D5, 0x01AB, 0x0080}, + {0x0080, 0x0195, 0x01EB}, + {0x0000, 0x0200, 0x0200}, /* A0, A1, A2 */ + }; + + /* transparent RGB->RGB matrix for combining + */ + static const uint32_t rgb2rgb_coeff[4][3] = { + {0x0080, 0x0000, 0x0000}, + {0x0000, 0x0080, 0x0000}, + {0x0000, 0x0000, 0x0080}, + {0x0000, 0x0000, 0x0000}, /* A0, A1, A2 */ + }; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ + static const uint32_t ycbcr2rgb_coeff[4][3] = { + {149, 0, 204}, + {149, 462, 408}, + {149, 255, 0}, + {8192 - 446, 266, 8192 - 554}, /* A0, A1, A2 */ + }; + + uint32_t param; + uint32_t *base = NULL; + + if (ic_task == IC_TASK_ENCODER) { + base = ipu_tpmem_base + 0x2008 / 4; + } else if (ic_task == IC_TASK_VIEWFINDER) { + if (csc_index == 1) + base = ipu_tpmem_base + 0x4028 / 4; + else + base = ipu_tpmem_base + 0x4040 / 4; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + if (csc_index == 1) + base = ipu_tpmem_base + 0x6060 / 4; + else + base = ipu_tpmem_base + 0x6078 / 4; + } else { + BUG(); + } + + if ((in_format == YCbCr) && (out_format == RGB)) { + /* Init CSC (YCbCr->RGB) */ + param = (ycbcr2rgb_coeff[3][0] << 27) | + (ycbcr2rgb_coeff[0][0] << 18) | + (ycbcr2rgb_coeff[1][1] << 9) | ycbcr2rgb_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 2, sat = 0 */ + param = (ycbcr2rgb_coeff[3][0] >> 5) | (2L << (40 - 32)); + __raw_writel(param, base++); + + param = (ycbcr2rgb_coeff[3][1] << 27) | + (ycbcr2rgb_coeff[0][1] << 18) | + (ycbcr2rgb_coeff[1][0] << 9) | ycbcr2rgb_coeff[2][0]; + __raw_writel(param, base++); + param = (ycbcr2rgb_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = (ycbcr2rgb_coeff[3][2] << 27) | + (ycbcr2rgb_coeff[0][2] << 18) | + (ycbcr2rgb_coeff[1][2] << 9) | ycbcr2rgb_coeff[2][1]; + __raw_writel(param, base++); + param = (ycbcr2rgb_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else if ((in_format == RGB) && (out_format == YCbCr)) { + /* Init CSC (RGB->YCbCr) */ + param = (rgb2ycbcr_coeff[3][0] << 27) | + (rgb2ycbcr_coeff[0][0] << 18) | + (rgb2ycbcr_coeff[1][1] << 9) | rgb2ycbcr_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 1, sat = 0 */ + param = (rgb2ycbcr_coeff[3][0] >> 5) | (1UL << 8); + __raw_writel(param, base++); + + param = (rgb2ycbcr_coeff[3][1] << 27) | + (rgb2ycbcr_coeff[0][1] << 18) | + (rgb2ycbcr_coeff[1][0] << 9) | rgb2ycbcr_coeff[2][0]; + __raw_writel(param, base++); + param = (rgb2ycbcr_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = (rgb2ycbcr_coeff[3][2] << 27) | + (rgb2ycbcr_coeff[0][2] << 18) | + (rgb2ycbcr_coeff[1][2] << 9) | rgb2ycbcr_coeff[2][1]; + __raw_writel(param, base++); + param = (rgb2ycbcr_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else if ((in_format == RGB) && (out_format == RGB)) { + /* Init CSC */ + param = + (rgb2rgb_coeff[3][0] << 27) | (rgb2rgb_coeff[0][0] << 18) | + (rgb2rgb_coeff[1][1] << 9) | rgb2rgb_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 2, sat = 0 */ + param = (rgb2rgb_coeff[3][0] >> 5) | (2UL << 8); + __raw_writel(param, base++); + + param = + (rgb2rgb_coeff[3][1] << 27) | (rgb2rgb_coeff[0][1] << 18) | + (rgb2rgb_coeff[1][0] << 9) | rgb2rgb_coeff[2][0]; + __raw_writel(param, base++); + param = (rgb2rgb_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = + (rgb2rgb_coeff[3][2] << 27) | (rgb2rgb_coeff[0][2] << 18) | + (rgb2rgb_coeff[1][2] << 9) | rgb2rgb_coeff[2][1]; + __raw_writel(param, base++); + param = (rgb2rgb_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else { + dev_err(g_ipu_dev, "Unsupported color space conversion\n"); + } +} + +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t *resizeCoeff, + uint32_t *downsizeCoeff) +{ + uint32_t tempSize; + uint32_t tempDownsize; + + /* Input size cannot be more than 4096 */ + /* Output size cannot be more than 1024 */ + if ((inSize > 4096) || (outSize > 1024)) + return false; + + /* Cannot downsize more than 8:1 */ + if ((outSize << 3) < inSize) + return false; + + /* Compute downsizing coefficient */ + /* Output of downsizing unit cannot be more than 1024 */ + tempDownsize = 0; + tempSize = inSize; + while (((tempSize > 1024) || (tempSize >= outSize * 2)) && + (tempDownsize < 2)) { + tempSize >>= 1; + tempDownsize++; + } + *downsizeCoeff = tempDownsize; + + /* compute resizing coefficient using the following equation: + resizeCoeff = M*(SI -1)/(SO - 1) + where M = 2^13, SI - input size, SO - output size */ + *resizeCoeff = (8192L * (tempSize - 1)) / (outSize - 1); + if (*resizeCoeff >= 16384L) { + dev_err(g_ipu_dev, "Warning! Overflow on resize coeff.\n"); + *resizeCoeff = 0x3FFF; + } + + dev_dbg(g_ipu_dev, "resizing from %u -> %u pixels, " + "downsize=%u, resize=%u.%lu (reg=%u)\n", inSize, outSize, + *downsizeCoeff, (*resizeCoeff >= 8192L) ? 1 : 0, + ((*resizeCoeff & 0x1FFF) * 10000L) / 8192L, *resizeCoeff); + + return true; +} + +void _ipu_vdi_toggle_top_field_man() +{ + uint32_t reg; + uint32_t mask_reg; + + reg = __raw_readl(VDI_C); + mask_reg = reg & VDI_C_TOP_FIELD_MAN_1; + if (mask_reg == VDI_C_TOP_FIELD_MAN_1) + reg &= ~VDI_C_TOP_FIELD_MAN_1; + else + reg |= VDI_C_TOP_FIELD_MAN_1; + + __raw_writel(reg, VDI_C); +} + diff --git a/drivers/mxc/ipu3/ipu_param_mem.h b/drivers/mxc/ipu3/ipu_param_mem.h new file mode 100644 index 000000000000..00ce4ea6c60c --- /dev/null +++ b/drivers/mxc/ipu3/ipu_param_mem.h @@ -0,0 +1,604 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include + +extern u32 *ipu_cpmem_base; + +struct ipu_ch_param_word { + uint32_t data[5]; + uint32_t res[3]; +}; + +struct ipu_ch_param { + struct ipu_ch_param_word word[2]; +}; + +#define ipu_ch_param_addr(ch) (((struct ipu_ch_param *)ipu_cpmem_base) + (ch)) + +#define _param_word(base, w) \ + (((struct ipu_ch_param *)(base))->word[(w)].data) + +#define ipu_ch_param_set_field(base, w, bit, size, v) { \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + _param_word(base, w)[i] |= (v) << off; \ + if (((bit)+(size)-1)/32 > i) { \ + _param_word(base, w)[i + 1] |= (v) >> (off ? (32 - off) : 0); \ + } \ +} + +#define ipu_ch_param_mod_field(base, w, bit, size, v) { \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + u32 mask = (1UL << size) - 1; \ + u32 temp = _param_word(base, w)[i]; \ + temp &= ~(mask << off); \ + _param_word(base, w)[i] = temp | (v) << off; \ + if (((bit)+(size)-1)/32 > i) { \ + temp = _param_word(base, w)[i + 1]; \ + temp &= ~(mask >> (32 - off)); \ + _param_word(base, w)[i + 1] = \ + temp | ((v) >> (off ? (32 - off) : 0)); \ + } \ +} + +#define ipu_ch_param_read_field(base, w, bit, size) ({ \ + u32 temp2; \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + u32 mask = (1UL << size) - 1; \ + u32 temp1 = _param_word(base, w)[i]; \ + temp1 = mask & (temp1 >> off); \ + if (((bit)+(size)-1)/32 > i) { \ + temp2 = _param_word(base, w)[i + 1]; \ + temp2 &= mask >> (off ? (32 - off) : 0); \ + temp1 |= temp2 << (off ? (32 - off) : 0); \ + } \ + temp1; \ +}) + +static inline void _ipu_ch_params_set_packing(struct ipu_ch_param *p, + int red_width, int red_offset, + int green_width, int green_offset, + int blue_width, int blue_offset, + int alpha_width, int alpha_offset) +{ + /* Setup red width and offset */ + ipu_ch_param_set_field(p, 1, 116, 3, red_width - 1); + ipu_ch_param_set_field(p, 1, 128, 5, red_offset); + /* Setup green width and offset */ + ipu_ch_param_set_field(p, 1, 119, 3, green_width - 1); + ipu_ch_param_set_field(p, 1, 133, 5, green_offset); + /* Setup blue width and offset */ + ipu_ch_param_set_field(p, 1, 122, 3, blue_width - 1); + ipu_ch_param_set_field(p, 1, 138, 5, blue_offset); + /* Setup alpha width and offset */ + ipu_ch_param_set_field(p, 1, 125, 3, alpha_width - 1); + ipu_ch_param_set_field(p, 1, 143, 5, alpha_offset); +} + +static inline void _ipu_ch_param_dump(int ch) +{ + struct ipu_ch_param *p = ipu_ch_param_addr(ch); + pr_debug("ch %d word 0 - %08X %08X %08X %08X %08X\n", ch, + p->word[0].data[0], p->word[0].data[1], p->word[0].data[2], + p->word[0].data[3], p->word[0].data[4]); + pr_debug("ch %d word 1 - %08X %08X %08X %08X %08X\n", ch, + p->word[1].data[0], p->word[1].data[1], p->word[1].data[2], + p->word[1].data[3], p->word[1].data[4]); + pr_debug("PFS 0x%x, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 85, 4)); + pr_debug("BPP 0x%x, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 107, 3)); + pr_debug("NPB 0x%x\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 78, 7)); + + pr_debug("FW %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 125, 13)); + pr_debug("FH %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 138, 12)); + pr_debug("Stride %d\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 102, 14)); + + pr_debug("Width0 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 116, 3)); + pr_debug("Width1 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 119, 3)); + pr_debug("Width2 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 122, 3)); + pr_debug("Width3 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 125, 3)); + pr_debug("Offset0 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 128, 5)); + pr_debug("Offset1 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 133, 5)); + pr_debug("Offset2 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 138, 5)); + pr_debug("Offset3 %d\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 143, 5)); +} + +static inline void _ipu_ch_param_init(int ch, + uint32_t pixel_fmt, uint32_t width, + uint32_t height, uint32_t stride, + uint32_t u, uint32_t v, + uint32_t uv_stride, dma_addr_t addr0, + dma_addr_t addr1) +{ + uint32_t u_offset = 0; + uint32_t v_offset = 0; + struct ipu_ch_param params; + + memset(¶ms, 0, sizeof(params)); + + ipu_ch_param_set_field(¶ms, 0, 125, 13, width - 1); + + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 0, 138, 12, (height / 2) - 1); + ipu_ch_param_set_field(¶ms, 1, 102, 14, (stride * 2) - 1); + } else { + ipu_ch_param_set_field(¶ms, 0, 138, 12, height - 1); + ipu_ch_param_set_field(¶ms, 1, 102, 14, stride - 1); + } + + /* EBA is 8-byte aligned */ + ipu_ch_param_set_field(¶ms, 1, 0, 29, addr0 >> 3); + ipu_ch_param_set_field(¶ms, 1, 29, 29, addr1 >> 3); + if (addr0%8) + dev_warn(g_ipu_dev, + "IDMAC%d's EBA0 is not 8-byte aligned\n", ch); + if (addr1%8) + dev_warn(g_ipu_dev, + "IDMAC%d's EBA1 is not 8-byte aligned\n", ch); + + switch (pixel_fmt) { + case IPU_PIX_FMT_GENERIC: + /*Represents 8-bit Generic data */ + ipu_ch_param_set_field(¶ms, 0, 107, 3, 5); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 6); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 63); /* burst size */ + + break; + case IPU_PIX_FMT_GENERIC_32: + /*Represents 32-bit Generic data */ + break; + case IPU_PIX_FMT_RGB565: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 5, 0, 6, 5, 5, 11, 8, 16); + break; + case IPU_PIX_FMT_BGR24: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 1); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 19); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 0, 8, 8, 8, 16, 8, 24); + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_YUV444: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 1); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 19); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 16, 8, 8, 8, 0, 8, 24); + break; + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_BGR32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 8, 8, 16, 8, 24, 8, 0); + break; + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_RGB32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 24, 8, 16, 8, 8, 8, 0); + break; + case IPU_PIX_FMT_ABGR32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 0, 8, 8, 8, 16, 8, 24); + break; + case IPU_PIX_FMT_UYVY: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 0xA); /* pix format */ + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + } else { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + } + break; + case IPU_PIX_FMT_YUYV: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 0x8); /* pix format */ + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + } else { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + } + break; + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + ipu_ch_param_set_field(¶ms, 1, 85, 4, 2); /* pix format */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = stride * height; + v_offset = u_offset + (uv_stride * height / 2); + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + uv_stride = uv_stride*2; + } else { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + } + break; + case IPU_PIX_FMT_YVU420P: + ipu_ch_param_set_field(¶ms, 1, 85, 4, 2); /* pix format */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + v_offset = stride * height; + u_offset = v_offset + (uv_stride * height / 2); + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + uv_stride = uv_stride*2; + } else { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + } + break; + case IPU_PIX_FMT_YVU422P: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 1); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + v_offset = (v == 0) ? stride * height : v; + u_offset = (u == 0) ? v_offset + v_offset / 2 : u; + break; + case IPU_PIX_FMT_YUV422P: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 1); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 2 : v; + break; + case IPU_PIX_FMT_NV12: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 4); /* pix format */ + uv_stride = stride; + u_offset = (u == 0) ? stride * height : u; + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + uv_stride = uv_stride*2; + } else { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + } + break; + default: + dev_err(g_ipu_dev, "mxc ipu: unimplemented pixel format\n"); + break; + } + /*set burst size to 16*/ + + + if (uv_stride) + ipu_ch_param_set_field(¶ms, 1, 128, 14, uv_stride - 1); + + /* Get the uv offset from user when need cropping */ + if (u || v) { + u_offset = u; + v_offset = v; + } + + /* UBO and VBO are 22-bit and 8-byte aligned */ + if (u_offset/8 > 0x3fffff) + dev_warn(g_ipu_dev, + "IDMAC%d's U offset exceeds IPU limitation\n", ch); + if (v_offset/8 > 0x3fffff) + dev_warn(g_ipu_dev, + "IDMAC%d's V offset exceeds IPU limitation\n", ch); + if (u_offset%8) + dev_warn(g_ipu_dev, + "IDMAC%d's U offset is not 8-byte aligned\n", ch); + if (v_offset%8) + dev_warn(g_ipu_dev, + "IDMAC%d's V offset is not 8-byte aligned\n", ch); + + ipu_ch_param_set_field(¶ms, 0, 46, 22, u_offset / 8); + ipu_ch_param_set_field(¶ms, 0, 68, 22, v_offset / 8); + + pr_debug("initializing idma ch %d @ %p\n", ch, ipu_ch_param_addr(ch)); + memcpy(ipu_ch_param_addr(ch), ¶ms, sizeof(params)); +}; + +static inline void _ipu_ch_param_set_burst_size(uint32_t ch, + uint16_t burst_pixels) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 78, 7, + burst_pixels - 1); +}; + +static inline int _ipu_ch_param_get_burst_size(uint32_t ch) +{ + return ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 78, 7) + 1; +}; + +static inline int _ipu_ch_param_get_bpp(uint32_t ch) +{ + return ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 107, 3); +}; + +static inline void _ipu_ch_param_set_buffer(uint32_t ch, int bufNum, + dma_addr_t phyaddr) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 29 * bufNum, 29, + phyaddr / 8); +}; + +static inline void _ipu_ch_param_set_rotation(uint32_t ch, + ipu_rotate_mode_t rot) +{ + u32 temp_rot = bitrev8(rot) >> 5; + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 0, 119, 3, temp_rot); +}; + +static inline void _ipu_ch_param_set_block_mode(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 0, 117, 2, 1); +}; + +static inline void _ipu_ch_param_set_alpha_use_separate_channel(uint32_t ch, + bool option) +{ + if (option) { + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 89, 1, 1); + } else { + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 89, 1, 0); + } +}; + +static inline void _ipu_ch_param_set_alpha_condition_read(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 149, 1, 1); +}; + +static inline void _ipu_ch_param_set_alpha_buffer_memory(uint32_t ch) +{ + int alp_mem_idx; + + switch (ch) { + case 14: /* PRP graphic */ + alp_mem_idx = 0; + break; + case 15: /* PP graphic */ + alp_mem_idx = 1; + break; + case 23: /* DP BG SYNC graphic */ + alp_mem_idx = 4; + break; + case 27: /* DP FG SYNC graphic */ + alp_mem_idx = 2; + break; + default: + dev_err(g_ipu_dev, "unsupported correlative channel of local " + "alpha channel\n"); + return; + } + + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 90, 3, alp_mem_idx); +}; + +static inline void _ipu_ch_param_set_interlaced_scan(uint32_t ch) +{ + u32 stride; + ipu_ch_param_set_field(ipu_ch_param_addr(ch), 0, 113, 1, 1); + stride = ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 102, 14) + 1; + /* ILO is 20-bit and 8-byte aligned */ + if (stride/8 > 0xfffff) + dev_warn(g_ipu_dev, + "IDMAC%d's ILO exceeds IPU limitation\n", ch); + if (stride%8) + dev_warn(g_ipu_dev, + "IDMAC%d's ILO is not 8-byte aligned\n", ch); + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 58, 20, stride / 8); + stride *= 2; + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 102, 14, stride - 1); +}; + +static inline void _ipu_ch_param_set_high_priority(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 93, 2, 1); +}; + +/* IDMAC U/V offset changing support */ +/* U and V input is not affected, */ +/* the update is done by new calculation according to */ +/* vertical_offset and horizontal_offset */ +static inline void _ipu_ch_offset_update(int ch, + uint32_t pixel_fmt, + uint32_t width, + uint32_t height, + uint32_t stride, + uint32_t u, + uint32_t v, + uint32_t uv_stride, + uint32_t vertical_offset, + uint32_t horizontal_offset) +{ + uint32_t u_offset = 0; + uint32_t v_offset = 0; + uint32_t u_fix = 0; + uint32_t v_fix = 0; + + switch (pixel_fmt) { + case IPU_PIX_FMT_GENERIC: + case IPU_PIX_FMT_GENERIC_32: + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_YUV444: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_ABGR32: + case IPU_PIX_FMT_UYVY: + case IPU_PIX_FMT_YUYV: + break; + + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = stride * (height - vertical_offset - 1) + + (stride - horizontal_offset) + + (uv_stride * vertical_offset / 2) + + horizontal_offset / 2; + v_offset = u_offset + (uv_stride * height / 2); + u_fix = u ? (u + (uv_stride * vertical_offset / 2) + + (horizontal_offset / 2) - + (stride * vertical_offset) - (horizontal_offset)) : + u_offset; + v_fix = v ? (v + (uv_stride * vertical_offset / 2) + + (horizontal_offset / 2) - + (stride * vertical_offset) - (horizontal_offset)) : + v_offset; + + break; + case IPU_PIX_FMT_YVU420P: + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + v_offset = stride * (height - vertical_offset - 1) + + (stride - horizontal_offset) + + (uv_stride * vertical_offset / 2) + + horizontal_offset / 2; + u_offset = v_offset + (uv_stride * height / 2); + u_fix = u ? (u + (uv_stride * vertical_offset / 2) + + (horizontal_offset / 2) - + (stride * vertical_offset) - (horizontal_offset)) : + u_offset; + v_fix = v ? (v + (uv_stride * vertical_offset / 2) + + (horizontal_offset / 2) - + (stride * vertical_offset) - (horizontal_offset)) : + v_offset; + + break; + case IPU_PIX_FMT_YVU422P: + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + v_offset = stride * (height - vertical_offset - 1) + + (stride - horizontal_offset) + + (uv_stride * vertical_offset) + + horizontal_offset / 2; + u_offset = v_offset + uv_stride * height; + u_fix = u ? (u + (uv_stride * vertical_offset) + + horizontal_offset / 2 - + (stride * vertical_offset) - (horizontal_offset)) : + u_offset; + v_fix = v ? (v + (uv_stride * vertical_offset) + + horizontal_offset / 2 - + (stride * vertical_offset) - (horizontal_offset)) : + v_offset; + break; + case IPU_PIX_FMT_YUV422P: + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = stride * (height - vertical_offset - 1) + + (stride - horizontal_offset) + + (uv_stride * vertical_offset) + + horizontal_offset / 2; + v_offset = u_offset + uv_stride * height; + u_fix = u ? (u + (uv_stride * vertical_offset) + + horizontal_offset / 2 - + (stride * vertical_offset) - (horizontal_offset)) : + u_offset; + v_fix = v ? (v + (uv_stride * vertical_offset) + + horizontal_offset / 2 - + (stride * vertical_offset) - (horizontal_offset)) : + v_offset; + break; + + case IPU_PIX_FMT_NV12: + uv_stride = stride; + u_offset = stride * (height - vertical_offset - 1) + + (stride - horizontal_offset) + + (uv_stride * vertical_offset / 2) + + horizontal_offset; + u_fix = u ? (u + (uv_stride * vertical_offset / 2) + + horizontal_offset - + (stride * vertical_offset) - (horizontal_offset)) : + u_offset; + + break; + default: + dev_err(g_ipu_dev, "mxc ipu: unimplemented pixel format\n"); + break; + } + + + + if (u_fix > u_offset) + u_offset = u_fix; + + if (v_fix > v_offset) + v_offset = v_fix; + + /* UBO and VBO are 22-bit and 8-byte aligned */ + if (u_offset/8 > 0x3fffff) + dev_warn(g_ipu_dev, + "IDMAC%d's U offset exceeds IPU limitation\n", ch); + if (v_offset/8 > 0x3fffff) + dev_warn(g_ipu_dev, + "IDMAC%d's V offset exceeds IPU limitation\n", ch); + if (u_offset%8) + dev_warn(g_ipu_dev, + "IDMAC%d's U offset is not 8-byte aligned\n", ch); + if (v_offset%8) + dev_warn(g_ipu_dev, + "IDMAC%d's V offset is not 8-byte aligned\n", ch); + + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 0, 46, 22, u_offset / 8); + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 0, 68, 22, v_offset / 8); + +}; + +static inline void _ipu_ch_params_set_alpha_width(uint32_t ch, int alpha_width) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), 1, 125, 3, alpha_width - 1); +}; + +#endif diff --git a/drivers/mxc/ipu3/ipu_prv.h b/drivers/mxc/ipu3/ipu_prv.h new file mode 100644 index 000000000000..794163ac96ed --- /dev/null +++ b/drivers/mxc/ipu3/ipu_prv.h @@ -0,0 +1,102 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include + +/* Globals */ +extern struct device *g_ipu_dev; +extern spinlock_t ipu_lock; +extern bool g_ipu_clk_enabled; +extern struct clk *g_ipu_clk; +extern struct clk *g_di_clk[2]; +extern struct clk *g_pixel_clk[2]; +extern struct clk *g_csi_clk[2]; +extern unsigned char g_dc_di_assignment[]; +extern int g_ipu_hw_rev; +extern int dmfc_type_setup; + +#define IDMA_CHAN_INVALID 0xFF +#define HIGH_RESOLUTION_WIDTH 1024 + +struct ipu_channel { + u8 video_in_dma; + u8 alpha_in_dma; + u8 graph_in_dma; + u8 out_dma; +}; + +enum ipu_dmfc_type { + DMFC_NORMAL = 0, + DMFC_HIGH_RESOLUTION_DC, + DMFC_HIGH_RESOLUTION_DP, + DMFC_HIGH_RESOLUTION_ONLY_DP, +}; + +int register_ipu_device(void); +ipu_color_space_t format_to_colorspace(uint32_t fmt); +bool ipu_pixel_format_has_alpha(uint32_t fmt); + +void ipu_dump_registers(void); + +uint32_t _ipu_channel_status(ipu_channel_t channel); + +void _ipu_init_dc_mappings(void); +int _ipu_dp_init(ipu_channel_t channel, uint32_t in_pixel_fmt, + uint32_t out_pixel_fmt); +void _ipu_dp_uninit(ipu_channel_t channel); +void _ipu_dc_init(int dc_chan, int di, bool interlaced, uint32_t pixel_fmt); +void _ipu_dc_uninit(int dc_chan); +void _ipu_dp_dc_enable(ipu_channel_t channel); +void _ipu_dp_dc_disable(ipu_channel_t channel, bool swap); +void _ipu_dmfc_init(int dmfc_type, int first); +void _ipu_dmfc_set_wait4eot(int dma_chan, int width); +int _ipu_disp_chan_is_interlaced(ipu_channel_t channel); + +void _ipu_ic_enable_task(ipu_channel_t channel); +void _ipu_ic_disable_task(ipu_channel_t channel); +void _ipu_ic_init_prpvf(ipu_channel_params_t *params, bool src_is_csi); +void _ipu_vdi_init(ipu_channel_t channel, ipu_channel_params_t *params); +void _ipu_vdi_uninit(void); +void _ipu_ic_uninit_prpvf(void); +void _ipu_ic_init_rotate_vf(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_vf(void); +void _ipu_ic_init_csi(ipu_channel_params_t *params); +void _ipu_ic_uninit_csi(void); +void _ipu_ic_init_prpenc(ipu_channel_params_t *params, bool src_is_csi); +void _ipu_ic_uninit_prpenc(void); +void _ipu_ic_init_rotate_enc(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_enc(void); +void _ipu_ic_init_pp(ipu_channel_params_t *params); +void _ipu_ic_uninit_pp(void); +void _ipu_ic_init_rotate_pp(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_pp(void); +int _ipu_ic_idma_init(int dma_chan, uint16_t width, uint16_t height, + int burst_size, ipu_rotate_mode_t rot); +void _ipu_vdi_toggle_top_field_man(void); +int _ipu_csi_init(ipu_channel_t channel, uint32_t csi); +void ipu_csi_set_test_generator(bool active, uint32_t r_value, + uint32_t g_value, uint32_t b_value, + uint32_t pix_clk, uint32_t csi); +void _ipu_csi_ccir_err_detection_enable(uint32_t csi); +void _ipu_csi_ccir_err_detection_disable(uint32_t csi); +void _ipu_smfc_init(ipu_channel_t channel, uint32_t mipi_id, uint32_t csi); +void _ipu_smfc_set_burst_size(ipu_channel_t channel, uint32_t bs); +void _ipu_dp_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]); + +#endif /* __INCLUDE_IPU_PRV_H__ */ diff --git a/drivers/mxc/ipu3/ipu_regs.h b/drivers/mxc/ipu3/ipu_regs.h new file mode 100644 index 000000000000..b9392b71b58d --- /dev/null +++ b/drivers/mxc/ipu3/ipu_regs.h @@ -0,0 +1,669 @@ +/* + * Copyright (C) 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_regs.h + * + * @brief IPU Register definitions + * + * @ingroup IPU + */ +#ifndef __IPU_REGS_INCLUDED__ +#define __IPU_REGS_INCLUDED__ + +#define IPU_DISP0_BASE 0x00000000 +#define IPU_MCU_T_DEFAULT 8 +#define IPU_DISP1_BASE (IPU_MCU_T_DEFAULT << 25) +#define IPU_REG_BASE 0x1E000000 +#define IPUV3M_REG_BASE 0x06000000 + +#define IPU_CM_REG_BASE 0x00000000 +#define IPU_IDMAC_REG_BASE 0x00008000 +#define IPU_ISP_REG_BASE 0x00010000 +#define IPU_DP_REG_BASE 0x00018000 +#define IPU_IC_REG_BASE 0x00020000 +#define IPU_IRT_REG_BASE 0x00028000 +#define IPU_CSI0_REG_BASE 0x00030000 +#define IPU_CSI1_REG_BASE 0x00038000 +#define IPU_DI0_REG_BASE 0x00040000 +#define IPU_DI1_REG_BASE 0x00048000 +#define IPU_SMFC_REG_BASE 0x00050000 +#define IPU_DC_REG_BASE 0x00058000 +#define IPU_DMFC_REG_BASE 0x00060000 +#define IPU_VDI_REG_BASE 0x00068000 +#define IPU_CPMEM_REG_BASE 0x01000000 +#define IPU_LUT_REG_BASE 0x01020000 +#define IPU_SRM_REG_BASE 0x01040000 +#define IPU_TPM_REG_BASE 0x01060000 +#define IPU_DC_TMPL_REG_BASE 0x01080000 +#define IPU_ISP_TBPR_REG_BASE 0x010C0000 + + +extern u32 *ipu_cm_reg; +extern u32 *ipu_idmac_reg; +extern u32 *ipu_dp_reg; +extern u32 *ipu_ic_reg; +extern u32 *ipu_dc_reg; +extern u32 *ipu_dc_tmpl_reg; +extern u32 *ipu_dmfc_reg; +extern u32 *ipu_di_reg[]; +extern u32 *ipu_smfc_reg; +extern u32 *ipu_csi_reg[]; +extern u32 *ipu_tpmem_base; +extern u32 *ipu_disp_base[]; +extern u32 *ipu_vdi_reg; + +/* Register addresses */ +/* IPU Common registers */ +#define IPU_CONF (ipu_cm_reg) + +#define IPU_SRM_PRI1 (ipu_cm_reg + 0x00A0/4) +#define IPU_SRM_PRI2 (ipu_cm_reg + 0x00A4/4) +#define IPU_FS_PROC_FLOW1 (ipu_cm_reg + 0x00A8/4) +#define IPU_FS_PROC_FLOW2 (ipu_cm_reg + 0x00AC/4) +#define IPU_FS_PROC_FLOW3 (ipu_cm_reg + 0x00B0/4) +#define IPU_FS_DISP_FLOW1 (ipu_cm_reg + 0x00B4/4) +#define IPU_FS_DISP_FLOW2 (ipu_cm_reg + 0x00B8/4) +#define IPU_SKIP (ipu_cm_reg + 0x00BC/4) +#define IPU_DISP_ALT_CONF (ipu_cm_reg + 0x00C0/4) +#define IPU_DISP_GEN (ipu_cm_reg + 0x00C4/4) +#define IPU_DISP_ALT1 (ipu_cm_reg + 0x00C8/4) +#define IPU_DISP_ALT2 (ipu_cm_reg + 0x00CC/4) +#define IPU_DISP_ALT3 (ipu_cm_reg + 0x00D0/4) +#define IPU_DISP_ALT4 (ipu_cm_reg + 0x00D4/4) +#define IPU_SNOOP (ipu_cm_reg + 0x00D8/4) +#define IPU_MEM_RST (ipu_cm_reg + 0x00DC/4) +#define IPU_PM (ipu_cm_reg + 0x00E0/4) +#define IPU_GPR (ipu_cm_reg + 0x00E4/4) +#define IPU_CHA_DB_MODE_SEL(ch) (ipu_cm_reg + 0x0150/4 + (ch / 32)) +#define IPU_ALT_CHA_DB_MODE_SEL(ch) (ipu_cm_reg + 0x0168/4 + (ch / 32)) +#define IPU_CHA_CUR_BUF(ch) ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x023C/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0124/4 + (ch / 32)); }) +#define IPU_ALT_CUR_BUF0 ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0244/4) : \ + (ipu_cm_reg + 0x012C/4); }) +#define IPU_ALT_CUR_BUF1 ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0248/4) : \ + (ipu_cm_reg + 0x0130/4); }) +#define IPU_SRM_STAT ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x024C/4) : \ + (ipu_cm_reg + 0x0134/4); }) +#define IPU_PROC_TASK_STAT ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0250/4) : \ + (ipu_cm_reg + 0x0138/4); }) +#define IPU_DISP_TASK_STAT ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0254/4) : \ + (ipu_cm_reg + 0x013C/4); }) +#define IPU_CHA_BUF0_RDY(ch) ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0268/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0140/4 + (ch / 32)); }) +#define IPU_CHA_BUF1_RDY(ch) ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0270/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0148/4 + (ch / 32)); }) +#define IPU_ALT_CHA_BUF0_RDY(ch) ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0278/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0158/4 + (ch / 32)); }) +#define IPU_ALT_CHA_BUF1_RDY(ch) ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0280/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0160/4 + (ch / 32)); }) + +#define IPU_INT_CTRL(n) (ipu_cm_reg + 0x003C/4 + ((n) - 1)) +#define IPU_INT_CTRL_IRQ(irq) IPU_INT_CTRL(((irq) / 32)) +#define IPU_INT_STAT_IRQ(irq) IPU_INT_STAT(((irq) / 32)) +#define IPU_INT_STAT(n) ({g_ipu_hw_rev >= 2 ? \ + (ipu_cm_reg + 0x0200/4 + ((n) - 1)) : \ + (ipu_cm_reg + 0x00E8/4 + ((n) - 1)); }) + +#define IPUIRQ_2_STATREG(irq) (IPU_INT_STAT(1) + ((irq) / 32)) +#define IPUIRQ_2_CTRLREG(irq) (IPU_INT_CTRL(1) + ((irq) / 32)) +#define IPUIRQ_2_MASK(irq) (1UL << ((irq) & 0x1F)) + +#define VDI_FSIZE (ipu_vdi_reg) +#define VDI_C (ipu_vdi_reg + 0x0004/4) + +/* CMOS Sensor Interface Registers */ +#define CSI_SENS_CONF(csi) (ipu_csi_reg[csi]) +#define CSI_SENS_FRM_SIZE(csi) (ipu_csi_reg[csi] + 0x0004/4) +#define CSI_ACT_FRM_SIZE(csi) (ipu_csi_reg[csi] + 0x0008/4) +#define CSI_OUT_FRM_CTRL(csi) (ipu_csi_reg[csi] + 0x000C/4) +#define CSI_TST_CTRL(csi) (ipu_csi_reg[csi] + 0x0010/4) +#define CSI_CCIR_CODE_1(csi) (ipu_csi_reg[csi] + 0x0014/4) +#define CSI_CCIR_CODE_2(csi) (ipu_csi_reg[csi] + 0x0018/4) +#define CSI_CCIR_CODE_3(csi) (ipu_csi_reg[csi] + 0x001C/4) +#define CSI_MIPI_DI(csi) (ipu_csi_reg[csi] + 0x0020/4) +#define CSI_SKIP(csi) (ipu_csi_reg[csi] + 0x0024/4) +#define CSI_CPD_CTRL(csi) (ipu_csi_reg[csi] + 0x0028/4) +#define CSI_CPD_RC(csi, n) (ipu_csi_reg[csi] + 0x002C/4 + n) +#define CSI_CPD_RS(csi, n) (ipu_csi_reg[csi] + 0x004C/4 + n) +#define CSI_CPD_GRC(csi, n) (ipu_csi_reg[csi] + 0x005C/4 + n) +#define CSI_CPD_GRS(csi, n) (ipu_csi_reg[csi] + 0x007C/4 + n) +#define CSI_CPD_GBC(csi, n) (ipu_csi_reg[csi] + 0x008C/4 + n) +#define CSI_CPD_GBS(csi, n) (ipu_csi_reg[csi] + 0x00AC/4 + n) +#define CSI_CPD_BC(csi, n) (ipu_csi_reg[csi] + 0x00BC/4 + n) +#define CSI_CPD_BS(csi, n) (ipu_csi_reg[csi] + 0x00DC/4 + n) +#define CSI_CPD_OFFSET1(csi) (ipu_csi_reg[csi] + 0x00EC/4) +#define CSI_CPD_OFFSET2(csi) (ipu_csi_reg[csi] + 0x00F0/4) + +/*SMFC Registers */ +#define SMFC_MAP (ipu_smfc_reg) +#define SMFC_WMC (ipu_smfc_reg + 0x0004/4) +#define SMFC_BS (ipu_smfc_reg + 0x0008/4) + +/* Image Converter Registers */ +#define IC_CONF (ipu_ic_reg) +#define IC_PRP_ENC_RSC (ipu_ic_reg + 0x0004/4) +#define IC_PRP_VF_RSC (ipu_ic_reg + 0x0008/4) +#define IC_PP_RSC (ipu_ic_reg + 0x000C/4) +#define IC_CMBP_1 (ipu_ic_reg + 0x0010/4) +#define IC_CMBP_2 (ipu_ic_reg + 0x0014/4) +#define IC_IDMAC_1 (ipu_ic_reg + 0x0018/4) +#define IC_IDMAC_2 (ipu_ic_reg + 0x001C/4) +#define IC_IDMAC_3 (ipu_ic_reg + 0x0020/4) +#define IC_IDMAC_4 (ipu_ic_reg + 0x0024/4) + +#define IDMAC_CONF (ipu_idmac_reg + 0x0000) +#define IDMAC_CHA_EN(ch) (ipu_idmac_reg + 0x0004/4 + (ch/32)) +#define IDMAC_SEP_ALPHA (ipu_idmac_reg + 0x000C/4) +#define IDMAC_ALT_SEP_ALPHA (ipu_idmac_reg + 0x0010/4) +#define IDMAC_CHA_PRI(ch) (ipu_idmac_reg + 0x0014/4 + (ch/32)) +#define IDMAC_WM_EN(ch) (ipu_idmac_reg + 0x001C/4 + (ch/32)) +#define IDMAC_CH_LOCK_EN_1 ({g_ipu_hw_rev >= 2 ? \ + (ipu_idmac_reg + 0x0024/4) : 0; }) +#define IDMAC_CH_LOCK_EN_2 ({g_ipu_hw_rev >= 2 ? \ + (ipu_idmac_reg + 0x0028/4) : \ + (ipu_idmac_reg + 0x0024/4); }) +#define IDMAC_SUB_ADDR_0 ({g_ipu_hw_rev >= 2 ? \ + (ipu_idmac_reg + 0x002C/4) : \ + (ipu_idmac_reg + 0x0028/4); }) +#define IDMAC_SUB_ADDR_1 ({g_ipu_hw_rev >= 2 ? \ + (ipu_idmac_reg + 0x0030/4) : \ + (ipu_idmac_reg + 0x002C/4); }) +#define IDMAC_SUB_ADDR_2 ({g_ipu_hw_rev >= 2 ? \ + (ipu_idmac_reg + 0x0034/4) : \ + (ipu_idmac_reg + 0x0030/4); }) +#define IDMAC_BAND_EN(ch) ({g_ipu_hw_rev >= 2 ? \ + (ipu_idmac_reg + 0x0040/4 + (ch/32)) : \ + (ipu_idmac_reg + 0x0034/4 + (ch/32)); }) +#define IDMAC_CHA_BUSY(ch) ({g_ipu_hw_rev >= 2 ? \ + (ipu_idmac_reg + 0x0100/4 + (ch/32)) : \ + (ipu_idmac_reg + 0x0040/4 + (ch/32)); }) + +#define DI_GENERAL(di) (ipu_di_reg[di]) +#define DI_BS_CLKGEN0(di) (ipu_di_reg[di] + 0x0004/4) +#define DI_BS_CLKGEN1(di) (ipu_di_reg[di] + 0x0008/4) + +#define DI_SW_GEN0(di, gen) (ipu_di_reg[di] + 0x000C/4 + (gen - 1)) +#define DI_SW_GEN1(di, gen) (ipu_di_reg[di] + 0x0030/4 + (gen - 1)) +#define DI_STP_REP(di, gen) (ipu_di_reg[di] + 0x0148/4 + (gen - 1)/2) +#define DI_SYNC_AS_GEN(di) (ipu_di_reg[di] + 0x0054/4) +#define DI_DW_GEN(di, gen) (ipu_di_reg[di] + 0x0058/4 + gen) +#define DI_DW_SET(di, gen, set) (ipu_di_reg[di] + 0x0088/4 + gen + 0xC*set) +#define DI_SER_CONF(di) (ipu_di_reg[di] + 0x015C/4) +#define DI_SSC(di) (ipu_di_reg[di] + 0x0160/4) +#define DI_POL(di) (ipu_di_reg[di] + 0x0164/4) +#define DI_AW0(di) (ipu_di_reg[di] + 0x0168/4) +#define DI_AW1(di) (ipu_di_reg[di] + 0x016C/4) +#define DI_SCR_CONF(di) (ipu_di_reg[di] + 0x0170/4) +#define DI_STAT(di) (ipu_di_reg[di] + 0x0174/4) + +#define DMFC_RD_CHAN (ipu_dmfc_reg) +#define DMFC_WR_CHAN (ipu_dmfc_reg + 0x0004/4) +#define DMFC_WR_CHAN_DEF (ipu_dmfc_reg + 0x0008/4) +#define DMFC_DP_CHAN (ipu_dmfc_reg + 0x000C/4) +#define DMFC_DP_CHAN_DEF (ipu_dmfc_reg + 0x0010/4) +#define DMFC_GENERAL1 (ipu_dmfc_reg + 0x0014/4) +#define DMFC_GENERAL2 (ipu_dmfc_reg + 0x0018/4) +#define DMFC_IC_CTRL (ipu_dmfc_reg + 0x001C/4) +#define DMFC_STAT (ipu_dmfc_reg + 0x0020/4) + +#define DC_MAP_CONF_PTR(n) (ipu_dc_reg + 0x0108/4 + n/2) +#define DC_MAP_CONF_VAL(n) (ipu_dc_reg + 0x0144/4 + n/2) + +#define _RL_CH_2_OFFSET(ch) ((ch == 0) ? 8 : ( \ + (ch == 1) ? 0x24 : ( \ + (ch == 2) ? 0x40 : ( \ + (ch == 5) ? 0x64 : ( \ + (ch == 6) ? 0x80 : ( \ + (ch == 8) ? 0x9C : ( \ + (ch == 9) ? 0xBC : (-1)))))))) +#define DC_RL_CH(ch, evt) (ipu_dc_reg + _RL_CH_2_OFFSET(ch)/4 + evt/2) + +#define DC_EVT_NF 0 +#define DC_EVT_NL 1 +#define DC_EVT_EOF 2 +#define DC_EVT_NFIELD 3 +#define DC_EVT_EOL 4 +#define DC_EVT_EOFIELD 5 +#define DC_EVT_NEW_ADDR 6 +#define DC_EVT_NEW_CHAN 7 +#define DC_EVT_NEW_DATA 8 + +#define DC_EVT_NEW_ADDR_W_0 0 +#define DC_EVT_NEW_ADDR_W_1 1 +#define DC_EVT_NEW_CHAN_W_0 2 +#define DC_EVT_NEW_CHAN_W_1 3 +#define DC_EVT_NEW_DATA_W_0 4 +#define DC_EVT_NEW_DATA_W_1 5 +#define DC_EVT_NEW_ADDR_R_0 6 +#define DC_EVT_NEW_ADDR_R_1 7 +#define DC_EVT_NEW_CHAN_R_0 8 +#define DC_EVT_NEW_CHAN_R_1 9 +#define DC_EVT_NEW_DATA_R_0 10 +#define DC_EVT_NEW_DATA_R_1 11 +#define DC_EVEN_UGDE0 12 +#define DC_ODD_UGDE0 13 +#define DC_EVEN_UGDE1 14 +#define DC_ODD_UGDE1 15 +#define DC_EVEN_UGDE2 16 +#define DC_ODD_UGDE2 17 +#define DC_EVEN_UGDE3 18 +#define DC_ODD_UGDE3 19 + +#define dc_ch_offset(ch) \ +({ \ + const u8 _offset[] = { \ + 0, 0x1C, 0x38, 0x54, 0x58, 0x5C, 0x78, 0, 0x94, 0xB4}; \ + _offset[ch]; \ +}) +#define DC_WR_CH_CONF(ch) (ipu_dc_reg + dc_ch_offset(ch)/4) +#define DC_WR_CH_ADDR(ch) (ipu_dc_reg + dc_ch_offset(ch)/4 + 4/4) + +#define DC_WR_CH_CONF_1 (ipu_dc_reg + 0x001C/4) +#define DC_WR_CH_ADDR_1 (ipu_dc_reg + 0x0020/4) +#define DC_WR_CH_CONF_5 (ipu_dc_reg + 0x005C/4) +#define DC_WR_CH_ADDR_5 (ipu_dc_reg + 0x0060/4) +#define DC_GEN (ipu_dc_reg + 0x00D4/4) +#define DC_DISP_CONF1(disp) (ipu_dc_reg + 0x00D8/4 + disp) +#define DC_DISP_CONF2(disp) (ipu_dc_reg + 0x00E8/4 + disp) +#define DC_STAT (ipu_dc_reg + 0x01C8/4) +#define DC_UGDE_0(evt) (ipu_dc_reg + 0x0174/4 + evt*4) +#define DC_UGDE_1(evt) (ipu_dc_reg + 0x0178/4 + evt*4) +#define DC_UGDE_2(evt) (ipu_dc_reg + 0x017C/4 + evt*4) +#define DC_UGDE_3(evt) (ipu_dc_reg + 0x0180/4 + evt*4) + +#define DP_SYNC 0 +#define DP_ASYNC0 0x60 +#define DP_ASYNC1 0xBC +#define DP_COM_CONF(flow) (ipu_dp_reg + flow/4) +#define DP_GRAPH_WIND_CTRL(flow) (ipu_dp_reg + 0x0004/4 + flow/4) +#define DP_FG_POS(flow) (ipu_dp_reg + 0x0008/4 + flow/4) +#define DP_GAMMA_C(flow, i) (ipu_dp_reg + 0x0014/4 + flow/4 + i) +#define DP_GAMMA_S(flow, i) (ipu_dp_reg + 0x0034/4 + flow/4 + i) +#define DP_CSC_A_0(flow) (ipu_dp_reg + 0x0044/4 + flow/4) +#define DP_CSC_A_1(flow) (ipu_dp_reg + 0x0048/4 + flow/4) +#define DP_CSC_A_2(flow) (ipu_dp_reg + 0x004C/4 + flow/4) +#define DP_CSC_A_3(flow) (ipu_dp_reg + 0x0050/4 + flow/4) +#define DP_CSC_0(flow) (ipu_dp_reg + 0x0054/4 + flow/4) +#define DP_CSC_1(flow) (ipu_dp_reg + 0x0058/4 + flow/4) + +enum { + IPU_CONF_CSI0_EN = 0x00000001, + IPU_CONF_CSI1_EN = 0x00000002, + IPU_CONF_IC_EN = 0x00000004, + IPU_CONF_ROT_EN = 0x00000008, + IPU_CONF_ISP_EN = 0x00000010, + IPU_CONF_DP_EN = 0x00000020, + IPU_CONF_DI0_EN = 0x00000040, + IPU_CONF_DI1_EN = 0x00000080, + IPU_CONF_DMFC_EN = 0x00000400, + IPU_CONF_SMFC_EN = 0x00000100, + IPU_CONF_DC_EN = 0x00000200, + IPU_CONF_VDI_EN = 0x00001000, + IPU_CONF_IDMAC_DIS = 0x00400000, + IPU_CONF_IC_DMFC_SEL = 0x02000000, + IPU_CONF_IC_DMFC_SYNC = 0x04000000, + IPU_CONF_VDI_DMFC_SYNC = 0x08000000, + IPU_CONF_CSI0_DATA_SOURCE = 0x10000000, + IPU_CONF_CSI0_DATA_SOURCE_OFFSET = 28, + IPU_CONF_CSI1_DATA_SOURCE = 0x20000000, + IPU_CONF_IC_INPUT = 0x40000000, + IPU_CONF_CSI_SEL = 0x80000000, + + DI0_COUNTER_RELEASE = 0x01000000, + DI1_COUNTER_RELEASE = 0x02000000, + + FS_PRPVF_ROT_SRC_SEL_MASK = 0x00000F00, + FS_PRPVF_ROT_SRC_SEL_OFFSET = 8, + FS_PRPENC_ROT_SRC_SEL_MASK = 0x0000000F, + FS_PRPENC_ROT_SRC_SEL_OFFSET = 0, + FS_PP_ROT_SRC_SEL_MASK = 0x000F0000, + FS_PP_ROT_SRC_SEL_OFFSET = 16, + FS_PP_SRC_SEL_MASK = 0x0000F000, + FS_PP_SRC_SEL_OFFSET = 12, + FS_PRP_SRC_SEL_MASK = 0x0F000000, + FS_PRP_SRC_SEL_OFFSET = 24, + FS_VF_IN_VALID = 0x80000000, + FS_ENC_IN_VALID = 0x40000000, + FS_VDI_SRC_SEL_MASK = 0x30000000, + FS_VDI_SRC_SEL_OFFSET = 28, + + + FS_PRPENC_DEST_SEL_MASK = 0x0000000F, + FS_PRPENC_DEST_SEL_OFFSET = 0, + FS_PRPVF_DEST_SEL_MASK = 0x000000F0, + FS_PRPVF_DEST_SEL_OFFSET = 4, + FS_PRPVF_ROT_DEST_SEL_MASK = 0x00000F00, + FS_PRPVF_ROT_DEST_SEL_OFFSET = 8, + FS_PP_DEST_SEL_MASK = 0x0000F000, + FS_PP_DEST_SEL_OFFSET = 12, + FS_PP_ROT_DEST_SEL_MASK = 0x000F0000, + FS_PP_ROT_DEST_SEL_OFFSET = 16, + FS_PRPENC_ROT_DEST_SEL_MASK = 0x00F00000, + FS_PRPENC_ROT_DEST_SEL_OFFSET = 20, + + FS_SMFC0_DEST_SEL_MASK = 0x0000000F, + FS_SMFC0_DEST_SEL_OFFSET = 0, + FS_SMFC1_DEST_SEL_MASK = 0x00000070, + FS_SMFC1_DEST_SEL_OFFSET = 4, + FS_SMFC2_DEST_SEL_MASK = 0x00000780, + FS_SMFC2_DEST_SEL_OFFSET = 7, + FS_SMFC3_DEST_SEL_MASK = 0x00003800, + FS_SMFC3_DEST_SEL_OFFSET = 11, + + FS_DC1_SRC_SEL_MASK = 0x00F00000, + FS_DC1_SRC_SEL_OFFSET = 20, + FS_DC2_SRC_SEL_MASK = 0x000F0000, + FS_DC2_SRC_SEL_OFFSET = 16, + FS_DP_SYNC0_SRC_SEL_MASK = 0x0000000F, + FS_DP_SYNC0_SRC_SEL_OFFSET = 0, + FS_DP_SYNC1_SRC_SEL_MASK = 0x000000F0, + FS_DP_SYNC1_SRC_SEL_OFFSET = 4, + FS_DP_ASYNC0_SRC_SEL_MASK = 0x00000F00, + FS_DP_ASYNC0_SRC_SEL_OFFSET = 8, + FS_DP_ASYNC1_SRC_SEL_MASK = 0x0000F000, + FS_DP_ASYNC1_SRC_SEL_OFFSET = 12, + + FS_AUTO_REF_PER_MASK = 0, + FS_AUTO_REF_PER_OFFSET = 16, + + TSTAT_VF_MASK = 0x0000000C, + TSTAT_VF_OFFSET = 2, + TSTAT_VF_ROT_MASK = 0x00000300, + TSTAT_VF_ROT_OFFSET = 8, + TSTAT_ENC_MASK = 0x00000003, + TSTAT_ENC_OFFSET = 0, + TSTAT_ENC_ROT_MASK = 0x000000C0, + TSTAT_ENC_ROT_OFFSET = 6, + TSTAT_PP_MASK = 0x00000030, + TSTAT_PP_OFFSET = 4, + TSTAT_PP_ROT_MASK = 0x00000C00, + TSTAT_PP_ROT_OFFSET = 10, + + TASK_STAT_IDLE = 0, + TASK_STAT_ACTIVE = 1, + TASK_STAT_WAIT4READY = 2, + + /* Image Converter Register bits */ + IC_CONF_PRPENC_EN = 0x00000001, + IC_CONF_PRPENC_CSC1 = 0x00000002, + IC_CONF_PRPENC_ROT_EN = 0x00000004, + IC_CONF_PRPVF_EN = 0x00000100, + IC_CONF_PRPVF_CSC1 = 0x00000200, + IC_CONF_PRPVF_CSC2 = 0x00000400, + IC_CONF_PRPVF_CMB = 0x00000800, + IC_CONF_PRPVF_ROT_EN = 0x00001000, + IC_CONF_PP_EN = 0x00010000, + IC_CONF_PP_CSC1 = 0x00020000, + IC_CONF_PP_CSC2 = 0x00040000, + IC_CONF_PP_CMB = 0x00080000, + IC_CONF_PP_ROT_EN = 0x00100000, + IC_CONF_IC_GLB_LOC_A = 0x10000000, + IC_CONF_KEY_COLOR_EN = 0x20000000, + IC_CONF_RWS_EN = 0x40000000, + IC_CONF_CSI_MEM_WR_EN = 0x80000000, + + IC_IDMAC_1_CB0_BURST_16 = 0x00000001, + IC_IDMAC_1_CB1_BURST_16 = 0x00000002, + IC_IDMAC_1_CB2_BURST_16 = 0x00000004, + IC_IDMAC_1_CB3_BURST_16 = 0x00000008, + IC_IDMAC_1_CB4_BURST_16 = 0x00000010, + IC_IDMAC_1_CB5_BURST_16 = 0x00000020, + IC_IDMAC_1_CB6_BURST_16 = 0x00000040, + IC_IDMAC_1_CB7_BURST_16 = 0x00000080, + IC_IDMAC_1_PRPENC_ROT_MASK = 0x00003800, + IC_IDMAC_1_PRPENC_ROT_OFFSET = 11, + IC_IDMAC_1_PRPVF_ROT_MASK = 0x0001C000, + IC_IDMAC_1_PRPVF_ROT_OFFSET = 14, + IC_IDMAC_1_PP_ROT_MASK = 0x000E0000, + IC_IDMAC_1_PP_ROT_OFFSET = 17, + IC_IDMAC_1_PP_FLIP_RS = 0x00400000, + IC_IDMAC_1_PRPVF_FLIP_RS = 0x00200000, + IC_IDMAC_1_PRPENC_FLIP_RS = 0x00100000, + + IC_IDMAC_2_PRPENC_HEIGHT_MASK = 0x000003FF, + IC_IDMAC_2_PRPENC_HEIGHT_OFFSET = 0, + IC_IDMAC_2_PRPVF_HEIGHT_MASK = 0x000FFC00, + IC_IDMAC_2_PRPVF_HEIGHT_OFFSET = 10, + IC_IDMAC_2_PP_HEIGHT_MASK = 0x3FF00000, + IC_IDMAC_2_PP_HEIGHT_OFFSET = 20, + + IC_IDMAC_3_PRPENC_WIDTH_MASK = 0x000003FF, + IC_IDMAC_3_PRPENC_WIDTH_OFFSET = 0, + IC_IDMAC_3_PRPVF_WIDTH_MASK = 0x000FFC00, + IC_IDMAC_3_PRPVF_WIDTH_OFFSET = 10, + IC_IDMAC_3_PP_WIDTH_MASK = 0x3FF00000, + IC_IDMAC_3_PP_WIDTH_OFFSET = 20, + + CSI_SENS_CONF_DATA_FMT_SHIFT = 8, + CSI_SENS_CONF_DATA_FMT_MASK = 0x00000700, + CSI_SENS_CONF_DATA_FMT_RGB_YUV444 = 0L, + CSI_SENS_CONF_DATA_FMT_YUV422_YUYV = 1L, + CSI_SENS_CONF_DATA_FMT_YUV422_UYVY = 2L, + CSI_SENS_CONF_DATA_FMT_BAYER = 3L, + CSI_SENS_CONF_DATA_FMT_RGB565 = 4L, + CSI_SENS_CONF_DATA_FMT_RGB555 = 5L, + CSI_SENS_CONF_DATA_FMT_RGB444 = 6L, + CSI_SENS_CONF_DATA_FMT_JPEG = 7L, + + CSI_SENS_CONF_VSYNC_POL_SHIFT = 0, + CSI_SENS_CONF_HSYNC_POL_SHIFT = 1, + CSI_SENS_CONF_DATA_POL_SHIFT = 2, + CSI_SENS_CONF_PIX_CLK_POL_SHIFT = 3, + CSI_SENS_CONF_SENS_PRTCL_MASK = 0x00000070L, + CSI_SENS_CONF_SENS_PRTCL_SHIFT = 4, + CSI_SENS_CONF_PACK_TIGHT_SHIFT = 7, + CSI_SENS_CONF_DATA_WIDTH_SHIFT = 11, + CSI_SENS_CONF_EXT_VSYNC_SHIFT = 15, + CSI_SENS_CONF_DIVRATIO_SHIFT = 16, + + CSI_SENS_CONF_DIVRATIO_MASK = 0x00FF0000L, + CSI_SENS_CONF_DATA_DEST_SHIFT = 24, + CSI_SENS_CONF_DATA_DEST_MASK = 0x07000000L, + CSI_SENS_CONF_JPEG8_EN_SHIFT = 27, + CSI_SENS_CONF_JPEG_EN_SHIFT = 28, + CSI_SENS_CONF_FORCE_EOF_SHIFT = 29, + CSI_SENS_CONF_DATA_EN_POL_SHIFT = 31, + + CSI_DATA_DEST_ISP = 1L, + CSI_DATA_DEST_IC = 2L, + CSI_DATA_DEST_IDMAC = 4L, + + CSI_CCIR_ERR_DET_EN = 0x01000000L, + CSI_HORI_DOWNSIZE_EN = 0x80000000L, + CSI_VERT_DOWNSIZE_EN = 0x40000000L, + CSI_TEST_GEN_MODE_EN = 0x01000000L, + + CSI_HSC_MASK = 0x1FFF0000, + CSI_HSC_SHIFT = 16, + CSI_VSC_MASK = 0x00000FFF, + CSI_VSC_SHIFT = 0, + + CSI_TEST_GEN_R_MASK = 0x000000FFL, + CSI_TEST_GEN_R_SHIFT = 0, + CSI_TEST_GEN_G_MASK = 0x0000FF00L, + CSI_TEST_GEN_G_SHIFT = 8, + CSI_TEST_GEN_B_MASK = 0x00FF0000L, + CSI_TEST_GEN_B_SHIFT = 16, + + CSI_MIPI_DI0_MASK = 0x000000FFL, + CSI_MIPI_DI0_SHIFT = 0, + CSI_MIPI_DI1_MASK = 0x0000FF00L, + CSI_MIPI_DI1_SHIFT = 8, + CSI_MIPI_DI2_MASK = 0x00FF0000L, + CSI_MIPI_DI2_SHIFT = 16, + CSI_MIPI_DI3_MASK = 0xFF000000L, + CSI_MIPI_DI3_SHIFT = 24, + + CSI_MAX_RATIO_SKIP_ISP_MASK = 0x00070000L, + CSI_MAX_RATIO_SKIP_ISP_SHIFT = 16, + CSI_SKIP_ISP_MASK = 0x00F80000L, + CSI_SKIP_ISP_SHIFT = 19, + CSI_MAX_RATIO_SKIP_SMFC_MASK = 0x00000007L, + CSI_MAX_RATIO_SKIP_SMFC_SHIFT = 0, + CSI_SKIP_SMFC_MASK = 0x000000F8L, + CSI_SKIP_SMFC_SHIFT = 3, + CSI_ID_2_SKIP_MASK = 0x00000300L, + CSI_ID_2_SKIP_SHIFT = 8, + + CSI_COLOR_FIRST_ROW_MASK = 0x00000002L, + CSI_COLOR_FIRST_COMP_MASK = 0x00000001L, + + SMFC_MAP_CH0_MASK = 0x00000007L, + SMFC_MAP_CH0_SHIFT = 0, + SMFC_MAP_CH1_MASK = 0x00000038L, + SMFC_MAP_CH1_SHIFT = 3, + SMFC_MAP_CH2_MASK = 0x000001C0L, + SMFC_MAP_CH2_SHIFT = 6, + SMFC_MAP_CH3_MASK = 0x00000E00L, + SMFC_MAP_CH3_SHIFT = 9, + + SMFC_WM0_SET_MASK = 0x00000007L, + SMFC_WM0_SET_SHIFT = 0, + SMFC_WM1_SET_MASK = 0x000001C0L, + SMFC_WM1_SET_SHIFT = 6, + SMFC_WM2_SET_MASK = 0x00070000L, + SMFC_WM2_SET_SHIFT = 16, + SMFC_WM3_SET_MASK = 0x01C00000L, + SMFC_WM3_SET_SHIFT = 22, + + SMFC_WM0_CLR_MASK = 0x00000038L, + SMFC_WM0_CLR_SHIFT = 3, + SMFC_WM1_CLR_MASK = 0x00000E00L, + SMFC_WM1_CLR_SHIFT = 9, + SMFC_WM2_CLR_MASK = 0x00380000L, + SMFC_WM2_CLR_SHIFT = 19, + SMFC_WM3_CLR_MASK = 0x0E000000L, + SMFC_WM3_CLR_SHIFT = 25, + + SMFC_BS0_MASK = 0x0000000FL, + SMFC_BS0_SHIFT = 0, + SMFC_BS1_MASK = 0x000000F0L, + SMFC_BS1_SHIFT = 4, + SMFC_BS2_MASK = 0x00000F00L, + SMFC_BS2_SHIFT = 8, + SMFC_BS3_MASK = 0x0000F000L, + SMFC_BS3_SHIFT = 12, + + PF_CONF_TYPE_MASK = 0x00000007, + PF_CONF_TYPE_SHIFT = 0, + PF_CONF_PAUSE_EN = 0x00000010, + PF_CONF_RESET = 0x00008000, + PF_CONF_PAUSE_ROW_MASK = 0x00FF0000, + PF_CONF_PAUSE_ROW_SHIFT = 16, + + DI_DW_GEN_ACCESS_SIZE_OFFSET = 24, + DI_DW_GEN_COMPONENT_SIZE_OFFSET = 16, + + DI_GEN_DI_CLK_EXT = 0x100000, + DI_GEN_POLARITY_DISP_CLK = 0x00020000, + DI_GEN_POLARITY_1 = 0x00000001, + DI_GEN_POLARITY_2 = 0x00000002, + DI_GEN_POLARITY_3 = 0x00000004, + DI_GEN_POLARITY_4 = 0x00000008, + DI_GEN_POLARITY_5 = 0x00000010, + DI_GEN_POLARITY_6 = 0x00000020, + DI_GEN_POLARITY_7 = 0x00000040, + DI_GEN_POLARITY_8 = 0x00000080, + + DI_POL_DRDY_DATA_POLARITY = 0x00000080, + DI_POL_DRDY_POLARITY_15 = 0x00000010, + + DI_VSYNC_SEL_OFFSET = 13, + + DC_WR_CH_CONF_FIELD_MODE = 0x00000200, + DC_WR_CH_CONF_PROG_TYPE_OFFSET = 5, + DC_WR_CH_CONF_PROG_TYPE_MASK = 0x000000E0, + DC_WR_CH_CONF_PROG_DI_ID = 0x00000004, + DC_WR_CH_CONF_PROG_DISP_ID_OFFSET = 3, + DC_WR_CH_CONF_PROG_DISP_ID_MASK = 0x00000018, + + DC_UGDE_0_ODD_EN = 0x02000000, + DC_UGDE_0_ID_CODED_MASK = 0x00000007, + DC_UGDE_0_ID_CODED_OFFSET = 0, + DC_UGDE_0_EV_PRIORITY_MASK = 0x00000078, + DC_UGDE_0_EV_PRIORITY_OFFSET = 3, + + DP_COM_CONF_FG_EN = 0x00000001, + DP_COM_CONF_GWSEL = 0x00000002, + DP_COM_CONF_GWAM = 0x00000004, + DP_COM_CONF_GWCKE = 0x00000008, + DP_COM_CONF_CSC_DEF_MASK = 0x00000300, + DP_COM_CONF_CSC_DEF_OFFSET = 8, + DP_COM_CONF_CSC_DEF_FG = 0x00000300, + DP_COM_CONF_CSC_DEF_BG = 0x00000200, + DP_COM_CONF_CSC_DEF_BOTH = 0x00000100, + DP_COM_CONF_GAMMA_EN = 0x00001000, + DP_COM_CONF_GAMMA_YUV_EN = 0x00002000, + + DI_SER_CONF_LLA_SER_ACCESS = 0x00000020, + DI_SER_CONF_SERIAL_CLK_POL = 0x00000010, + DI_SER_CONF_SERIAL_DATA_POL = 0x00000008, + DI_SER_CONF_SERIAL_RS_POL = 0x00000004, + DI_SER_CONF_SERIAL_CS_POL = 0x00000002, + DI_SER_CONF_WAIT4SERIAL = 0x00000001, + + VDI_C_CH_420 = 0x00000000, + VDI_C_CH_422 = 0x00000002, + VDI_C_MOT_SEL_FULL = 0x00000008, + VDI_C_MOT_SEL_LOW = 0x00000004, + VDI_C_MOT_SEL_MED = 0x00000000, + VDI_C_BURST_SIZE1_4 = 0x00000030, + VDI_C_BURST_SIZE2_4 = 0x00000300, + VDI_C_BURST_SIZE3_4 = 0x00003000, + VDI_C_VWM1_SET_1 = 0x00000000, + VDI_C_VWM1_CLR_2 = 0x00080000, + VDI_C_VWM3_SET_1 = 0x00000000, + VDI_C_VWM3_CLR_2 = 0x02000000, + VDI_C_TOP_FIELD_MAN_1 = 0x40000000, + VDI_C_TOP_FIELD_AUTO_1 = 0x80000000, +}; + +enum di_pins { + DI_PIN11 = 0, + DI_PIN12 = 1, + DI_PIN13 = 2, + DI_PIN14 = 3, + DI_PIN15 = 4, + DI_PIN16 = 5, + DI_PIN17 = 6, + DI_PIN_CS = 7, + + DI_PIN_SER_CLK = 0, + DI_PIN_SER_RS = 1, +}; + +enum di_sync_wave { + DI_SYNC_NONE = -1, + DI_SYNC_CLK = 0, + DI_SYNC_INT_HSYNC = 1, + DI_SYNC_HSYNC = 2, + DI_SYNC_VSYNC = 3, + DI_SYNC_DE = 5, +}; + +/* DC template opcodes */ +#define WROD(lf) (0x18 | (lf << 1)) +#define WRG (0x01) + +#endif diff --git a/drivers/mxc/mcu_pmic/Kconfig b/drivers/mxc/mcu_pmic/Kconfig new file mode 100644 index 000000000000..cb6815e92d86 --- /dev/null +++ b/drivers/mxc/mcu_pmic/Kconfig @@ -0,0 +1,17 @@ +# +# PMIC Modules configuration +# + +config MXC_PMIC_MC9S08DZ60 + tristate "MC9S08DZ60 PMIC" + depends on ARCH_MXC && I2C + ---help--- + This is the MXC MC9S08DZ60(MCU) PMIC support. + +config MXC_MC9SDZ60_RTC + tristate "MC9SDZ60 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC9SDZ60 + ---help--- + This is the MC9SDZ60 RTC module driver. This module provides kernel API + for RTC part of MC9SDZ60. + If you want MC9SDZ60 RTC support, you should say Y here diff --git a/drivers/mxc/mcu_pmic/Makefile b/drivers/mxc/mcu_pmic/Makefile new file mode 100644 index 000000000000..96aae94d5290 --- /dev/null +++ b/drivers/mxc/mcu_pmic/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the mc9sdz60 pmic drivers. +# + +obj-$(CONFIG_MXC_PMIC_MC9SDZ60) += pmic_mc9sdz60_mod.o +pmic_mc9sdz60_mod-objs := mcu_pmic_core.o max8660.o mc9s08dz60.o mcu_pmic_gpio.o diff --git a/drivers/mxc/mcu_pmic/max8660.c b/drivers/mxc/mcu_pmic/max8660.c new file mode 100644 index 000000000000..1f3f19b52ddf --- /dev/null +++ b/drivers/mxc/mcu_pmic/max8660.c @@ -0,0 +1,154 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file max8660.c + * @brief Driver for max8660 + * + * @ingroup pmic + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mcu_pmic_core.h" +#include "max8660.h" + +/* I2C bus id and device address of mcu */ +#define I2C1_BUS 0 +#define MAX8660_I2C_ADDR 0x68 + +static struct i2c_client *max8660_i2c_client; + + /* reg names for max8660 + REG_MAX8660_OUTPUT_ENABLE_1, + REG_MAX8660_OUTPUT_ENABLE_2, + REG_MAX8660_VOLT__CHANGE_1, + REG_MAX8660_V3_TARGET_VOLT_1, + REG_MAX8660_V3_TARGET_VOLT_2, + REG_MAX8660_V4_TARGET_VOLT_1, + REG_MAX8660_V4_TARGET_VOLT_2, + REG_MAX8660_V5_TARGET_VOLT_1, + REG_MAX8660_V5_TARGET_VOLT_2, + REG_MAX8660_V6V7_TARGET_VOLT, + REG_MAX8660_FORCE_PWM + */ + + /* save down the reg values for the device is write only */ +static u8 max8660_reg_value_table[] = { + 0x0, 0x0, 0x0, 0x17, 0x17, 0x1F, 0x1F, 0x04, 0x04, 0x0, 0x0 +}; +static int max8660_dev_present; + +int is_max8660_present(void) +{ + return max8660_dev_present; +} + +int max8660_get_buffered_reg_val(int reg_name, u8 *value) +{ + if (!max8660_dev_present) + return -1; + /* outof range */ + if (reg_name < REG_MAX8660_OUTPUT_ENABLE_1 + || reg_name > REG_MAX8660_FORCE_PWM) + return -1; + *value = + max8660_reg_value_table[reg_name - REG_MAX8660_OUTPUT_ENABLE_1]; + return 0; +} +int max8660_save_buffered_reg_val(int reg_name, u8 value) +{ + + /* outof range */ + if (reg_name < REG_MAX8660_OUTPUT_ENABLE_1 + || reg_name > REG_MAX8660_FORCE_PWM) + return -1; + max8660_reg_value_table[reg_name - REG_MAX8660_OUTPUT_ENABLE_1] = value; + return 0; +} + +int max8660_write_reg(u8 reg, u8 value) +{ + if (max8660_dev_present && (i2c_smbus_write_byte_data( + max8660_i2c_client, reg, value) >= 0)) + return 0; + return -1; +} + +/*! + * max8660 I2C attach function + * + * @param adapter struct i2c_client * + * @return 0 for max8660 successfully detected + */ +static int max8660_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + max8660_i2c_client = client; + retval = i2c_smbus_write_byte_data(max8660_i2c_client, + MAX8660_OUTPUT_ENABLE_1, 0); + if (retval == 0) { + max8660_dev_present = 1; + pr_info("max8660 probed !\n"); + } else { + max8660_dev_present = 0; + pr_info("max8660 not detected!\n"); + } + return retval; +} + +/*! + * max8660 I2C detach function + * + * @param client struct i2c_client * + * @return 0 + */ +static int max8660_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id max8660_id[] = { + { "max8660", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, max8660_id); + +static struct i2c_driver max8660_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max8660",}, + .probe = max8660_probe, + .remove = max8660_remove, + .id_table = max8660_id, +}; + +/* called by pmic core when init*/ +int max8660_init(void) +{ + int err; + err = i2c_add_driver(&max8660_i2c_driver); + return err; +} +void max8660_exit(void) +{ + i2c_del_driver(&max8660_i2c_driver); +} diff --git a/drivers/mxc/mcu_pmic/max8660.h b/drivers/mxc/mcu_pmic/max8660.h new file mode 100644 index 000000000000..bc326710bbd2 --- /dev/null +++ b/drivers/mxc/mcu_pmic/max8660.h @@ -0,0 +1,49 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file max8660.h + * @brief Driver for max8660 + * + * @ingroup pmic + */ +#ifndef _MAX8660_H_ +#define _MAX8660_H_ + +#ifdef __KERNEL__ + +#define MAX8660_OUTPUT_ENABLE_1 0x10 +#define MAX8660_OUTPUT_ENABLE_2 0x12 +#define MAX8660_VOLT_CHANGE_CONTROL 0x20 +#define MAX8660_V3_TARGET_VOLT_1 0x23 +#define MAX8660_V3_TARGET_VOLT_2 0x24 +#define MAX8660_V4_TARGET_VOLT_1 0x29 +#define MAX8660_V4_TARGET_VOLT_2 0x2A +#define MAX8660_V5_TARGET_VOLT_1 0x32 +#define MAX8660_V5_TARGET_VOLT_2 0x33 +#define MAX8660_V6V7_TARGET_VOLT 0x39 +#define MAX8660_FORCE_PWM 0x80 + +int is_max8660_present(void); +int max8660_write_reg(u8 reg, u8 value); +int max8660_save_buffered_reg_val(int reg_name, u8 value); +int max8660_get_buffered_reg_val(int reg_name, u8 *value); +int max8660_init(void); +void max8660_exit(void); + +extern int reg_max8660_probe(void); +extern int reg_max8660_remove(void); + +#endif /* __KERNEL__ */ + +#endif /* _MAX8660_H_ */ diff --git a/drivers/mxc/mcu_pmic/mc9s08dz60.c b/drivers/mxc/mcu_pmic/mc9s08dz60.c new file mode 100644 index 000000000000..cf0c9a0c2794 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mc9s08dz60.c @@ -0,0 +1,197 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file mc9s08dz60.c + * @brief Driver for MC9sdz60 + * + * @ingroup pmic + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "mc9s08dz60.h" + +/* I2C bus id and device address of mcu */ +#define I2C1_BUS 0 +#define MC9S08DZ60_I2C_ADDR 0xD2 /* 7bits I2C address */ +static struct i2c_client *mc9s08dz60_i2c_client; + +int mc9s08dz60_read_reg(u8 reg, u8 *value) +{ + *value = (u8) i2c_smbus_read_byte_data(mc9s08dz60_i2c_client, reg); + return 0; +} + +int mc9s08dz60_write_reg(u8 reg, u8 value) +{ + if (i2c_smbus_write_byte_data(mc9s08dz60_i2c_client, reg, value) < 0) + return -1; + return 0; +} + +static ssize_t mc9s08dz60_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int i; + u8 value; + int offset = 7; + + for (i = 0; i < 7; i++) { + mc9s08dz60_read_reg(i, &value); + pr_info("reg%02x: %02x\t", i, value); + mc9s08dz60_read_reg(i + offset, &value); + pr_info("reg%02x: %02x\t", i + offset, value); + mc9s08dz60_read_reg(i + offset * 2, &value); + pr_info("reg%02x: %02x\t", i + offset * 2, value); + mc9s08dz60_read_reg(i + offset * 3, &value); + pr_info("reg%02x: %02x\t", i + offset * 3, value); + mc9s08dz60_read_reg(i + offset * 4, &value); + pr_info("reg%02x: %02x\t", i + offset * 4, value); + mc9s08dz60_read_reg(i + offset * 5, &value); + pr_info("reg%02x: %02x\n", i + offset * 5, value); + } + + return 0; +} + +static ssize_t mc9s08dz60_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int ret; + unsigned long reg, new_value; + u8 value; + char *p; + + strict_strtoul(buf, 16, ®); + + p = NULL; + p = memchr(buf, ' ', count); + + if (p == NULL) { + mc9s08dz60_read_reg(reg, &value); + pr_info("reg%02lu: %06x\n", reg, value); + return count; + } + + p += 1; + + strict_strtoul(p, 16, &new_value); + value = new_value; + + ret = mc9s08dz60_write_reg((u8)reg, value); + if (ret == 0) + pr_info("write reg%02lx: %06x\n", reg, value); + else + pr_info("register update failed\n"); + + return count; +} + +static struct device_attribute mc9s08dz60_dev_attr = { + .attr = { + .name = "mc9s08dz60_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = mc9s08dz60_show, + .store = mc9s08dz60_store, +}; + + +/*! + * mc9s08dz60 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return 0 + */ +static int mc9s08dz60_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mc9s08dz60 *mc9s08dz60 = NULL; + struct mc9s08dz60_platform_data *plat_data = client->dev.platform_data; + pr_info("mc9s08dz60 probing .... \n"); + + mc9s08dz60 = kzalloc(sizeof(struct mc9s08dz60), GFP_KERNEL); + if (mc9s08dz60 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc9s08dz60); + mc9s08dz60->dev = &client->dev; + mc9s08dz60->i2c_client = client; + + if (plat_data && plat_data->init) { + ret = plat_data->init(mc9s08dz60); + if (ret != 0) + return -1; + } + + ret = device_create_file(&client->dev, &mc9s08dz60_dev_attr); + if (ret) + dev_err(&client->dev, "create device file failed!\n"); + + + mc9s08dz60_i2c_client = client; + + return 0; +} + +/*! + * mc9s08dz60 I2C detach function + * + * @param client struct i2c_client * + * @return 0 + */ +static int mc9s08dz60_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc9s08dz60_id[] = { + { "mc9s08dz60", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mc9s08dz60_id); + +static struct i2c_driver mc9s08dz60_i2c_driver = { + .driver = {.owner = THIS_MODULE, + .name = "mc9s08dz60", + }, + .probe = mc9s08dz60_probe, + .remove = mc9s08dz60_remove, + .id_table = mc9s08dz60_id, +}; + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +int mc9s08dz60_init(void) +{ + int err; + err = i2c_add_driver(&mc9s08dz60_i2c_driver); + return err; +} +void mc9s08dz60_exit(void) +{ + i2c_del_driver(&mc9s08dz60_i2c_driver); +} diff --git a/drivers/mxc/mcu_pmic/mc9s08dz60.h b/drivers/mxc/mcu_pmic/mc9s08dz60.h new file mode 100644 index 000000000000..58e4e81573e4 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mc9s08dz60.h @@ -0,0 +1,73 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9s08dz60.h + * @brief Driver for mc9s08dz60 + * + * @ingroup pmic + */ +#ifndef _MC9SDZ60_H_ +#define _MC9SDZ60_H_ + +#define MCU_VERSION 0x00 +/*#define Reserved 0x01*/ +#define MCU_SECS 0x02 +#define MCU_MINS 0x03 +#define MCU_HRS 0x04 +#define MCU_DAY 0x05 +#define MCU_DATE 0x06 +#define MCU_MONTH 0x07 +#define MCU_YEAR 0x08 + +#define MCU_ALARM_SECS 0x09 +#define MCU_ALARM_MINS 0x0A +#define MCU_ALARM_HRS 0x0B +/* #define Reserved 0x0C*/ +/* #define Reserved 0x0D*/ +#define MCU_TS_CONTROL 0x0E +#define MCU_X_LOW 0x0F +#define MCU_Y_LOW 0x10 +#define MCU_XY_HIGH 0x11 +#define MCU_X_LEFT_LOW 0x12 +#define MCU_X_LEFT_HIGH 0x13 +#define MCU_X_RIGHT 0x14 +#define MCU_Y_TOP_LOW 0x15 +#define MCU_Y_TOP_HIGH 0x16 +#define MCU_Y_BOTTOM 0x17 +/* #define Reserved 0x18*/ +/* #define Reserved 0x19*/ +#define MCU_RESET_1 0x1A +#define MCU_RESET_2 0x1B +#define MCU_POWER_CTL 0x1C +#define MCU_DELAY_CONFIG 0x1D +/* #define Reserved 0x1E */ +/* #define Reserved 0x1F */ +#define MCU_GPIO_1 0x20 +#define MCU_GPIO_2 0x21 +#define MCU_KPD_1 0x22 +#define MCU_KPD_2 0x23 +#define MCU_KPD_CONTROL 0x24 +#define MCU_INT_ENABLE_1 0x25 +#define MCU_INT_ENABLE_2 0x26 +#define MCU_INT_FLAG_1 0x27 +#define MCU_INT_FLAG_2 0x28 +#define MCU_DES_FLAG 0x29 +int mc9s08dz60_read_reg(u8 reg, u8 *value); +int mc9s08dz60_write_reg(u8 reg, u8 value); +int mc9s08dz60_init(void); +void mc9s08dz60_exit(void); + +extern int reg_mc9s08dz60_probe(void); +extern int reg_mc9s08dz60_remove(void); + +#endif /* _MC9SDZ60_H_ */ diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_core.c b/drivers/mxc/mcu_pmic/mcu_pmic_core.c new file mode 100644 index 000000000000..592f6623527a --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_core.c @@ -0,0 +1,226 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9s08dz60/mcu_pmic_core.c + * @brief This is the main file of mc9s08dz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mcu_pmic_core.h" +#include "mc9s08dz60.h" +#include "max8660.h" + +/* bitfield macros for mcu pmic*/ +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + + +/* map reg names (enum pmic_reg in pmic_external.h) to real addr*/ +const static u8 mcu_pmic_reg_addr_table[] = { + MCU_VERSION, + MCU_SECS, + MCU_MINS, + MCU_HRS, + MCU_DAY, + MCU_DATE, + MCU_MONTH, + MCU_YEAR, + MCU_ALARM_SECS, + MCU_ALARM_MINS, + MCU_ALARM_HRS, + MCU_TS_CONTROL, + MCU_X_LOW, + MCU_Y_LOW, + MCU_XY_HIGH, + MCU_X_LEFT_LOW, + MCU_X_LEFT_HIGH, + MCU_X_RIGHT, + MCU_Y_TOP_LOW, + MCU_Y_TOP_HIGH, + MCU_Y_BOTTOM, + MCU_RESET_1, + MCU_RESET_2, + MCU_POWER_CTL, + MCU_DELAY_CONFIG, + MCU_GPIO_1, + MCU_GPIO_2, + MCU_KPD_1, + MCU_KPD_2, + MCU_KPD_CONTROL, + MCU_INT_ENABLE_1, + MCU_INT_ENABLE_2, + MCU_INT_FLAG_1, + MCU_INT_FLAG_2, + MCU_DES_FLAG, + MAX8660_OUTPUT_ENABLE_1, + MAX8660_OUTPUT_ENABLE_2, + MAX8660_VOLT_CHANGE_CONTROL, + MAX8660_V3_TARGET_VOLT_1, + MAX8660_V3_TARGET_VOLT_2, + MAX8660_V4_TARGET_VOLT_1, + MAX8660_V4_TARGET_VOLT_2, + MAX8660_V5_TARGET_VOLT_1, + MAX8660_V5_TARGET_VOLT_2, + MAX8660_V6V7_TARGET_VOLT, + MAX8660_FORCE_PWM +}; + +static int mcu_pmic_read(int reg_num, unsigned int *reg_val) +{ + int ret; + u8 value = 0; + /* mcu ops */ + if (reg_num >= REG_MCU_VERSION && reg_num <= REG_MCU_DES_FLAG) + ret = mc9s08dz60_read_reg(mcu_pmic_reg_addr_table[reg_num], + &value); + else if (reg_num >= REG_MAX8660_OUTPUT_ENABLE_1 + && reg_num <= REG_MAX8660_FORCE_PWM) + ret = max8660_get_buffered_reg_val(reg_num, &value); + else + return -1; + + if (ret < 0) + return -1; + *reg_val = value; + + return 0; +} + +static int mcu_pmic_write(int reg_num, const unsigned int reg_val) +{ + int ret; + u8 value = reg_val; + /* mcu ops */ + if (reg_num >= REG_MCU_VERSION && reg_num <= REG_MCU_DES_FLAG) { + + ret = + mc9s08dz60_write_reg( + mcu_pmic_reg_addr_table[reg_num], value); + if (ret < 0) + return -1; + } else if (reg_num >= REG_MAX8660_OUTPUT_ENABLE_1 + && reg_num <= REG_MAX8660_FORCE_PWM) { + ret = + max8660_write_reg(mcu_pmic_reg_addr_table[reg_num], value); + + if (ret < 0) + return -1; + + ret = max8660_save_buffered_reg_val(reg_num, value); + } else + return -1; + + return 0; +} + +int mcu_pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = mcu_pmic_read(reg, &temp); + if (ret != 0) + return -1; + *reg_value = (temp & reg_mask); + + pr_debug("Read REG[ %d ] = 0x%x\n", reg, *reg_value); + + return ret; +} + + +int mcu_pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = mcu_pmic_read(reg, &temp); + if (ret != 0) + return -1; + temp = (temp & (~reg_mask)) | reg_value; + + ret = mcu_pmic_write(reg, temp); + if (ret != 0) + return -1; + + pr_debug("Write REG[ %d ] = 0x%x\n", reg, reg_value); + + return ret; +} + +/*! + * make max8660 - mc9s08dz60 enter low-power mode + */ +static void pmic_power_off(void) +{ + mcu_pmic_write_reg(REG_MCU_POWER_CTL, 0x10, 0x10); +} + +static int __init mcu_pmic_init(void) +{ + int err; + + /* init chips */ + err = max8660_init(); + if (err) + goto fail1; + + err = mc9s08dz60_init(); + if (err) + goto fail1; + + if (is_max8660_present()) { + pr_info("max8660 is present \n"); + pm_power_off = pmic_power_off; + } else + pr_debug("max8660 is not present\n"); + pr_info("mcu_pmic_init completed!\n"); + return 0; + +fail1: + pr_err("mcu_pmic_init failed!\n"); + return err; +} + +static void __exit mcu_pmic_exit(void) +{ + reg_max8660_remove(); + mc9s08dz60_exit(); + max8660_exit(); +} + +subsys_initcall_sync(mcu_pmic_init); +module_exit(mcu_pmic_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("mcu pmic driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_core.h b/drivers/mxc/mcu_pmic/mcu_pmic_core.h new file mode 100644 index 000000000000..2fa5ec45c52b --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_core.h @@ -0,0 +1,43 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mcu_pmic_core.h + * @brief Driver for max8660 + * + * @ingroup pmic + */ +#ifndef _MCU_PMIC_CORE_H_ +#define _MCU_PMIC_CORE_H_ + +#include + +#define MAX8660_REG_START (REG_MCU_DES_FLAG + 1) +enum { + + /* reg names for max8660 */ + REG_MAX8660_OUTPUT_ENABLE_1 = MAX8660_REG_START, + REG_MAX8660_OUTPUT_ENABLE_2, + REG_MAX8660_VOLT_CHANGE_CONTROL_1, + REG_MAX8660_V3_TARGET_VOLT_1, + REG_MAX8660_V3_TARGET_VOLT_2, + REG_MAX8660_V4_TARGET_VOLT_1, + REG_MAX8660_V4_TARGET_VOLT_2, + REG_MAX8660_V5_TARGET_VOLT_1, + REG_MAX8660_V5_TARGET_VOLT_2, + REG_MAX8660_V6V7_TARGET_VOLT, + REG_MAX8660_FORCE_PWM +}; + + +#endif /* _MCU_PMIC_CORE_H_ */ diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c b/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c new file mode 100644 index 000000000000..f9cda516198e --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c @@ -0,0 +1,131 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9s08dz60/mcu_pmic_gpio.c + * @brief This is the main file of mc9s08dz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include +#include +#include +#include + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +int pmic_gpio_set_bit_val(int reg, unsigned int bit, + unsigned int val) +{ + int reg_name; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + switch (reg) { + case MCU_GPIO_REG_RESET_1: + reg_name = REG_MCU_RESET_1; + break; + case MCU_GPIO_REG_RESET_2: + reg_name = REG_MCU_RESET_2; + break; + case MCU_GPIO_REG_POWER_CONTROL: + reg_name = REG_MCU_POWER_CTL; + break; + case MCU_GPIO_REG_GPIO_CONTROL_1: + reg_name = REG_MCU_GPIO_1; + break; + case MCU_GPIO_REG_GPIO_CONTROL_2: + reg_name = REG_MCU_GPIO_2; + break; + default: + return -1; + } + + SET_BIT_IN_BYTE(reg_mask, bit); + if (0 == val) + CHECK_ERROR(mcu_pmic_write_reg(reg_name, 0, reg_mask)); + else + CHECK_ERROR(mcu_pmic_write_reg(reg_name, reg_mask, reg_mask)); + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_set_bit_val); + +int pmic_gpio_get_bit_val(int reg, unsigned int bit, + unsigned int *val) +{ + int reg_name; + unsigned int reg_read_val; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + switch (reg) { + case MCU_GPIO_REG_RESET_1: + reg_name = REG_MCU_RESET_1; + break; + case MCU_GPIO_REG_RESET_2: + reg_name = REG_MCU_RESET_2; + break; + case MCU_GPIO_REG_POWER_CONTROL: + reg_name = REG_MCU_POWER_CTL; + break; + case MCU_GPIO_REG_GPIO_CONTROL_1: + reg_name = REG_MCU_GPIO_1; + break; + case MCU_GPIO_REG_GPIO_CONTROL_2: + reg_name = REG_MCU_GPIO_2; + break; + default: + return -1; + } + + SET_BIT_IN_BYTE(reg_mask, bit); + CHECK_ERROR(mcu_pmic_read_reg(reg_name, ®_read_val, reg_mask)); + if (0 == reg_read_val) + *val = 0; + else + *val = 1; + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_get_bit_val); + +int pmic_gpio_get_designation_bit_val(unsigned int bit, + unsigned int *val) +{ + unsigned int reg_read_val; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + SET_BIT_IN_BYTE(reg_mask, bit); + CHECK_ERROR( + mcu_pmic_read_reg(REG_MCU_DES_FLAG, ®_read_val, reg_mask)); + if (0 == reg_read_val) + *val = 0; + else + *val = 1; + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_get_designation_bit_val); diff --git a/drivers/mxc/mlb/Kconfig b/drivers/mxc/mlb/Kconfig new file mode 100644 index 000000000000..7e3b16c2ddae --- /dev/null +++ b/drivers/mxc/mlb/Kconfig @@ -0,0 +1,13 @@ +# +# MLB configuration +# + +menu "MXC Media Local Bus Driver" + +config MXC_MLB + tristate "MLB support" + depends on ARCH_MX35 || ARCH_MX53 + ---help--- + Say Y to get the MLB support. + +endmenu diff --git a/drivers/mxc/mlb/Makefile b/drivers/mxc/mlb/Makefile new file mode 100644 index 000000000000..60662eb1c031 --- /dev/null +++ b/drivers/mxc/mlb/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the kernel MLB driver +# + +obj-$(CONFIG_MXC_MLB) += mxc_mlb.o diff --git a/drivers/mxc/mlb/mxc_mlb.c b/drivers/mxc/mlb/mxc_mlb.c new file mode 100644 index 000000000000..8ccbf0fe4768 --- /dev/null +++ b/drivers/mxc/mlb/mxc_mlb.c @@ -0,0 +1,1060 @@ +/* + * linux/drivers/mxc/mlb/mxc_mlb.c + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + * MLB module memory map registers define + */ +#define MLB_REG_DCCR 0x0 +#define MLB_REG_SSCR 0x4 +#define MLB_REG_SDCR 0x8 +#define MLB_REG_SMCR 0xC +#define MLB_REG_VCCR 0x1C +#define MLB_REG_SBCR 0x20 +#define MLB_REG_ABCR 0x24 +#define MLB_REG_CBCR 0x28 +#define MLB_REG_IBCR 0x2C +#define MLB_REG_CICR 0x30 +#define MLB_REG_CECRn 0x40 +#define MLB_REG_CSCRn 0x44 +#define MLB_REG_CCBCRn 0x48 +#define MLB_REG_CNBCRn 0x4C +#define MLB_REG_LCBCRn 0x280 + +#define MLB_DCCR_FS_OFFSET 28 +#define MLB_DCCR_EN (1 << 31) +#define MLB_DCCR_LBM_OFFSET 30 +#define MLB_DCCR_RESET (1 << 23) +#define MLB_CECR_CE (1 << 31) +#define MLB_CECR_TR (1 << 30) +#define MLB_CECR_CT_OFFSET 28 +#define MLB_CECR_MBS (1 << 19) +#define MLB_CSCR_CBPE (1 << 0) +#define MLB_CSCR_CBDB (1 << 1) +#define MLB_CSCR_CBD (1 << 2) +#define MLB_CSCR_CBS (1 << 3) +#define MLB_CSCR_BE (1 << 4) +#define MLB_CSCR_ABE (1 << 5) +#define MLB_CSCR_LFS (1 << 6) +#define MLB_CSCR_PBPE (1 << 8) +#define MLB_CSCR_PBDB (1 << 9) +#define MLB_CSCR_PBD (1 << 10) +#define MLB_CSCR_PBS (1 << 11) +#define MLB_CSCR_RDY (1 << 16) +#define MLB_CSCR_BM (1 << 31) +#define MLB_CSCR_BF (1 << 30) +#define MLB_SSCR_SDML (1 << 5) + +#define MLB_CONTROL_TX_CHANN (0 << 4) +#define MLB_CONTROL_RX_CHANN (1 << 4) +#define MLB_ASYNC_TX_CHANN (2 << 4) +#define MLB_ASYNC_RX_CHANN (3 << 4) + +#define MLB_MINOR_DEVICES 2 +#define MLB_CONTROL_DEV_NAME "ctrl" +#define MLB_ASYNC_DEV_NAME "async" + +#define TX_CHANNEL 0 +#define RX_CHANNEL 1 +#define TX_CHANNEL_BUF_SIZE 1024 +#define RX_CHANNEL_BUF_SIZE (2*1024) +/* max package data size */ +#define ASYNC_PACKET_SIZE 1024 +#define CTRL_PACKET_SIZE 64 +#define RX_RING_NODES 10 + +#define MLB_IRAM_SIZE (MLB_MINOR_DEVICES * (TX_CHANNEL_BUF_SIZE + RX_CHANNEL_BUF_SIZE)) +#define _get_txchan(dev) mlb_devinfo[dev].channels[TX_CHANNEL] +#define _get_rxchan(dev) mlb_devinfo[dev].channels[RX_CHANNEL] + +enum { + MLB_CTYPE_SYNC, + MLB_CTYPE_ISOC, + MLB_CTYPE_ASYNC, + MLB_CTYPE_CTRL, +}; + +/*! + * Rx ring buffer + */ +struct mlb_rx_ringnode { + int size; + char *data; +}; + +struct mlb_channel_info { + + /* channel offset in memmap */ + const unsigned int reg_offset; + /* channel address */ + int address; + /*! + * channel buffer start address + * for Rx, buf_head pointer to a loop ring buffer + */ + unsigned long buf_head; + /* physical buffer head address */ + unsigned long phy_head; + /* channel buffer size */ + unsigned int buf_size; + /* channel buffer current ptr */ + unsigned long buf_ptr; + /* buffer spin lock */ + rwlock_t buf_lock; +}; + +struct mlb_dev_info { + + /* device node name */ + const char dev_name[20]; + /* channel type */ + const unsigned int channel_type; + /* channel info for tx/rx */ + struct mlb_channel_info channels[2]; + /* rx ring buffer */ + struct mlb_rx_ringnode rx_bufs[RX_RING_NODES]; + /* rx ring buffer read/write ptr */ + unsigned int rdpos, wtpos; + /* exception event */ + unsigned long ex_event; + /* channel started up or not */ + atomic_t on; + /* device open count */ + atomic_t opencnt; + /* wait queue head for channel */ + wait_queue_head_t rd_wq; + wait_queue_head_t wt_wq; + /* spinlock for event access */ + spinlock_t event_lock; +}; + +static struct mlb_dev_info mlb_devinfo[MLB_MINOR_DEVICES] = { + { + .dev_name = MLB_CONTROL_DEV_NAME, + .channel_type = MLB_CTYPE_CTRL, + .channels = { + [0] = { + .reg_offset = MLB_CONTROL_TX_CHANN, + .buf_size = TX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[0].channels[0]. + buf_lock), + }, + [1] = { + .reg_offset = MLB_CONTROL_RX_CHANN, + .buf_size = RX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[0].channels[1]. + buf_lock), + }, + }, + .on = ATOMIC_INIT(0), + .opencnt = ATOMIC_INIT(0), + .rd_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[0].rd_wq), + .wt_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[0].wt_wq), + .event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[0].event_lock), + }, + { + .dev_name = MLB_ASYNC_DEV_NAME, + .channel_type = MLB_CTYPE_ASYNC, + .channels = { + [0] = { + .reg_offset = MLB_ASYNC_TX_CHANN, + .buf_size = TX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[1].channels[0]. + buf_lock), + }, + [1] = { + .reg_offset = MLB_ASYNC_RX_CHANN, + .buf_size = RX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[1].channels[1]. + buf_lock), + }, + }, + .on = ATOMIC_INIT(0), + .opencnt = ATOMIC_INIT(0), + .rd_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[1].rd_wq), + .wt_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[1].wt_wq), + .event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[1].event_lock), + }, +}; + +static struct regulator *reg_nvcc; /* NVCC_MLB regulator */ +static struct clk *mlb_clk; +static struct cdev mxc_mlb_dev; /* chareset device */ +static dev_t dev; +static struct class *mlb_class; /* device class */ +static struct device *class_dev; +static unsigned long mlb_base; /* mlb module base address */ +static unsigned int irq; +static unsigned long iram_base; +static __iomem void *iram_addr; + +/*! + * Initial the MLB module device + */ +static void mlb_dev_init(void) +{ + unsigned long dccr_val; + unsigned long phyaddr; + + /* reset the MLB module */ + __raw_writel(MLB_DCCR_RESET, mlb_base + MLB_REG_DCCR); + while (__raw_readl(mlb_base + MLB_REG_DCCR) + & MLB_DCCR_RESET) + ; + + /*! + * Enable MLB device, disable loopback mode, + * set default fps to 512, set mlb device address to 0 + */ + dccr_val = MLB_DCCR_EN; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + + /* disable all the system interrupt */ + __raw_writel(0x5F, mlb_base + MLB_REG_SMCR); + + /* write async, control tx/rx base address */ + phyaddr = _get_txchan(0).phy_head >> 16; + __raw_writel(phyaddr << 16 | phyaddr, mlb_base + MLB_REG_CBCR); + phyaddr = _get_txchan(1).phy_head >> 16; + __raw_writel(phyaddr << 16 | phyaddr, mlb_base + MLB_REG_ABCR); + +} + +static void mlb_dev_exit(void) +{ + __raw_writel(0, mlb_base + MLB_REG_DCCR); +} + +/*! + * MLB receive start function + * + * load phy_head to next buf register to start next rx + * here use single-packet buffer, set start=end + */ +static void mlb_start_rx(int cdev_id) +{ + struct mlb_channel_info *chinfo = &_get_rxchan(cdev_id); + unsigned long next; + + next = chinfo->phy_head & 0xFFFC; + /* load next buf */ + __raw_writel((next << 16) | next, mlb_base + + MLB_REG_CNBCRn + chinfo->reg_offset); + /* set ready bit to start next rx */ + __raw_writel(MLB_CSCR_RDY, mlb_base + MLB_REG_CSCRn + + chinfo->reg_offset); +} + +/*! + * MLB transmit start function + * make sure aquiring the rw buf_lock, when calling this + */ +static void mlb_start_tx(int cdev_id) +{ + struct mlb_channel_info *chinfo = &_get_txchan(cdev_id); + unsigned long begin, end; + + begin = chinfo->phy_head; + end = (chinfo->phy_head + chinfo->buf_ptr - chinfo->buf_head) & 0xFFFC; + /* load next buf */ + __raw_writel((begin << 16) | end, mlb_base + + MLB_REG_CNBCRn + chinfo->reg_offset); + /* set ready bit to start next tx */ + __raw_writel(MLB_CSCR_RDY, mlb_base + MLB_REG_CSCRn + + chinfo->reg_offset); +} + +/*! + * Enable the MLB channel + */ +static void mlb_channel_enable(int chan_dev_id, int on) +{ + unsigned long tx_regval = 0, rx_regval = 0; + /*! + * setup the direction, enable, channel type, + * mode select, channel address and mask buf start + */ + if (on) { + unsigned int ctype = mlb_devinfo[chan_dev_id].channel_type; + tx_regval = MLB_CECR_CE | MLB_CECR_TR | MLB_CECR_MBS | + (ctype << MLB_CECR_CT_OFFSET) | + _get_txchan(chan_dev_id).address; + rx_regval = MLB_CECR_CE | MLB_CECR_MBS | + (ctype << MLB_CECR_CT_OFFSET) | + _get_rxchan(chan_dev_id).address; + + atomic_set(&mlb_devinfo[chan_dev_id].on, 1); + } else { + atomic_set(&mlb_devinfo[chan_dev_id].on, 0); + } + + /* update the rx/tx channel entry config */ + __raw_writel(tx_regval, mlb_base + MLB_REG_CECRn + + _get_txchan(chan_dev_id).reg_offset); + __raw_writel(rx_regval, mlb_base + MLB_REG_CECRn + + _get_rxchan(chan_dev_id).reg_offset); + + if (on) + mlb_start_rx(chan_dev_id); +} + +/*! + * MLB interrupt handler + */ +void mlb_tx_isr(int minor, unsigned int cis) +{ + struct mlb_channel_info *chinfo = &_get_txchan(minor); + + if (cis & MLB_CSCR_CBD) { + /* buffer done, reset the buf_ptr */ + write_lock(&chinfo->buf_lock); + chinfo->buf_ptr = chinfo->buf_head; + write_unlock(&chinfo->buf_lock); + /* wake up the writer */ + wake_up_interruptible(&mlb_devinfo[minor].wt_wq); + } +} + +void mlb_rx_isr(int minor, unsigned int cis) +{ + struct mlb_channel_info *chinfo = &_get_rxchan(minor); + unsigned long end; + unsigned int len; + + if (cis & MLB_CSCR_CBD) { + + int wpos, rpos; + + rpos = mlb_devinfo[minor].rdpos; + wpos = mlb_devinfo[minor].wtpos; + + /* buffer done, get current buffer ptr */ + end = + __raw_readl(mlb_base + MLB_REG_CCBCRn + chinfo->reg_offset); + end >>= 16; /* end here is phy */ + len = end - (chinfo->phy_head & 0xFFFC); + + /*! + * copy packet from IRAM buf to ring buf. + * if the wpos++ == rpos, drop this packet + */ + if (((wpos + 1) % RX_RING_NODES) != rpos) { + +#ifdef DEBUG + if (mlb_devinfo[minor].channel_type == MLB_CTYPE_CTRL) { + if (len > CTRL_PACKET_SIZE) + pr_debug + ("mxc_mlb: ctrl packet" + "overflow\n"); + } else { + if (len > ASYNC_PACKET_SIZE) + pr_debug + ("mxc_mlb: async packet" + "overflow\n"); + } +#endif + memcpy(mlb_devinfo[minor].rx_bufs[wpos].data, + (const void *)chinfo->buf_head, len); + mlb_devinfo[minor].rx_bufs[wpos].size = len; + + /* update the ring wpos */ + mlb_devinfo[minor].wtpos = (wpos + 1) % RX_RING_NODES; + + /* wake up the reader */ + wake_up_interruptible(&mlb_devinfo[minor].rd_wq); + + pr_debug("recv package, len:%d, rdpos: %d, wtpos: %d\n", + len, rpos, mlb_devinfo[minor].wtpos); + } else { + pr_debug + ("drop package, due to no space, (%d,%d)\n", + rpos, mlb_devinfo[minor].wtpos); + } + + /* start next rx */ + mlb_start_rx(minor); + } +} + +static irqreturn_t mlb_isr(int irq, void *dev_id) +{ + unsigned long int_status, sscr, tx_cis, rx_cis; + struct mlb_dev_info *pdev; + int minor; + + sscr = __raw_readl(mlb_base + MLB_REG_SSCR); + pr_debug("mxc_mlb: system interrupt:%lx\n", sscr); + __raw_writel(0x7F, mlb_base + MLB_REG_SSCR); + + int_status = __raw_readl(mlb_base + MLB_REG_CICR) & 0xFFFF; + pr_debug("mxc_mlb: channel interrupt ids: %lx\n", int_status); + + for (minor = 0; minor < MLB_MINOR_DEVICES; minor++) { + + pdev = &mlb_devinfo[minor]; + tx_cis = rx_cis = 0; + + /* get tx channel interrupt status */ + if (int_status & (1 << (_get_txchan(minor).reg_offset >> 4))) + tx_cis = __raw_readl(mlb_base + MLB_REG_CSCRn + + _get_txchan(minor).reg_offset); + /* get rx channel interrupt status */ + if (int_status & (1 << (_get_rxchan(minor).reg_offset >> 4))) + rx_cis = __raw_readl(mlb_base + MLB_REG_CSCRn + + _get_rxchan(minor).reg_offset); + + if (!tx_cis && !rx_cis) + continue; + + pr_debug("tx/rx int status: 0x%08lx/0x%08lx\n", tx_cis, rx_cis); + /* fill exception event */ + spin_lock(&pdev->event_lock); + pdev->ex_event |= tx_cis & 0x303; + pdev->ex_event |= (rx_cis & 0x303) << 16; + spin_unlock(&pdev->event_lock); + + /* clear the interrupt status */ + __raw_writel(tx_cis & 0xFFFF, mlb_base + MLB_REG_CSCRn + + _get_txchan(minor).reg_offset); + __raw_writel(rx_cis & 0xFFFF, mlb_base + MLB_REG_CSCRn + + _get_rxchan(minor).reg_offset); + + /* handel tx channel */ + if (tx_cis) + mlb_tx_isr(minor, tx_cis); + /* handle rx channel */ + if (rx_cis) + mlb_rx_isr(minor, rx_cis); + + } + + return IRQ_HANDLED; +} + +static int mxc_mlb_open(struct inode *inode, struct file *filp) +{ + int minor; + + minor = MINOR(inode->i_rdev); + + if (minor < 0 || minor >= MLB_MINOR_DEVICES) + return -ENODEV; + + /* open for each channel device */ + if (atomic_cmpxchg(&mlb_devinfo[minor].opencnt, 0, 1) != 0) + return -EBUSY; + + /* reset the buffer read/write ptr */ + _get_txchan(minor).buf_ptr = _get_txchan(minor).buf_head; + _get_rxchan(minor).buf_ptr = _get_rxchan(minor).buf_head; + mlb_devinfo[minor].rdpos = mlb_devinfo[minor].wtpos = 0; + mlb_devinfo[minor].ex_event = 0; + + return 0; +} + +static int mxc_mlb_release(struct inode *inode, struct file *filp) +{ + int minor; + + minor = MINOR(inode->i_rdev); + + /* clear channel settings and info */ + mlb_channel_enable(minor, 0); + + /* decrease the open count */ + atomic_set(&mlb_devinfo[minor].opencnt, 0); + + return 0; +} + +static int mxc_mlb_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + unsigned long flags, event; + int minor; + + minor = MINOR(inode->i_rdev); + + switch (cmd) { + + case MLB_CHAN_SETADDR: + { + unsigned int caddr; + /* get channel address from user space */ + if (copy_from_user(&caddr, argp, sizeof(caddr))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + _get_txchan(minor).address = (caddr >> 16) & 0xFFFF; + _get_rxchan(minor).address = caddr & 0xFFFF; + break; + } + + case MLB_CHAN_STARTUP: + if (atomic_read(&mlb_devinfo[minor].on)) { + pr_debug("mxc_mlb: channel areadly startup\n"); + break; + } + mlb_channel_enable(minor, 1); + break; + case MLB_CHAN_SHUTDOWN: + if (atomic_read(&mlb_devinfo[minor].on) == 0) { + pr_debug("mxc_mlb: channel areadly shutdown\n"); + break; + } + mlb_channel_enable(minor, 0); + break; + case MLB_CHAN_GETEVENT: + /* get and clear the ex_event */ + spin_lock_irqsave(&mlb_devinfo[minor].event_lock, flags); + event = mlb_devinfo[minor].ex_event; + mlb_devinfo[minor].ex_event = 0; + spin_unlock_irqrestore(&mlb_devinfo[minor].event_lock, flags); + + if (event) { + if (copy_to_user(argp, &event, sizeof(event))) { + pr_err("mxc_mlb: copy to user failed\n"); + return -EFAULT; + } + } else { + pr_debug("mxc_mlb: no exception event now\n"); + return -EAGAIN; + } + break; + case MLB_SET_FPS: + { + unsigned int fps; + unsigned long dccr_val; + + /* get fps from user space */ + if (copy_from_user(&fps, argp, sizeof(fps))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + /* check fps value */ + if (fps != 256 && fps != 512 && fps != 1024) { + pr_debug("mxc_mlb: invalid fps argument\n"); + return -EINVAL; + } + + dccr_val = __raw_readl(mlb_base + MLB_REG_DCCR); + dccr_val &= ~(0x3 << MLB_DCCR_FS_OFFSET); + dccr_val |= (fps >> 9) << MLB_DCCR_FS_OFFSET; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + break; + } + + case MLB_GET_VER: + { + unsigned long version; + + /* get MLB device module version */ + version = __raw_readl(mlb_base + MLB_REG_VCCR); + + if (copy_to_user(argp, &version, sizeof(version))) { + pr_err("mxc_mlb: copy to user failed\n"); + return -EFAULT; + } + break; + } + + case MLB_SET_DEVADDR: + { + unsigned long dccr_val; + unsigned char devaddr; + + /* get MLB device address from user space */ + if (copy_from_user + (&devaddr, argp, sizeof(unsigned char))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + dccr_val = __raw_readl(mlb_base + MLB_REG_DCCR); + dccr_val &= ~0xFF; + dccr_val |= devaddr; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + + break; + } + default: + pr_info("mxc_mlb: Invalid ioctl command\n"); + return -EINVAL; + } + + return 0; +} + +/*! + * MLB read routine + * + * Read the current received data from queued buffer, + * and free this buffer for hw to fill ingress data. + */ +static ssize_t mxc_mlb_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + int minor, ret; + int size, rdpos; + struct mlb_rx_ringnode *rxbuf; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + rdpos = mlb_devinfo[minor].rdpos; + rxbuf = mlb_devinfo[minor].rx_bufs; + + /* check the current rx buffer is available or not */ + if (rdpos == mlb_devinfo[minor].wtpos) { + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + /* if !O_NONBLOCK, we wait for recv packet */ + ret = wait_event_interruptible(mlb_devinfo[minor].rd_wq, + (mlb_devinfo[minor].wtpos != + rdpos)); + if (ret < 0) + return ret; + } + + size = rxbuf[rdpos].size; + if (size > count) { + /* the user buffer is too small */ + pr_warning + ("mxc_mlb: received data size is bigger than count\n"); + return -EINVAL; + } + + /* copy rx buffer data to user buffer */ + if (copy_to_user(buf, rxbuf[rdpos].data, size)) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + /* update the read ptr */ + mlb_devinfo[minor].rdpos = (rdpos + 1) % RX_RING_NODES; + + *f_pos = 0; + + return size; +} + +/*! + * MLB write routine + * + * Copy the user data to tx channel buffer, + * and prepare the channel current/next buffer ptr. + */ +static ssize_t mxc_mlb_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + int minor; + unsigned long flags; + DEFINE_WAIT(__wait); + int ret; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + if (count > _get_txchan(minor).buf_size) { + /* too many data to write */ + pr_warning("mxc_mlb: overflow write data\n"); + return -EFBIG; + } + + *f_pos = 0; + + /* check the current tx buffer is used or not */ + write_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr != _get_txchan(minor).buf_head) { + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + + /* there's already some datas being transmit now */ + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + /* if !O_NONBLOCK, we wait for transmit finish */ + for (;;) { + prepare_to_wait(&mlb_devinfo[minor].wt_wq, + &__wait, TASK_INTERRUPTIBLE); + + write_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr == + _get_txchan(minor).buf_head) + break; + + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, + flags); + if (!signal_pending(current)) { + schedule(); + continue; + } + return -ERESTARTSYS; + } + finish_wait(&mlb_devinfo[minor].wt_wq, &__wait); + } + + /* copy user buffer to tx buffer */ + if (copy_from_user((void *)_get_txchan(minor).buf_ptr, buf, count)) { + pr_err("mxc_mlb: copy from user failed\n"); + ret = -EFAULT; + goto out; + } + _get_txchan(minor).buf_ptr += count; + + /* set current/next buffer start/end */ + mlb_start_tx(minor); + + ret = count; + +out: + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + return ret; +} + +static unsigned int mxc_mlb_poll(struct file *filp, + struct poll_table_struct *wait) +{ + int minor; + unsigned int ret = 0; + unsigned long flags; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + poll_wait(filp, &mlb_devinfo[minor].rd_wq, wait); + poll_wait(filp, &mlb_devinfo[minor].wt_wq, wait); + + /* check the tx buffer is avaiable or not */ + read_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr == _get_txchan(minor).buf_head) + ret |= POLLOUT | POLLWRNORM; + read_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + + /* check the rx buffer filled or not */ + if (mlb_devinfo[minor].rdpos != mlb_devinfo[minor].wtpos) + ret |= POLLIN | POLLRDNORM; + + /* check the exception event */ + if (mlb_devinfo[minor].ex_event) + ret |= POLLIN | POLLRDNORM; + + return ret; +} + +/*! + * char dev file operations structure + */ +static struct file_operations mxc_mlb_fops = { + + .owner = THIS_MODULE, + .open = mxc_mlb_open, + .release = mxc_mlb_release, + .ioctl = mxc_mlb_ioctl, + .poll = mxc_mlb_poll, + .read = mxc_mlb_read, + .write = mxc_mlb_write, +}; + +/*! + * This function is called whenever the MLB device is detected. + */ +static int __devinit mxc_mlb_probe(struct platform_device *pdev) +{ + int ret, mlb_major, i, j; + struct mxc_mlb_platform_data *plat_data; + struct resource *res; + void __iomem *base, *bufaddr; + unsigned long phyaddr; + + /* malloc the Rx ring buffer firstly */ + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + char *buf; + int bufsize; + + if (mlb_devinfo[i].channel_type == MLB_CTYPE_ASYNC) + bufsize = ASYNC_PACKET_SIZE; + else + bufsize = CTRL_PACKET_SIZE; + + buf = kmalloc(bufsize * RX_RING_NODES, GFP_KERNEL); + if (buf == NULL) { + ret = -ENOMEM; + dev_err(&pdev->dev, "can not alloc rx buffers\n"); + goto err4; + } + for (j = 0; j < RX_RING_NODES; j++) { + mlb_devinfo[i].rx_bufs[j].data = buf; + buf += bufsize; + } + } + + /** + * Register MLB lld as two character devices + * One for Packet date channel, the other for control data channel + */ + ret = alloc_chrdev_region(&dev, 0, MLB_MINOR_DEVICES, "mxc_mlb"); + mlb_major = MAJOR(dev); + + if (ret < 0) { + dev_err(&pdev->dev, "can't get major %d\n", mlb_major); + goto err3; + } + + cdev_init(&mxc_mlb_dev, &mxc_mlb_fops); + mxc_mlb_dev.owner = THIS_MODULE; + + ret = cdev_add(&mxc_mlb_dev, dev, MLB_MINOR_DEVICES); + if (ret) { + dev_err(&pdev->dev, "can't add cdev\n"); + goto err2; + } + + /* create class and device for udev information */ + mlb_class = class_create(THIS_MODULE, "mlb"); + if (IS_ERR(mlb_class)) { + dev_err(&pdev->dev, "failed to create mlb class\n"); + ret = -ENOMEM; + goto err2; + } + + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + + class_dev = device_create(mlb_class, NULL, MKDEV(mlb_major, i), + NULL, mlb_devinfo[i].dev_name); + if (IS_ERR(class_dev)) { + dev_err(&pdev->dev, "failed to create mlb %s" + " class device\n", mlb_devinfo[i].dev_name); + ret = -ENOMEM; + goto err1; + } + } + + /* get irq line */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No mlb irq line provided\n"); + goto err1; + } + + irq = res->start; + /* request irq */ + if (request_irq(irq, mlb_isr, 0, "mlb", NULL)) { + dev_err(&pdev->dev, "failed to request irq\n"); + ret = -EBUSY; + goto err1; + } + + /* ioremap from phy mlb to kernel space */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No mlb base address provided\n"); + goto err0; + } + + base = ioremap(res->start, res->end - res->start); + dev_dbg(&pdev->dev, "mapped mlb base address: 0x%08x\n", + (unsigned int)base); + + if (base == NULL) { + dev_err(&pdev->dev, "failed to do ioremap with mlb base\n"); + goto err0; + } + mlb_base = (unsigned long)base; + + /*! + * get rx/tx buffer address from platform data + * make sure the buf_address is 4bytes aligned + * + * ------------------- <-- plat_data->buf_address + * | minor 0 tx buf | + * ----------------- + * | minor 0 rx buf | + * ----------------- + * | .... | + * ----------------- + * | minor n tx buf | + * ----------------- + * | minor n rx buf | + * ------------------- + */ + + plat_data = (struct mxc_mlb_platform_data *)pdev->dev.platform_data; + + bufaddr = iram_addr = iram_alloc(MLB_IRAM_SIZE, &iram_base); + phyaddr = iram_base; + + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + /* set the virtual and physical buf head address */ + _get_txchan(i).buf_head = bufaddr; + _get_txchan(i).phy_head = phyaddr; + + bufaddr += TX_CHANNEL_BUF_SIZE; + phyaddr += TX_CHANNEL_BUF_SIZE; + + _get_rxchan(i).buf_head = (unsigned long)bufaddr; + _get_rxchan(i).phy_head = phyaddr; + + bufaddr += RX_CHANNEL_BUF_SIZE; + phyaddr += RX_CHANNEL_BUF_SIZE; + + dev_dbg(&pdev->dev, "phy_head: tx(%lx), rx(%lx)\n", + _get_txchan(i).phy_head, _get_rxchan(i).phy_head); + dev_dbg(&pdev->dev, "buf_head: tx(%lx), rx(%lx)\n", + _get_txchan(i).buf_head, _get_rxchan(i).buf_head); + } + + /* enable GPIO */ + gpio_mlb_active(); + + if (plat_data->reg_nvcc) { + /* power on MLB */ + reg_nvcc = regulator_get(&pdev->dev, plat_data->reg_nvcc); + if (!IS_ERR(reg_nvcc)) { + /* set MAX LDO6 for NVCC to 2.5V */ + regulator_set_voltage(reg_nvcc, 2500000, 2500000); + regulator_enable(reg_nvcc); + } + } + + /* enable clock */ + mlb_clk = clk_get(&pdev->dev, plat_data->mlb_clk); + clk_enable(mlb_clk); + + /* initial MLB module */ + mlb_dev_init(); + + return 0; + +err0: + free_irq(irq, NULL); +err1: + for (--i; i >= 0; i--) + device_destroy(mlb_class, MKDEV(mlb_major, i)); + + class_destroy(mlb_class); +err2: + cdev_del(&mxc_mlb_dev); +err3: + unregister_chrdev_region(dev, MLB_MINOR_DEVICES); +err4: + for (i = 0; i < MLB_MINOR_DEVICES; i++) + kfree(mlb_devinfo[i].rx_bufs[0].data); + + return ret; +} + +static int __devexit mxc_mlb_remove(struct platform_device *pdev) +{ + int i; + + mlb_dev_exit(); + + /* disable mlb clock */ + clk_disable(mlb_clk); + clk_put(mlb_clk); + + /* disable mlb power */ + regulator_disable(reg_nvcc); + regulator_put(reg_nvcc); + + /* inactive GPIO */ + gpio_mlb_inactive(); + + iram_free(iram_base, MLB_IRAM_SIZE); + + /* iounmap */ + iounmap((void *)mlb_base); + + free_irq(irq, NULL); + + /* destroy mlb device class */ + for (i = MLB_MINOR_DEVICES - 1; i >= 0; i--) + device_destroy(mlb_class, MKDEV(MAJOR(dev), i)); + class_destroy(mlb_class); + + /* Unregister the two MLB devices */ + cdev_del(&mxc_mlb_dev); + unregister_chrdev_region(dev, MLB_MINOR_DEVICES); + + for (i = 0; i < MLB_MINOR_DEVICES; i++) + kfree(mlb_devinfo[i].rx_bufs[0].data); + + return 0; +} + +#ifdef CONFIG_PM +static int mxc_mlb_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int mxc_mlb_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define mxc_mlb_suspend NULL +#define mxc_mlb_resume NULL +#endif + +/*! + * platform driver structure for MLB + */ +static struct platform_driver mxc_mlb_driver = { + .driver = { + .name = "mxc_mlb"}, + .probe = mxc_mlb_probe, + .remove = __devexit_p(mxc_mlb_remove), + .suspend = mxc_mlb_suspend, + .resume = mxc_mlb_resume, +}; + +static int __init mxc_mlb_init(void) +{ + return platform_driver_register(&mxc_mlb_driver); +} + +static void __exit mxc_mlb_exit(void) +{ + platform_driver_unregister(&mxc_mlb_driver); +} + +module_init(mxc_mlb_init); +module_exit(mxc_mlb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MLB low level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/Kconfig b/drivers/mxc/pmic/Kconfig new file mode 100644 index 000000000000..4ee91110fc03 --- /dev/null +++ b/drivers/mxc/pmic/Kconfig @@ -0,0 +1,64 @@ +# +# PMIC device driver configuration +# + +menu "MXC PMIC support" + +config MXC_PMIC + boolean + +config MXC_PMIC_MC13783 + tristate "MC13783 PMIC" + depends on ARCH_MXC && SPI + select MXC_PMIC + ---help--- + This is the MXC MC13783(PMIC) support. It include + ADC, Audio, Battery, Connectivity, Light, Power and RTC. + +config MXC_PMIC_MC13892 + tristate "MC13892 PMIC" + depends on ARCH_MXC && (I2C || SPI) + select MXC_PMIC + ---help--- + This is the MXC MC13892(PMIC) support. It include + ADC, Battery, Connectivity, Light, Power and RTC. + +config MXC_PMIC_I2C + bool "Support PMIC I2C Interface" + depends on MXC_PMIC_MC13892 && I2C + +config MXC_PMIC_SPI + bool "Support PMIC SPI Interface" + depends on (MXC_PMIC_MC13892 || MXC_PMIC_MC13783) && SPI + +config MXC_PMIC_MC34704 + tristate "MC34704 PMIC" + depends on ARCH_MXC && I2C + select MXC_PMIC + ---help--- + This is the MXC MC34704 PMIC support. + +config MXC_PMIC_MC9SDZ60 + tristate "MC9sDZ60 PMIC" + depends on ARCH_MXC && I2C + select MXC_PMIC + ---help--- + This is the MXC MC9sDZ60(MCU) PMIC support. + +config MXC_PMIC_CHARDEV + tristate "MXC PMIC device interface" + depends on MXC_PMIC + help + Say Y here to use "pmic" device files, found in the /dev directory + on the system. They make it possible to have user-space programs + use or controll PMIC. Mainly its useful for notifying PMIC events + to user-space programs. + +comment "MXC PMIC Client Drivers" + depends on MXC_PMIC + +source "drivers/mxc/pmic/mc13783/Kconfig" + +source "drivers/mxc/pmic/mc13892/Kconfig" + +endmenu diff --git a/drivers/mxc/pmic/Makefile b/drivers/mxc/pmic/Makefile new file mode 100644 index 000000000000..385c07e8509f --- /dev/null +++ b/drivers/mxc/pmic/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the MXC PMIC drivers. +# + +obj-y += core/ +obj-$(CONFIG_MXC_PMIC_MC13783) += mc13783/ +obj-$(CONFIG_MXC_PMIC_MC13892) += mc13892/ diff --git a/drivers/mxc/pmic/core/Makefile b/drivers/mxc/pmic/core/Makefile new file mode 100644 index 000000000000..bb42231e3aa8 --- /dev/null +++ b/drivers/mxc/pmic/core/Makefile @@ -0,0 +1,21 @@ +# +# Makefile for the PMIC core drivers. +# +obj-$(CONFIG_MXC_PMIC_MC13783) += pmic_mc13783_mod.o +pmic_mc13783_mod-objs := pmic_external.o pmic_event.o pmic_common.o pmic_core_spi.o mc13783.o + +obj-$(CONFIG_MXC_PMIC_MC13892) += pmic_mc13892_mod.o +pmic_mc13892_mod-objs := pmic_external.o pmic_event.o pmic_common.o mc13892.o + +ifneq ($(CONFIG_MXC_PMIC_SPI),) +pmic_mc13892_mod-objs += pmic_core_spi.o +endif + +ifneq ($(CONFIG_MXC_PMIC_I2C),) +pmic_mc13892_mod-objs += pmic_core_i2c.o +endif + +obj-$(CONFIG_MXC_PMIC_MC34704) += pmic_mc34704_mod.o +pmic_mc34704_mod-objs := pmic_external.o pmic_event.o mc34704.o + +obj-$(CONFIG_MXC_PMIC_CHARDEV) += pmic-dev.o diff --git a/drivers/mxc/pmic/core/mc13783.c b/drivers/mxc/pmic/core/mc13783.c new file mode 100644 index 000000000000..4d64ab7d7ce3 --- /dev/null +++ b/drivers/mxc/pmic/core/mc13783.c @@ -0,0 +1,380 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic/core/mc13783.c + * @brief This file contains MC13783 specific PMIC code. This implementaion + * may differ for each PMIC chip. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pmic.h" + +/* + * Defines + */ +#define EVENT_MASK_0 0x697fdf +#define EVENT_MASK_1 0x3efffb +#define MXC_PMIC_FRAME_MASK 0x00FFFFFF +#define MXC_PMIC_MAX_REG_NUM 0x3F +#define MXC_PMIC_REG_NUM_SHIFT 0x19 +#define MXC_PMIC_WRITE_BIT_SHIFT 31 + +static unsigned int events_enabled0; +static unsigned int events_enabled1; +struct mxc_pmic pmic_drv_data; + +/*! + * This function is called to read a register on PMIC. + * + * @param reg_num number of the pmic register to be read + * @param reg_val return value of register + * + * @return Returns 0 on success -1 on failure. + */ +int pmic_read(unsigned int reg_num, unsigned int *reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) &frame, 1); + + *reg_val = frame & MXC_PMIC_FRAME_MASK; + + return ret; +} + +/*! + * This function is called to write a value to the register on PMIC. + * + * @param reg_num number of the pmic register to be written + * @param reg_val value to be written + * + * @return Returns 0 on success -1 on failure. + */ +int pmic_write(int reg_num, const unsigned int reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= (1 << MXC_PMIC_WRITE_BIT_SHIFT); + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + frame |= reg_val & MXC_PMIC_FRAME_MASK; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) &frame, 1); + + return ret; +} + +void *pmic_alloc_data(struct device *dev) +{ + struct mc13783 *mc13783; + + mc13783 = kzalloc(sizeof(struct mc13783), GFP_KERNEL); + if (mc13783 == NULL) + return NULL; + + mc13783->dev = dev; + + return (void *)mc13783; +} + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi) +{ + /* Setup the SPI slave i.e.PMIC */ + pmic_drv_data.spi = spi; + + spi->mode = SPI_MODE_2 | SPI_CS_HIGH; + spi->bits_per_word = 32; + + return spi_setup(spi); +} + +/*! + * This function initializes the PMIC registers. + * + * @return None + */ +int pmic_init_registers(void) +{ + CHECK_ERROR(pmic_write(REG_INTERRUPT_MASK_0, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_MASK_1, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_STATUS_0, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_STATUS_1, MXC_PMIC_FRAME_MASK)); + return PMIC_SUCCESS; +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t *ver) +{ + int rev_id = 0; + int rev1 = 0; + int rev2 = 0; + int finid = 0; + int icid = 0; + + ver->id = PMIC_MC13783; + pmic_read(REG_REVISION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + /* Ver 0.2 is actually 3.2a. Report as 3.2 */ + if ((rev1 == 0) && (rev2 == 2)) { + rev1 = 3; + } + + if (rev1 == 0 || icid != 2) { + ver->revision = -1; + printk(KERN_NOTICE + "mc13783: Not detected.\tAccess failed\t!!!\n"); + } else { + ver->revision = ((rev1 * 10) + rev2); + printk(KERN_INFO "mc13783 Rev %d.%d FinVer %x detected\n", rev1, + rev2, finid); + } + + return; + +} + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int status0, status1; + int bit_set; + + pmic_read(REG_INTERRUPT_STATUS_0, &status0); + pmic_read(REG_INTERRUPT_STATUS_1, &status1); + pmic_write(REG_INTERRUPT_STATUS_0, status0); + pmic_write(REG_INTERRUPT_STATUS_1, status1); + status0 &= events_enabled0; + status1 &= events_enabled1; + + while (status0) { + bit_set = ffs(status0) - 1; + *(active_events + count) = bit_set; + count++; + status0 ^= (1 << bit_set); + } + while (status1) { + bit_set = ffs(status1) - 1; + *(active_events + count) = bit_set + 24; + count++; + status1 ^= (1 << bit_set); + } + + return count; +} + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_E1HZI) { + mask_reg = REG_INTERRUPT_MASK_0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 |= event_bit; + } else { + event -= 24; + mask_reg = REG_INTERRUPT_MASK_1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 |= event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: unmasking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, 0, event_bit); + + pr_debug("Enable Event : %d\n", event); + + return ret; +} + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_E1HZI) { + mask_reg = REG_INTERRUPT_MASK_0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 &= ~event_bit; + } else { + event -= 24; + mask_reg = REG_INTERRUPT_MASK_1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 &= ~event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: masking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, event_bit, event_bit); + + pr_debug("Disable Event : %d\n", event); + + return ret; +} + +/*! + * This function is called to read all sensor bits of PMIC. + * + * @param sensor Sensor to be checked. + * + * @return This function returns true if the sensor bit is high; + * or returns false if the sensor bit is low. + */ +bool pmic_check_sensor(t_sensor sensor) +{ + unsigned int reg_val = 0; + + CHECK_ERROR(pmic_read_reg + (REG_INTERRUPT_SENSE_0, ®_val, PMIC_ALL_BITS)); + + if ((1 << sensor) & reg_val) + return true; + else + return false; +} + +/*! + * This function checks one sensor of PMIC. + * + * @param sensor_bits structure of all sensor bits. + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ + +PMIC_STATUS pmic_get_sensors(t_sensor_bits *sensor_bits) +{ + int sense_0 = 0; + int sense_1 = 0; + + memset(sensor_bits, 0, sizeof(t_sensor_bits)); + + pmic_read_reg(REG_INTERRUPT_SENSE_0, &sense_0, 0xffffff); + pmic_read_reg(REG_INTERRUPT_SENSE_1, &sense_1, 0xffffff); + + sensor_bits->sense_chgdets = (sense_0 & (1 << 6)) ? true : false; + sensor_bits->sense_chgovs = (sense_0 & (1 << 7)) ? true : false; + sensor_bits->sense_chgrevs = (sense_0 & (1 << 8)) ? true : false; + sensor_bits->sense_chgshorts = (sense_0 & (1 << 9)) ? true : false; + sensor_bits->sense_cccvs = (sense_0 & (1 << 10)) ? true : false; + sensor_bits->sense_chgcurrs = (sense_0 & (1 << 11)) ? true : false; + sensor_bits->sense_bpons = (sense_0 & (1 << 12)) ? true : false; + sensor_bits->sense_lobatls = (sense_0 & (1 << 13)) ? true : false; + sensor_bits->sense_lobaths = (sense_0 & (1 << 14)) ? true : false; + sensor_bits->sense_usb4v4s = (sense_0 & (1 << 16)) ? true : false; + sensor_bits->sense_usb2v0s = (sense_0 & (1 << 17)) ? true : false; + sensor_bits->sense_usb0v8s = (sense_0 & (1 << 18)) ? true : false; + sensor_bits->sense_id_floats = (sense_0 & (1 << 19)) ? true : false; + sensor_bits->sense_id_gnds = (sense_0 & (1 << 20)) ? true : false; + sensor_bits->sense_se1s = (sense_0 & (1 << 21)) ? true : false; + sensor_bits->sense_ckdets = (sense_0 & (1 << 22)) ? true : false; + + sensor_bits->sense_onofd1s = (sense_1 & (1 << 3)) ? true : false; + sensor_bits->sense_onofd2s = (sense_1 & (1 << 4)) ? true : false; + sensor_bits->sense_onofd3s = (sense_1 & (1 << 5)) ? true : false; + sensor_bits->sense_pwrrdys = (sense_1 & (1 << 11)) ? true : false; + sensor_bits->sense_thwarnhs = (sense_1 & (1 << 12)) ? true : false; + sensor_bits->sense_thwarnls = (sense_1 & (1 << 13)) ? true : false; + sensor_bits->sense_clks = (sense_1 & (1 << 14)) ? true : false; + sensor_bits->sense_mc2bs = (sense_1 & (1 << 17)) ? true : false; + sensor_bits->sense_hsdets = (sense_1 & (1 << 18)) ? true : false; + sensor_bits->sense_hsls = (sense_1 & (1 << 19)) ? true : false; + sensor_bits->sense_alspths = (sense_1 & (1 << 20)) ? true : false; + sensor_bits->sense_ahsshorts = (sense_1 & (1 << 21)) ? true : false; + return PMIC_SUCCESS; +} + +EXPORT_SYMBOL(pmic_check_sensor); +EXPORT_SYMBOL(pmic_get_sensors); diff --git a/drivers/mxc/pmic/core/mc13892.c b/drivers/mxc/pmic/core/mc13892.c new file mode 100644 index 000000000000..5d04aad2720c --- /dev/null +++ b/drivers/mxc/pmic/core/mc13892.c @@ -0,0 +1,335 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic/core/mc13892.c + * @brief This file contains MC13892 specific PMIC code. This implementaion + * may differ for each PMIC chip. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pmic.h" + +/* + * Defines + */ +#define MC13892_I2C_RETRY_TIMES 10 +#define MXC_PMIC_FRAME_MASK 0x00FFFFFF +#define MXC_PMIC_MAX_REG_NUM 0x3F +#define MXC_PMIC_REG_NUM_SHIFT 0x19 +#define MXC_PMIC_WRITE_BIT_SHIFT 31 + +static unsigned int events_enabled0; +static unsigned int events_enabled1; +static struct mxc_pmic pmic_drv_data; +#ifndef CONFIG_MXC_PMIC_I2C +struct i2c_client *mc13892_client; +#endif + +int pmic_i2c_24bit_read(struct i2c_client *client, unsigned int reg_num, + unsigned int *value) +{ + unsigned char buf[3]; + int ret; + int i; + + memset(buf, 0, 3); + for (i = 0; i < MC13892_I2C_RETRY_TIMES; i++) { + ret = i2c_smbus_read_i2c_block_data(client, reg_num, 3, buf); + if (ret == 3) + break; + msleep(1); + } + + if (ret == 3) { + *value = buf[0] << 16 | buf[1] << 8 | buf[2]; + return ret; + } else { + pr_err("24bit read error, ret = %d\n", ret); + return -1; /* return -1 on failure */ + } +} + +int pmic_i2c_24bit_write(struct i2c_client *client, + unsigned int reg_num, unsigned int reg_val) +{ + char buf[3]; + int ret; + int i; + + buf[0] = (reg_val >> 16) & 0xff; + buf[1] = (reg_val >> 8) & 0xff; + buf[2] = (reg_val) & 0xff; + + for (i = 0; i < MC13892_I2C_RETRY_TIMES; i++) { + ret = i2c_smbus_write_i2c_block_data(client, reg_num, 3, buf); + if (ret == 0) + break; + msleep(1); + } + if (i == MC13892_I2C_RETRY_TIMES) + pr_err("24bit write error, ret = %d\n", ret); + + return ret; +} + +int pmic_read(int reg_num, unsigned int *reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (pmic_drv_data.spi != NULL) { + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) &frame, 1); + + *reg_val = frame & MXC_PMIC_FRAME_MASK; + } else { + if (mc13892_client == NULL) + return PMIC_ERROR; + + if (pmic_i2c_24bit_read(mc13892_client, reg_num, reg_val) == -1) + return PMIC_ERROR; + } + + return PMIC_SUCCESS; +} + +int pmic_write(int reg_num, const unsigned int reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (pmic_drv_data.spi != NULL) { + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= (1 << MXC_PMIC_WRITE_BIT_SHIFT); + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + frame |= reg_val & MXC_PMIC_FRAME_MASK; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) &frame, 1); + + return ret; + } else { + if (mc13892_client == NULL) + return PMIC_ERROR; + + return pmic_i2c_24bit_write(mc13892_client, reg_num, reg_val); + } +} + +void *pmic_alloc_data(struct device *dev) +{ + struct mc13892 *mc13892; + + mc13892 = kzalloc(sizeof(struct mc13892), GFP_KERNEL); + if (mc13892 == NULL) + return NULL; + + mc13892->dev = dev; + + return (void *)mc13892; +} + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi) +{ + /* Setup the SPI slave i.e.PMIC */ + pmic_drv_data.spi = spi; + + spi->mode = SPI_MODE_0 | SPI_CS_HIGH; + spi->bits_per_word = 32; + + return spi_setup(spi); +} + +int pmic_init_registers(void) +{ + CHECK_ERROR(pmic_write(REG_INT_MASK0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_MASK0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_STATUS0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_STATUS1, 0xFFFFFF)); + /* disable auto charge */ + if (machine_is_mx51_3ds()) + CHECK_ERROR(pmic_write(REG_CHARGE, 0xB40003)); + + pm_power_off = mc13892_power_off; + + return PMIC_SUCCESS; +} + +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int status0, status1; + int bit_set; + + pmic_read(REG_INT_STATUS0, &status0); + pmic_read(REG_INT_STATUS1, &status1); + pmic_write(REG_INT_STATUS0, status0); + pmic_write(REG_INT_STATUS1, status1); + status0 &= events_enabled0; + status1 &= events_enabled1; + + while (status0) { + bit_set = ffs(status0) - 1; + *(active_events + count) = bit_set; + count++; + status0 ^= (1 << bit_set); + } + while (status1) { + bit_set = ffs(status1) - 1; + *(active_events + count) = bit_set + 24; + count++; + status1 ^= (1 << bit_set); + } + + return count; +} + +#define EVENT_MASK_0 0x387fff +#define EVENT_MASK_1 0x1177ef + +int pmic_event_unmask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_1HZI) { + mask_reg = REG_INT_MASK0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 |= event_bit; + } else { + event -= 24; + mask_reg = REG_INT_MASK1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 |= event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: unmasking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, 0, event_bit); + + pr_debug("Enable Event : %d\n", event); + + return ret; +} + +int pmic_event_mask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_1HZI) { + mask_reg = REG_INT_MASK0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 &= ~event_bit; + } else { + event -= 24; + mask_reg = REG_INT_MASK1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 &= ~event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: masking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, event_bit, event_bit); + + pr_debug("Disable Event : %d\n", event); + + return ret; +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t *ver) +{ + int rev_id = 0; + int rev1 = 0; + int rev2 = 0; + int finid = 0; + int icid = 0; + + ver->id = PMIC_MC13892; + pmic_read(REG_IDENTIFICATION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + ver->revision = ((rev1 * 10) + rev2); + printk(KERN_INFO "mc13892 Rev %d.%d FinVer %x detected\n", rev1, + rev2, finid); +} + +void mc13892_power_off(void) +{ + unsigned int value; + + pmic_read_reg(REG_POWER_CTL0, &value, 0xffffff); + + value |= 0x000008; + + pmic_write_reg(REG_POWER_CTL0, value, 0xffffff); +} diff --git a/drivers/mxc/pmic/core/mc34704.c b/drivers/mxc/pmic/core/mc34704.c new file mode 100644 index 000000000000..8e0d6b5a3b05 --- /dev/null +++ b/drivers/mxc/pmic/core/mc34704.c @@ -0,0 +1,329 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic/core/mc34704.c + * @brief This file contains MC34704 specific PMIC code. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmic.h" + +/* + * Globals + */ +static pmic_version_t mxc_pmic_version = { + .id = PMIC_MC34704, + .revision = 0, +}; +static unsigned int events_enabled; +unsigned int active_events[MAX_ACTIVE_EVENTS]; +struct i2c_client *mc34704_client; +static void pmic_trigger_poll(void); + +#define MAX_MC34704_REG 0x59 +static unsigned int mc34704_reg_readonly[MAX_MC34704_REG / 32 + 1] = { + (1 << 0x03) || (1 << 0x05) || (1 << 0x07) || (1 << 0x09) || + (1 << 0x0B) || (1 << 0x0E) || (1 << 0x11) || (1 << 0x14) || + (1 << 0x17) || (1 << 0x18), + 0, +}; +static unsigned int mc34704_reg_written[MAX_MC34704_REG / 32 + 1]; +static unsigned char mc34704_shadow_regs[MAX_MC34704_REG - 1]; +#define IS_READONLY(r) ((1 << ((r) % 32)) & mc34704_reg_readonly[(r) / 32]) +#define WAS_WRITTEN(r) ((1 << ((r) % 32)) & mc34704_reg_written[(r) / 32]) +#define MARK_WRITTEN(r) do { \ + mc34704_reg_written[(r) / 32] |= (1 << ((r) % 32)); \ +} while (0) + +int pmic_read(int reg_nr, unsigned int *reg_val) +{ + int c; + + /* + * Use the shadow register if we've written to it + */ + if (WAS_WRITTEN(reg_nr)) { + *reg_val = mc34704_shadow_regs[reg_nr]; + return PMIC_SUCCESS; + } + + /* + * Otherwise, actually read the real register. + * Write-only registers will read as zero. + */ + c = i2c_smbus_read_byte_data(mc34704_client, reg_nr); + if (c == -1) { + pr_debug("mc34704: error reading register 0x%02x\n", reg_nr); + return PMIC_ERROR; + } else { + *reg_val = c; + return PMIC_SUCCESS; + } +} + +int pmic_write(int reg_nr, const unsigned int reg_val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(mc34704_client, reg_nr, reg_val); + if (ret == -1) { + return PMIC_ERROR; + } else { + /* + * Update our software copy of the register since you + * can't read what you wrote. + */ + if (!IS_READONLY(reg_nr)) { + mc34704_shadow_regs[reg_nr] = reg_val; + MARK_WRITTEN(reg_nr); + } + return PMIC_SUCCESS; + } +} + +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int faults; + int bit_set; + + /* Check for any relevant PMIC faults */ + pmic_read(REG_MC34704_FAULTS, &faults); + faults &= events_enabled; + + /* + * Mask all active events, because there is no way to acknowledge + * or dismiss them in the PMIC -- they're sticky. + */ + events_enabled &= ~faults; + + /* Account for all unmasked faults */ + while (faults) { + bit_set = ffs(faults) - 1; + *(active_events + count) = bit_set; + count++; + faults ^= (1 << bit_set); + } + return count; +} + +int pmic_event_unmask(type_event event) +{ + unsigned int event_bit = 0; + unsigned int prior_events = events_enabled; + + event_bit = (1 << event); + events_enabled |= event_bit; + + pr_debug("Enable Event : %d\n", event); + + /* start the polling task as needed */ + if (events_enabled && prior_events == 0) + pmic_trigger_poll(); + + return 0; +} + +int pmic_event_mask(type_event event) +{ + unsigned int event_bit = 0; + + event_bit = (1 << event); + events_enabled &= ~event_bit; + + pr_debug("Disable Event : %d\n", event); + + return 0; +} + +/*! + * PMIC event polling task. This task is called periodically to poll + * for possible MC34704 events (No interrupt supplied by the hardware). + */ +static void pmic_event_task(struct work_struct *work); +DECLARE_DELAYED_WORK(pmic_ws, pmic_event_task); + +static void pmic_trigger_poll(void) +{ + schedule_delayed_work(&pmic_ws, HZ / 10); +} + +static void pmic_event_task(struct work_struct *work) +{ + unsigned int count = 0; + int i; + + count = pmic_get_active_events(active_events); + pr_debug("active events number %d\n", count); + + /* call handlers for all active events */ + for (i = 0; i < count; i++) + pmic_event_callback(active_events[i]); + + /* re-trigger this task, but only if somebody is watching */ + if (events_enabled) + pmic_trigger_poll(); + + return; +} + +pmic_version_t pmic_get_version(void) +{ + return mxc_pmic_version; +} +EXPORT_SYMBOL(pmic_get_version); + +int __devinit pmic_init_registers(void) +{ + /* + * Set some registers to what they should be, + * if for no other reason than to initialize our + * software register copies. + */ + CHECK_ERROR(pmic_write(REG_MC34704_GENERAL2, 0x09)); + CHECK_ERROR(pmic_write(REG_MC34704_VGSET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG2SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG3SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG4SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG5SET1, 0)); + + return PMIC_SUCCESS; +} + +static int __devinit is_chip_onboard(struct i2c_client *client) +{ + int val; + + /* + * This PMIC has no version or ID register, so just see + * if it ACK's and returns 0 on some write-only register as + * evidence of its presence. + */ + val = i2c_smbus_read_byte_data(client, REG_MC34704_GENERAL2); + if (val != 0) + return -1; + + return 0; +} + +static int __devinit pmic_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mc34704 *mc34704; + struct mc34704_platform_data *plat_data = client->dev.platform_data; + + if (!plat_data || !plat_data->init) + return -ENODEV; + + ret = is_chip_onboard(client); + + if (ret == -1) + return -ENODEV; + + mc34704 = kzalloc(sizeof(struct mc34704), GFP_KERNEL); + if (mc34704 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc34704); + mc34704->dev = &client->dev; + mc34704->i2c_client = client; + + mc34704_client = client; + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize PMI registers */ + if (pmic_init_registers() != PMIC_SUCCESS) + return PMIC_ERROR; + + ret = plat_data->init(mc34704); + if (ret != 0) + return PMIC_ERROR; + + dev_info(&client->dev, "Loaded\n"); + + return PMIC_SUCCESS; +} + +static int pmic_remove(struct i2c_client *client) +{ + return 0; +} + +static int pmic_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int pmic_resume(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc34704_id[] = { + {"mc34704", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mc34704_id); + +static struct i2c_driver pmic_driver = { + .driver = { + .name = "mc34704", + .bus = NULL, + }, + .probe = pmic_probe, + .remove = pmic_remove, + .suspend = pmic_suspend, + .resume = pmic_resume, + .id_table = mc34704_id, +}; + +static int __init pmic_init(void) +{ + return i2c_add_driver(&pmic_driver); +} + +static void __exit pmic_exit(void) +{ + i2c_del_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("MC34704 PMIC driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic-dev.c b/drivers/mxc/pmic/core/pmic-dev.c new file mode 100644 index 000000000000..4bbebb66c346 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic-dev.c @@ -0,0 +1,316 @@ +/* + * Copyright 2005-2011 Freescale Semiconductor, Inc. All rights reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic-dev.c + * @brief This provides /dev interface to the user program. They make it + * possible to have user-space programs use or control PMIC. Mainly its + * useful for notifying PMIC events to user-space programs. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PMIC_NAME "pmic" +#define CIRC_BUF_MAX 16 + +static int pmic_major; +static struct class *pmic_class; +static struct fasync_struct *pmic_dev_queue; + +static DECLARE_MUTEX(event_mutex); +static struct circ_buf pmic_events; + +static void callbackfn(void *event) +{ + printk(KERN_INFO "\n\n DETECTED PMIC EVENT : %d\n\n", + (unsigned int)event); +} + +static void user_notify_callback(void *event) +{ + down(&event_mutex); + if (CIRC_SPACE(pmic_events.head, pmic_events.tail, CIRC_BUF_MAX)) { + pmic_events.buf[pmic_events.head] = (int)event; + pmic_events.head = (pmic_events.head + 1) & (CIRC_BUF_MAX - 1); + } else { + pr_info("Failed to notify event to the user\n"); + } + up(&event_mutex); + + kill_fasync(&pmic_dev_queue, SIGIO, POLL_IN); +} + +/*! + * This function implements IOCTL controls on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_dev_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + register_info reg_info; + pmic_event_callback_t event_sub; + type_event event = EVENT_NB; + int ret = 0; + + if (_IOC_TYPE(cmd) != 'P') + return -ENOTTY; + + switch (cmd) { + case PMIC_READ_REG: + if (copy_from_user(®_info, (register_info *) arg, + sizeof(register_info))) { + return -EFAULT; + } + ret = + pmic_read_reg(reg_info.reg, &(reg_info.reg_value), + 0x00ffffff); + pr_debug("read reg %d %x\n", reg_info.reg, reg_info.reg_value); + if (copy_to_user((register_info *) arg, ®_info, + sizeof(register_info))) { + return -EFAULT; + } + break; + + case PMIC_WRITE_REG: + if (copy_from_user(®_info, (register_info *) arg, + sizeof(register_info))) { + return -EFAULT; + } + ret = + pmic_write_reg(reg_info.reg, reg_info.reg_value, + 0x00ffffff); + pr_debug("write reg %d %x\n", reg_info.reg, reg_info.reg_value); + if (copy_to_user((register_info *) arg, ®_info, + sizeof(register_info))) { + return -EFAULT; + } + break; + + case PMIC_SUBSCRIBE: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = callbackfn; + event_sub.param = (void *)event; + ret = pmic_event_subscribe(event, event_sub); + pr_debug("subscribe done\n"); + break; + + case PMIC_UNSUBSCRIBE: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = callbackfn; + event_sub.param = (void *)event; + ret = pmic_event_unsubscribe(event, event_sub); + pr_debug("unsubscribe done\n"); + break; + + case PMIC_NOTIFY_USER: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = user_notify_callback; + event_sub.param = (void *)event; + ret = pmic_event_subscribe(event, event_sub); + break; + + case PMIC_GET_NOTIFY: + down(&event_mutex); + if (CIRC_CNT(pmic_events.head, pmic_events.tail, CIRC_BUF_MAX)) { + event = (int)pmic_events.buf[pmic_events.tail]; + pmic_events.tail = (pmic_events.tail + 1) & (CIRC_BUF_MAX - 1); + } else { + pr_info("No valid notified event\n"); + } + up(&event_mutex); + + if (put_user(event, (int __user *)arg)) { + return -EFAULT; + } + break; + + default: + printk(KERN_ERR "%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + return ret; +} + +/*! + * This function implements the open method on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_dev_open(struct inode *inode, struct file *file) +{ + pr_debug("open\n"); + return PMIC_SUCCESS; +} + +/*! + * This function implements the release method on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int pmic_dev_free(struct inode *inode, struct file *file) +{ + pr_debug("free\n"); + return PMIC_SUCCESS; +} + +static int pmic_dev_fasync(int fd, struct file *filp, int mode) +{ + return fasync_helper(fd, filp, mode, &pmic_dev_queue); +} + +/*! + * This structure defines file operations for a PMIC device. + */ +static struct file_operations pmic_fops = { + /*! + * the owner + */ + .owner = THIS_MODULE, + /*! + * the ioctl operation + */ + .ioctl = pmic_dev_ioctl, + /*! + * the open operation + */ + .open = pmic_dev_open, + /*! + * the release operation + */ + .release = pmic_dev_free, + /*! + * the release operation + */ + .fasync = pmic_dev_fasync, +}; + +/*! + * This function implements the init function of the PMIC char device. + * This function is called when the module is loaded. It registers + * the character device for PMIC to be used by user-space programs. + * + * @return This function returns 0. + */ +static int __init pmic_dev_init(void) +{ + int ret = 0; + struct device *pmic_device; + pmic_version_t pmic_ver; + + pmic_ver = pmic_get_version(); + if (pmic_ver.revision < 0) { + printk(KERN_ERR "No PMIC device found\n"); + return -ENODEV; + } + + pmic_major = register_chrdev(0, PMIC_NAME, &pmic_fops); + if (pmic_major < 0) { + printk(KERN_ERR "unable to get a major for pmic\n"); + return pmic_major; + } + + pmic_class = class_create(THIS_MODULE, PMIC_NAME); + if (IS_ERR(pmic_class)) { + printk(KERN_ERR "Error creating pmic class.\n"); + ret = PMIC_ERROR; + goto err; + } + + pmic_device = device_create(pmic_class, NULL, MKDEV(pmic_major, 0), NULL, + PMIC_NAME); + if (IS_ERR(pmic_device)) { + printk(KERN_ERR "Error creating pmic class device.\n"); + ret = PMIC_ERROR; + goto err1; + } + + pmic_events.buf = kmalloc(CIRC_BUF_MAX * sizeof(char), GFP_KERNEL); + if (NULL == pmic_events.buf) { + ret = -ENOMEM; + goto err2; + } + pmic_events.head = pmic_events.tail = 0; + + printk(KERN_INFO "PMIC Character device: successfully loaded\n"); + return ret; + err2: + device_destroy(pmic_class, MKDEV(pmic_major, 0)); + err1: + class_destroy(pmic_class); + err: + unregister_chrdev(pmic_major, PMIC_NAME); + return ret; + +} + +/*! + * This function implements the exit function of the PMIC character device. + * This function is called when the module is unloaded. It unregisters + * the PMIC character device. + * + */ +static void __exit pmic_dev_exit(void) +{ + device_destroy(pmic_class, MKDEV(pmic_major, 0)); + class_destroy(pmic_class); + + unregister_chrdev(pmic_major, PMIC_NAME); + + printk(KERN_INFO "PMIC Character device: successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_dev_init); +module_exit(pmic_dev_exit); + +MODULE_DESCRIPTION("PMIC Protocol /dev entries driver"); +MODULE_AUTHOR("FreeScale"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic.h b/drivers/mxc/pmic/core/pmic.h new file mode 100644 index 000000000000..7849e8d684a3 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic.h @@ -0,0 +1,138 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __PMIC_H__ +#define __PMIC_H__ + + /*! + * @file pmic.h + * @brief This file contains prototypes of all the functions to be + * defined for each PMIC chip. The implementation of these may differ + * from PMIC chip to PMIC chip. + * + * @ingroup PMIC_CORE + */ + +#include + +#define MAX_ACTIVE_EVENTS 10 + +/*! + * This structure is a way for the PMIC core driver to define their own + * \b spi_device structure. This structure includes the core \b spi_device + * structure that is provided by Linux SPI Framework/driver as an + * element and may contain other elements that are required by core driver. + */ +struct mxc_pmic { + /*! + * Master side proxy for an SPI slave device(PMIC) + */ + struct spi_device *spi; +}; + +/*! + * This function is called to transfer data to PMIC on SPI. + * + * @param spi the SPI slave device(PMIC) + * @param buf the pointer to the data buffer + * @param len the length of the data to be transferred + * + * @return Returns 0 on success -1 on failure. + */ +static inline int spi_rw(struct spi_device *spi, u8 * buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = (const void *)buf, + .rx_buf = buf, + .len = len, + .cs_change = 0, + .delay_usecs = 0, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + if (spi_sync(spi, &m) != 0 || m.status != 0) + return PMIC_ERROR; + return len - m.actual_length; +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t *ver); + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi); + +/*! + * This function initializes the PMIC registers. + * + * @return None + */ +int pmic_init_registers(void); + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events); + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event); + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event); + +#ifdef CONFIG_MXC_PMIC_FIXARB +extern PMIC_STATUS pmic_fix_arbitration(struct spi_device *spi); +#else +static inline PMIC_STATUS pmic_fix_arbitration(struct spi_device *spi) +{ + return PMIC_SUCCESS; +} +#endif + +void *pmic_alloc_data(struct device *dev); + +int pmic_start_event_thread(int irq_num); + +void pmic_stop_event_thread(void); + +#endif /* __PMIC_H__ */ diff --git a/drivers/mxc/pmic/core/pmic_common.c b/drivers/mxc/pmic/core/pmic_common.c new file mode 100644 index 000000000000..fa40b8321488 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_common.c @@ -0,0 +1,131 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_common.c + * @brief This is the common file for the PMIC Core/Protocol driver. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pmic.h" + +/* + * Global variables + */ +pmic_version_t mxc_pmic_version; +unsigned int active_events[MAX_ACTIVE_EVENTS]; + + +static struct completion event_completion; +static struct task_struct *tstask; + +static int pmic_event_thread_func(void *v) +{ + unsigned int loop; + unsigned int count = 0; + unsigned int irq = (int)v; + + while (1) { + wait_for_completion_interruptible( + &event_completion); + if (kthread_should_stop()) + break; + + count = pmic_get_active_events( + active_events); + pr_debug("active events number %d\n", count); + + do { + for (loop = 0; loop < count; loop++) + pmic_event_callback(active_events[loop]); + + count = pmic_get_active_events(active_events); + + } while (count != 0); + enable_irq(irq); + } + + return 0; +} + +int pmic_start_event_thread(int irq_num) +{ + int ret = 0; + + if (tstask) + return ret; + + init_completion(&event_completion); + + tstask = kthread_run(pmic_event_thread_func, + (void *)irq_num, "pmic-event-thread"); + ret = IS_ERR(tstask) ? -1 : 0; + if (IS_ERR(tstask)) + tstask = NULL; + return ret; +} + +void pmic_stop_event_thread(void) +{ + if (tstask) { + complete(&event_completion); + kthread_stop(tstask); + } +} + +/*! + * This function is called when pmic interrupt occurs on the processor. + * It is the interrupt handler for the pmic module. + * + * @param irq the irq number + * @param dev_id the pointer on the device + * + * @return The function returns IRQ_HANDLED when handled. + */ +irqreturn_t pmic_irq_handler(int irq, void *dev_id) +{ + disable_irq_nosync(irq); + complete(&event_completion); + + return IRQ_HANDLED; +} + +/*! + * This function is used to determine the PMIC type and its revision. + * + * @return Returns the PMIC type and its revision. + */ + +pmic_version_t pmic_get_version(void) +{ + return mxc_pmic_version; +} +EXPORT_SYMBOL(pmic_get_version); diff --git a/drivers/mxc/pmic/core/pmic_core_i2c.c b/drivers/mxc/pmic/core/pmic_core_i2c.c new file mode 100644 index 000000000000..40d029acbab7 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_core_i2c.c @@ -0,0 +1,348 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_core_i2c.c + * @brief This is the main file for the PMIC Core/Protocol driver. i2c + * should be providing the interface between the PMIC and the MCU. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmic.h" + +#define MC13892_GENERATION_ID_LSH 6 +#define MC13892_IC_ID_LSH 13 + +#define MC13892_GENERATION_ID_WID 3 +#define MC13892_IC_ID_WID 6 + +#define MC13892_GEN_ID_VALUE 0x7 +#define MC13892_IC_ID_VALUE 1 + +/* + * Global variables + */ +struct i2c_client *mc13892_client; + +extern pmic_version_t mxc_pmic_version; +extern irqreturn_t pmic_irq_handler(int irq, void *dev_id); +/* + * Platform device structure for PMIC client drivers + */ +static struct platform_device adc_ldm = { + .name = "pmic_adc", + .id = 1, +}; +static struct platform_device battery_ldm = { + .name = "pmic_battery", + .id = 1, +}; +static struct platform_device power_ldm = { + .name = "pmic_power", + .id = 1, +}; +static struct platform_device rtc_ldm = { + .name = "pmic_rtc", + .id = 1, +}; +static struct platform_device light_ldm = { + .name = "pmic_light", + .id = 1, +}; +static struct platform_device rleds_ldm = { + .name = "pmic_leds", + .id = 'r', +}; +static struct platform_device gleds_ldm = { + .name = "pmic_leds", + .id = 'g', +}; +static struct platform_device bleds_ldm = { + .name = "pmic_leds", + .id = 'b', +}; + +static void pmic_pdev_register(struct device *dev) +{ + platform_device_register(&adc_ldm); + + if (!cpu_is_mx53()) + platform_device_register(&battery_ldm); + + platform_device_register(&rtc_ldm); + platform_device_register(&power_ldm); + platform_device_register(&light_ldm); + platform_device_register(&rleds_ldm); + platform_device_register(&gleds_ldm); + platform_device_register(&bleds_ldm); +} + +/*! + * This function unregisters platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_unregister(void) +{ + platform_device_unregister(&adc_ldm); + platform_device_unregister(&battery_ldm); + platform_device_unregister(&rtc_ldm); + platform_device_unregister(&power_ldm); + platform_device_unregister(&light_ldm); +} + +static int __devinit is_chip_onboard(struct i2c_client *client) +{ + unsigned int ret = 0; + + /*bind the right device to the driver */ + if (pmic_i2c_24bit_read(client, REG_IDENTIFICATION, &ret) == -1) + return -1; + + if (MC13892_GEN_ID_VALUE != BITFEXT(ret, MC13892_GENERATION_ID)) { + /*compare the address value */ + dev_err(&client->dev, + "read generation ID 0x%x is not equal to 0x%x!\n", + BITFEXT(ret, MC13892_GENERATION_ID), + MC13892_GEN_ID_VALUE); + return -1; + } + + return 0; +} + +static ssize_t mc13892_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, value; + int offset = (REG_TEST4 + 1) / 4; + + for (i = 0; i < offset; i++) { + pmic_read(i, &value); + pr_info("reg%02d: %06x\t\t", i, value); + pmic_read(i + offset, &value); + pr_info("reg%02d: %06x\t\t", i + offset, value); + pmic_read(i + offset * 2, &value); + pr_info("reg%02d: %06x\t\t", i + offset * 2, value); + pmic_read(i + offset * 3, &value); + pr_info("reg%02d: %06x\n", i + offset * 3, value); + } + + return 0; +} + +static ssize_t mc13892_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int reg, value, ret; + char *p; + + reg = simple_strtoul(buf, NULL, 10); + + p = NULL; + p = memchr(buf, ' ', count); + + if (p == NULL) { + pmic_read(reg, &value); + pr_debug("reg%02d: %06x\n", reg, value); + return count; + } + + p += 1; + + value = simple_strtoul(p, NULL, 16); + + ret = pmic_write(reg, value); + if (ret == 0) + pr_debug("write reg%02d: %06x\n", reg, value); + else + pr_debug("register update failed\n"); + + return count; +} + +static struct device_attribute mc13892_dev_attr = { + .attr = { + .name = "mc13892_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = mc13892_show, + .store = mc13892_store, +}; + +static int __devinit pmic_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + int pmic_irq; + struct mc13892 *mc13892; + struct mc13892_platform_data *plat_data = client->dev.platform_data; + + ret = is_chip_onboard(client); + if (ret == -1) + return -ENODEV; + + mc13892 = kzalloc(sizeof(struct mc13892), GFP_KERNEL); + if (mc13892 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc13892); + mc13892->dev = &client->dev; + mc13892->i2c_client = client; + + /* so far, we got matched chip on board */ + + mc13892_client = client; + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize GPIO for PMIC Interrupt */ + gpio_pmic_active(); + + /* Get the PMIC Version */ + pmic_get_revision(&mxc_pmic_version); + if (mxc_pmic_version.revision < 0) { + dev_err((struct device *)client, + "PMIC not detected!!! Access Failed\n"); + return -ENODEV; + } else { + dev_dbg((struct device *)client, + "Detected pmic core IC version number is %d\n", + mxc_pmic_version.revision); + } + + /* Initialize the PMIC parameters */ + ret = pmic_init_registers(); + if (ret != PMIC_SUCCESS) + return PMIC_ERROR; + + pmic_irq = (int)(client->irq); + if (pmic_irq == 0) + return PMIC_ERROR; + + ret = pmic_start_event_thread(pmic_irq); + if (ret) { + pr_err("mc13892 pmic driver init: \ + fail to start event thread\n"); + return PMIC_ERROR; + } + + /* Set and install PMIC IRQ handler */ + + set_irq_type(pmic_irq, IRQF_TRIGGER_HIGH); + + ret = + request_irq(pmic_irq, pmic_irq_handler, 0, "PMIC_IRQ", + 0); + + if (ret) { + dev_err(&client->dev, "request irq %d error!\n", pmic_irq); + return ret; + } + enable_irq_wake(pmic_irq); + + if (plat_data && plat_data->init) { + ret = plat_data->init(mc13892); + if (ret != 0) + return PMIC_ERROR; + } + + ret = device_create_file(&client->dev, &mc13892_dev_attr); + if (ret) + dev_err(&client->dev, "create device file failed!\n"); + + pmic_pdev_register(&client->dev); + + dev_info(&client->dev, "Loaded\n"); + + return PMIC_SUCCESS; +} + +static int pmic_remove(struct i2c_client *client) +{ + int pmic_irq = (int)(client->irq); + + pmic_stop_event_thread(); + free_irq(pmic_irq, 0); + pmic_pdev_unregister(); + return 0; +} + +static int pmic_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int pmic_resume(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc13892_id[] = { + {"mc13892", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mc13892_id); + +static struct i2c_driver pmic_driver = { + .driver = { + .name = "mc13892", + .bus = NULL, + }, + .probe = pmic_probe, + .remove = pmic_remove, + .suspend = pmic_suspend, + .resume = pmic_resume, + .id_table = mc13892_id, +}; + +static int __init pmic_init(void) +{ + return i2c_add_driver(&pmic_driver); +} + +static void __exit pmic_exit(void) +{ + i2c_del_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("Core/Protocol driver for PMIC"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic_core_spi.c b/drivers/mxc/pmic/core/pmic_core_spi.c new file mode 100644 index 000000000000..df0a895491c7 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_core_spi.c @@ -0,0 +1,303 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_core_spi.c + * @brief This is the main file for the PMIC Core/Protocol driver. SPI + * should be providing the interface between the PMIC and the MCU. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pmic.h" + +/* + * Static functions + */ +static void pmic_pdev_register(void); +static void pmic_pdev_unregister(void); + +/* + * Platform device structure for PMIC client drivers + */ +static struct platform_device adc_ldm = { + .name = "pmic_adc", + .id = 1, +}; +static struct platform_device battery_ldm = { + .name = "pmic_battery", + .id = 1, +}; +static struct platform_device power_ldm = { + .name = "pmic_power", + .id = 1, +}; +static struct platform_device rtc_ldm = { + .name = "pmic_rtc", + .id = 1, +}; +static struct platform_device light_ldm = { + .name = "pmic_light", + .id = 1, +}; +static struct platform_device rleds_ldm = { + .name = "pmic_leds", + .id = 'r', +}; +static struct platform_device gleds_ldm = { + .name = "pmic_leds", + .id = 'g', +}; +static struct platform_device bleds_ldm = { + .name = "pmic_leds", + .id = 'b', +}; + +/* + * External functions + */ +extern void pmic_event_list_init(void); +extern void pmic_event_callback(type_event event); +extern void gpio_pmic_active(void); +extern irqreturn_t pmic_irq_handler(int irq, void *dev_id); +extern pmic_version_t mxc_pmic_version; + +/*! + * This function registers platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_register(void) +{ + platform_device_register(&adc_ldm); + platform_device_register(&battery_ldm); + platform_device_register(&rtc_ldm); + platform_device_register(&power_ldm); + platform_device_register(&light_ldm); + platform_device_register(&rleds_ldm); + platform_device_register(&gleds_ldm); + platform_device_register(&bleds_ldm); +} + +/*! + * This function unregisters platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_unregister(void) +{ + platform_device_unregister(&adc_ldm); + platform_device_unregister(&battery_ldm); + platform_device_unregister(&rtc_ldm); + platform_device_unregister(&power_ldm); + platform_device_unregister(&light_ldm); +} + +/*! + * This function puts the SPI slave device in low-power mode/state. + * + * @param spi the SPI slave device + * @param message the power state to enter + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int pmic_suspend(struct spi_device *spi, pm_message_t message) +{ + return PMIC_SUCCESS; +} + +/*! + * This function brings the SPI slave device back from low-power mode/state. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int pmic_resume(struct spi_device *spi) +{ + return PMIC_SUCCESS; +} + +static struct spi_driver pmic_driver; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit pmic_probe(struct spi_device *spi) +{ + int ret = 0; + struct pmic_platform_data *plat_data = spi->dev.platform_data; + + /* Initialize the PMIC parameters */ + ret = pmic_spi_setup(spi); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize GPIO for PMIC Interrupt */ + gpio_pmic_active(); + + /* Get the PMIC Version */ + pmic_get_revision(&mxc_pmic_version); + if (mxc_pmic_version.revision < 0) { + dev_err((struct device *)spi, + "PMIC not detected!!! Access Failed\n"); + return -ENODEV; + } else { + dev_dbg((struct device *)spi, + "Detected pmic core IC version number is %d\n", + mxc_pmic_version.revision); + } + + spi_set_drvdata(spi, pmic_alloc_data(&(spi->dev))); + + /* Initialize the PMIC parameters */ + ret = pmic_init_registers(); + if (ret != PMIC_SUCCESS) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return PMIC_ERROR; + } + + ret = pmic_start_event_thread(spi->irq); + if (ret) { + pr_err("mc13892 pmic driver init: \ + fail to start event thread\n"); + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return PMIC_ERROR; + } + + /* Set and install PMIC IRQ handler */ + set_irq_type(spi->irq, IRQF_TRIGGER_HIGH); + ret = request_irq(spi->irq, pmic_irq_handler, 0, "PMIC_IRQ", 0); + if (ret) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + dev_err((struct device *)spi, "gpio1: irq%d error.", spi->irq); + return ret; + } + + enable_irq_wake(spi->irq); + + if (plat_data && plat_data->init) { + ret = plat_data->init(spi_get_drvdata(spi)); + if (ret != 0) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return PMIC_ERROR; + } + } + + power_ldm.dev.platform_data = spi->dev.platform_data; + + pmic_pdev_register(); + + printk(KERN_INFO "Device %s probed\n", dev_name(&spi->dev)); + + return PMIC_SUCCESS; +} + +/*! + * This function is called whenever the SPI slave device is removed. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devexit pmic_remove(struct spi_device *spi) +{ + pmic_stop_event_thread(); + free_irq(spi->irq, 0); + + pmic_pdev_unregister(); + + printk(KERN_INFO "Device %s removed\n", dev_name(&spi->dev)); + + return PMIC_SUCCESS; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct spi_driver pmic_driver = { + .driver = { + .name = "pmic_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = pmic_probe, + .remove = __devexit_p(pmic_remove), + .suspend = pmic_suspend, + .resume = pmic_resume, +}; + +/* + * Initialization and Exit + */ + +/*! + * This function implements the init function of the PMIC device. + * This function is called when the module is loaded. It registers + * the PMIC Protocol driver. + * + * @return This function returns 0. + */ +static int __init pmic_init(void) +{ + return spi_register_driver(&pmic_driver); +} + +/*! + * This function implements the exit function of the PMIC device. + * This function is called when the module is unloaded. It unregisters + * the PMIC Protocol driver. + * + */ +static void __exit pmic_exit(void) +{ + pr_debug("Unregistering the PMIC Protocol Driver\n"); + spi_unregister_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("Core/Protocol driver for PMIC"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic_event.c b/drivers/mxc/pmic/core/pmic_event.c new file mode 100644 index 000000000000..5ab593b3a6f9 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_event.c @@ -0,0 +1,236 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_event.c + * @brief This file manage all event of PMIC component. + * + * It contains event subscription, unsubscription and callback + * launch methods implemeted. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmic.h" + +/*! + * This structure is used to keep a list of subscribed + * callbacks for an event. + */ +typedef struct { + /*! + * Keeps a list of subscribed clients to an event. + */ + struct list_head list; + + /*! + * Callback function with parameter, called when event occurs + */ + pmic_event_callback_t callback; +} pmic_event_callback_list_t; + +/* Create a mutex to be used to prevent concurrent access to the event list */ +static DEFINE_MUTEX(event_mutex); + +/* This is a pointer to the event handler array. It defines the currently + * active set of events and user-defined callback functions. + */ +static struct list_head pmic_events[PMIC_MAX_EVENTS]; + +/*! + * This function initializes event list for PMIC event handling. + * + */ +void pmic_event_list_init(void) +{ + int i; + + for (i = 0; i < PMIC_MAX_EVENTS; i++) { + INIT_LIST_HEAD(&pmic_events[i]); + } + + mutex_init(&event_mutex); + return; +} + +/*! + * This function is used to subscribe on an event. + * + * @param event the event number to be subscribed + * @param callback the callback funtion to be subscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_subscribe(type_event event, + pmic_event_callback_t callback) +{ + pmic_event_callback_list_t *new = NULL; + + pr_debug("Event:%d Subscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Create a new linked list entry */ + new = kmalloc(sizeof(pmic_event_callback_list_t), GFP_KERNEL); + if (NULL == new) { + return -ENOMEM; + } + /* Initialize the list node fields */ + new->callback.func = callback.func; + new->callback.param = callback.param; + INIT_LIST_HEAD(&new->list); + + /* Obtain the lock to access the list */ + if (mutex_lock_interruptible(&event_mutex)) { + kfree(new); + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_unmask(event) != PMIC_SUCCESS) { + kfree(new); + mutex_unlock(&event_mutex); + return PMIC_ERROR; + } + } + + /* Add this entry to the event list */ + list_add_tail(&new->list, &pmic_events[event]); + + /* Release the lock */ + mutex_unlock(&event_mutex); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to unsubscribe on an event. + * + * @param event the event number to be unsubscribed + * @param callback the callback funtion to be unsubscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_unsubscribe(type_event event, + pmic_event_callback_t callback) +{ + struct list_head *p; + struct list_head *n; + pmic_event_callback_list_t *temp = NULL; + int ret = PMIC_EVENT_NOT_SUBSCRIBED; + + pr_debug("Event:%d Unsubscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Obtain the lock to access the list */ + if (mutex_lock_interruptible(&event_mutex)) { + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Find the entry in the list */ + list_for_each_safe(p, n, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + if (temp->callback.func == callback.func + && temp->callback.param == callback.param) { + /* Remove the entry from the list */ + list_del(p); + kfree(temp); + ret = PMIC_SUCCESS; + break; + } + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_mask(event) != PMIC_SUCCESS) { + ret = PMIC_UNSUBSCRIBE_ERROR; + } + } + + /* Release the lock */ + mutex_unlock(&event_mutex); + + return ret; +} + +/*! + * This function calls all callback of a specific event. + * + * @param event the active event number + * + * @return None + */ +void pmic_event_callback(type_event event) +{ + struct list_head *p; + pmic_event_callback_list_t *temp = NULL; + + /* Obtain the lock to access the list */ + if (mutex_lock_interruptible(&event_mutex)) { + return; + } + + if (list_empty(&pmic_events[event])) { + pr_debug("PMIC Event:%d detected. No callback subscribed\n", + event); + mutex_unlock(&event_mutex); + return; + } + + list_for_each(p, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + temp->callback.func(temp->callback.param); + } + + /* Release the lock */ + mutex_unlock(&event_mutex); + + return; + +} + +EXPORT_SYMBOL(pmic_event_subscribe); +EXPORT_SYMBOL(pmic_event_unsubscribe); diff --git a/drivers/mxc/pmic/core/pmic_external.c b/drivers/mxc/pmic/core/pmic_external.c new file mode 100644 index 000000000000..b9faae037b81 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_external.c @@ -0,0 +1,100 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_external.c + * @brief This file contains all external functions of PMIC drivers. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include + +#include +#include + +/* + * External Functions + */ +extern int pmic_read(int reg_num, unsigned int *reg_val); +extern int pmic_write(int reg_num, const unsigned int reg_val); + +/*! + * This function is called by PMIC clients to read a register on PMIC. + * + * @param reg number of register + * @param reg_value return value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = pmic_read(reg, &temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + *reg_value = (temp & reg_mask); + + pr_debug("Read REG[ %d ] = 0x%x\n", reg, *reg_value); + + return ret; +} + +/*! + * This function is called by PMIC clients to write a register on PMIC. + * + * @param reg number of register + * @param reg_value New value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = pmic_read(reg, &temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + temp = (temp & (~reg_mask)) | reg_value; +#ifdef CONFIG_MXC_PMIC_MC13783 + if (reg == REG_POWER_MISCELLANEOUS) + temp &= 0xFFFE7FFF; +#endif + ret = pmic_write(reg, temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + pr_debug("Write REG[ %d ] = 0x%x\n", reg, reg_value); + + return ret; +} + +EXPORT_SYMBOL(pmic_read_reg); +EXPORT_SYMBOL(pmic_write_reg); diff --git a/drivers/mxc/pmic/mc13783/Kconfig b/drivers/mxc/pmic/mc13783/Kconfig new file mode 100644 index 000000000000..02496c624e2e --- /dev/null +++ b/drivers/mxc/pmic/mc13783/Kconfig @@ -0,0 +1,55 @@ +# +# PMIC Modules configuration +# + +config MXC_MC13783_ADC + tristate "MC13783 ADC support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 ADC module driver. This module provides kernel API + for the ADC system of MC13783. + It controls also the touch screen interface. + If you want MC13783 ADC support, you should say Y here + +config MXC_MC13783_AUDIO + tristate "MC13783 Audio support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 audio module driver. This module provides kernel API + for audio part of MC13783. + If you want MC13783 audio support, you should say Y here +config MXC_MC13783_RTC + tristate "MC13783 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 RTC module driver. This module provides kernel API + for RTC part of MC13783. + If you want MC13783 RTC support, you should say Y here +config MXC_MC13783_LIGHT + tristate "MC13783 Light and Backlight support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 Light module driver. This module provides kernel API + for led and backlight control part of MC13783. + If you want MC13783 Light support, you should say Y here +config MXC_MC13783_BATTERY + tristate "MC13783 Battery API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 battery module driver. This module provides kernel API + for battery control part of MC13783. + If you want MC13783 battery support, you should say Y here +config MXC_MC13783_CONNECTIVITY + tristate "MC13783 Connectivity API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 connectivity module driver. This module provides kernel API + for USB/RS232 connectivity control part of MC13783. + If you want MC13783 connectivity support, you should say Y here +config MXC_MC13783_POWER + tristate "MC13783 Power API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 power and supplies module driver. This module provides kernel API + for power and regulator control part of MC13783. + If you want MC13783 power support, you should say Y here diff --git a/drivers/mxc/pmic/mc13783/Makefile b/drivers/mxc/pmic/mc13783/Makefile new file mode 100644 index 000000000000..7bbba23f5ab9 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the mc13783 pmic drivers. +# + +obj-$(CONFIG_MXC_MC13783_ADC) += pmic_adc-mod.o +obj-$(CONFIG_MXC_MC13783_AUDIO) += pmic_audio-mod.o +obj-$(CONFIG_MXC_MC13783_RTC) += pmic_rtc-mod.o +obj-$(CONFIG_MXC_MC13783_LIGHT) += pmic_light-mod.o +obj-$(CONFIG_MXC_MC13783_BATTERY) += pmic_battery-mod.o +obj-$(CONFIG_MXC_MC13783_CONNECTIVITY) += pmic_convity-mod.o +obj-$(CONFIG_MXC_MC13783_POWER) += pmic_power-mod.o +pmic_adc-mod-objs := pmic_adc.o +pmic_audio-mod-objs := pmic_audio.o +pmic_rtc-mod-objs := pmic_rtc.o +pmic_light-mod-objs := pmic_light.o +pmic_battery-mod-objs := pmic_battery.o +pmic_convity-mod-objs := pmic_convity.o +pmic_power-mod-objs := pmic_power.o diff --git a/drivers/mxc/pmic/mc13783/pmic_adc.c b/drivers/mxc/pmic/mc13783/pmic_adc.c new file mode 100644 index 000000000000..b8ec3b87b901 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_adc.c @@ -0,0 +1,1541 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_adc.c + * @brief This is the main file of PMIC(mc13783) ADC driver. + * + * @ingroup PMIC_ADC + */ + +/* + * Includes + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../core/pmic.h" +#include "pmic_adc_defs.h" + +#define NB_ADC_REG 5 + +static int pmic_adc_major; + +/* internal function */ +static void callback_tsi(void *); +static void callback_adcdone(void *); +static void callback_adcbisdone(void *); +static void callback_adc_comp_high(void *); + +/*! + * Number of users waiting in suspendq + */ +static int swait; + +/*! + * To indicate whether any of the adc devices are suspending + */ +static int suspend_flag; + +/*! + * The suspendq is used by blocking application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_adc_class; + +/* + * ADC mc13783 API + */ +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_adc_init); +EXPORT_SYMBOL(pmic_adc_deinit); +EXPORT_SYMBOL(pmic_adc_convert); +EXPORT_SYMBOL(pmic_adc_convert_8x); +EXPORT_SYMBOL(pmic_adc_convert_multichnnel); +EXPORT_SYMBOL(pmic_adc_set_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_sample); +EXPORT_SYMBOL(pmic_adc_get_battery_current); +EXPORT_SYMBOL(pmic_adc_active_comparator); +EXPORT_SYMBOL(pmic_adc_deactive_comparator); + +static DECLARE_COMPLETION(adcdone_it); +static DECLARE_COMPLETION(adcbisdone_it); +static DECLARE_COMPLETION(adc_tsi); +static pmic_event_callback_t tsi_event; +static pmic_event_callback_t event_adc; +static pmic_event_callback_t event_adc_bis; +static pmic_event_callback_t adc_comp_h; +static bool data_ready_adc_1; +static bool data_ready_adc_2; +static bool adc_ts; +static bool wait_ts; +static bool monitor_en; +static bool monitor_adc; +static t_check_mode wcomp_mode; +static DECLARE_MUTEX(convert_mutex); + +void (*monitoring_cb) (void); /*call back to be called when event is detected. */ + +static DECLARE_WAIT_QUEUE_HEAD(queue_adc_busy); +static t_adc_state adc_dev[2]; + +static unsigned channel_num[] = { + 0, + 1, + 3, + 4, + 2, + 12, + 13, + 14, + 15, + -1, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 7, + 6, + -1, + -1, + -1, + -1, + 5, + 7 +}; + +static bool pmic_adc_ready; + +int is_pmic_adc_ready() +{ + return pmic_adc_ready; +} +EXPORT_SYMBOL(is_pmic_adc_ready); + + +/*! + * This is the suspend of power management for the mc13783 ADC API. + * It supports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + unsigned int reg_value = 0; + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_ADC_0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_2, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_4, reg_value, PMIC_ALL_BITS)); + + return 0; +}; + +/*! + * This is the resume of power management for the mc13783 adc API. + * It supports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_adc_resume(struct platform_device *pdev) +{ + /* nothing for mc13783 adc */ + unsigned int adc_0_reg, adc_1_reg; + suspend_flag = 0; + + /* let interrupt of TSI again */ + adc_0_reg = ADC_WAIT_TSI_0; + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + adc_1_reg = ADC_WAIT_TSI_1 | (ADC_BIS * adc_ts); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/* + * Call back functions + */ + +/*! + * This is the callback function called on TSI mc13783 event, used in synchronous call. + */ +static void callback_tsi(void *unused) +{ + pr_debug("*** TSI IT mc13783 PMIC_ADC_GET_TOUCH_SAMPLE ***\n"); + if (wait_ts) { + complete(&adc_tsi); + pmic_event_mask(EVENT_TSI); + } +} + +/*! + * This is the callback function called on ADCDone mc13783 event. + */ +static void callback_adcdone(void *unused) +{ + if (data_ready_adc_1) { + complete(&adcdone_it); + } +} + +/*! + * This is the callback function called on ADCDone mc13783 event. + */ +static void callback_adcbisdone(void *unused) +{ + pr_debug("* adcdone bis it callback *\n"); + if (data_ready_adc_2) { + complete(&adcbisdone_it); + } +} + +/*! + * This is the callback function called on mc13783 event. + */ +static void callback_adc_comp_high(void *unused) +{ + pr_debug("* adc comp it high *\n"); + if (wcomp_mode == CHECK_HIGH || wcomp_mode == CHECK_LOW_OR_HIGH) { + /* launch callback */ + if (monitoring_cb != NULL) { + monitoring_cb(); + } + } +} + +/*! + * This function performs filtering and rejection of excessive noise prone + * samples. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int pmic_adc_filter(t_touch_screen *ts_curr) +{ + unsigned int ydiff1, ydiff2, ydiff3, xdiff1, xdiff2, xdiff3; + unsigned int sample_sumx, sample_sumy; + static unsigned int prev_x[FILTLEN], prev_y[FILTLEN]; + int index = 0; + unsigned int y_curr, x_curr; + static int filt_count; + /* Added a variable filt_type to decide filtering at run-time */ + unsigned int filt_type = 0; + + if (ts_curr->contact_resistance == 0) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + filt_count = 0; + return 0; + } + + ydiff1 = abs(ts_curr->y_position1 - ts_curr->y_position2); + ydiff2 = abs(ts_curr->y_position2 - ts_curr->y_position3); + ydiff3 = abs(ts_curr->y_position1 - ts_curr->y_position3); + if ((ydiff1 > DELTA_Y_MAX) || + (ydiff2 > DELTA_Y_MAX) || (ydiff3 > DELTA_Y_MAX)) { + pr_debug("pmic_adc_filter: Ret pos 1\n"); + return -1; + } + + xdiff1 = abs(ts_curr->x_position1 - ts_curr->x_position2); + xdiff2 = abs(ts_curr->x_position2 - ts_curr->x_position3); + xdiff3 = abs(ts_curr->x_position1 - ts_curr->x_position3); + + if ((xdiff1 > DELTA_X_MAX) || + (xdiff2 > DELTA_X_MAX) || (xdiff3 > DELTA_X_MAX)) { + pr_debug("mc13783_adc_filter: Ret pos 2\n"); + return -1; + } + /* Compute two closer values among the three available Y readouts */ + + if (ydiff1 < ydiff2) { + if (ydiff1 < ydiff3) { + /* Sample 0 & 1 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position2; + } else { + /* Sample 0 & 2 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } else { + if (ydiff2 < ydiff3) { + /* Sample 1 & 2 closest together */ + sample_sumy = ts_curr->y_position2 + + ts_curr->y_position3; + } else { + /* Sample 0 & 2 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } + + /* + * Compute two closer values among the three available X + * readouts + */ + if (xdiff1 < xdiff2) { + if (xdiff1 < xdiff3) { + /* Sample 0 & 1 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position2; + } else { + /* Sample 0 & 2 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } else { + if (xdiff2 < xdiff3) { + /* Sample 1 & 2 closest together */ + sample_sumx = ts_curr->x_position2 + + ts_curr->x_position3; + } else { + /* Sample 0 & 2 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } + /* + * Wait FILTER_MIN_DELAY number of samples to restart + * filtering + */ + if (filt_count < FILTER_MIN_DELAY) { + /* + * Current output is the average of the two closer + * values and no filtering is used + */ + y_curr = (sample_sumy / 2); + x_curr = (sample_sumx / 2); + ts_curr->y_position = y_curr; + ts_curr->x_position = x_curr; + filt_count++; + } else { + if (abs(sample_sumx - (prev_x[0] + prev_x[1])) > + (DELTA_X_MAX * 16)) { + pr_debug("pmic_adc_filter: : Ret pos 3\n"); + return -1; + } + if (abs(sample_sumy - (prev_y[0] + prev_y[1])) > + (DELTA_Y_MAX * 16)) { + return -1; + } + sample_sumy /= 2; + sample_sumx /= 2; + /* Use hard filtering if the sample difference < 10 */ + if ((abs(sample_sumy - prev_y[0]) > 10) || + (abs(sample_sumx - prev_x[0]) > 10)) { + filt_type = 1; + } + + /* + * Current outputs are the average of three previous + * values and the present readout + */ + y_curr = sample_sumy; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) { + y_curr = y_curr + (prev_y[index]); + } else { + y_curr = y_curr + (prev_y[index] / 3); + } + } + if (filt_type == 0) { + y_curr = y_curr >> 2; + } else { + y_curr = y_curr >> 1; + } + ts_curr->y_position = y_curr; + + x_curr = sample_sumx; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) { + x_curr = x_curr + (prev_x[index]); + } else { + x_curr = x_curr + (prev_x[index] / 3); + } + } + if (filt_type == 0) { + x_curr = x_curr >> 2; + } else { + x_curr = x_curr >> 1; + } + ts_curr->x_position = x_curr; + + } + + /* Update previous X and Y values */ + for (index = (FILTLEN - 1); index > 0; index--) { + prev_x[index] = prev_x[index - 1]; + prev_y[index] = prev_y[index - 1]; + } + + /* + * Current output will be the most recent past for the + * next sample + */ + prev_y[0] = y_curr; + prev_x[0] = x_curr; + + return 0; +} + +/*! + * This function implements the open method on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_adc_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + pr_debug("mc13783_adc : mc13783_adc_open()\n"); + return 0; +} + +/*! + * This function implements the release method on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_adc_free(struct inode *inode, struct file *file) +{ + pr_debug("mc13783_adc : mc13783_adc_free()\n"); + return 0; +} + +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +int pmic_adc_init(void) +{ + unsigned int reg_value = 0, i = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + for (i = 0; i < ADC_NB_AVAILABLE; i++) { + adc_dev[i] = ADC_FREE; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_2, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_4, reg_value, PMIC_ALL_BITS)); + reg_value = 0x001000; + CHECK_ERROR(pmic_write_reg(REG_ARBITRATION_PERIPHERAL_AUDIO, reg_value, + 0xFFFFFF)); + + data_ready_adc_1 = false; + data_ready_adc_2 = false; + adc_ts = false; + wait_ts = false; + monitor_en = false; + monitor_adc = false; + wcomp_mode = CHECK_LOW; + monitoring_cb = NULL; + /* sub to ADCDone IT */ + event_adc.param = NULL; + event_adc.func = callback_adcdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCDONEI, event_adc)); + + /* sub to ADCDoneBis IT */ + event_adc_bis.param = NULL; + event_adc_bis.func = callback_adcbisdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCBISDONEI, event_adc_bis)); + + /* sub to Touch Screen IT */ + tsi_event.param = NULL; + tsi_event.func = callback_tsi; + CHECK_ERROR(pmic_event_subscribe(EVENT_TSI, tsi_event)); + + /* ADC reading above high limit */ + adc_comp_h.param = NULL; + adc_comp_h.func = callback_adc_comp_high; + CHECK_ERROR(pmic_event_subscribe(EVENT_WHIGHI, adc_comp_h)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deinit(void) +{ + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCDONEI, event_adc)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCBISDONEI, event_adc_bis)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TSI, tsi_event)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_WHIGHI, adc_comp_h)); + + return PMIC_SUCCESS; +} + +/*! + * This function initializes adc_param structure. + * + * @param adc_param Structure to be initialized. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_init_param(t_adc_param *adc_param) +{ + int i = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + adc_param->delay = 0; + adc_param->conv_delay = false; + adc_param->single_channel = false; + adc_param->group = false; + adc_param->channel_0 = BATTERY_VOLTAGE; + adc_param->channel_1 = BATTERY_VOLTAGE; + adc_param->read_mode = 0; + adc_param->wait_tsi = 0; + adc_param->chrgraw_devide_5 = true; + adc_param->read_ts = false; + adc_param->ts_value.x_position = 0; + adc_param->ts_value.y_position = 0; + adc_param->ts_value.contact_resistance = 0; + for (i = 0; i <= MAX_CHANNEL; i++) { + adc_param->value[i] = 0; + } + return 0; +} + +/*! + * This function starts the convert. + * + * @param adc_param contains all adc configuration and return value. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS mc13783_adc_convert(t_adc_param *adc_param) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, reg_1 = 0, result_reg = + 0, i = 0; + unsigned int result = 0, temp = 0; + pmic_version_t mc13783_ver; + pr_debug("mc13783 ADC - mc13783_adc_convert ....\n"); + if (suspend_flag == 1) { + return -EBUSY; + } + + if (adc_param->wait_tsi) { + /* we need to set ADCEN 1 for TSI interrupt on mc13783 1.x */ + /* configure adc to wait tsi interrupt */ + INIT_COMPLETION(adc_tsi); + pr_debug("mc13783 ADC - pmic_write_reg ....\n"); + /*for ts don't use bis */ + adc_0_reg = 0x001c00 | (ADC_BIS * 0); + pmic_event_unmask(EVENT_TSI); + CHECK_ERROR(pmic_write_reg + (REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + /*for ts don't use bis */ + adc_1_reg = 0x200001 | (ADC_BIS * 0); + CHECK_ERROR(pmic_write_reg + (REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + pr_debug("wait tsi ....\n"); + wait_ts = true; + wait_for_completion_interruptible(&adc_tsi); + wait_ts = false; + } + if (adc_param->read_ts == false) + down(&convert_mutex); + use_bis = mc13783_adc_request(adc_param->read_ts); + if (use_bis < 0) { + pr_debug("process has received a signal and got interrupted\n"); + return -EINTR; + } + + /* CONFIGURE ADC REG 0 */ + adc_0_reg = 0; + adc_1_reg = 0; + if (adc_param->read_ts == false) { + adc_0_reg = adc_param->read_mode & 0x00003F; + /* add auto inc */ + adc_0_reg |= ADC_INC; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (adc_param->chrgraw_devide_5) { + adc_0_reg |= ADC_CHRGRAW_D5; + } + } + if (adc_param->single_channel) { + adc_1_reg |= ADC_SGL_CH; + } + + if (adc_param->conv_delay) { + adc_1_reg |= ADC_ATO; + } + + if (adc_param->group) { + adc_1_reg |= ADC_ADSEL; + } + + if (adc_param->single_channel) { + adc_1_reg |= ADC_SGL_CH; + } + + adc_1_reg |= (adc_param->channel_0 << ADC_CH_0_POS) & + ADC_CH_0_MASK; + adc_1_reg |= (adc_param->channel_1 << ADC_CH_1_POS) & + ADC_CH_1_MASK; + } else { + adc_0_reg = 0x003c00 | (ADC_BIS * use_bis) | ADC_INC; + } + pr_debug("Write Reg %i = %x\n", REG_ADC_0, adc_0_reg); + /*Change has been made here */ + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, + ADC_INC | ADC_BIS | ADC_CHRGRAW_D5 | + 0xfff00ff)); + /* CONFIGURE ADC REG 1 */ + if (adc_param->read_ts == false) { + adc_1_reg |= ADC_NO_ADTRIG; + adc_1_reg |= ADC_EN; + adc_1_reg |= (adc_param->delay << ADC_DELAY_POS) & + ADC_DELAY_MASK; + if (use_bis) { + adc_1_reg |= ADC_BIS; + } + } else { + /* configure and start convert to read x and y position */ + /* configure to read 2 value in channel selection 1 & 2 */ + adc_1_reg = 0x100409 | (ADC_BIS * use_bis) | ADC_NO_ADTRIG; + } + reg_1 = adc_1_reg; + if (use_bis == 0) { + data_ready_adc_1 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_1 = true; + pr_debug("Write Reg %i = %x\n", REG_ADC_1, adc_1_reg); + INIT_COMPLETION(adcdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, + ADC_SGL_CH | ADC_ATO | ADC_ADSEL + | ADC_CH_0_MASK | ADC_CH_1_MASK | + ADC_NO_ADTRIG | ADC_EN | + ADC_DELAY_MASK | ASC_ADC | ADC_BIS)); + pr_debug("wait adc done \n"); + wait_for_completion_interruptible(&adcdone_it); + data_ready_adc_1 = false; + } else { + data_ready_adc_2 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_2 = true; + INIT_COMPLETION(adcbisdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, 0xFFFFFF)); + temp = 0x800000; + CHECK_ERROR(pmic_write_reg(REG_ADC_3, temp, 0xFFFFFF)); + temp = 0x001000; + pmic_write_reg(REG_ARBITRATION_PERIPHERAL_AUDIO, temp, + 0xFFFFFF); + pr_debug("wait adc done bis\n"); + wait_for_completion_interruptible(&adcbisdone_it); + data_ready_adc_2 = false; + } + /* read result and store in adc_param */ + result = 0; + if (use_bis == 0) { + result_reg = REG_ADC_2; + } else { + result_reg = REG_ADC_4; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_1, 4 << ADC_CH_1_POS, + ADC_CH_0_MASK | ADC_CH_1_MASK)); + + for (i = 0; i <= 3; i++) { + CHECK_ERROR(pmic_read_reg(result_reg, &result, PMIC_ALL_BITS)); + pr_debug("result %i = %x\n", result_reg, result); + adc_param->value[i] = ((result & ADD1_RESULT_MASK) >> 2); + adc_param->value[i + 4] = ((result & ADD2_RESULT_MASK) >> 14); + } + if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.x_position1 = adc_param->value[0]; + adc_param->ts_value.x_position2 = adc_param->value[1]; + adc_param->ts_value.x_position3 = adc_param->value[2]; + adc_param->ts_value.y_position1 = adc_param->value[3]; + adc_param->ts_value.y_position2 = adc_param->value[4]; + adc_param->ts_value.y_position3 = adc_param->value[5]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + + } + + /*if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + } */ + mc13783_adc_release(use_bis); + if (adc_param->read_ts == false) + up(&convert_mutex); + + return PMIC_SUCCESS; +} + +/*! + * This function select the required read_mode for a specific channel. + * + * @param channel The channel to be sampled + * + * @return This function returns the requires read_mode + */ +t_reading_mode mc13783_set_read_mode(t_channel channel) +{ + t_reading_mode read_mode = 0; + + switch (channel) { + case LICELL: + read_mode = M_LITHIUM_CELL; + break; + case CHARGE_CURRENT: + read_mode = M_CHARGE_CURRENT; + break; + case BATTERY_CURRENT: + read_mode = M_BATTERY_CURRENT; + break; + case THERMISTOR: + read_mode = M_THERMISTOR; + break; + case DIE_TEMP: + read_mode = M_DIE_TEMPERATURE; + break; + case USB_ID: + read_mode = M_UID; + break; + default: + read_mode = 0; + } + + return read_mode; +} + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + PMIC_STATUS ret; + + if (suspend_flag == 1) { + return -EBUSY; + } + + channel = channel_num[channel]; + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert\n"); + adc_param.read_ts = false; + adc_param.read_mode = mc13783_set_read_mode(channel); + + adc_param.single_channel = true; + /* Find the group */ + if ((channel >= 0) && (channel <= 7)) { + adc_param.channel_0 = channel; + adc_param.group = false; + } else if ((channel >= 8) && (channel <= 15)) { + adc_param.channel_0 = channel & 0x07; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + ret = mc13783_adc_convert(&adc_param); + *result = adc_param.value[0]; + return ret; +} + +/*! + * This function triggers a conversion and returns eight sampling results of + * one channel. + * + * @param channel The channel to be sampled + * @param result The pointer to array to store eight sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert_8x(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) { + return -EBUSY; + } + + channel = channel_num[channel]; + + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_8x\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13783_set_read_mode(channel); + if ((channel >= 0) && (channel <= 7)) { + adc_param.channel_0 = channel; + adc_param.channel_1 = channel; + adc_param.group = false; + } else if ((channel >= 8) && (channel <= 15)) { + adc_param.channel_0 = channel & 0x07; + adc_param.channel_1 = channel & 0x07; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + + ret = mc13783_adc_convert(&adc_param); + for (i = 0; i <= 7; i++) { + result[i] = adc_param.value[i]; + } + return ret; +} + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert_multichnnel(t_channel channels, + unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) { + return -EBUSY; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_multichnnel\n"); + + channels = channel_num[channels]; + + if (channels == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + + adc_param.read_ts = false; + adc_param.single_channel = false; + if ((channels >= 0) && (channels <= 7)) { + adc_param.channel_0 = channels; + adc_param.channel_1 = ((channels + 4) % 4) + 4; + adc_param.group = false; + } else if ((channels >= 8) && (channels <= 15)) { + channels = channels & 0x07; + adc_param.channel_0 = channels; + adc_param.channel_1 = ((channels + 4) % 4) + 4; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + adc_param.read_mode = 0x00003f; + adc_param.read_ts = false; + ret = mc13783_adc_convert(&adc_param); + + for (i = 0; i <= 7; i++) { + result[i] = adc_param.value[i]; + } + return ret; +} + +/*! + * This function sets touch screen operation mode. + * + * @param touch_mode Touch screen operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_set_touch_mode(t_touch_mode touch_mode) +{ + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_0, + BITFVAL(MC13783_ADC0_TS_M, touch_mode), + BITFMASK(MC13783_ADC0_TS_M))); + return PMIC_SUCCESS; +} + +/*! + * This function retrieves the current touch screen operation mode. + * + * @param touch_mode Pointer to the retrieved touch screen operation + * mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_mode(t_touch_mode *touch_mode) +{ + unsigned int value; + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_read_reg(REG_ADC_0, &value, PMIC_ALL_BITS)); + + *touch_mode = BITFEXT(value, MC13783_ADC0_TS_M); + + return PMIC_SUCCESS; +} + +/*! + * This function retrieves the current touch screen (X,Y) coordinates. + * + * @param touch_sample Pointer to touch sample. + * @param wait indicates whether this call must block or not. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_sample(t_touch_screen *touch_sample, int wait) +{ + if (mc13783_adc_read_ts(touch_sample, wait) != 0) + return PMIC_ERROR; + if (0 == pmic_adc_filter(touch_sample)) + return PMIC_SUCCESS; + else + return PMIC_ERROR; +} + +/*! + * This function read the touch screen value. + * + * @param ts_value return value of touch screen + * @param wait_tsi if true, this function is synchronous (wait in TSI event). + * + * @return This function returns 0. + */ +PMIC_STATUS mc13783_adc_read_ts(t_touch_screen *ts_value, int wait_tsi) +{ + t_adc_param param; + pr_debug("mc13783_adc : mc13783_adc_read_ts\n"); + if (suspend_flag == 1) { + return -EBUSY; + } + if (wait_ts) { + pr_debug("mc13783_adc : error TS busy \n"); + return PMIC_ERROR; + } + mc13783_adc_init_param(¶m); + param.wait_tsi = wait_tsi; + param.read_ts = true; + if (mc13783_adc_convert(¶m) != 0) + return PMIC_ERROR; + /* check if x-y is ok */ + if ((param.ts_value.x_position1 < TS_X_MAX) && + (param.ts_value.x_position1 >= TS_X_MIN) && + (param.ts_value.y_position1 < TS_Y_MAX) && + (param.ts_value.y_position1 >= TS_Y_MIN) && + (param.ts_value.x_position2 < TS_X_MAX) && + (param.ts_value.x_position2 >= TS_X_MIN) && + (param.ts_value.y_position2 < TS_Y_MAX) && + (param.ts_value.y_position2 >= TS_Y_MIN) && + (param.ts_value.x_position3 < TS_X_MAX) && + (param.ts_value.x_position3 >= TS_X_MIN) && + (param.ts_value.y_position3 < TS_Y_MAX) && + (param.ts_value.y_position3 >= TS_Y_MIN)) { + ts_value->x_position = param.ts_value.x_position; + ts_value->x_position1 = param.ts_value.x_position1; + ts_value->x_position2 = param.ts_value.x_position2; + ts_value->x_position3 = param.ts_value.x_position3; + ts_value->y_position = param.ts_value.y_position; + ts_value->y_position1 = param.ts_value.y_position1; + ts_value->y_position2 = param.ts_value.y_position2; + ts_value->y_position3 = param.ts_value.y_position3; + ts_value->contact_resistance = + param.ts_value.contact_resistance + 1; + + } else { + ts_value->x_position = 0; + ts_value->y_position = 0; + ts_value->contact_resistance = 0; + + } + return PMIC_SUCCESS; +} + +/*! + * This function starts a Battery Current mode conversion. + * + * @param mode Conversion mode. + * @param result Battery Current measurement result. + * if \a mode = ADC_8CHAN_1X, the result is \n + * result[0] = (BATTP - BATT_I) \n + * if \a mode = ADC_1CHAN_8X, the result is \n + * result[0] = BATTP \n + * result[1] = BATT_I \n + * result[2] = BATTP \n + * result[3] = BATT_I \n + * result[4] = BATTP \n + * result[5] = BATT_I \n + * result[6] = BATTP \n + * result[7] = BATT_I + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_battery_current(t_conversion_mode mode, + unsigned short *result) +{ + PMIC_STATUS ret; + t_channel channel; + if (suspend_flag == 1) { + return -EBUSY; + } + channel = BATTERY_CURRENT; + if (mode == ADC_8CHAN_1X) { + ret = pmic_adc_convert(channel, result); + } else { + ret = pmic_adc_convert_8x(channel, result); + } + return ret; +} + +/*! + * This function request a ADC. + * + * @return This function returns index of ADC to be used (0 or 1) if successful. + * return -1 if error. + */ +int mc13783_adc_request(bool read_ts) +{ + int adc_index = -1; + if (read_ts != 0) { + /*for ts we use bis=0 */ + if (adc_dev[0] == ADC_USED) + return -1; + /*no wait here */ + adc_dev[0] = ADC_USED; + adc_index = 0; + } else { + /*for other adc use bis = 1 */ + if (adc_dev[1] == ADC_USED) { + return -1; + /*no wait here */ + } + adc_dev[1] = ADC_USED; + adc_index = 1; + } + pr_debug("mc13783_adc : request ADC %d\n", adc_index); + return adc_index; +} + +/*! + * This function release an ADC. + * + * @param adc_index index of ADC to be released. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_release(int adc_index) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + pr_debug("mc13783_adc : release ADC %d\n", adc_index); + if ((adc_dev[adc_index] == ADC_MONITORING) || + (adc_dev[adc_index] == ADC_USED)) { + adc_dev[adc_index] = ADC_FREE; + wake_up(&queue_adc_busy); + return 0; + } + return -1; +} + +/*! + * This function initializes monitoring structure. + * + * @param monitor Structure to be initialized. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_init_monitor_param(t_monitoring_param *monitor) +{ + pr_debug("mc13783_adc : init monitor\n"); + monitor->delay = 0; + monitor->conv_delay = false; + monitor->channel = BATTERY_VOLTAGE; + monitor->read_mode = 0; + monitor->comp_low = 0; + monitor->comp_high = 0; + monitor->group = 0; + monitor->check_mode = CHECK_LOW_OR_HIGH; + monitor->callback = NULL; + return 0; +} + +/*! + * This function actives the comparator. When comparator is active and ADC + * is enabled, the 8th converted value will be digitally compared against the + * window defined by WLOW and WHIGH registers. + * + * @param low Comparison window low threshold (WLOW). + * @param high Comparison window high threshold (WHIGH). + * @param channel The channel to be sampled + * @param callback Callback function to be called when the converted + * value is beyond the comparison window. The callback + * function will pass a parameter of type + * \b t_comp_expection to indicate the reason of + * comparator exception. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_active_comparator(unsigned char low, + unsigned char high, + t_channel channel, + t_comparator_cb callback) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, adc_3_reg = 0; + t_monitoring_param monitoring; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (monitor_en) { + pr_debug("mc13783_adc : monitoring already configured\n"); + return PMIC_ERROR; + } + monitor_en = true; + mc13783_adc_init_monitor_param(&monitoring); + monitoring.comp_low = low; + monitoring.comp_high = high; + monitoring.channel = channel; + monitoring.callback = (void *)callback; + + use_bis = mc13783_adc_request(false); + if (use_bis < 0) { + pr_debug("mc13783_adc : request error\n"); + return PMIC_ERROR; + } + monitor_adc = use_bis; + + adc_0_reg = 0; + + /* TO DO ADOUT CONFIGURE */ + adc_0_reg = monitoring.read_mode & ADC_MODE_MASK; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + adc_0_reg |= ADC_WCOMP; + + /* CONFIGURE ADC REG 1 */ + adc_1_reg = 0; + adc_1_reg |= ADC_EN; + if (monitoring.conv_delay) { + adc_1_reg |= ADC_ATO; + } + if (monitoring.group) { + adc_1_reg |= ADC_ADSEL; + } + adc_1_reg |= (monitoring.channel << ADC_CH_0_POS) & ADC_CH_0_MASK; + adc_1_reg |= (monitoring.delay << ADC_DELAY_POS) & ADC_DELAY_MASK; + if (use_bis) { + adc_1_reg |= ADC_BIS; + } + + adc_3_reg |= (monitoring.comp_high << ADC_WCOMP_H_POS) & + ADC_WCOMP_H_MASK; + adc_3_reg |= (monitoring.comp_low << ADC_WCOMP_L_POS) & + ADC_WCOMP_L_MASK; + if (use_bis) { + adc_3_reg |= ADC_BIS; + } + + wcomp_mode = monitoring.check_mode; + /* call back to be called when event is detected. */ + monitoring_cb = monitoring.callback; + + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, adc_3_reg, PMIC_ALL_BITS)); + return PMIC_SUCCESS; +} + +/*! + * This function deactivates the comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deactive_comparator(void) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (!monitor_en) { + pr_debug("mc13783_adc : adc monitoring free\n"); + return PMIC_ERROR; + } + + if (monitor_en) { + reg_value = ADC_BIS; + } + + /* clear all reg value */ + CHECK_ERROR(pmic_write_reg(REG_ADC_0, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, reg_value, PMIC_ALL_BITS)); + + reg_value = 0; + + if (monitor_adc) { + CHECK_ERROR(pmic_write_reg + (REG_ADC_4, reg_value, PMIC_ALL_BITS)); + } else { + CHECK_ERROR(pmic_write_reg + (REG_ADC_2, reg_value, PMIC_ALL_BITS)); + } + + mc13783_adc_release(monitor_adc); + monitor_en = false; + return PMIC_SUCCESS; +} + +/*! + * This function implements IOCTL controls on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_adc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_adc_convert_param *convert_param; + t_touch_mode touch_mode; + t_touch_screen touch_sample; + unsigned short b_current; + t_adc_comp_param *comp_param; + if ((_IOC_TYPE(cmd) != 'p') && (_IOC_TYPE(cmd) != 'D')) + return -ENOTTY; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + convert_param = kmalloc(sizeof(t_adc_convert_param), GFP_KERNEL); + comp_param = kmalloc(sizeof(t_adc_comp_param), GFP_KERNEL); + switch (cmd) { + case PMIC_ADC_INIT: + pr_debug("init adc\n"); + CHECK_ERROR(pmic_adc_init()); + break; + + case PMIC_ADC_DEINIT: + pr_debug("deinit adc\n"); + CHECK_ERROR(pmic_adc_deinit()); + break; + + case PMIC_ADC_CONVERT: + if (convert_param == NULL) + return -ENOMEM; + + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_convert(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_CONVERT_8X: + if (convert_param == NULL) + return -ENOMEM; + + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_convert_8x(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_CONVERT_MULTICHANNEL: + if (convert_param == NULL) + return -ENOMEM; + + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_adc_convert_multichnnel + (convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_SET_TOUCH_MODE: + CHECK_ERROR(pmic_adc_set_touch_mode((t_touch_mode) arg)); + break; + + case PMIC_ADC_GET_TOUCH_MODE: + CHECK_ERROR(pmic_adc_get_touch_mode(&touch_mode)); + if (copy_to_user((t_touch_mode *) arg, &touch_mode, + sizeof(t_touch_mode))) { + return -EFAULT; + } + break; + + case PMIC_ADC_GET_TOUCH_SAMPLE: + pr_debug("pmic_adc_ioctl: " "PMIC_ADC_GET_TOUCH_SAMPLE\n"); + CHECK_ERROR(pmic_adc_get_touch_sample(&touch_sample, 1)); + if (copy_to_user((t_touch_screen *) arg, &touch_sample, + sizeof(t_touch_screen))) { + return -EFAULT; + } + break; + + case PMIC_ADC_GET_BATTERY_CURRENT: + CHECK_ERROR(pmic_adc_get_battery_current(ADC_8CHAN_1X, + &b_current)); + if (copy_to_user((unsigned short *)arg, &b_current, + sizeof(unsigned short))) { + + return -EFAULT; + } + break; + + case PMIC_ADC_ACTIVATE_COMPARATOR: + if (comp_param == NULL) + return -ENOMEM; + + if (copy_from_user(comp_param, (t_adc_comp_param *) arg, + sizeof(t_adc_comp_param))) { + kfree(comp_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_active_comparator(comp_param->wlow, + comp_param->whigh, + comp_param-> + channel, + comp_param-> + callback), + (kfree(comp_param))); + break; + + case PMIC_ADC_DEACTIVE_COMPARATOR: + CHECK_ERROR(pmic_adc_deactive_comparator()); + break; + + default: + pr_debug("pmic_adc_ioctl: unsupported ioctl command 0x%x\n", + cmd); + return -EINVAL; + } + return 0; +} + +static struct file_operations mc13783_adc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_adc_ioctl, + .open = pmic_adc_open, + .release = pmic_adc_free, +}; + +static int pmic_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_adc_major = register_chrdev(0, "pmic_adc", &mc13783_adc_fops); + + if (pmic_adc_major < 0) { + pr_debug(KERN_ERR "Unable to get a major for pmic_adc\n"); + return pmic_adc_major; + } + init_waitqueue_head(&suspendq); + + pmic_adc_class = class_create(THIS_MODULE, "pmic_adc"); + if (IS_ERR(pmic_adc_class)) { + pr_debug(KERN_ERR "Error creating pmic_adc class.\n"); + ret = PTR_ERR(pmic_adc_class); + goto err_out1; + } + + temp_class = device_create(pmic_adc_class, NULL, + MKDEV(pmic_adc_major, 0), NULL, "pmic_adc"); + if (IS_ERR(temp_class)) { + pr_debug(KERN_ERR "Error creating pmic_adc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + ret = pmic_adc_init(); + if (ret != PMIC_SUCCESS) { + pr_debug(KERN_ERR "Error in pmic_adc_init.\n"); + goto err_out4; + } + + pmic_adc_ready = 1; + pr_debug(KERN_INFO "PMIC ADC successfully probed\n"); + return ret; + + err_out4: + device_destroy(pmic_adc_class, MKDEV(pmic_adc_major, 0)); + err_out2: + class_destroy(pmic_adc_class); + err_out1: + unregister_chrdev(pmic_adc_major, "pmic_adc"); + return ret; +} + +static int pmic_adc_module_remove(struct platform_device *pdev) +{ + pmic_adc_ready = 0; + pmic_adc_deinit(); + device_destroy(pmic_adc_class, MKDEV(pmic_adc_major, 0)); + class_destroy(pmic_adc_class); + unregister_chrdev(pmic_adc_major, "pmic_adc"); + pr_debug(KERN_INFO "PMIC ADC successfully removed\n"); + return 0; +} + +static struct platform_driver pmic_adc_driver_ldm = { + .driver = { + .name = "pmic_adc", + }, + .suspend = pmic_adc_suspend, + .resume = pmic_adc_resume, + .probe = pmic_adc_module_probe, + .remove = pmic_adc_module_remove, +}; + +/* + * Initialization and Exit + */ +static int __init pmic_adc_module_init(void) +{ + pr_debug("PMIC ADC driver loading...\n"); + return platform_driver_register(&pmic_adc_driver_ldm); +} + +static void __exit pmic_adc_module_exit(void) +{ + platform_driver_unregister(&pmic_adc_driver_ldm); + pr_debug("PMIC ADC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_adc_module_init); +module_exit(pmic_adc_module_exit); + +MODULE_DESCRIPTION("PMIC ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_adc_defs.h b/drivers/mxc/pmic/mc13783/pmic_adc_defs.h new file mode 100644 index 000000000000..ad051dc6fcdc --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_adc_defs.h @@ -0,0 +1,321 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_adc_defs.h + * @brief This header contains all defines for PMIC(mc13783) ADC driver. + * + * @ingroup PMIC_ADC + */ + +#ifndef __MC13783_ADC__DEFS_H__ +#define __MC13783_ADC__DEFS_H__ + +#define MC13783_ADC_DEVICE "/dev/mc13783_adc" + +#define DEF_ADC_0 0x008000 +#define DEF_ADC_3 0x000080 + +#define ADC_NB_AVAILABLE 2 + +#define MAX_CHANNEL 7 + +/* + * Maximun allowed variation in the three X/Y co-ordinates acquired from + * touch-screen + */ +#define DELTA_Y_MAX 50 +#define DELTA_X_MAX 50 + +/* Upon clearing the filter, this is the delay in restarting the filter */ +#define FILTER_MIN_DELAY 4 + +/* Length of X and Y Touch screen filters */ +#define FILTLEN 3 + +#define TS_X_MAX 1000 +#define TS_Y_MAX 1000 + +#define TS_X_MIN 80 +#define TS_Y_MIN 80 + +#define MC13783_ADC0_TS_M_LSH 14 +#define MC13783_ADC0_TS_M_WID 3 +/* + * ADC 0 + */ +#define ADC_WAIT_TSI_0 0x001C00 + +/* + * ADC 1 + */ + +#define ADC_EN 0x000001 +#define ADC_SGL_CH 0x000002 +#define ADC_ADSEL 0x000008 +#define ADC_CH_0_POS 5 +#define ADC_CH_0_MASK 0x0000E0 +#define ADC_CH_1_POS 8 +#define ADC_CH_1_MASK 0x000700 +#define ADC_DELAY_POS 11 +#define ADC_DELAY_MASK 0x07F800 +#define ADC_ATO 0x080000 +#define ASC_ADC 0x100000 +#define ADC_WAIT_TSI_1 0x300001 +#define ADC_CHRGRAW_D5 0x008000 + +/* + * ADC 2 - 4 + */ +#define ADD1_RESULT_MASK 0x00000FFC +#define ADD2_RESULT_MASK 0x00FFC000 +#define ADC_TS_MASK 0x00FFCFFC + +/* + * ADC 3 + */ +#define ADC_INC 0x030000 +#define ADC_BIS 0x800000 + +/* + * ADC 3 + */ +#define ADC_NO_ADTRIG 0x200000 +#define ADC_WCOMP 0x040000 +#define ADC_WCOMP_H_POS 0 +#define ADC_WCOMP_L_POS 9 +#define ADC_WCOMP_H_MASK 0x00003F +#define ADC_WCOMP_L_MASK 0x007E00 + +#define ADC_MODE_MASK 0x00003F + +/* + * Interrupt Status 0 + */ +#define ADC_INT_BISDONEI 0x02 + +/*! + * Define state mode of ADC. + */ +typedef enum adc_state { + /*! + * Free. + */ + ADC_FREE, + /*! + * Used. + */ + ADC_USED, + /*! + * Monitoring + */ + ADC_MONITORING, +} t_adc_state; + +/*! + * This enumeration, is used to configure the mode of ADC. + */ +typedef enum reading_mode { + /*! + * Enables lithium cell reading + */ + M_LITHIUM_CELL = 0x000001, + /*! + * Enables charge current reading + */ + M_CHARGE_CURRENT = 0x000002, + /*! + * Enables battery current reading + */ + M_BATTERY_CURRENT = 0x000004, + /*! + * Enables thermistor reading + */ + M_THERMISTOR = 0x000008, + /*! + * Enables die temperature reading + */ + M_DIE_TEMPERATURE = 0x000010, + /*! + * Enables UID reading + */ + M_UID = 0x000020, +} t_reading_mode; + +/*! + * This enumeration, is used to configure the monitoring mode. + */ +typedef enum check_mode { + /*! + * Comparator low level + */ + CHECK_LOW, + /*! + * Comparator high level + */ + CHECK_HIGH, + /*! + * Comparator low or high level + */ + CHECK_LOW_OR_HIGH, +} t_check_mode; + +/*! + * This structure is used to configure and report adc value. + */ +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Sets the single channel mode + */ + bool single_channel; + /*! + * Selects the set of inputs + */ + bool group; + /*! + * Channel selection 1 + */ + t_channel channel_0; + /*! + * Channel selection 2 + */ + t_channel channel_1; + /*! + * Used to configure ADC mode with t_reading_mode + */ + t_reading_mode read_mode; + /*! + * Sets the Touch screen mode + */ + bool read_ts; + /*! + * Wait TSI event before touch screen reading + */ + bool wait_tsi; + /*! + * Sets CHRGRAW scaling to divide by 5 + * Only supported on 2.0 and higher + */ + bool chrgraw_devide_5; + /*! + * Return ADC values + */ + unsigned int value[8]; + /*! + * Return touch screen values + */ + t_touch_screen ts_value; +} t_adc_param; + +/*! + * This structure is used to configure the monitoring mode of ADC. + */ +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Channel selection 1 + */ + t_channel channel; + /*! + * Selects the set of inputs + */ + bool group; + /*! + * Used to configure ADC mode with t_reading_mode + */ + unsigned int read_mode; + /*! + * Comparator low level in WCOMP mode + */ + unsigned int comp_low; + /*! + * Comparator high level in WCOMP mode + */ + unsigned int comp_high; + /*! + * Sets type of monitoring (low, high or both) + */ + t_check_mode check_mode; + /*! + * Callback to be launched when event is detected + */ + void (*callback) (void); +} t_monitoring_param; + +/*! + * This function performs filtering and rejection of excessive noise prone + * samples. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int pmic_adc_filter(t_touch_screen *ts_curr); + +/*! + * This function request a ADC. + * + * @return This function returns index of ADC to be used (0 or 1) if successful. + * return -1 if error. + */ +int mc13783_adc_request(bool read_ts); + +/*! + * This function is used to update buffer of touch screen value in read mode. + */ +void update_buffer(void); + +/*! + * This function release an ADC. + * + * @param adc_index index of ADC to be released. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_release(int adc_index); + +/*! + * This function select the required read_mode for a specific channel. + * + * @param channel The channel to be sampled + * + * @return This function returns the requires read_mode + */ +t_reading_mode mc13783_set_read_mode(t_channel channel); + +/*! + * This function read the touch screen value. + * + * @param touch_sample return value of touch screen + * @param wait_tsi if true, this function is synchronous (wait in TSI event). + * + * @return This function returns 0. + */ +PMIC_STATUS mc13783_adc_read_ts(t_touch_screen *touch_sample, int wait_tsi); + +#endif /* __MC13783_ADC__DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_audio.c b/drivers/mxc/pmic/mc13783/pmic_audio.c new file mode 100644 index 000000000000..470c93e50b2b --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_audio.c @@ -0,0 +1,5873 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_audio.c + * @brief Implementation of the PMIC(mc13783) Audio driver APIs. + * + * The PMIC Audio driver and this API were developed to support the + * audio playback, recording, and mixing capabilities of the power + * management ICs that are available from Freescale Semiconductor, Inc. + * + * The following operating modes are supported: + * + * @verbatim + Operating Mode mc13783 + ---------------------------- ------- + Stereo DAC Playback Yes + Stereo DAC Input Mixing Yes + Voice CODEC Playback Yes + Voice CODEC Input Mixing Yes + Voice CODEC Mono Recording Yes + Voice CODEC Stereo Recording Yes + Microphone Bias Control Yes + Output Amplifier Control Yes + Output Mixing Control Yes + Input Amplifier Control Yes + Master/Slave Mode Select Yes + Anti Pop Bias Circuit Control Yes + @endverbatim + * + * Note that the Voice CODEC may also be referred to as the Telephone + * CODEC in the PMIC DTS documentation. Also note that, while the power + * management ICs do provide similar audio capabilities, each PMIC may + * support additional configuration settings and features. Therefore, it + * is highly recommended that the appropriate power management IC DTS + * documents be used in conjunction with this API interface. + * + * @ingroup PMIC_AUDIO + */ + +#include +#include /* For tasklet interface. */ +#include /* For kernel module interface. */ +#include +#include /* For spinlock interface. */ +#include /* For PMIC ADC driver interface. */ +#include +#include /* For PMIC Audio driver interface. */ + +/* + * mc13783 PMIC Audio API + */ + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(MIN_STDAC_SAMPLING_RATE_HZ); +EXPORT_SYMBOL(MAX_STDAC_SAMPLING_RATE_HZ); +EXPORT_SYMBOL(pmic_audio_open); +EXPORT_SYMBOL(pmic_audio_close); +EXPORT_SYMBOL(pmic_audio_set_protocol); +EXPORT_SYMBOL(pmic_audio_get_protocol); +EXPORT_SYMBOL(pmic_audio_enable); +EXPORT_SYMBOL(pmic_audio_disable); +EXPORT_SYMBOL(pmic_audio_reset); +EXPORT_SYMBOL(pmic_audio_reset_all); +EXPORT_SYMBOL(pmic_audio_set_callback); +EXPORT_SYMBOL(pmic_audio_clear_callback); +EXPORT_SYMBOL(pmic_audio_get_callback); +EXPORT_SYMBOL(pmic_audio_antipop_enable); +EXPORT_SYMBOL(pmic_audio_antipop_disable); +EXPORT_SYMBOL(pmic_audio_digital_filter_reset); +EXPORT_SYMBOL(pmic_audio_vcodec_set_clock); +EXPORT_SYMBOL(pmic_audio_vcodec_get_clock); +EXPORT_SYMBOL(pmic_audio_vcodec_set_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_vcodec_get_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_vcodec_set_secondary_txslot); +EXPORT_SYMBOL(pmic_audio_vcodec_get_secondary_txslot); +EXPORT_SYMBOL(pmic_audio_vcodec_set_config); +EXPORT_SYMBOL(pmic_audio_vcodec_clear_config); +EXPORT_SYMBOL(pmic_audio_vcodec_get_config); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_bypass); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_bypass); +EXPORT_SYMBOL(pmic_audio_stdac_set_clock); +EXPORT_SYMBOL(pmic_audio_stdac_get_clock); +EXPORT_SYMBOL(pmic_audio_stdac_set_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_stdac_get_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_stdac_set_config); +EXPORT_SYMBOL(pmic_audio_stdac_clear_config); +EXPORT_SYMBOL(pmic_audio_stdac_get_config); +EXPORT_SYMBOL(pmic_audio_input_set_config); +EXPORT_SYMBOL(pmic_audio_input_clear_config); +EXPORT_SYMBOL(pmic_audio_input_get_config); +EXPORT_SYMBOL(pmic_audio_vcodec_set_mic); +EXPORT_SYMBOL(pmic_audio_vcodec_get_mic); +EXPORT_SYMBOL(pmic_audio_vcodec_set_mic_on_off); +EXPORT_SYMBOL(pmic_audio_vcodec_get_mic_on_off); +EXPORT_SYMBOL(pmic_audio_vcodec_set_record_gain); +EXPORT_SYMBOL(pmic_audio_vcodec_get_record_gain); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_micbias); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_micbias); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_mixer); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_mixer); +EXPORT_SYMBOL(pmic_audio_stdac_enable_mixer); +EXPORT_SYMBOL(pmic_audio_stdac_disable_mixer); +EXPORT_SYMBOL(pmic_audio_output_set_port); +EXPORT_SYMBOL(pmic_audio_output_get_port); +EXPORT_SYMBOL(pmic_audio_output_clear_port); +EXPORT_SYMBOL(pmic_audio_output_set_stereo_in_gain); +EXPORT_SYMBOL(pmic_audio_output_get_stereo_in_gain); +EXPORT_SYMBOL(pmic_audio_output_set_pgaGain); +EXPORT_SYMBOL(pmic_audio_output_get_pgaGain); +EXPORT_SYMBOL(pmic_audio_output_enable_mixer); +EXPORT_SYMBOL(pmic_audio_output_disable_mixer); +EXPORT_SYMBOL(pmic_audio_output_set_balance); +EXPORT_SYMBOL(pmic_audio_output_get_balance); +EXPORT_SYMBOL(pmic_audio_output_enable_mono_adder); +EXPORT_SYMBOL(pmic_audio_output_disable_mono_adder); +EXPORT_SYMBOL(pmic_audio_output_set_mono_adder_gain); +EXPORT_SYMBOL(pmic_audio_output_get_mono_adder_gain); +EXPORT_SYMBOL(pmic_audio_output_set_config); +EXPORT_SYMBOL(pmic_audio_output_clear_config); +EXPORT_SYMBOL(pmic_audio_output_get_config); +EXPORT_SYMBOL(pmic_audio_output_enable_phantom_ground); +EXPORT_SYMBOL(pmic_audio_output_disable_phantom_ground); +EXPORT_SYMBOL(pmic_audio_set_autodetect); +#ifdef DEBUG_AUDIO +EXPORT_SYMBOL(pmic_audio_dump_registers); +#endif /* DEBUG_AUDIO */ +/*! + * Define the minimum sampling rate (in Hz) that is supported by the + * Stereo DAC. + */ +const unsigned MIN_STDAC_SAMPLING_RATE_HZ = 8000; + +/*! + * Define the maximum sampling rate (in Hz) that is supported by the + * Stereo DAC. + */ +const unsigned MAX_STDAC_SAMPLING_RATE_HZ = 96000; + +/*! @def SET_BITS + * Set a register field to a given value. + */ +#define SET_BITS(reg, field, value) (((value) << reg.field.offset) & \ + reg.field.mask) +/*! @def GET_BITS + * Get the current value of a given register field. + */ +#define GET_BITS(reg, field, value) (((value) & reg.field.mask) >> \ + reg.field.offset) + +/*! + * @brief Define the possible states for a device handle. + * + * This enumeration is used to track the current state of each device handle. + */ +typedef enum { + HANDLE_FREE, /*!< Handle is available for use. */ + HANDLE_IN_USE /*!< Handle is currently in use. */ +} HANDLE_STATE; + +/*! + * @brief Identifies the hardware interrupt source. + * + * This enumeration identifies which of the possible hardware interrupt + * sources actually caused the current interrupt handler to be called. + */ +typedef enum { + CORE_EVENT_MC2BI, /*!< Microphone Bias 2 detect. */ + CORE_EVENT_HSDETI, /*!< Detect Headset attach */ + CORE_EVENT_HSLI, /*!< Detect Stereo Headset */ + CORE_EVENT_ALSPTHI, /*!< Detect Thermal shutdown of ALSP */ + CORE_EVENT_AHSSHORTI /*!< Detect Short circuit on AHS outputs */ +} PMIC_CORE_EVENT; + +/*! + * @brief This structure is used to track the state of a microphone input. + */ +typedef struct { + PMIC_AUDIO_INPUT_PORT mic; /*!< Microphone input port. */ + PMIC_AUDIO_INPUT_MIC_STATE micOnOff; /*!< Microphone On/Off state. */ + PMIC_AUDIO_MIC_AMP_MODE ampMode; /*!< Input amplifier mode. */ + PMIC_AUDIO_MIC_GAIN gain; /*!< Input amplifier gain level. */ +} PMIC_MICROPHONE_STATE; + +/*! + * @brief Tracks whether a headset is currently attached or not. + */ +typedef enum { + NO_HEADSET, /*!< No headset currently attached. */ + HEADSET_ON /*!< Headset has been attached. */ +} HEADSET_STATUS; + +/*! + * @brief mc13783 only enum that indicates the path to output taken + * by the voice codec output + */ +typedef enum { + VCODEC_DIRECT_OUT, /*!< Vcodec signal out direct */ + VCODEC_MIXER_OUT /*!< Output via the mixer */ +} PMIC_AUDIO_VCODEC_OUTPUT_PATH; + +/*! + * @brief This structure is used to define a specific hardware register field. + * + * All hardware register fields are defined using an offset to the LSB + * and a mask. The offset is used to right shift a register value before + * applying the mask to actually obtain the value of the field. + */ +typedef struct { + const unsigned char offset; /*!< Offset of LSB of register field. */ + const unsigned int mask; /*!< Mask value used to isolate register field. */ +} REGFIELD; + +/*! + * @brief This structure lists all fields of the AUD_CODEC hardware register. + */ +typedef struct { + REGFIELD CDCSSISEL; /*!< codec SSI bus select */ + REGFIELD CDCCLKSEL; /*!< Codec clock input select */ + REGFIELD CDCSM; /*!< Codec slave / master select */ + REGFIELD CDCBCLINV; /*!< Codec bitclock inversion */ + REGFIELD CDCFSINV; /*!< Codec framesync inversion */ + REGFIELD CDCFS; /*!< Bus protocol selection - 2 bits */ + REGFIELD CDCCLK; /*!< Codec clock setting - 3 bits */ + REGFIELD CDCFS8K16K; /*!< Codec framesync select */ + REGFIELD CDCEN; /*!< Codec enable */ + REGFIELD CDCCLKEN; /*!< Codec clocking enable */ + REGFIELD CDCTS; /*!< Codec SSI tristate */ + REGFIELD CDCDITH; /*!< Codec dithering */ + REGFIELD CDCRESET; /*!< Codec filter reset */ + REGFIELD CDCBYP; /*!< Codec bypass */ + REGFIELD CDCALM; /*!< Codec analog loopback */ + REGFIELD CDCDLM; /*!< Codec digital loopback */ + REGFIELD AUDIHPF; /*!< Transmit high pass filter enable */ + REGFIELD AUDOHPF; /*!< Receive high pass filter enable */ +} REGISTER_AUD_CODEC; + +/*! + * @brief This variable is used to access the AUD_CODEC hardware register. + * + * This variable defines how to access all of the fields within the + * AUD_CODEC hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUD_CODEC regAUD_CODEC = { + {0, 0x000001}, /* CDCSSISEL */ + {1, 0x000002}, /* CDCCLKSEL */ + {2, 0x000004}, /* CDCSM */ + {3, 0x000008}, /* CDCBCLINV */ + {4, 0x000010}, /* CDCFSINV */ + {5, 0x000060}, /* CDCFS */ + {7, 0x000380}, /* CDCCLK */ + {10, 0x000400}, /* CDCFS8K16K */ + {11, 0x000800}, /* CDCEN */ + {12, 0x001000}, /* CDCCLKEN */ + {13, 0x002000}, /* CDCTS */ + {14, 0x004000}, /* CDCDITH */ + {15, 0x008000}, /* CDCRESET */ + {16, 0x010000}, /* CDCBYP */ + {17, 0x020000}, /* CDCALM */ + {18, 0x040000}, /* CDCDLM */ + {19, 0x080000}, /* AUDIHPF */ + {20, 0x100000} /* AUDOHPF */ + /* Unused */ + /* Unused */ + /* Unused */ + +}; + +/*! + * @brief This structure lists all fields of the ST_DAC hardware register. + */ + /* VVV */ +typedef struct { + REGFIELD STDCSSISEL; /*!< Stereo DAC SSI bus select */ + REGFIELD STDCCLKSEL; /*!< Stereo DAC clock input select */ + REGFIELD STDCSM; /*!< Stereo DAC slave / master select */ + REGFIELD STDCBCLINV; /*!< Stereo DAC bitclock inversion */ + REGFIELD STDCFSINV; /*!< Stereo DAC framesync inversion */ + REGFIELD STDCFS; /*!< Bus protocol selection - 2 bits */ + REGFIELD STDCCLK; /*!< Stereo DAC clock setting - 3 bits */ + REGFIELD STDCFSDLYB; /*!< Stereo DAC framesync delay bar */ + REGFIELD STDCEN; /*!< Stereo DAC enable */ + REGFIELD STDCCLKEN; /*!< Stereo DAC clocking enable */ + REGFIELD STDCRESET; /*!< Stereo DAC filter reset */ + REGFIELD SPDIF; /*!< Stereo DAC SSI SPDIF mode. Mode no longer available. */ + REGFIELD SR; /*!< Stereo DAC sample rate - 4 bits */ +} REGISTER_ST_DAC; + +/*! + * @brief This variable is used to access the ST_DAC hardware register. + * + * This variable defines how to access all of the fields within the + * ST_DAC hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_ST_DAC regST_DAC = { + {0, 0x000001}, /* STDCSSISEL */ + {1, 0x000002}, /* STDCCLKSEL */ + {2, 0x000004}, /* STDCSM */ + {3, 0x000008}, /* STDCBCLINV */ + {4, 0x000010}, /* STDCFSINV */ + {5, 0x000060}, /* STDCFS */ + {7, 0x000380}, /* STDCCLK */ + {10, 0x000400}, /* STDCFSDLYB */ + {11, 0x000800}, /* STDCEN */ + {12, 0x001000}, /* STDCCLKEN */ + {15, 0x008000}, /* STDCRESET */ + {16, 0x010000}, /* SPDIF */ + {17, 0x1E0000} /* SR */ +}; + +/*! + * @brief This structure lists all of the fields in the SSI_NETWORK hardware register. + */ +typedef struct { + REGFIELD CDCTXRXSLOT; /*!< Codec timeslot assignment - 2 bits */ + REGFIELD CDCTXSECSLOT; /*!< Codec secondary transmit timeslot - 2 bits */ + REGFIELD CDCRXSECSLOT; /*!< Codec secondary receive timeslot - 2 bits */ + REGFIELD CDCRXSECGAIN; /*!< Codec secondary receive channel gain setting - 2 bits */ + REGFIELD CDCSUMGAIN; /*!< Codec summed receive signal gain setting */ + REGFIELD CDCFSDLY; /*!< Codec framesync delay */ + REGFIELD STDCSLOTS; /*!< Stereo DAC number of timeslots select - 2 bits */ + REGFIELD STDCRXSLOT; /*!< Stereo DAC timeslot assignment - 2 bits */ + REGFIELD STDCRXSECSLOT; /*!< Stereo DAC secondary receive timeslot - 2 bits */ + REGFIELD STDCRXSECGAIN; /*!< Stereo DAC secondary receive channel gain setting - 2 bits */ + REGFIELD STDCSUMGAIN; /*!< Stereo DAC summed receive signal gain setting */ +} REGISTER_SSI_NETWORK; + +/*! + * @brief This variable is used to access the SSI_NETWORK hardware register. + * + * This variable defines how to access all of the fields within the + * SSI_NETWORK hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_SSI_NETWORK regSSI_NETWORK = { + {2, 0x00000c}, /* CDCTXRXSLOT */ + {4, 0x000030}, /* CDCTXSECSLOT */ + {6, 0x0000c0}, /* CDCRXSECSLOT */ + {8, 0x000300}, /* CDCRXSECGAIN */ + {10, 0x000400}, /* CDCSUMGAIN */ + {11, 0x000800}, /* CDCFSDLY */ + {12, 0x003000}, /* STDCSLOTS */ + {14, 0x00c000}, /* STDCRXSLOT */ + {16, 0x030000}, /* STDCRXSECSLOT */ + {18, 0x0c0000}, /* STDCRXSECGAIN */ + {20, 0x100000} /* STDCSUMGAIN */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_TX hardware register. + * + * + */ +typedef struct { + REGFIELD MC1BEN; /*!< Microphone bias 1 enable */ + REGFIELD MC2BEN; /*!< Microphone bias 2 enable */ + REGFIELD MC2BDETDBNC; /*!< Microphone bias detect debounce setting */ + REGFIELD MC2BDETEN; /*!< Microphone bias 2 detect enable */ + REGFIELD AMC1REN; /*!< Amplifier Amc1R enable */ + REGFIELD AMC1RITOV; /*!< Amplifier Amc1R current to voltage mode enable */ + REGFIELD AMC1LEN; /*!< Amplifier Amc1L enable */ + REGFIELD AMC1LITOV; /*!< Amplifier Amc1L current to voltage mode enable */ + REGFIELD AMC2EN; /*!< Amplifier Amc2 enable */ + REGFIELD AMC2ITOV; /*!< Amplifier Amc2 current to voltage mode enable */ + REGFIELD ATXINEN; /*!< Amplifier Atxin enable */ + REGFIELD ATXOUTEN; /*!< Reserved for output TXOUT enable, currently not used */ + REGFIELD RXINREC; /*!< RXINR/RXINL to voice CODEC ADC routing enable */ + REGFIELD PGATXR; /*!< Transmit gain setting right - 5 bits */ + REGFIELD PGATXL; /*!< Transmit gain setting left - 5 bits */ +} REGISTER_AUDIO_TX; + +/*! + * @brief This variable is used to access the AUDIO_TX hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_TX hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_TX regAUDIO_TX = { + {0, 0x000001}, /* MC1BEN */ + {1, 0x000002}, /* MC2BEN */ + {2, 0x000004}, /* MC2BDETDBNC */ + {3, 0x000008}, /* MC2BDETEN */ + {5, 0x000020}, /* AMC1REN */ + {6, 0x000040}, /* AMC1RITOV */ + {7, 0x000080}, /* AMC1LEN */ + {8, 0x000100}, /* AMC1LITOV */ + {9, 0x000200}, /* AMC2EN */ + {10, 0x000400}, /* AMC2ITOV */ + {11, 0x000800}, /* ATXINEN */ + {12, 0x001000}, /* ATXOUTEN */ + {13, 0x002000}, /* RXINREC */ + {14, 0x07c000}, /* PGATXR */ + {19, 0xf80000} /* PGATXL */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_RX_0 hardware register. + */ +typedef struct { + REGFIELD VAUDIOON; /*!< Forces VAUDIO in active on mode */ + REGFIELD BIASEN; /*!< Audio bias enable */ + REGFIELD BIASSPEED; /*!< Turn on ramp speed of the audio bias */ + REGFIELD ASPEN; /*!< Amplifier Asp enable */ + REGFIELD ASPSEL; /*!< Asp input selector */ + REGFIELD ALSPEN; /*!< Amplifier Alsp enable */ + REGFIELD ALSPREF; /*!< Bias Alsp at common audio reference */ + REGFIELD ALSPSEL; /*!< Alsp input selector */ + REGFIELD LSPLEN; /*!< Output LSPL enable */ + REGFIELD AHSREN; /*!< Amplifier AhsR enable */ + REGFIELD AHSLEN; /*!< Amplifier AhsL enable */ + REGFIELD AHSSEL; /*!< Ahsr and Ahsl input selector */ + REGFIELD HSPGDIS; /*!< Phantom ground disable */ + REGFIELD HSDETEN; /*!< Headset detect enable */ + REGFIELD HSDETAUTOB; /*!< Amplifier state determined by headset detect */ + REGFIELD ARXOUTREN; /*!< Output RXOUTR enable */ + REGFIELD ARXOUTLEN; /*!< Output RXOUTL enable */ + REGFIELD ARXOUTSEL; /*!< Arxout input selector */ + REGFIELD CDCOUTEN; /*!< Output CDCOUT enable */ + REGFIELD HSLDETEN; /*!< Headset left channel detect enable */ + REGFIELD ADDCDC; /*!< Adder channel codec selection */ + REGFIELD ADDSTDC; /*!< Adder channel stereo DAC selection */ + REGFIELD ADDRXIN; /*!< Adder channel line in selection */ +} REGISTER_AUDIO_RX_0; + +/*! + * @brief This variable is used to access the AUDIO_RX_0 hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_RX_0 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_RX_0 regAUDIO_RX_0 = { + {0, 0x000001}, /* VAUDIOON */ + {1, 0x000002}, /* BIASEN */ + {2, 0x000004}, /* BIASSPEED */ + {3, 0x000008}, /* ASPEN */ + {4, 0x000010}, /* ASPSEL */ + {5, 0x000020}, /* ALSPEN */ + {6, 0x000040}, /* ALSPREF */ + {7, 0x000080}, /* ALSPSEL */ + {8, 0x000100}, /* LSPLEN */ + {9, 0x000200}, /* AHSREN */ + {10, 0x000400}, /* AHSLEN */ + {11, 0x000800}, /* AHSSEL */ + {12, 0x001000}, /* HSPGDIS */ + {13, 0x002000}, /* HSDETEN */ + {14, 0x004000}, /* HSDETAUTOB */ + {15, 0x008000}, /* ARXOUTREN */ + {16, 0x010000}, /* ARXOUTLEN */ + {17, 0x020000}, /* ARXOUTSEL */ + {18, 0x040000}, /* CDCOUTEN */ + {19, 0x080000}, /* HSLDETEN */ + {21, 0x200000}, /* ADDCDC */ + {22, 0x400000}, /* ADDSTDC */ + {23, 0x800000} /* ADDRXIN */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_RX_1 hardware register. + */ +typedef struct { + REGFIELD PGARXEN; /*!< Codec receive PGA enable */ + REGFIELD PGARX; /*!< Codec receive gain setting - 4 bits */ + REGFIELD PGASTEN; /*!< Stereo DAC PGA enable */ + REGFIELD PGAST; /*!< Stereo DAC gain setting - 4 bits */ + REGFIELD ARXINEN; /*!< Amplifier Arx enable */ + REGFIELD ARXIN; /*!< Amplifier Arx additional gain setting */ + REGFIELD PGARXIN; /*!< PGArxin gain setting - 4 bits */ + REGFIELD MONO; /*!< Mono adder setting - 2 bits */ + REGFIELD BAL; /*!< Balance control - 3 bits */ + REGFIELD BALLR; /*!< Left / right balance */ +} REGISTER_AUDIO_RX_1; + +/*! + * @brief This variable is used to access the AUDIO_RX_1 hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_RX_1 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_RX_1 regAUDIO_RX_1 = { + {0, 0x000001}, /* PGARXEN */ + {1, 0x00001e}, /* PGARX */ + {5, 0x000020}, /* PGASTEN */ + {6, 0x0003c0}, /* PGAST */ + {10, 0x000400}, /* ARXINEN */ + {11, 0x000800}, /* ARXIN */ + {12, 0x00f000}, /* PGARXIN */ + {16, 0x030000}, /* MONO */ + {18, 0x1c0000}, /* BAL */ + {21, 0x200000} /* BALLR */ +}; + +/*! Define a mask to access the entire hardware register. */ +static const unsigned int REG_FULLMASK = 0xffffff; + +/*! Reset value for the AUD_CODEC register. */ +static const unsigned int RESET_AUD_CODEC = 0x180027; + +/*! Reset value for the ST_DAC register. + * + * Note that we avoid resetting any of the arbitration bits. + */ +static const unsigned int RESET_ST_DAC = 0x0E0004; + +/*! Reset value for the SSI_NETWORK register. */ +static const unsigned int RESET_SSI_NETWORK = 0x013060; + +/*! Reset value for the AUDIO_TX register. + * + * Note that we avoid resetting any of the arbitration bits. + */ +static const unsigned int RESET_AUDIO_TX = 0x420000; + +/*! Reset value for the AUDIO_RX_0 register. */ +static const unsigned int RESET_AUDIO_RX_0 = 0x001000; + +/*! Reset value for the AUDIO_RX_1 register. */ +static const unsigned int RESET_AUDIO_RX_1 = 0x00D35A; + +/*! Reset mask for the SSI network Vcodec part. first 12 bits + * 0 - 11 */ +static const unsigned int REG_SSI_VCODEC_MASK = 0x000fff; + +/*! Reset mask for the SSI network STDAC part. last 12 bits + * 12 - 24 */ +static const unsigned int REG_SSI_STDAC_MASK = 0xfff000; + +/*! Constant NULL value for initializing/reseting the audio handles. */ +static const PMIC_AUDIO_HANDLE AUDIO_HANDLE_NULL = (PMIC_AUDIO_HANDLE) NULL; + +/*! + * @brief This structure maintains the current state of the Stereo DAC. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access + the Stereo DAC. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_DATA_BUS busID; /*!< Data bus used to access + the Stereo DAC. */ + bool protocol_set; + PMIC_AUDIO_BUS_PROTOCOL protocol; /*!< Data bus protocol. */ + PMIC_AUDIO_BUS_MODE masterSlave; /*!< Master/Slave mode + select. */ + PMIC_AUDIO_NUMSLOTS numSlots; /*!< Number of timeslots + used. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification + callback function + pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ + PMIC_AUDIO_CLOCK_IN_SOURCE clockIn; /*!< Stereo DAC clock input + source select. */ + PMIC_AUDIO_STDAC_SAMPLING_RATE samplingRate; /*!< Stereo DAC sampling rate + select. */ + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ clockFreq; /*!< Stereo DAC clock input + frequency. */ + PMIC_AUDIO_CLOCK_INVERT invert; /*!< Stereo DAC clock signal + invert select. */ + PMIC_AUDIO_STDAC_TIMESLOTS timeslot; /*!< Stereo DAC data + timeslots select. */ + PMIC_AUDIO_STDAC_CONFIG config; /*!< Stereo DAC configuration + options. */ +} PMIC_AUDIO_STDAC_STATE; + +/*! + * @brief This variable maintains the current state of the Stereo DAC. + * + * This variable tracks the current state of the Stereo DAC audio hardware + * along with any information that is required by the device driver to + * manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the Stereo DAC. + */ +static PMIC_AUDIO_STDAC_STATE stDAC = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + AUDIO_DATA_BUS_1, /* busID */ + false, + NORMAL_MSB_JUSTIFIED_MODE, /* protocol */ + BUS_MASTER_MODE, /* masterSlave */ + USE_2_TIMESLOTS, /* numSlots */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + CLOCK_IN_CLIA, /* clockIn */ + STDAC_RATE_44_1_KHZ, /* samplingRate */ + STDAC_CLI_13MHZ, /* clockFreq */ + NO_INVERT, /* invert */ + USE_TS0_TS1, /* timeslot */ + (PMIC_AUDIO_STDAC_CONFIG) 0 /* config */ +}; + +/*! + * @brief This structure maintains the current state of the Voice CODEC. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access + the Voice CODEC. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_DATA_BUS busID; /*!< Data bus used to access + the Voice CODEC. */ + bool protocol_set; + PMIC_AUDIO_BUS_PROTOCOL protocol; /*!< Data bus protocol. */ + PMIC_AUDIO_BUS_MODE masterSlave; /*!< Master/Slave mode + select. */ + PMIC_AUDIO_NUMSLOTS numSlots; /*!< Number of timeslots + used. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification + callback function + pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification + mask. */ + PMIC_AUDIO_CLOCK_IN_SOURCE clockIn; /*!< Voice CODEC clock input + source select. */ + PMIC_AUDIO_VCODEC_SAMPLING_RATE samplingRate; /*!< Voice CODEC sampling + rate select. */ + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ clockFreq; /*!< Voice CODEC clock input + frequency. */ + PMIC_AUDIO_CLOCK_INVERT invert; /*!< Voice CODEC clock + signal invert select. */ + PMIC_AUDIO_VCODEC_TIMESLOT timeslot; /*!< Voice CODEC data + timeslot select. */ + PMIC_AUDIO_VCODEC_TIMESLOT secondaryTXtimeslot; + + PMIC_AUDIO_VCODEC_CONFIG config; /*!< Voice CODEC + configuration + options. */ + PMIC_MICROPHONE_STATE leftChannelMic; /*!< Left channel + microphone + configuration. */ + PMIC_MICROPHONE_STATE rightChannelMic; /*!< Right channel + microphone + configuration. */ +} PMIC_AUDIO_VCODEC_STATE; + +/*! + * @brief This variable maintains the current state of the Voice CODEC. + * + * This variable tracks the current state of the Voice CODEC audio hardware + * along with any information that is required by the device driver to + * manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the Voice CODEC. + */ +static PMIC_AUDIO_VCODEC_STATE vCodec = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + AUDIO_DATA_BUS_2, /* busID */ + false, + NETWORK_MODE, /* protocol */ + BUS_SLAVE_MODE, /* masterSlave */ + USE_4_TIMESLOTS, /* numSlots */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + CLOCK_IN_CLIB, /* clockIn */ + VCODEC_RATE_8_KHZ, /* samplingRate */ + VCODEC_CLI_13MHZ, /* clockFreq */ + NO_INVERT, /* invert */ + USE_TS0, /* timeslot pri */ + USE_TS2, /* timeslot sec TX */ + INPUT_HIGHPASS_FILTER | OUTPUT_HIGHPASS_FILTER, /* config */ + /* leftChannelMic */ + {NO_MIC, /* mic */ + MICROPHONE_OFF, /* micOnOff */ + AMP_OFF, /* ampMode */ + MIC_GAIN_0DB /* gain */ + }, + /* rightChannelMic */ + {NO_MIC, /* mic */ + MICROPHONE_OFF, /* micOnOff */ + AMP_OFF, /* ampMode */ + MIC_GAIN_0DB /* gain */ + } +}; + +/*! + * @brief This maintains the current state of the External Stereo Input. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access the + External Stereo Inputs. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification callback + function pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ + PMIC_AUDIO_STEREO_IN_GAIN inputGain; /*!< External Stereo Input + amplifier gain level. */ +} PMIC_AUDIO_EXT_STEREO_IN_STATE; + +/*! + * @brief This maintains the current state of the External Stereo Input. + * + * This variable tracks the current state of the External Stereo Input audio + * hardware along with any information that is required by the device driver + * to manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the External + * Stereo Input. + */ +static PMIC_AUDIO_EXT_STEREO_IN_STATE extStereoIn = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + STEREO_IN_GAIN_0DB /* inputGain */ +}; + +/*! + * @brief This maintains the current state of the callback & Eventmask. + */ +typedef struct { + PMIC_AUDIO_CALLBACK callback; /*!< Event notification callback + function pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ +} PMIC_AUDIO_EVENT_STATE; + +static PMIC_AUDIO_EVENT_STATE event_state = { + (PMIC_AUDIO_CALLBACK) NULL, /*Callback */ + (PMIC_AUDIO_EVENTS) NULL, /* EventMask */ + +}; + +/*! + * @brief This maintains the current state of the Audio Output Section. + */ +typedef struct { + PMIC_AUDIO_OUTPUT_PORT outputPort; /*!< Current audio + output port. */ + PMIC_AUDIO_OUTPUT_PGA_GAIN vCodecoutputPGAGain; /*!< Output PGA gain + level codec */ + PMIC_AUDIO_OUTPUT_PGA_GAIN stDacoutputPGAGain; /*!< Output PGA gain + level stDAC */ + PMIC_AUDIO_OUTPUT_PGA_GAIN extStereooutputPGAGain; /*!< Output PGA gain + level stereo ext */ + PMIC_AUDIO_OUTPUT_BALANCE_GAIN balanceLeftGain; /*!< Left channel + balance gain + level. */ + PMIC_AUDIO_OUTPUT_BALANCE_GAIN balanceRightGain; /*!< Right channel + balance gain + level. */ + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN monoAdderGain; /*!< Mono adder gain + level. */ + PMIC_AUDIO_OUTPUT_CONFIG config; /*!< Audio output + section config + options. */ + PMIC_AUDIO_VCODEC_OUTPUT_PATH vCodecOut; + +} PMIC_AUDIO_AUDIO_OUTPUT_STATE; + +/*! + * @brief This variable maintains the current state of the Audio Output Section. + * + * This variable tracks the current state of the Audio Output Section. + * + * The initial values represent the reset/power on state of the Audio + * Output Section. + */ +static PMIC_AUDIO_AUDIO_OUTPUT_STATE audioOutput = { + (PMIC_AUDIO_OUTPUT_PORT) NULL, /* outputPort */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + BAL_GAIN_0DB, /* balanceLeftGain */ + BAL_GAIN_0DB, /* balanceRightGain */ + MONOADD_GAIN_0DB, /* monoAdderGain */ + (PMIC_AUDIO_OUTPUT_CONFIG) 0, /* config */ + VCODEC_DIRECT_OUT +}; + +/*! The current headset status. */ +static HEADSET_STATUS headsetState = NO_HEADSET; + +/* Removed PTT variable */ +/*! Define a 1 ms wait interval that is needed to ensure that certain + * hardware operations are successfully completed. + */ +static const unsigned long delay_1ms = (HZ / 1000); + +/*! + * @brief This spinlock is used to provide mutual exclusion. + * + * Create a spinlock that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the audio data structures are consistent at all times + * when possibly accessed by multiple threads of execution (for example, + * while simultaneously handling a user request and an interrupt event). + * + * We need to use a spinlock whenever we do need to provide mutual + * exclusion while possibly executing in a hardware interrupt context. + * Spinlocks should be held for the minimum time that is necessary + * because hardware interrupts are disabled while a spinlock is held. + * + */ +static DEFINE_SPINLOCK(lock); + +/*! + * @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the audio data structures are consistent at all times + * when possibly accessed by multiple threads of execution. + * + * Note that we use a mutex instead of the spinlock whenever disabling + * interrupts while in the critical section is not required. This helps + * to minimize kernel interrupt handling latency. + */ +static DECLARE_MUTEX(mutex); + +/*! + * @brief Global variable to track currently active interrupt events. + * + * This global variable is used to keep track of all of the currently + * active interrupt events for the audio driver. Note that access to this + * variable may occur while within an interrupt context and, therefore, + * must be guarded by using a spinlock. + */ +/* static PMIC_CORE_EVENT eventID = 0; */ + +/* Prototypes for all static audio driver functions. */ +/* +static PMIC_STATUS pmic_audio_mic_boost_enable(void); +static PMIC_STATUS pmic_audio_mic_boost_disable(void);*/ +static PMIC_STATUS pmic_audio_close_handle(const PMIC_AUDIO_HANDLE handle); +static PMIC_STATUS pmic_audio_reset_device(const PMIC_AUDIO_HANDLE handle); + +static PMIC_STATUS pmic_audio_deregister(void *callback, + PMIC_AUDIO_EVENTS * const eventMask); + +/************************************************************************* + * Audio device access APIs. + ************************************************************************* + */ + +/*! + * @name General Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Audio + * hardware. + */ +/*@{*/ + +PMIC_STATUS pmic_audio_set_autodetect(int val) +{ + PMIC_STATUS status; + unsigned int reg_mask = 0, reg_write = 0; + reg_mask = SET_BITS(regAUDIO_RX_0, VAUDIOON, 1); + status = pmic_write_reg(REG_AUDIO_RX_0, reg_mask, reg_mask); + if (status != PMIC_SUCCESS) + return status; + reg_mask = 0; + if (val == 1) { + reg_write = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + } else { + reg_write = 0; + } + reg_mask = + SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | SET_BITS(regAUDIO_RX_0, + HSDETAUTOB, 1); + status = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + return status; +} + +/*! + * @brief Request exclusive access to the PMIC Audio hardware. + * + * Attempt to open and gain exclusive access to a key PMIC audio hardware + * component (e.g., the Stereo DAC or the Voice CODEC). Depending upon the + * type of audio operation that is desired and the nature of the audio data + * stream, the Stereo DAC and/or the Voice CODEC will be a required hardware + * component and needs to be acquired by calling this function. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls to complete + * the configuration of either the Stereo DAC or the Voice CODEC and along + * with any other associated audio hardware components that will be needed. + * + * The same handle must also be used in the close call when use of the PMIC + * audio hardware is no longer required. + * + * The open request will fail if the requested audio hardware component has + * already been acquired by a previous open call but not yet closed. + * + * @param handle Device handle to be used for subsequent PMIC + * audio API calls. + * @param device The required PMIC audio hardware component. + * + * @retval PMIC_SUCCESS If the open request was successful + * @retval PMIC_PARAMETER_ERROR If the handle argument is NULL. + * @retval PMIC_ERROR If the audio hardware component is + * unavailable. + */ +PMIC_STATUS pmic_audio_open(PMIC_AUDIO_HANDLE * const handle, + const PMIC_AUDIO_SOURCE device) +{ + PMIC_STATUS rc = PMIC_ERROR; + + if (handle == (PMIC_AUDIO_HANDLE *) NULL) { + /* Do not dereference a NULL pointer. */ + return PMIC_PARAMETER_ERROR; + } + + /* We only need to acquire a mutex here because the interrupt handler + * never modifies the device handle or device handle state. Therefore, + * we don't need to worry about conflicts with the interrupt handler + * or the need to execute in an interrupt context. + * + * But we do need a critical section here to avoid problems in case + * multiple calls to pmic_audio_open() are made since we can only allow + * one of them to succeed. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Check the current device handle state and acquire the handle if + * it is available. + */ + + if ((device == STEREO_DAC) && (stDAC.handleState == HANDLE_FREE)) { + stDAC.handle = (PMIC_AUDIO_HANDLE) (&stDAC); + stDAC.handleState = HANDLE_IN_USE; + *handle = stDAC.handle; + rc = PMIC_SUCCESS; + } else if ((device == VOICE_CODEC) + && (vCodec.handleState == HANDLE_FREE)) { + vCodec.handle = (PMIC_AUDIO_HANDLE) (&vCodec); + vCodec.handleState = HANDLE_IN_USE; + *handle = vCodec.handle; + rc = PMIC_SUCCESS; + } else if ((device == EXTERNAL_STEREO_IN) && + (extStereoIn.handleState == HANDLE_FREE)) { + extStereoIn.handle = (PMIC_AUDIO_HANDLE) (&extStereoIn); + extStereoIn.handleState = HANDLE_IN_USE; + *handle = extStereoIn.handle; + rc = PMIC_SUCCESS; + } else { + *handle = AUDIO_HANDLE_NULL; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Terminate further access to the PMIC audio hardware. + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * Note that we will shutdown/reset the Voice CODEC or Stereo DAC as well as + * any associated audio input/output components that are no longer required. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_close(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We need a critical section here to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* We can now call pmic_audio_close_handle() to actually do the work. */ + rc = pmic_audio_close_handle(handle); + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Configure the data bus protocol to be used. + * + * Provide the parameters needed to properly configure the audio data bus + * protocol so that data can be read/written to either the Stereo DAC or + * the Voice CODEC. + * + * @param handle Device handle from pmic_audio_open() call. + * @param busID Select data bus to be used. + * @param protocol Select the data bus protocol. + * @param masterSlave Select the data bus timing mode. + * @param numSlots Define the number of timeslots (only if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful configured. + * @retval PMIC_PARAMETER_ERROR If the handle or the protocol parameters + * are invalid. + */ +PMIC_STATUS pmic_audio_set_protocol(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_DATA_BUS busID, + const PMIC_AUDIO_BUS_PROTOCOL protocol, + const PMIC_AUDIO_BUS_MODE masterSlave, + const PMIC_AUDIO_NUMSLOTS numSlots) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int ST_DAC_MASK = SET_BITS(regST_DAC, STDCSSISEL, 1) | + SET_BITS(regST_DAC, STDCFS, 3) | SET_BITS(regST_DAC, STDCSM, 1); + + unsigned int reg_mask; + /*unsigned int VCODEC_MASK = SET_BITS(regAUD_CODEC, CDCSSISEL, 1) | + SET_BITS(regAUD_CODEC, CDCFS, 3) | SET_BITS(regAUD_CODEC, CDCSM, 1); */ + + unsigned int SSI_NW_MASK = SET_BITS(regSSI_NETWORK, STDCSLOTS, 1); + unsigned int reg_value = 0; + unsigned int ssi_nw_value = 0; + + /* Enter a critical section so that we can ensure only one + * state change request is completed at a time. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else { + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + if ((stDAC.handleState == HANDLE_IN_USE) && + (stDAC.busID == busID) && (stDAC.protocol_set)) { + pr_debug("The requested bus already in USE\n"); + rc = PMIC_PARAMETER_ERROR; + } else if ((masterSlave == BUS_MASTER_MODE) + && (numSlots != USE_4_TIMESLOTS)) { + pr_debug + ("mc13783 supports only 4 slots in Master mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else if ((masterSlave == BUS_SLAVE_MODE) + && (numSlots != USE_4_TIMESLOTS)) { + pr_debug + ("Driver currently supports only 4 slots in Slave mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else if (!((protocol == NETWORK_MODE) || + (protocol == I2S_MODE))) { + pr_debug + ("mc13783 Voice codec works only in Network and I2S modes\n"); + rc = PMIC_NOT_SUPPORTED; + } else { + pr_debug + ("Proceeding to configure Voice Codec\n"); + if (busID == AUDIO_DATA_BUS_1) { + reg_value = + SET_BITS(regAUD_CODEC, CDCSSISEL, + 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCSSISEL, + 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCSSISEL, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + if (masterSlave == BUS_MASTER_MODE) { + reg_value = + SET_BITS(regAUD_CODEC, CDCSM, 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCSM, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCSM, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + if (protocol == NETWORK_MODE) { + reg_value = + SET_BITS(regAUD_CODEC, CDCFS, 1); + } else { /* protocol == I2S, other options have been already eliminated */ + reg_value = + SET_BITS(regAUD_CODEC, CDCFS, 2); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCFS, 3); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + ssi_nw_value = + SET_BITS(regSSI_NETWORK, CDCFSDLY, 1); + /*if (pmic_write_reg + (REG_AUDIO_CODEC, reg_value, + VCODEC_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { */ + vCodec.busID = busID; + vCodec.protocol = protocol; + vCodec.masterSlave = masterSlave; + vCodec.numSlots = numSlots; + vCodec.protocol_set = true; + + pr_debug + ("mc13783 Voice codec successfully configured\n"); + rc = PMIC_SUCCESS; + } + + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + if ((vCodec.handleState == HANDLE_IN_USE) && + (vCodec.busID == busID) && (vCodec.protocol_set)) { + pr_debug("The requested bus already in USE\n"); + rc = PMIC_PARAMETER_ERROR; + } else if (((protocol == NORMAL_MSB_JUSTIFIED_MODE) || + (protocol == I2S_MODE)) + && (numSlots != USE_2_TIMESLOTS)) { + pr_debug + ("STDAC uses only 2 slots in Normal and I2S modes\n"); + rc = PMIC_PARAMETER_ERROR; + } else if ((protocol == NETWORK_MODE) && + !((numSlots == USE_2_TIMESLOTS) || + (numSlots == USE_4_TIMESLOTS) || + (numSlots == USE_8_TIMESLOTS))) { + pr_debug + ("STDAC uses only 2,4 or 8 slots in Network mode\n"); + rc = PMIC_PARAMETER_ERROR; + } else if (protocol == SPD_IF_MODE) { + pr_debug + ("STDAC driver currently does not support SPD IF mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else { + pr_debug + ("Proceeding to configure Stereo DAC\n"); + if (busID == AUDIO_DATA_BUS_1) { + reg_value = + SET_BITS(regST_DAC, STDCSSISEL, 0); + } else { + reg_value = + SET_BITS(regST_DAC, STDCSSISEL, 1); + } + if (masterSlave == BUS_MASTER_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCSM, 0); + } else { + reg_value |= + SET_BITS(regST_DAC, STDCSM, 1); + } + if (protocol == NETWORK_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCFS, 1); + } else if (protocol == + NORMAL_MSB_JUSTIFIED_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCFS, 0); + } else { /* I2S mode as the other option has already been eliminated */ + reg_value |= + SET_BITS(regST_DAC, STDCFS, 2); + } + + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, + reg_value, ST_DAC_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + if (numSlots == USE_2_TIMESLOTS) { + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 3); + } else if (numSlots == USE_4_TIMESLOTS) { + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 2); + } else { /* Use 8 timeslots - L , R and 6 other */ + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 1); + } + if (pmic_write_reg + (REG_AUDIO_SSI_NETWORK, + reg_value, + SSI_NW_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + stDAC.busID = busID; + stDAC.protocol = protocol; + stDAC.protocol_set = true; + stDAC.masterSlave = masterSlave; + stDAC.numSlots = numSlots; + pr_debug + ("mc13783 Stereo DAC successfully configured\n"); + rc = PMIC_SUCCESS; + } + } + + } + } else { + rc = PMIC_PARAMETER_ERROR; + /* Handle can only be Voice Codec or Stereo DAC */ + pr_debug("Handles only STDAC and VCODEC\n"); + } + + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Retrieve the current data bus protocol configuration. + * + * Retrieve the parameters that define the current audio data bus protocol. + * + * @param handle Device handle from pmic_audio_open() call. + * @param busID The data bus being used. + * @param protocol The data bus protocol being used. + * @param masterSlave The data bus timing mode being used. + * @param numSlots The number of timeslots being used (if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_protocol(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_DATA_BUS * const busID, + PMIC_AUDIO_BUS_PROTOCOL * const protocol, + PMIC_AUDIO_BUS_MODE * const masterSlave, + PMIC_AUDIO_NUMSLOTS * const numSlots) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + if ((busID != (PMIC_AUDIO_DATA_BUS *) NULL) && + (protocol != (PMIC_AUDIO_BUS_PROTOCOL *) NULL) && + (masterSlave != (PMIC_AUDIO_BUS_MODE *) NULL) && + (numSlots != (PMIC_AUDIO_NUMSLOTS *) NULL)) { + /* Enter a critical section so that we return a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + *busID = stDAC.busID; + *protocol = stDAC.protocol; + *masterSlave = stDAC.masterSlave; + *numSlots = stDAC.numSlots; + rc = PMIC_SUCCESS; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + *busID = vCodec.busID; + *protocol = vCodec.protocol; + *masterSlave = vCodec.masterSlave; + *numSlots = vCodec.numSlots; + rc = PMIC_SUCCESS; + } + + /* Exit critical section. */ + up(&mutex); + } + + return rc; +} + +/*! + * @brief Enable the Stereo DAC or the Voice CODEC. + * + * Explicitly enable the Stereo DAC or the Voice CODEC to begin audio + * playback or recording as required. This should only be done after + * successfully configuring all of the associated audio components (e.g., + * microphones, amplifiers, etc.). + * + * Note that the timed delays used in this function are necessary to + * ensure reliable operation of the Voice CODEC and Stereo DAC. The + * Stereo DAC seems to be particularly sensitive and it has been observed + * to fail to generate the required master mode clock signals if it is + * not allowed enough time to initialize properly. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful enabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be enabled. + */ +PMIC_STATUS pmic_audio_enable(const PMIC_AUDIO_HANDLE handle) +{ + const unsigned int AUDIO_BIAS_ENABLE = SET_BITS(regAUDIO_RX_0, + VAUDIOON, 1); + const unsigned int STDAC_ENABLE = SET_BITS(regST_DAC, STDCEN, 1); + const unsigned int VCODEC_ENABLE = SET_BITS(regAUD_CODEC, CDCEN, 1); + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + pmic_write_reg(REG_AUDIO_RX_0, AUDIO_BIAS_ENABLE, + AUDIO_BIAS_ENABLE); + reg_mask = + SET_BITS(regAUDIO_RX_0, HSDETEN, + 1) | SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write = + SET_BITS(regAUDIO_RX_0, HSDETEN, + 1) | SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + if (rc == PMIC_SUCCESS) + pr_debug("pmic_audio_enable\n"); + /* We can enable the Stereo DAC. */ + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + STDAC_ENABLE, STDAC_ENABLE); + /*pmic_read_reg(REG_AUDIO_STEREO_DAC, ®_value); */ + if (rc != PMIC_SUCCESS) { + pr_debug("Failed to enable the Stereo DAC\n"); + rc = PMIC_ERROR; + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + /* Must first set the audio bias bit to power up the audio circuits. */ + pmic_write_reg(REG_AUDIO_RX_0, AUDIO_BIAS_ENABLE, + AUDIO_BIAS_ENABLE); + reg_mask = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + /* Then we can enable the Voice CODEC. */ + rc = pmic_write_reg(REG_AUDIO_CODEC, VCODEC_ENABLE, + VCODEC_ENABLE); + + /* pmic_read_reg(REG_AUDIO_CODEC, ®_value); */ + if (rc != PMIC_SUCCESS) { + pr_debug("Failed to enable the Voice codec\n"); + rc = PMIC_ERROR; + } + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Disable the Stereo DAC or the Voice CODEC. + * + * Explicitly disable the Stereo DAC or the Voice CODEC to end audio + * playback or recording as required. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful disabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be disabled. + */ +PMIC_STATUS pmic_audio_disable(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int STDAC_DISABLE = SET_BITS(regST_DAC, STDCEN, 1); + const unsigned int VCODEC_DISABLE = SET_BITS(regAUD_CODEC, CDCEN, 1); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, 0, STDAC_DISABLE); + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, 0, VCODEC_DISABLE); + } + if (rc == PMIC_SUCCESS) { + pr_debug("Disabled successfully\n"); + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their initial power on state. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + rc = pmic_audio_reset_device(handle); + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Reset all audio hardware control registers to their power on state. + * + * This resets all of the audio hardware control registers back to their + * power on states. Use this function with care since it also invalidates + * (i.e., automatically closes) all currently opened device handles. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset_all(void) +{ + PMIC_STATUS rc = PMIC_SUCCESS; + unsigned int audio_ssi_reset = 0; + unsigned int audio_rx1_reset = 0; + /* We need a critical section here to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* First close all opened device handles, also deregisters callbacks. */ + pmic_audio_close_handle(stDAC.handle); + pmic_audio_close_handle(vCodec.handle); + pmic_audio_close_handle(extStereoIn.handle); + + if (pmic_write_reg(REG_AUDIO_RX_1, RESET_AUDIO_RX_1, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + audio_rx1_reset = 1; + } + if (pmic_write_reg(REG_AUDIO_SSI_NETWORK, RESET_SSI_NETWORK, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + audio_ssi_reset = 1; + } + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, RESET_ST_DAC, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + if (audio_ssi_reset) { + /* better to check if SSI is also reset as some fields are represennted in SSI reg */ + stDAC.busID = AUDIO_DATA_BUS_1; + stDAC.protocol = NORMAL_MSB_JUSTIFIED_MODE; + stDAC.masterSlave = BUS_MASTER_MODE; + stDAC.protocol_set = false; + stDAC.numSlots = USE_2_TIMESLOTS; + stDAC.clockIn = CLOCK_IN_CLIA; + stDAC.samplingRate = STDAC_RATE_44_1_KHZ; + stDAC.clockFreq = STDAC_CLI_13MHZ; + stDAC.invert = NO_INVERT; + stDAC.timeslot = USE_TS0_TS1; + stDAC.config = (PMIC_AUDIO_STDAC_CONFIG) 0; + } + } + + if (pmic_write_reg(REG_AUDIO_CODEC, RESET_AUD_CODEC, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + if (audio_ssi_reset) { + vCodec.busID = AUDIO_DATA_BUS_2; + vCodec.protocol = NETWORK_MODE; + vCodec.masterSlave = BUS_SLAVE_MODE; + vCodec.protocol_set = false; + vCodec.numSlots = USE_4_TIMESLOTS; + vCodec.clockIn = CLOCK_IN_CLIB; + vCodec.samplingRate = VCODEC_RATE_8_KHZ; + vCodec.clockFreq = VCODEC_CLI_13MHZ; + vCodec.invert = NO_INVERT; + vCodec.timeslot = USE_TS0; + vCodec.config = + INPUT_HIGHPASS_FILTER | OUTPUT_HIGHPASS_FILTER; + } + } + + if (pmic_write_reg(REG_AUDIO_RX_0, RESET_AUDIO_RX_0, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. */ + audioOutput.outputPort = (PMIC_AUDIO_OUTPUT_PORT) NULL; + audioOutput.vCodecoutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.stDacoutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.extStereooutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.balanceLeftGain = BAL_GAIN_0DB; + audioOutput.balanceRightGain = BAL_GAIN_0DB; + audioOutput.monoAdderGain = MONOADD_GAIN_0DB; + audioOutput.config = (PMIC_AUDIO_OUTPUT_CONFIG) 0; + audioOutput.vCodecOut = VCODEC_DIRECT_OUT; + } + + if (pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * reset the vCodec fields since all of the input/recording + * devices are only connected to the Voice CODEC and are managed + * as part of the Voice CODEC state. + */ + if (audio_rx1_reset) { + vCodec.leftChannelMic.mic = NO_MIC; + vCodec.leftChannelMic.micOnOff = MICROPHONE_OFF; + vCodec.leftChannelMic.ampMode = CURRENT_TO_VOLTAGE; + vCodec.leftChannelMic.gain = MIC_GAIN_0DB; + vCodec.rightChannelMic.mic = NO_MIC; + vCodec.rightChannelMic.micOnOff = MICROPHONE_OFF; + vCodec.rightChannelMic.ampMode = AMP_OFF; + vCodec.rightChannelMic.gain = MIC_GAIN_0DB; + } + } + /* Finally, also reset any global state variables. */ + headsetState = NO_HEADSET; + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Set the Audio callback function. + * + * Register a callback function that will be used to signal PMIC audio + * events. For example, the OSS audio driver should register a callback + * function in order to be notified of headset connect/disconnect events. + * + * @param func A pointer to the callback function. + * @param eventMask A mask selecting events to be notified. + * @param hs_state To know the headset state. + * + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * registered. + * @retval PMIC_PARAMETER_ERROR If the handle or the eventMask is invalid. + */ +PMIC_STATUS pmic_audio_set_callback(void *func, + const PMIC_AUDIO_EVENTS eventMask, + PMIC_HS_STATE *hs_state) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + pmic_event_callback_t eventNotify; + + /* We need to start a critical section here to ensure a consistent state + * in case simultaneous calls to pmic_audio_set_callback() are made. In + * that case, we must serialize the calls to ensure that the "callback" + * and "eventMask" state variables are always consistent. + * + * Note that we don't actually need to acquire the spinlock until later + * when we are finally ready to update the "callback" and "eventMask" + * state variables which are shared with the interrupt handler. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + rc = PMIC_ERROR; + /* Register for PMIC events from the core protocol driver. */ + if (eventMask & MICROPHONE_DETECTED) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_MC2BI); + rc = pmic_event_subscribe(EVENT_MC2BI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSDETI " + "failed\n", __FILE__); + goto End; + } + } + + if (eventMask & HEADSET_DETECTED) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + rc = pmic_event_subscribe(EVENT_HSDETI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSDETI " + "failed\n", __FILE__); + goto Cleanup_HDT; + } + + } + if (eventMask & HEADSET_STEREO) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + rc = pmic_event_subscribe(EVENT_HSLI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSLI " + "failed\n", __FILE__); + goto Cleanup_HST; + } + } + if (eventMask & HEADSET_THERMAL_SHUTDOWN) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + rc = pmic_event_subscribe(EVENT_ALSPTHI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_ALSPTHI " + "failed\n", __FILE__); + goto Cleanup_TSD; + } + pr_debug("Registered for EVENT_ALSPTHI\n"); + } + if (eventMask & HEADSET_SHORT_CIRCUIT) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + rc = pmic_event_subscribe(EVENT_AHSSHORTI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_AHSSHORTI " + "failed\n", __FILE__); + goto Cleanup_HShort; + } + pr_debug("Registered for EVENT_AHSSHORTI\n"); + } + + /* We also need the spinlock here to avoid possible problems + * with the interrupt handler when we update the + * "callback" and "eventMask" state variables. + */ + spin_lock_irqsave(&lock, flags); + + /* Successfully registered for all events. */ + event_state.callback = func; + event_state.eventMask = eventMask; + + /* The spinlock is no longer needed now that we've finished + * updating the "callback" and "eventMask" state variables. + */ + spin_unlock_irqrestore(&lock, flags); + + goto End; + + /* This section unregisters any already registered events if we should + * encounter an error partway through the registration process. Note + * that we don't check the return status here since it is already set + * to PMIC_ERROR before we get here. + */ + Cleanup_HShort: + + if (eventMask & HEADSET_SHORT_CIRCUIT) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + pmic_event_unsubscribe(EVENT_AHSSHORTI, eventNotify); + } + + Cleanup_TSD: + + if (eventMask & HEADSET_THERMAL_SHUTDOWN) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + pmic_event_unsubscribe(EVENT_ALSPTHI, eventNotify); + } + + Cleanup_HST: + + if (eventMask & HEADSET_STEREO) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + pmic_event_unsubscribe(EVENT_HSLI, eventNotify); + } + + Cleanup_HDT: + + if (eventMask & HEADSET_DETECTED) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + pmic_event_unsubscribe(EVENT_HSDETI, eventNotify); + } + + End: + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Deregisters the existing audio callback function. + * + * Deregister the callback function that was previously registered by calling + * pmic_audio_set_callback(). + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * deregistered. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_clear_callback(void) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We need a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (event_state.callback != (PMIC_AUDIO_CALLBACK) NULL) { + rc = pmic_audio_deregister(&(event_state.callback), + &(event_state.eventMask)); + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Get the current audio callback function settings. + * + * Get the current callback function and event mask. + * + * @param func The current callback function. + * @param eventMask The current event selection mask. + * + * @retval PMIC_SUCCESS If the callback information was + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_callback(PMIC_AUDIO_CALLBACK * const func, + PMIC_AUDIO_EVENTS * const eventMask) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We only need to acquire the mutex here because we will not be updating + * anything that may affect the interrupt handler. We just need to ensure + * that the callback fields are not changed while we are in the critical + * section by calling either pmic_audio_set_callback() or + * pmic_audio_clear_callback(). + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((func != (PMIC_AUDIO_CALLBACK *) NULL) && + (eventMask != (PMIC_AUDIO_EVENTS *) NULL)) { + + *func = event_state.callback; + *eventMask = event_state.eventMask; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Enable the anti-pop circuitry to avoid extra noise when inserting + * or removing a external device (e.g., a headset). + * + * Enable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. A slow ramp speed may be needed to avoid extra noise. + * + * @param rampSpeed The desired anti-pop circuitry ramp speed. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * enabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * enabled. + */ +PMIC_STATUS pmic_audio_antipop_enable(const PMIC_AUDIO_ANTI_POP_RAMP_SPEED + rampSpeed) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, BIASEN, 1) | + SET_BITS(regAUDIO_RX_0, BIASSPEED, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* + * Antipop is enabled by enabling the BIAS (BIASEN) and setting the + * BIASSPEED . + * BIASEN is just to make sure that BIAS is enabled + */ + reg_value = SET_BITS(regAUDIO_RX_0, BIASEN, 1) + | SET_BITS(regAUDIO_RX_0, BIASSPEED, 0) | SET_BITS(regAUDIO_RX_0, + HSLDETEN, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_value, reg_mask); + return rc; +} + +/*! + * @brief Disable the anti-pop circuitry. + * + * Disable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * disabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * disabled. + */ +PMIC_STATUS pmic_audio_antipop_disable(void) +{ + PMIC_STATUS rc = PMIC_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, BIASSPEED, 1) | + SET_BITS(regAUDIO_RX_0, BIASEN, 1); + const unsigned int reg_write = SET_BITS(regAUDIO_RX_0, BIASSPEED, 1) | + SET_BITS(regAUDIO_RX_0, BIASEN, 0); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* + * Antipop is disabled by setting BIASSPEED = 0. BIASEN bit remains set + * as only antipop needs to be disabled + */ + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + return rc; +} + +/*! + * @brief Performs a reset of the Voice CODEC/Stereo DAC digital filter. + * + * The digital filter should be reset whenever the clock or sampling rate + * configuration has been changed. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the digital filter was successfully + * reset. + * @retval PMIC_ERROR If the digital filter could not be reset. + */ +PMIC_STATUS pmic_audio_digital_filter_reset(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regST_DAC, STDCRESET, 1); + if (pmic_write_reg(REG_AUDIO_STEREO_DAC, reg_mask, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC filter reset\n"); + } + + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUD_CODEC, CDCRESET, 1); + if (pmic_write_reg(REG_AUDIO_CODEC, reg_mask, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("CODEC filter reset\n"); + } + } + return rc; +} + +/*! + * @brief Get the most recent PTT button voltage reading. + * + * This feature is not supported by mc13783 + * @param level PTT button level. + * + * @retval PMIC_SUCCESS If the most recent PTT button voltage was + * returned. + * @retval PMIC_PARAMETER_ERROR If a NULL pointer argument was given. + */ +PMIC_STATUS pmic_audio_get_ptt_button_level(unsigned int *const level) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +#ifdef DEBUG_AUDIO + +/*! + * @brief Provide a hexadecimal dump of all PMIC audio registers (DEBUG only) + * + * This function is intended strictly for debugging purposes only and will + * print the current values of the following PMIC registers: + * + * - AUD_CODEC + * - ST_DAC + * - AUDIO_RX_0 + * - AUDIO_RX_1 + * - AUDIO_TX + * - AUDIO_SSI_NW + * + * The register fields will not be decoded. + * + * Note that we don't dump any of the arbitration bits because we cannot + * access the true arbitration bit settings when reading the registers + * from the secondary SPI bus. + * + * Also note that we must not call this function with interrupts disabled, + * for example, while holding a spinlock, because calls to pmic_read_reg() + * eventually end up in the SPI driver which will want to perform a + * schedule() operation. If schedule() is called with interrupts disabled, + * then you will see messages like the following: + * + * BUG: scheduling while atomic: ... + * + */ +void pmic_audio_dump_registers(void) +{ + unsigned int reg_value = 0; + + /* Dump the AUD_CODEC (Voice CODEC) register. */ + if (pmic_read_reg(REG_AUDIO_CODEC, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio Codec = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio codec\n"); + } + + /* Dump the ST DAC (Stereo DAC) register. */ + if (pmic_read_reg + (REG_AUDIO_STEREO_DAC, ®_value, REG_FULLMASK) == PMIC_SUCCESS) { + pr_debug("Stereo DAC = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read Stereo DAC\n"); + } + + /* Dump the SSI NW register. */ + if (pmic_read_reg + (REG_AUDIO_SSI_NETWORK, ®_value, REG_FULLMASK) == PMIC_SUCCESS) { + pr_debug("SSI Network = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read SSI network\n"); + } + + /* Dump the Audio RX 0 register. */ + if (pmic_read_reg(REG_AUDIO_RX_0, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio RX 0 = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio RX 0\n"); + } + + /* Dump the Audio RX 1 register. */ + if (pmic_read_reg(REG_AUDIO_RX_1, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio RX 1 = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio RX 1\n"); + } + /* Dump the Audio TX register. */ + if (pmic_read_reg(REG_AUDIO_TX, ®_value, REG_FULLMASK) == + PMIC_SUCCESS) { + pr_debug("Audio Tx = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio TX\n"); + } + +} + +#endif /* DEBUG_AUDIO */ + +/*@}*/ + +/************************************************************************* + * General Voice CODEC configuration. + ************************************************************************* + */ + +/*! + * @name General Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice + * CODEC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Voice CODEC clock source and operating characteristics. + * + * Define the Voice CODEC clock source and operating characteristics. This + * must be done before the Voice CODEC is enabled. + * + * + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn Select the clock signal source. + * @param clockFreq Select the clock signal frequency. + * @param samplingRate Select the audio data sampling rate. + * @param invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE + clockIn, + const PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_VCODEC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Validate all of the calling parameters. */ + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + if ((clockIn != CLOCK_IN_CLIA) && (clockIn != CLOCK_IN_CLIB)) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((clockFreq >= VCODEC_CLI_13MHZ) + && (clockFreq <= VCODEC_CLI_33_6MHZ))) { + rc = PMIC_PARAMETER_ERROR; + } else if ((samplingRate != VCODEC_RATE_8_KHZ) + && (samplingRate != VCODEC_RATE_16_KHZ)) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((invert >= NO_INVERT) + && (invert <= INVERT_FRAMESYNC))) { + rc = PMIC_PARAMETER_ERROR; + } else { + /*reg_mask = SET_BITS(regAUD_CODEC, CDCCLK, 7) | + SET_BITS(regAUD_CODEC, CDCCLKSEL, 1) | + SET_BITS(regAUD_CODEC, CDCFS8K16K, 1) | + SET_BITS(regAUD_CODEC, CDCBCLINV, 1) | + SET_BITS(regAUD_CODEC, CDCFSINV, 1); */ + if (clockIn == CLOCK_IN_CLIA) { + reg_value = + SET_BITS(regAUD_CODEC, CDCCLKSEL, 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCCLKSEL, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCCLKSEL, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + reg_value = 0; + if (clockFreq == VCODEC_CLI_13MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 0); + } else if (clockFreq == VCODEC_CLI_15_36MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 1); + } else if (clockFreq == VCODEC_CLI_16_8MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 2); + } else if (clockFreq == VCODEC_CLI_26MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 4); + } else { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 7); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCCLK, 7); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + reg_value = 0; + reg_mask = 0; + + if (samplingRate == VCODEC_RATE_8_KHZ) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFS8K16K, 0); + } else { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFS8K16K, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCFS8K16K, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + reg_value = 0; + reg_mask = + SET_BITS(regAUD_CODEC, CDCBCLINV, + 1) | SET_BITS(regAUD_CODEC, CDCFSINV, 1); + + if (invert & INVERT_BITCLOCK) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCBCLINV, 1); + } + if (invert & INVERT_FRAMESYNC) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFSINV, 1); + } + if (invert & NO_INVERT) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCBCLINV, 0); + reg_value |= + SET_BITS(regAUD_CODEC, CDCFSINV, 0); + } + if (pmic_write_reg + (REG_AUDIO_CODEC, reg_value, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("CODEC clock set\n"); + vCodec.clockIn = clockIn; + vCodec.clockFreq = clockFreq; + vCodec.samplingRate = samplingRate; + vCodec.invert = invert; + } + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Voice CODEC clock source and operating characteristics. + * + * Get the current Voice CODEC clock source and operating characteristics. + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn The clock signal source. + * @param clockFreq The clock signal frequency. + * @param samplingRate The audio data sampling rate. + * @param invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_VCODEC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_CLOCK_INVERT * const invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure that we return a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (clockIn != (PMIC_AUDIO_CLOCK_IN_SOURCE *) NULL) && + (clockFreq != (PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ *) NULL) && + (samplingRate != (PMIC_AUDIO_VCODEC_SAMPLING_RATE *) NULL) && + (invert != (PMIC_AUDIO_CLOCK_INVERT *) NULL)) { + *clockIn = vCodec.clockIn; + *clockFreq = vCodec.clockFreq; + *samplingRate = vCodec.samplingRate; + *invert = vCodec.invert; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Voice CODEC primary audio channel timeslot. + * + * Set the Voice CODEC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regSSI_NETWORK, CDCTXRXSLOT, 3); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + ((timeslot == USE_TS0) || (timeslot == USE_TS1) || + (timeslot == USE_TS2) || (timeslot == USE_TS3))) { + reg_write = SET_BITS(regSSI_NETWORK, CDCTXRXSLOT, timeslot); + + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + vCodec.timeslot = timeslot; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Voice CODEC primary audio channel timeslot. + * + * Get the current Voice CODEC primary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_VCODEC_TIMESLOT *) NULL)) { + *timeslot = vCodec.timeslot; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Voice CODEC secondary recording audio channel timeslot. + * + * Set the Voice CODEC secondary audio channel timeslot. This function must be + * used if the default timeslot for the secondary audio channel is to be + * changed. The secondary audio channel timeslot is used to transmit the audio + * data that was recorded by the Voice CODEC from the secondary audio input + * channel. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_VCODEC_TIMESLOT + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regSSI_NETWORK, CDCTXSECSLOT, 3); + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + /* How to handle primary slot and secondary slot being the same */ + if ((timeslot >= USE_TS0) && (timeslot <= USE_TS3) + && (timeslot != vCodec.timeslot)) { + reg_write = + SET_BITS(regSSI_NETWORK, CDCTXSECSLOT, timeslot); + + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + vCodec.secondaryTXtimeslot = timeslot; + } + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Voice CODEC secondary recording audio channel timeslot. + * + * Get the Voice CODEC secondary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_VCODEC_TIMESLOT *) NULL)) { + rc = PMIC_SUCCESS; + *timeslot = vCodec.secondaryTXtimeslot; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Set/Enable the Voice CODEC options. + * + * Set or enable various Voice CODEC options. The available options include + * the use of dithering, highpass digital filters, and loopback modes. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Voice CODEC options to enable. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (config & DITHERING) { + reg_write = SET_BITS(regAUD_CODEC, CDCDITH, 0); + reg_mask = SET_BITS(regAUD_CODEC, CDCDITH, 1); + } + + if (config & INPUT_HIGHPASS_FILTER) { + reg_write |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + reg_mask |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + } + + if (config & OUTPUT_HIGHPASS_FILTER) { + reg_write |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + reg_mask |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + } + + if (config & DIGITAL_LOOPBACK) { + reg_write |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + } + + if (config & ANALOG_LOOPBACK) { + reg_write |= SET_BITS(regAUD_CODEC, CDCALM, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCALM, 1); + } + + if (config & VCODEC_MASTER_CLOCK_OUTPUTS) { + reg_write |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1) | + SET_BITS(regAUD_CODEC, CDCTS, 0); + reg_mask |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1) | + SET_BITS(regAUD_CODEC, CDCTS, 1); + + } + + if (config & TRISTATE_TS) { + reg_write |= SET_BITS(regAUD_CODEC, CDCTS, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCTS, 1); + } + + if (reg_mask == 0) { + /* We should not reach this point without having to configure + * anything so we flag it as an error. + */ + rc = PMIC_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_CODEC, + reg_write, reg_mask); + } + + if (rc == PMIC_SUCCESS) { + vCodec.config |= config; + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear/Disable the Voice CODEC options. + * + * Clear or disable various Voice CODEC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Voice CODEC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_vcodec_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG + config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (config & DITHERING) { + reg_mask = SET_BITS(regAUD_CODEC, CDCDITH, 1); + reg_write = SET_BITS(regAUD_CODEC, CDCDITH, 1); + } + + if (config & INPUT_HIGHPASS_FILTER) { + reg_mask |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + } + + if (config & OUTPUT_HIGHPASS_FILTER) { + reg_mask |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + } + + if (config & DIGITAL_LOOPBACK) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + } + + if (config & ANALOG_LOOPBACK) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCALM, 1); + } + + if (config & VCODEC_MASTER_CLOCK_OUTPUTS) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1); + } + + if (config & TRISTATE_TS) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCTS, 1); + } + + if (reg_mask == 0) { + /* We should not reach this point without having to configure + * anything so we flag it as an error. + */ + rc = PMIC_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_CODEC, + reg_write, reg_mask); + } + + if (rc == PMIC_SUCCESS) { + vCodec.config |= config; + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Voice CODEC options. + * + * Get the current Voice CODEC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current set of Voice CODEC options. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_CONFIG * + const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (config != (PMIC_AUDIO_VCODEC_CONFIG *) NULL)) { + *config = vCodec.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the Voice CODEC bypass audio pathway. + * + * Enables the Voice CODEC bypass pathway for audio data. This allows direct + * output of the voltages on the TX data bus line to the output amplifiers + * (bypassing the digital-to-analog converters within the Voice CODEC). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_bypass(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = SET_BITS(regAUD_CODEC, CDCBYP, 1); + const unsigned int reg_mask = SET_BITS(regAUD_CODEC, CDCBYP, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, reg_write, reg_mask); + } + + return rc; +} + +/*! + * @brief Disable the Voice CODEC bypass audio pathway. + * + * Disables the Voice CODEC bypass pathway for audio data. This means that + * the TX data bus line will deliver digital data to the digital-to-analog + * converters within the Voice CODEC. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_bypass(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regAUD_CODEC, CDCBYP, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, reg_write, reg_mask); + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * General Stereo DAC configuration. + ************************************************************************* + */ + +/*! + * @name General Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo + * DAC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Stereo DAC clock source and operating characteristics. + * + * Define the Stereo DAC clock source and operating characteristics. This + * must be done before the Stereo DAC is enabled. + * + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn Select the clock signal source. + * @param clockFreq Select the clock signal frequency. + * @param samplingRate Select the audio data sampling rate. + * @param invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE clockIn, + const PMIC_AUDIO_STDAC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_STDAC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + /* Validate all of the calling parameters. */ + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + if ((clockIn != CLOCK_IN_CLIA) && (clockIn != CLOCK_IN_CLIB)) { + rc = PMIC_PARAMETER_ERROR; + } else if ((stDAC.masterSlave == BUS_MASTER_MODE) + && !((clockFreq >= STDAC_CLI_3_36864MHZ) + && (clockFreq <= STDAC_CLI_33_6MHZ))) { + rc = PMIC_PARAMETER_ERROR; + } else if ((stDAC.masterSlave == BUS_SLAVE_MODE) + && !((clockFreq >= STDAC_MCLK_PLL_DISABLED) + && (clockFreq <= STDAC_BCLK_IN_PLL))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((samplingRate >= STDAC_RATE_8_KHZ) + && (samplingRate <= STDAC_RATE_96_KHZ))) { + rc = PMIC_PARAMETER_ERROR; + } + /* + else if(!((invert >= NO_INVERT) && (invert <= INVERT_FRAMESYNC))) + { + rc = PMIC_PARAMETER_ERROR; + } */ + else { + reg_mask = SET_BITS(regST_DAC, STDCCLK, 7) | + SET_BITS(regST_DAC, STDCCLKSEL, 1) | + SET_BITS(regST_DAC, SR, 15) | + SET_BITS(regST_DAC, STDCBCLINV, 1) | + SET_BITS(regST_DAC, STDCFSINV, 1); + if (clockIn == CLOCK_IN_CLIA) { + reg_value = SET_BITS(regST_DAC, STDCCLKSEL, 0); + } else { + reg_value = SET_BITS(regST_DAC, STDCCLKSEL, 1); + } + /* How to take care of sample rates in SLAVE mode */ + if ((clockFreq == STDAC_CLI_3_36864MHZ) + || ((clockFreq == STDAC_FSYNC_IN_PLL))) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 6); + } else if ((clockFreq == STDAC_CLI_12MHZ) + || (clockFreq == STDAC_MCLK_PLL_DISABLED)) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 5); + } else if (clockFreq == STDAC_CLI_13MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 0); + } else if (clockFreq == STDAC_CLI_15_36MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 1); + } else if (clockFreq == STDAC_CLI_16_8MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 2); + } else if (clockFreq == STDAC_CLI_26MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 4); + } else if ((clockFreq == STDAC_CLI_33_6MHZ) + || (clockFreq == STDAC_BCLK_IN_PLL)) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 7); + } + + reg_value |= SET_BITS(regST_DAC, SR, samplingRate); + + if (invert & INVERT_BITCLOCK) { + reg_value |= SET_BITS(regST_DAC, STDCBCLINV, 1); + } + if (invert & INVERT_FRAMESYNC) { + reg_value |= SET_BITS(regST_DAC, STDCFSINV, 1); + } + if (invert & NO_INVERT) { + reg_value |= SET_BITS(regST_DAC, STDCBCLINV, 0); + reg_value |= SET_BITS(regST_DAC, STDCFSINV, 0); + } + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, reg_value, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC clock set\n"); + rc = PMIC_SUCCESS; + stDAC.clockIn = clockIn; + stDAC.clockFreq = clockFreq; + stDAC.samplingRate = samplingRate; + stDAC.invert = invert; + } + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Stereo DAC clock source and operating characteristics. + * + * Get the current Stereo DAC clock source and operating characteristics. + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn The clock signal source. + * @param clockFreq The clock signal frequency. + * @param samplingRate The audio data sampling rate. + * @param invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_STDAC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_CLOCK_INVERT * const invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (clockIn != (PMIC_AUDIO_CLOCK_IN_SOURCE *) NULL) && + (samplingRate != (PMIC_AUDIO_STDAC_SAMPLING_RATE *) NULL) && + (clockFreq != (PMIC_AUDIO_STDAC_CLOCK_IN_FREQ *) NULL) && + (invert != (PMIC_AUDIO_CLOCK_INVERT *) NULL)) { + *clockIn = stDAC.clockIn; + *samplingRate = stDAC.samplingRate; + *clockFreq = stDAC.clockFreq; + *invert = stDAC.invert; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Stereo DAC primary audio channel timeslot. + * + * Set the Stereo DAC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regSSI_NETWORK, STDCRXSLOT, 3); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if ((timeslot == USE_TS0_TS1) || (timeslot == USE_TS2_TS3) + || (timeslot == USE_TS4_TS5) || (timeslot == USE_TS6_TS7)) { + if (pmic_write_reg + (REG_AUDIO_SSI_NETWORK, timeslot, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC primary timeslot set\n"); + stDAC.timeslot = timeslot; + rc = PMIC_SUCCESS; + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Stereo DAC primary audio channel timeslot. + * + * Get the current Stereo DAC primary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_TIMESLOTS * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_STDAC_TIMESLOTS *) NULL)) { + *timeslot = stDAC.timeslot; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set/Enable the Stereo DAC options. + * + * Set or enable various Stereo DAC options. The available options include + * resetting the digital filter and enabling the bus master clock outputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Stereo DAC options to enable. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_stdac_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if (config & STDAC_MASTER_CLOCK_OUTPUTS) { + reg_write |= SET_BITS(regST_DAC, STDCCLKEN, 1); + reg_mask |= SET_BITS(regST_DAC, STDCCLKEN, 1); + } + + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + stDAC.config |= config; + pr_debug("STDAC config set\n"); + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear/Disable the Stereo DAC options. + * + * Clear or disable various Stereo DAC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Stereo DAC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_stdac_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + + if (config & STDAC_MASTER_CLOCK_OUTPUTS) { + reg_mask |= SET_BITS(regST_DAC, STDCCLKEN, 1); + } + + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + stDAC.config &= ~config; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Stereo DAC options. + * + * Get the current Stereo DAC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current set of Stereo DAC options. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_CONFIG * const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (config != (PMIC_AUDIO_STDAC_CONFIG *) NULL)) { + *config = stDAC.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio input section configuration. + ************************************************************************* + */ + +/*! + * @name Audio Input Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio + * input hardware. + */ +/*@{*/ + +/*! + * @brief Set/Enable the audio input section options. + * + * Set or enable various audio input section options. The only available + * option right now is to enable the automatic disabling of the microphone + * input amplifiers when a microphone/headset is inserted or removed. + * NOT SUPPORTED BY MC13783 + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The audio input section options to enable. + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be successfully set/enabled. + */ +PMIC_STATUS pmic_audio_input_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Clear/Disable the audio input section options. + * + * Clear or disable various audio input section options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The audio input section options to be + * cleared/disabled. + * NOT SUPPORTED BY MC13783 + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be cleared/disabled. + */ +PMIC_STATUS pmic_audio_input_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; + +} + +/*! + * @brief Get the current audio input section options. + * + * Get the current audio input section options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] config The current set of audio input section options. + * NOT SUPPORTED BY MC13783 + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_input_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_CONFIG * const config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio recording using the Voice CODEC. + ************************************************************************* + */ + +/*! + * @name Audio Recording Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio recording. + */ +/*@{*/ + +/*! + * @brief Select the microphone inputs to be used for Voice CODEC recording. + * + * Select left (mc13783-only) and right microphone inputs for Voice CODEC + * recording. It is possible to disable or not use a particular microphone + * input channel by specifying NO_MIC as a parameter. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel Select the left microphone input channel. + * @param rightChannel Select the right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input ports + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be successfully enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_PORT leftChannel, + const PMIC_AUDIO_INPUT_PORT rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!((leftChannel == NO_MIC) || (leftChannel == MIC1_LEFT))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((rightChannel == NO_MIC) + || (rightChannel == MIC1_RIGHT_MIC_MONO) + || (rightChannel == TXIN_EXT) + || (rightChannel == MIC2_AUX))) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (leftChannel == NO_MIC) { + } else { /* Left channel MIC enable */ + reg_mask = SET_BITS(regAUDIO_TX, AMC1LEN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1); + reg_write = SET_BITS(regAUDIO_TX, AMC1LEN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 0); + } + /*For right channel enable one and clear the other two as well as RXINREC */ + if (rightChannel == NO_MIC) { + } else if (rightChannel == MIC1_RIGHT_MIC_MONO) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 0) | + SET_BITS(regAUDIO_TX, ATXINEN, 0); + } else if (rightChannel == MIC2_AUX) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 0) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 0); + } else { /* TX line in */ + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 0) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 0) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("MIC inputs configured successfully\n"); + vCodec.leftChannelMic.mic = leftChannel; + vCodec.rightChannelMic.mic = + rightChannel; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current microphone inputs being used for Voice CODEC + * recording. + * + * Get the left (mc13783-only) and right microphone inputs currently being + * used for Voice CODEC recording. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The left microphone input channel. + * @param rightChannel The right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_PORT * const leftChannel, + PMIC_AUDIO_INPUT_PORT * + const rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannel != (PMIC_AUDIO_INPUT_PORT *) NULL) && + (rightChannel != (PMIC_AUDIO_INPUT_PORT *) NULL)) { + *leftChannel = vCodec.leftChannelMic.mic; + *rightChannel = vCodec.rightChannelMic.mic; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Enable/disable the microphone input. + * + * This function enables/disables the current microphone input channel. The + * input amplifier is automatically turned off when the microphone input is + * disabled. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The left microphone input channel state. + * @param rightChannel the right microphone input channel state. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input states + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic_on_off(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_MIC_STATE + leftChannel, + const PMIC_AUDIO_INPUT_MIC_STATE + rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + unsigned int curr_left = 0; + unsigned int curr_right = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + curr_left = vCodec.leftChannelMic.mic; + curr_right = vCodec.rightChannelMic.mic; + if ((curr_left == NO_MIC) && (curr_right == NO_MIC)) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (curr_left == MIC1_LEFT) { + if ((leftChannel == MICROPHONE_ON) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 0); + + } else if ((leftChannel == MICROPHONE_OFF) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, 0); + + } else { + /* Both are in same state . Nothing to be done */ + } + + } + if (curr_right == MIC1_RIGHT_MIC_MONO) { + if ((rightChannel == MICROPHONE_ON) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } else if (curr_right == MIC2_AUX) { + if ((rightChannel == MICROPHONE_ON) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } else if (curr_right == TXIN_EXT) { + if ((rightChannel == MICROPHONE_ON) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } + if (reg_mask == 0) { + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("MIC states configured successfully\n"); + vCodec.leftChannelMic.micOnOff = + leftChannel; + vCodec.rightChannelMic.micOnOff = + rightChannel; + } + } + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Return the current state of the microphone inputs. + * + * This function returns the current state (on/off) of the microphone + * input channels. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The current left microphone input channel + * state. + * @param rightChannel the current right microphone input channel + * state. + * + * @retval PMIC_SUCCESS If the microphone input channel states + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channel states + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic_on_off(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_MIC_STATE * + const leftChannel, + PMIC_AUDIO_INPUT_MIC_STATE * + const rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannel != (PMIC_AUDIO_INPUT_MIC_STATE *) NULL) && + (rightChannel != (PMIC_AUDIO_INPUT_MIC_STATE *) NULL)) { + *leftChannel = vCodec.leftChannelMic.micOnOff; + *rightChannel = vCodec.rightChannelMic.micOnOff; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the microphone input amplifier mode and gain level. + * + * This function sets the current microphone input amplifier operating mode + * and gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannelMode The left microphone input amplifier mode. + * @param leftChannelGain The left microphone input amplifier gain level. + * @param rightChannelMode The right microphone input amplifier mode. + * @param rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifiers were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input amplifier + * modes or gain levels were invalid. + * @retval PMIC_ERROR If the microphone input amplifiers could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_record_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_AMP_MODE + leftChannelMode, + const PMIC_AUDIO_MIC_GAIN + leftChannelGain, + const PMIC_AUDIO_MIC_AMP_MODE + rightChannelMode, + const PMIC_AUDIO_MIC_GAIN + rightChannelGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!(((leftChannelGain >= MIC_GAIN_MINUS_8DB) + && (leftChannelGain <= MIC_GAIN_PLUS_23DB)) + && ((rightChannelGain >= MIC_GAIN_MINUS_8DB) + && (rightChannelGain <= MIC_GAIN_PLUS_23DB)))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("VCODEC set record gain - wrong gain value\n"); + } else if (((leftChannelMode != AMP_OFF) + && (leftChannelMode != VOLTAGE_TO_VOLTAGE) + && (leftChannelMode != CURRENT_TO_VOLTAGE)) + || ((rightChannelMode != VOLTAGE_TO_VOLTAGE) + && (rightChannelMode != CURRENT_TO_VOLTAGE) + && (rightChannelMode != AMP_OFF))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("VCODEC set record gain - wrong amp mode\n"); + } else { + if (vCodec.leftChannelMic.mic == MIC1_LEFT) { + reg_mask = SET_BITS(regAUDIO_TX, AMC1LITOV, 1) | + SET_BITS(regAUDIO_TX, PGATXL, 31); + if (leftChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write = + SET_BITS(regAUDIO_TX, AMC1LITOV, 0); + } else { + reg_write = + SET_BITS(regAUDIO_TX, AMC1LITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXL, + leftChannelGain); + } + if (vCodec.rightChannelMic.mic == MIC1_RIGHT_MIC_MONO) { + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1RITOV, + 1) | SET_BITS(regAUDIO_TX, PGATXR, + 31); + if (rightChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write |= + SET_BITS(regAUDIO_TX, AMC1RITOV, 0); + } else { + reg_write |= + SET_BITS(regAUDIO_TX, AMC1RITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } else if (vCodec.rightChannelMic.mic == MIC2_AUX) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC2ITOV, 1); + reg_mask |= SET_BITS(regAUDIO_TX, PGATXR, 31); + if (rightChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write |= + SET_BITS(regAUDIO_TX, AMC2ITOV, 0); + } else { + reg_write |= + SET_BITS(regAUDIO_TX, AMC2ITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } else if (vCodec.rightChannelMic.mic == TXIN_EXT) { + reg_mask |= SET_BITS(regAUDIO_TX, PGATXR, 31); + /* No current to voltage option for TX IN amplifier */ + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } + + if (reg_mask == 0) { + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + reg_write = + SET_BITS(regAUDIO_TX, PGATXL, + leftChannelGain); + reg_mask = SET_BITS(regAUDIO_TX, PGATXL, 31); + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("MIC amp mode and gain set\n"); + vCodec.leftChannelMic.ampMode = + leftChannelMode; + vCodec.leftChannelMic.gain = + leftChannelGain; + vCodec.rightChannelMic.ampMode = + rightChannelMode; + vCodec.rightChannelMic.gain = + rightChannelGain; + + } + } + } + } + + /* Exit critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current microphone input amplifier mode and gain level. + * + * This function gets the current microphone input amplifier operating mode + * and gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannelMode The left microphone input amplifier mode. + * @param leftChannelGain The left microphone input amplifier gain level. + * @param rightChannelMode The right microphone input amplifier mode. + * @param rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifier modes + * and gain levels were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input amplifier modes + * and gain levels could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_record_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_MIC_AMP_MODE * + const leftChannelMode, + PMIC_AUDIO_MIC_GAIN * + const leftChannelGain, + PMIC_AUDIO_MIC_AMP_MODE * + const rightChannelMode, + PMIC_AUDIO_MIC_GAIN * + const rightChannelGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannelMode != (PMIC_AUDIO_MIC_AMP_MODE *) NULL) && + (leftChannelGain != (PMIC_AUDIO_MIC_GAIN *) NULL) && + (rightChannelMode != (PMIC_AUDIO_MIC_AMP_MODE *) NULL) && + (rightChannelGain != (PMIC_AUDIO_MIC_GAIN *) NULL)) { + *leftChannelMode = vCodec.leftChannelMic.ampMode; + *leftChannelGain = vCodec.leftChannelMic.gain; + *rightChannelMode = vCodec.rightChannelMic.ampMode; + *rightChannelGain = vCodec.rightChannelMic.gain; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable a microphone bias circuit. + * + * This function enables one of the available microphone bias circuits. + * + * @param handle Device handle from pmic_audio_open() call. + * @param biasCircuit The microphone bias circuit to be enabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (biasCircuit & MIC_BIAS1) { + reg_write = SET_BITS(regAUDIO_TX, MC1BEN, 1); + reg_mask = SET_BITS(regAUDIO_TX, MC1BEN, 1); + } + if (biasCircuit & MIC_BIAS2) { + reg_write |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + reg_mask |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + } + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_TX, reg_write, reg_mask); + } + } + + return rc; +} + +/*! + * @brief Disable a microphone bias circuit. + * + * This function disables one of the available microphone bias circuits. + * + * @param handle Device handle from pmic_audio_open() call. + * @param biasCircuit The microphone bias circuit to be disabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (biasCircuit & MIC_BIAS1) { + reg_mask = SET_BITS(regAUDIO_TX, MC1BEN, 1); + } + + if (biasCircuit & MIC_BIAS2) { + reg_mask |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + } + + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_TX, reg_write, reg_mask); + } + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Playback Using the Voice CODEC. + ************************************************************************* + */ + +/*! + * @name Audio Playback Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Voice CODEC mixer. + * + * This function configures and enables the Voice CODEC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * @param rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param gainIn The secondary audio channel gain level. + * @param gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + rxSecondaryTimeslot, + const PMIC_AUDIO_VCODEC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_VCODEC_MIX_OUT_GAIN + gainOut) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!((rxSecondaryTimeslot >= USE_TS0) + && (rxSecondaryTimeslot <= USE_TS3))) { + pr_debug + ("VCODEC enable mixer - wrong sec rx timeslot\n"); + } else if (!((gainIn >= VCODEC_NO_MIX) + && (gainIn <= VCODEC_MIX_IN_MINUS_12DB))) { + pr_debug("VCODEC enable mixer - wrong mix in gain\n"); + + } else if (!((gainOut >= VCODEC_MIX_OUT_0DB) + && (gainOut <= VCODEC_MIX_OUT_MINUS_6DB))) { + pr_debug("VCODEC enable mixer - wrong mix out gain\n"); + } else { + + reg_mask = SET_BITS(regSSI_NETWORK, CDCRXSECSLOT, 3) | + SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, 3) | + SET_BITS(regSSI_NETWORK, CDCSUMGAIN, 1); + reg_write = + SET_BITS(regSSI_NETWORK, CDCRXSECSLOT, + rxSecondaryTimeslot) | + SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, + gainIn) | SET_BITS(regSSI_NETWORK, + CDCSUMGAIN, gainOut); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Vcodec mixer enabled\n"); + } + } + } + + return rc; +} + +/*! + * @brief Disable the Voice CODEC mixer. + * + * This function disables the Voice CODEC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, 1); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + VCODEC_NO_MIX, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Vcodec mixer disabled\n"); + } + + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Playback Using the Stereo DAC. + ************************************************************************* + */ + +/*! + * @name Audio Playback Using the Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo DAC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Stereo DAC mixer. + * + * This function configures and enables the Stereo DAC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * @param rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param gainIn The secondary audio channel gain level. + * @param gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_stdac_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + rxSecondaryTimeslot, + const PMIC_AUDIO_STDAC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_STDAC_MIX_OUT_GAIN + gainOut) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if (!((rxSecondaryTimeslot >= USE_TS0_TS1) + && (rxSecondaryTimeslot <= USE_TS6_TS7))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong sec timeslot\n"); + } else if (!((gainIn >= STDAC_NO_MIX) + && (gainIn <= STDAC_MIX_IN_MINUS_12DB))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong mix in gain\n"); + } else if (!((gainOut >= STDAC_MIX_OUT_0DB) + && (gainOut <= STDAC_MIX_OUT_MINUS_6DB))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong mix out gain\n"); + } else { + + reg_mask = SET_BITS(regSSI_NETWORK, STDCRXSECSLOT, 3) | + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, 3) | + SET_BITS(regSSI_NETWORK, STDCSUMGAIN, 1); + reg_write = + SET_BITS(regSSI_NETWORK, STDCRXSECSLOT, + rxSecondaryTimeslot) | + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, + gainIn) | SET_BITS(regSSI_NETWORK, + STDCSUMGAIN, gainOut); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("STDAC mixer enabled\n"); + } + } + + } + + return rc; +} + +/*! + * @brief Disable the Stereo DAC mixer. + * + * This function disables the Stereo DAC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_stdac_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, reg_write, reg_mask); + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Output Control + ************************************************************************* + */ + +/*! + * @name Audio Output Section Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio output + * section to support playback. + */ +/*@{*/ + +/*! + * @brief Select the audio output ports. + * + * This function selects the audio output ports to be used. This also enables + * the appropriate output amplifiers. + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports to be used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * acquired. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * acquired. + */ +PMIC_STATUS pmic_audio_output_set_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((port == MONO_ALERT) || (port == MONO_EXTOUT)) { + rc = PMIC_NOT_SUPPORTED; + } else { + if (((handle == stDAC.handle) + && (stDAC.handleState == HANDLE_IN_USE)) + || ((handle == extStereoIn.handle) + && (extStereoIn.handleState == HANDLE_IN_USE)) + || ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut == VCODEC_MIXER_OUT))) { + /* Stereo signal and MIXER source needs to be routed to the port + / Avoid Codec direct out */ + + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 1) | SET_BITS(regAUDIO_RX_0, + ALSPREF, + 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSLEN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 1); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSREN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 1); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + } + if (port & STEREO_LEFT_LOW_POWER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + + reg_write |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut == VCODEC_DIRECT_OUT)) { + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 1) | SET_BITS(regAUDIO_RX_0, + ALSPREF, + 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 0); + } + + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSLEN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSREN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 0); + } + if (port & MONO_CDCOUT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + + reg_write |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + } + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("output ports enabled\n"); + audioOutput.outputPort = port; + + } + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Deselect/disable the audio output ports. + * + * This function disables the audio output ports that were previously enabled + * by calling pmic_audio_output_set_port(). + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports to be disabled. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * disabled. + */ +PMIC_STATUS pmic_audio_output_clear_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((port == MONO_ALERT) || (port == MONO_EXTOUT)) { + rc = PMIC_NOT_SUPPORTED; + } else { + if (((handle == stDAC.handle) + && (stDAC.handleState == HANDLE_IN_USE)) + || ((handle == extStereoIn.handle) + && (extStereoIn.handleState == HANDLE_IN_USE)) + || ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut == VCODEC_MIXER_OUT))) { + /* Stereo signal and MIXER source needs to be routed to the port / + Avoid Codec direct out */ + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 0) | SET_BITS(regAUDIO_RX_0, + ALSPREF, 0); + + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 0); + } + if (port & STEREO_LEFT_LOW_POWER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, LSPLEN, 0); + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut == VCODEC_DIRECT_OUT)) { + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 0) | SET_BITS(regAUDIO_RX_0, + ALSPREF, 0); + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 0); + } + if (port & MONO_CDCOUT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 0); + } + } +#ifdef CONFIG_HEADSET_DETECT_ENABLE + + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } +#endif + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("output ports disabled\n"); + audioOutput.outputPort &= ~port; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current audio output ports. + * + * This function retrieves the audio output ports that are currently being + * used. + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports currently being used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_output_get_port(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PORT * const port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + (port != (PMIC_AUDIO_OUTPUT_PORT *) NULL)) { + *port = audioOutput.outputPort; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the gain level for the external stereo inputs. + * + * This function sets the gain levels for the external stereo inputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The external stereo input gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STEREO_IN_GAIN + gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1) | + SET_BITS(regAUDIO_RX_1, ARXIN, 1); + unsigned int reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* The ARX amplifier for stereo is also enabled over here */ + + if ((gain == STEREO_IN_GAIN_0DB) || (gain == STEREO_IN_GAIN_PLUS_18DB)) { + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + + if (gain == STEREO_IN_GAIN_0DB) { + reg_write |= SET_BITS(regAUDIO_RX_1, ARXIN, 1); + } else { + reg_write |= SET_BITS(regAUDIO_RX_1, ARXIN, 0); + } + + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Ext stereo gain set\n"); + extStereoIn.inputGain = gain; + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } + + return rc; +} + +/*! + * @brief Get the current gain level for the external stereo inputs. + * + * This function retrieves the current gain levels for the external stereo + * inputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current external stereo input gain + * level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STEREO_IN_GAIN * + const gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE) && + (gain != (PMIC_AUDIO_STEREO_IN_GAIN *) NULL)) { + *gain = extStereoIn.inputGain; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the output PGA gain level. + * + * This function sets the audio output PGA gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_pgaGain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PGA_GAIN gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + unsigned int reg_gain; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (!((gain >= OUTPGA_GAIN_MINUS_33DB) + && (gain <= OUTPGA_GAIN_PLUS_6DB))) { + rc = PMIC_NOT_SUPPORTED; + pr_debug("output set PGA gain - wrong gain value\n"); + } else { + reg_gain = gain + 2; + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXIN, 15) | + SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXIN, reg_gain) | + SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARX, 15); + reg_write = SET_BITS(regAUDIO_RX_1, PGARX, reg_gain); + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGAST, 15); + reg_write = SET_BITS(regAUDIO_RX_1, PGAST, reg_gain); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA gains set\n"); + + if (handle == stDAC.handle) { + audioOutput.stDacoutputPGAGain = gain; + } else if (handle == vCodec.handle) { + audioOutput.vCodecoutputPGAGain = gain; + } else { + audioOutput.extStereooutputPGAGain = + gain; + } + } else { + pr_debug + ("Error writing PGA gains to register\n"); + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the output PGA gain level. + * + * This function retrieves the current audio output PGA gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_pgaGain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PGA_GAIN * + const gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (gain != (PMIC_AUDIO_OUTPUT_PGA_GAIN *) NULL) { + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.extStereooutputPGAGain; + rc = PMIC_SUCCESS; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.vCodecoutputPGAGain; + rc = PMIC_SUCCESS; + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.stDacoutputPGAGain; + rc = PMIC_SUCCESS; + } else { + rc = PMIC_PARAMETER_ERROR; + } + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the output mixer. + * + * This function enables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + unsigned int reg_mask_mix = 0; + unsigned int reg_write_mix = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if (((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE))) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + audioOutput.vCodecOut = VCODEC_MIXER_OUT; + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write_mix, reg_mask_mix); + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA mixers enabled\n"); + rc = PMIC_SUCCESS; + } + + } else { + pr_debug("Error writing mixer enable to register\n"); + } + + } + + return rc; +} + +/*! + * @brief Disable the output mixer. + * + * This function disables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + unsigned int reg_mask_mix = 0; + unsigned int reg_write_mix = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + if (((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE))) { + /*reg_mask = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGASTEN, 0); */ + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 0); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGARXEN, 0); + audioOutput.vCodecOut = VCODEC_DIRECT_OUT; + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 0); + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + /*reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 0); */ + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write_mix, reg_mask_mix); + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA mixers disabled\n"); + } + } + } + return rc; +} + +/*! + * @brief Configure and enable the output balance amplifiers. + * + * This function configures and enables the output balance amplifiers. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftGain The desired left channel gain level. + * @param rightGain The desired right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifiers were + * successfully configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or gain levels were invalid. + * @retval PMIC_ERROR If the output balance amplifiers could not + * be reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_output_set_balance(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + leftGain, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + rightGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + unsigned int reg_mask_ch = 0; + unsigned int reg_write_ch = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if (!((leftGain >= BAL_GAIN_MINUS_21DB) && (leftGain <= BAL_GAIN_0DB))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((rightGain >= BAL_GAIN_MINUS_21DB) + && (rightGain <= BAL_GAIN_0DB))) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + /* In mc13783 only one channel can be attenuated wrt the other. + * It is not possible to specify attenuation for both + * This function will return an error if both channels + * are required to be attenuated + * The BALLR bit is set/reset depending on whether leftGain + * or rightGain is specified*/ + if ((rightGain == BAL_GAIN_0DB) + && (leftGain == BAL_GAIN_0DB)) { + /* Nothing to be done */ + } else if ((rightGain != BAL_GAIN_0DB) + && (leftGain == BAL_GAIN_0DB)) { + /* Attenuate right channel */ + reg_mask = SET_BITS(regAUDIO_RX_1, BAL, 7); + reg_mask_ch = SET_BITS(regAUDIO_RX_1, BALLR, 1); + reg_write = + SET_BITS(regAUDIO_RX_1, BAL, + (BAL_GAIN_0DB - rightGain)); + /* The enum and the register values are reversed in order .. */ + reg_write_ch = + SET_BITS(regAUDIO_RX_1, BALLR, 0); + /* BALLR = 0 selects right channel for atten */ + } else if ((rightGain == BAL_GAIN_0DB) + && (leftGain != BAL_GAIN_0DB)) { + /* Attenuate left channel */ + + reg_mask = SET_BITS(regAUDIO_RX_1, BAL, 7); + reg_mask_ch = SET_BITS(regAUDIO_RX_1, BALLR, 1); + reg_write = + SET_BITS(regAUDIO_RX_1, BAL, + (BAL_GAIN_0DB - leftGain)); + reg_write_ch = + SET_BITS(regAUDIO_RX_1, BALLR, 1); + /* BALLR = 1 selects left channel for atten */ + } else { + rc = PMIC_PARAMETER_ERROR; + } + + if ((reg_mask == 0) || (reg_mask_ch == 0)) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write_ch, reg_mask_ch); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, + reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("Output balance attenuation set\n"); + audioOutput.balanceLeftGain = + leftGain; + audioOutput.balanceRightGain = + rightGain; + } + } + } + } + } + return rc; +} + +/*! + * @brief Get the current output balance amplifier gain levels. + * + * This function retrieves the current output balance amplifier gain levels. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftGain The current left channel gain level. + * @param rightGain The current right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifier gain levels + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the output balance amplifier gain levels + * could be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_balance(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const leftGain, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const rightGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + ((leftGain != (PMIC_AUDIO_OUTPUT_BALANCE_GAIN *) NULL) && + (rightGain != (PMIC_AUDIO_OUTPUT_BALANCE_GAIN *) NULL))) { + *leftGain = audioOutput.balanceLeftGain; + *rightGain = audioOutput.balanceRightGain; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Configure and enable the output mono adder. + * + * This function configures and enables the output mono adder. + * + * @param handle Device handle from pmic_audio_open() call. + * @param mode The desired mono adder operating mode. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mono adder mode was + * invalid. + * @retval PMIC_ERROR If the mono adder could not be reconfigured + * or enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mono_adder(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MONO_ADDER_MODE + mode) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, MONO, 3); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((mode >= MONO_ADDER_OFF) && (mode <= STEREO_OPPOSITE_PHASE)) { + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (mode == MONO_ADDER_OFF) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 0); + } else if (mode == MONO_ADD_LEFT_RIGHT) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 2); + } else if (mode == MONO_ADD_OPPOSITE_PHASE) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 3); + } else { /* stereo opposite */ + + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 1); + } + + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output mono adder mode set\n"); + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } else { + rc = PMIC_PARAMETER_ERROR; + } + return rc; +} + +/*! + * @brief Disable the output mono adder. + * + * This function disables the output mono adder. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mono_adder(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, MONO, 3); + + /* No critical section required here since we are not updating any + * global data. + */ + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + } + + return rc; +} + +/*! + * @brief Configure the mono adder output gain level. + * + * This function configures the mono adder output amplifier gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The desired output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be reconfigured. + */ +PMIC_STATUS pmic_audio_output_set_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + gain) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Get the current mono adder output gain level. + * + * This function retrieves the current mono adder output amplifier gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + * const gain) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Set various audio output section options. + * + * This function sets one or more audio output section configuration + * options. The currently supported options include whether to disable + * the non-inverting mono speaker output, enabling the loudspeaker common + * bias circuit, enabling detection of headset insertion/removal, and + * whether to automatically disable the headset amplifiers when a headset + * insertion/removal has been detected. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The desired audio output section + * configuration options to be set. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be set. + */ +PMIC_STATUS pmic_audio_output_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (config & MONO_SPEAKER_INVERT_OUT_ONLY) { + /* If this is one of the parameters */ + rc = PMIC_NOT_SUPPORTED; + } else { + if (config & MONO_LOUDSPEAKER_COMMON_BIAS) { + reg_mask = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + } + if (config & HEADSET_DETECT_ENABLE) { + reg_mask |= SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + } + if (config & STEREO_HEADSET_AMP_AUTO_DISABLE) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output config set\n"); + audioOutput.config |= config; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear various audio output section options. + * + * This function clears one or more audio output section configuration + * options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The desired audio output section + * configuration options to be cleared. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully cleared. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be cleared. + */ +PMIC_STATUS pmic_audio_output_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG + config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + /*unsigned int reg_write_RX = 0; + unsigned int reg_mask_RX = 0; + unsigned int reg_write_TX = 0; + unsigned int reg_mask_TX = 0; */ + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (config & MONO_SPEAKER_INVERT_OUT_ONLY) { + /* If this is one of the parameters */ + rc = PMIC_NOT_SUPPORTED; + } else { + if (config & MONO_LOUDSPEAKER_COMMON_BIAS) { + reg_mask = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ALSPREF, 0); + } + + if (config & HEADSET_DETECT_ENABLE) { + reg_mask |= SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETEN, 0); + } + + if (config & STEREO_HEADSET_AMP_AUTO_DISABLE) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 0); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output config cleared\n"); + audioOutput.config &= ~config; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current audio output section options. + * + * This function retrieves the current audio output section configuration + * option settings. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current audio output section + * configuration option settings. + * + * @retval PMIC_SUCCESS If the current configuration options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the current configuration options + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_CONFIG * + const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + (config != (PMIC_AUDIO_OUTPUT_CONFIG *) NULL)) { + *config = audioOutput.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function enables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_phantom_ground() +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + rc = pmic_write_reg(REG_AUDIO_RX_0, 0, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Phantom ground enabled\n"); + + } + return rc; +} + +/*! + * @brief Disable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function disables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_phantom_ground() +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + rc = pmic_write_reg(REG_AUDIO_RX_0, 1, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Phantom ground disabled\n"); + + } + return rc; +} + +/*@}*/ + +/************************************************************************** + * Static functions. + ************************************************************************** + */ + +/*! + * @name Audio Driver Internal Support Functions + * These non-exported internal functions are used to support the functionality + * of the exported audio APIs. + */ +/*@{*/ + +/*! + * @brief Enables the 5.6V boost for the microphone bias 2 circuit. + * + * This function enables the switching regulator SW3 and configures it to + * provide the 5.6V boost that is required for driving the microphone bias 2 + * circuit when using a 5-pole jack configuration (which is the case for the + * Sphinx board). + * + * @retval PMIC_SUCCESS The 5.6V boost was successfully enabled. + * @retval PMIC_ERROR Failed to enable the 5.6V boost. + */ +/* +static PMIC_STATUS pmic_audio_mic_boost_enable(void) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + return rc; +} +*/ +/*! + * @brief Disables the 5.6V boost for the microphone bias 2 circuit. + * + * This function disables the switching regulator SW3 to turn off the 5.6V + * boost for the microphone bias 2 circuit. + * + * @retval PMIC_SUCCESS The 5.6V boost was successfully disabled. + * @retval PMIC_ERROR Failed to disable the 5.6V boost. + */ +/* +static PMIC_STATUS pmic_audio_mic_boost_disable(void) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + return rc; +} +*/ + +/*! + * @brief Free a device handle previously acquired by calling pmic_audio_open(). + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * Note that we will shutdown/reset the Voice CODEC or Stereo DAC as well as + * any associated audio input/output components that are no longer required. + * + * Also note that this function should only be called with the mutex already + * acquired. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +static PMIC_STATUS pmic_audio_close_handle(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Match up the handle to the audio device and then close it. */ + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* Also shutdown the Stereo DAC hardware. The simplest way to + * do this is to simply call pmic_audio_reset_device() which will + * restore the ST_DAC register to it's initial power-on state. + * + * This will also shutdown the audio output section if no one + * else is still using it. + */ + rc = pmic_audio_reset_device(stDAC.handle); + + if (rc == PMIC_SUCCESS) { + stDAC.handle = AUDIO_HANDLE_NULL; + stDAC.handleState = HANDLE_FREE; + } + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + /* Also shutdown the Voice CODEC and audio input hardware. The + * simplest way to do this is to simply call pmic_audio_reset_device() + * which will restore the AUD_CODEC register to it's initial + * power-on state. + * + * This will also shutdown the audio output section if no one + * else is still using it. + */ + rc = pmic_audio_reset_device(vCodec.handle); + if (rc == PMIC_SUCCESS) { + vCodec.handle = AUDIO_HANDLE_NULL; + vCodec.handleState = HANDLE_FREE; + } + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + + /* Call pmic_audio_reset_device() here to shutdown the audio output + * section if no one else is still using it. + */ + rc = pmic_audio_reset_device(extStereoIn.handle); + + if (rc == PMIC_SUCCESS) { + extStereoIn.handle = AUDIO_HANDLE_NULL; + extStereoIn.handleState = HANDLE_FREE; + } + } + + return rc; +} + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their initial power on state. + * + * This function can only be called if the mutex has already been acquired. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +static PMIC_STATUS pmic_audio_reset_device(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* Also shutdown the audio output section if nobody else is using it. + if ((vCodec.handleState == HANDLE_FREE) && + (extStereoIn.handleState == HANDLE_FREE)) + { + pmic_write_reg(REG_RX_AUD_AMPS, RESET_RX_AUD_AMPS, + REG_FULLMASK); + } */ + + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + RESET_ST_DAC, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + RESET_SSI_NETWORK, + REG_SSI_STDAC_MASK); + if (rc == PMIC_SUCCESS) { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + stDAC.busID = AUDIO_DATA_BUS_1; + stDAC.protocol = NORMAL_MSB_JUSTIFIED_MODE; + stDAC.protocol_set = false; + stDAC.masterSlave = BUS_MASTER_MODE; + stDAC.numSlots = USE_2_TIMESLOTS; + stDAC.clockIn = CLOCK_IN_CLIA; + stDAC.samplingRate = STDAC_RATE_44_1_KHZ; + stDAC.clockFreq = STDAC_CLI_13MHZ; + stDAC.invert = NO_INVERT; + stDAC.timeslot = USE_TS0_TS1; + stDAC.config = (PMIC_AUDIO_STDAC_CONFIG) 0; + + } + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + /* Disable the audio input section when disabling the Voice CODEC. */ + pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, REG_FULLMASK); + + rc = pmic_write_reg(REG_AUDIO_CODEC, + RESET_AUD_CODEC, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + RESET_SSI_NETWORK, + REG_SSI_VCODEC_MASK); + if (rc == PMIC_SUCCESS) { + + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + vCodec.busID = AUDIO_DATA_BUS_2; + vCodec.protocol = NETWORK_MODE; + vCodec.protocol_set = false; + vCodec.masterSlave = BUS_SLAVE_MODE; + vCodec.numSlots = USE_4_TIMESLOTS; + vCodec.clockIn = CLOCK_IN_CLIB; + vCodec.samplingRate = VCODEC_RATE_8_KHZ; + vCodec.clockFreq = VCODEC_CLI_13MHZ; + vCodec.invert = NO_INVERT; + vCodec.timeslot = USE_TS0; + vCodec.config = + INPUT_HIGHPASS_FILTER | + OUTPUT_HIGHPASS_FILTER; + + } + } + + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + /* Disable the Ext stereo Amplifier and disable it as analog mixer input */ + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + pmic_write_reg(REG_AUDIO_RX_1, 0, reg_mask); + + reg_mask = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + pmic_write_reg(REG_AUDIO_RX_0, 0, reg_mask); + + /* We don't need to reset any other registers for this case. */ + rc = PMIC_SUCCESS; + } + + return rc; +} + +/*! + * @brief Deregister the callback function and event mask currently associated + * with an audio device handle. + * + * This function deregisters any existing callback function and event mask for + * the given audio device handle. This is done by either calling the + * pmic_audio_clear_callback() API or by closing the device handle. + * + * Note that this function should only be called with the mutex already + * acquired. We will also acquire the spinlock here to prevent possible + * race conditions with the interrupt handler. + * + * @param[in] callback The current event callback function pointer. + * @param[in] eventMask The current audio event mask. + * + * @retval PMIC_SUCCESS If the callback function and event mask + * were both successfully deregistered. + * @retval PMIC_ERROR If either the callback function or the + * event mask was not successfully + * deregistered. + */ + +static PMIC_STATUS pmic_audio_deregister(void *callback, + PMIC_AUDIO_EVENTS * const eventMask) +{ + unsigned long flags; + pmic_event_callback_t eventNotify; + PMIC_STATUS rc = PMIC_SUCCESS; + + /* Deregister each of the PMIC events that we had previously + * registered for by calling pmic_event_subscribe(). + */ + if (*eventMask & (HEADSET_DETECTED)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + if (pmic_event_unsubscribe(EVENT_HSDETI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_DETECTED); + pr_debug("Deregistered for EVENT_HSDETI\n"); + } else { + rc = PMIC_ERROR; + } + } + + if (*eventMask & (HEADSET_STEREO)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + if (pmic_event_unsubscribe(EVENT_HSLI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_STEREO); + pr_debug("Deregistered for EVENT_HSLI\n"); + } else { + rc = PMIC_ERROR; + } + } + if (*eventMask & (HEADSET_THERMAL_SHUTDOWN)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + if (pmic_event_unsubscribe(EVENT_ALSPTHI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_THERMAL_SHUTDOWN); + pr_debug("Deregistered for EVENT_ALSPTHI\n"); + } else { + rc = PMIC_ERROR; + } + } + if (*eventMask & (HEADSET_SHORT_CIRCUIT)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + if (pmic_event_unsubscribe(EVENT_AHSSHORTI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_SHORT_CIRCUIT); + pr_debug("Deregistered for EVENT_AHSSHORTI\n"); + } else { + rc = PMIC_ERROR; + } + } + + if (rc == PMIC_SUCCESS) { + /* We need to grab the spinlock here to create a critical section to + * avoid any possible race conditions with the interrupt handler + */ + spin_lock_irqsave(&lock, flags); + + /* Restore the initial reset values for the callback function + * and event mask parameters. This should be NULL and zero, + * respectively. + */ + callback = NULL; + *eventMask = 0; + + /* Exit the critical section. */ + spin_unlock_irqrestore(&lock, flags); + } + + return rc; +} + +/*! + * @brief enable/disable fm output. + * + * @param[in] enable true to enable false to disable + */ +PMIC_STATUS pmic_audio_fm_output_enable(bool enable) +{ + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + if (enable) { + pmic_audio_antipop_enable(ANTI_POP_RAMP_FAST); + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, HSPGDIS, 0); + } else { + reg_mask |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 0); + } + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + if (rc != PMIC_SUCCESS) + return rc; + if (enable) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + } else { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 0); + } + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + return rc; +} + +/*@}*/ + +/************************************************************************** + * Module initialization and termination functions. + * + * Note that if this code is compiled into the kernel, then the + * module_init() function will be called within the device_initcall() + * group. + ************************************************************************** + */ + +/*! + * @name Audio Driver Loading/Unloading Functions + * These non-exported internal functions are used to support the audio + * device driver initialization and de-initialization operations. + */ +/*@{*/ + +/*! + * @brief This is the audio device driver initialization function. + * + * This function is called by the kernel when this device driver is first + * loaded. + */ +static int __init mc13783_pmic_audio_init(void) +{ + printk(KERN_INFO "PMIC Audio driver loading...\n"); + + return 0; +} + +/*! + * @brief This is the audio device driver de-initialization function. + * + * This function is called by the kernel when this device driver is about + * to be unloaded. + */ +static void __exit mc13783_pmic_audio_exit(void) +{ + printk(KERN_INFO "PMIC Audio driver unloading...\n"); + + /* Close all device handles that are still open. This will also + * deregister any callbacks that may still be active. + */ + if (stDAC.handleState == HANDLE_IN_USE) { + pmic_audio_close(stDAC.handle); + } + if (vCodec.handleState == HANDLE_IN_USE) { + pmic_audio_close(vCodec.handle); + } + if (extStereoIn.handleState == HANDLE_IN_USE) { + pmic_audio_close(extStereoIn.handle); + } + + /* Explicitly reset all of the audio registers so that there is no + * possibility of leaving the audio hardware in a state + * where it can cause problems if there is no device driver loaded. + */ + pmic_write_reg(REG_AUDIO_STEREO_DAC, RESET_ST_DAC, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_CODEC, RESET_AUD_CODEC, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_SSI_NETWORK, RESET_SSI_NETWORK, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_RX_0, RESET_AUDIO_RX_0, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_RX_1, RESET_AUDIO_RX_1, REG_FULLMASK); +} + +/*@}*/ + +/* + * Module entry points and description information. + */ + +module_init(mc13783_pmic_audio_init); +module_exit(mc13783_pmic_audio_exit); + +MODULE_DESCRIPTION("PMIC - mc13783 ADC driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_battery.c b/drivers/mxc/pmic/mc13783/pmic_battery.c new file mode 100644 index 000000000000..3e9d14145afd --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_battery.c @@ -0,0 +1,1220 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_battery.c + * @brief This is the main file of PMIC(mc13783) Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "pmic_battery_defs.h" + +#include +#ifdef CONFIG_MXC_HWEVENT +#include +#endif + +static int pmic_battery_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait; + +/*! + * To indicate whether any of the battery devices are suspending + */ +static int suspend_flag; + +/*! + * The suspendq is used to block application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_battery_class; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_batt_enable_charger); +EXPORT_SYMBOL(pmic_batt_disable_charger); +EXPORT_SYMBOL(pmic_batt_set_charger); +EXPORT_SYMBOL(pmic_batt_get_charger_setting); +EXPORT_SYMBOL(pmic_batt_get_charge_current); +EXPORT_SYMBOL(pmic_batt_enable_eol); +EXPORT_SYMBOL(pmic_batt_bp_enable_eol); +EXPORT_SYMBOL(pmic_batt_disable_eol); +EXPORT_SYMBOL(pmic_batt_set_out_control); +EXPORT_SYMBOL(pmic_batt_set_threshold); +EXPORT_SYMBOL(pmic_batt_led_control); +EXPORT_SYMBOL(pmic_batt_set_reverse_supply); +EXPORT_SYMBOL(pmic_batt_set_unregulated); +EXPORT_SYMBOL(pmic_batt_set_5k_pull); +EXPORT_SYMBOL(pmic_batt_event_subscribe); +EXPORT_SYMBOL(pmic_batt_event_unsubscribe); + +static DECLARE_MUTEX(count_mutex); /* open count mutex */ +static int open_count; /* open count for device file */ + +/*! + * Callback function for events, we want on MGN board + */ +static void callback_chg_detect(void) +{ +#ifdef CONFIG_MXC_HWEVENT + t_sensor_bits sensor; + struct mxc_hw_event event = { HWE_BAT_CHARGER_PLUG, 0 }; + + pr_debug("In callback_chg_detect\n"); + + /* get sensor values */ + pmic_get_sensors(&sensor); + + pr_debug("Callback, charger detect:%d\n", sensor.sense_chgdets); + + if (sensor.sense_chgdets) + event.args = 1; + else + event.args = 0; + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_low_battery(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_BATTERY_LOW, 0 }; + + pr_debug("In callback_low_battery\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_power_fail(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_POWER_FAILED, 0 }; + + pr_debug("In callback_power_fail\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_chg_overvoltage(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_CHARGER_OVERVOLTAGE, 0 }; + + pr_debug("In callback_chg_overvoltage\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_chg_full(void) +{ +#ifdef CONFIG_MXC_HWEVENT + t_sensor_bits sensor; + struct mxc_hw_event event = { HWE_BAT_CHARGER_FULL, 0 }; + + pr_debug("In callback_chg_full\n"); + + /* disable charge function */ + pmic_batt_disable_charger(BATT_MAIN_CHGR); + + /* get charger sensor */ + pmic_get_sensors(&sensor); + + /* if did not detect the charger */ + if (sensor.sense_chgdets) + return; + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +/*! + * This is the suspend of power management for the pmic battery API. + * It suports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + unsigned int reg_value = 0; + + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_CHARGER, reg_value, PMIC_ALL_BITS)); + + return 0; +}; + +/*! + * This is the resume of power management for the pmic battery API. + * It suports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_battery_resume(struct platform_device *pdev) +{ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/*! + * This function is used to start charging a battery. For different charger, + * different voltage and current range are supported. \n + * + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_enable_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, c_current) | + BITFVAL(MC13783_BATT_DAC_V_DAC, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_V_COIN, c_voltage) | + BITFVAL(MC13783_BATT_DAC_COIN_CH_EN, + MC13783_BATT_DAC_COIN_CH_EN_ENABLED); + mask = BITFMASK(MC13783_BATT_DAC_V_COIN) | + BITFMASK(MC13783_BATT_DAC_COIN_CH_EN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, c_current); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function turns off a charger. + * + * @param chgr Charger as defined in \b t_batt_charger. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_charger(t_batt_charger chgr) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, 0) | + BITFVAL(MC13783_BATT_DAC_V_DAC, 0); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_COIN_CH_EN, + MC13783_BATT_DAC_COIN_CH_EN_DISABLED); + mask = BITFMASK(MC13783_BATT_DAC_COIN_CH_EN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, 0); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to change the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, c_current) | + BITFVAL(MC13783_BATT_DAC_V_DAC, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_V_COIN, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_V_COIN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, c_current); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function is used to retrive the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Output parameter for charging voltage setting. + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charger_setting(t_batt_charger chgr, + unsigned char *c_voltage, + unsigned char *c_current) +{ + unsigned int val, reg; + + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + case BATT_TRCKLE_CHGR: + reg = REG_CHARGER; + break; + case BATT_CELL_CHGR: + reg = REG_POWER_CONTROL_0; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, &val, PMIC_ALL_BITS)); + + switch (chgr) { + case BATT_MAIN_CHGR: + *c_voltage = BITFEXT(val, MC13783_BATT_DAC_V_DAC);; + *c_current = BITFEXT(val, MC13783_BATT_DAC_DAC); + break; + + case BATT_CELL_CHGR: + *c_voltage = BITFEXT(val, MC13783_BATT_DAC_V_COIN); + *c_current = 0; + break; + + case BATT_TRCKLE_CHGR: + *c_voltage = 0; + *c_current = BITFEXT(val, MC13783_BATT_DAC_TRCKLE); + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery voltage. + * + * @param b_voltage Output parameter for voltage setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_voltage(unsigned short *b_voltage) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + channel = BATTERY_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_voltage = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery current. + * + * @param b_current Output parameter for current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_current(unsigned short *b_current) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = BATTERY_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_current = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery temperature. + * + * @param b_temper Output parameter for temperature setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_temperature(unsigned short *b_temper) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = GEN_PURPOSE_AD5; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_temper = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery charging voltage. + * + * @param c_voltage Output parameter for charging voltage setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charge_voltage(unsigned short *c_voltage) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = CHARGE_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *c_voltage = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery charging current. + * + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charge_current(unsigned short *c_current) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = CHARGE_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *c_current = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function enables End-of-Life comparator. Not supported on + * mc13783. Use pmic_batt_bp_enable_eol function. + * + * @param threshold End-of-Life threshold. + * + * @return This function returns PMIC_UNSUPPORTED + */ +PMIC_STATUS pmic_batt_enable_eol(unsigned char threshold) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables End-of-Life comparator. + * + * @param typical Falling Edge Threshold threshold. + * @verbatim + BPDET UVDET LOBATL + ____ _____ ___________ + 0 2.6 UVDET + 0.2 + 1 2.6 UVDET + 0.3 + 2 2.6 UVDET + 0.4 + 3 2.6 UVDET + 0.5 + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_bp_enable_eol(t_bp_threshold typical) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_EOL_CMP_EN, + MC13783_BATT_DAC_EOL_CMP_EN_ENABLE) | + BITFVAL(MC13783_BATT_DAC_EOL_SEL, typical); + mask = BITFMASK(MC13783_BATT_DAC_EOL_CMP_EN) | + BITFMASK(MC13783_BATT_DAC_EOL_SEL); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables End-of-Life comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_eol(void) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_EOL_CMP_EN, + MC13783_BATT_DAC_EOL_CMP_EN_DISABLE); + mask = BITFMASK(MC13783_BATT_DAC_EOL_CMP_EN); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the output controls. + * It sets the FETOVRD and FETCTRL bits of mc13783 + * + * @param control type of control. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_out_control(t_control control) +{ + unsigned int val, mask; + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (control) { + case CONTROL_HARDWARE: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 0) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 0); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + case CONTROL_BPFET_LOW: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 1) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 0); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + case CONTROL_BPFET_HIGH: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 1) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 1); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function sets over voltage threshold. + * + * @param threshold value of over voltage threshold. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_threshold(int threshold) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + if (threshold > BAT_THRESHOLD_MAX) + return PMIC_PARAMETER_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_OVCTRL, threshold); + mask = BITFMASK(MC13783_BATT_DAC_OVCTRL); + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function controls charge LED. + * + * @param on If on is ture, LED will be turned on, + * or otherwise, LED will be turned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_led_control(bool on) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_LED_EN, on); + mask = BITFMASK(MC13783_BATT_DAC_LED_EN); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets reverse supply mode. + * + * @param enable If enable is ture, reverse supply mode is enable, + * or otherwise, reverse supply mode is disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_reverse_supply(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_REVERSE_SUPPLY, enable); + mask = BITFMASK(MC13783_BATT_DAC_REVERSE_SUPPLY); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets unregulatored charging mode on main battery. + * + * @param enable If enable is ture, unregulated charging mode is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_unregulated(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_UNREGULATED, enable); + mask = BITFMASK(MC13783_BATT_DAC_UNREGULATED); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets a 5K pull down at CHRGRAW. + * To be used in the dual path charging configuration. + * + * @param enable If enable is true, 5k pull down is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_5k_pull(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_5K, enable); + mask = BITFMASK(MC13783_BATT_DAC_5K); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS mc13783_battery_event(t_batt_event event, void *callback, bool sub) +{ + pmic_event_callback_t bat_callback; + type_event bat_event; + + bat_callback.func = callback; + bat_callback.param = NULL; + switch (event) { + case BAT_IT_CHG_DET: + bat_event = EVENT_CHGDETI; + break; + case BAT_IT_CHG_OVERVOLT: + bat_event = EVENT_CHGOVI; + break; + case BAT_IT_CHG_REVERSE: + bat_event = EVENT_CHGREVI; + break; + case BAT_IT_CHG_SHORT_CIRCUIT: + bat_event = EVENT_CHGSHORTI; + break; + case BAT_IT_CCCV: + bat_event = EVENT_CCCVI; + break; + case BAT_IT_BELOW_THRESHOLD: + bat_event = EVENT_CHRGCURRI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(bat_event, bat_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe(bat_event, bat_callback)); + } + return 0; +} + +/*! + * This function is used to subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_subscribe(t_batt_event event, void *callback) +{ + if (suspend_flag == 1) + return PMIC_ERROR; + + return mc13783_battery_event(event, callback, true); +} + +/*! + * This function is used to un subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_unsubscribe(t_batt_event event, void *callback) +{ + if (suspend_flag == 1) + return PMIC_ERROR; + + return mc13783_battery_event(event, callback, false); +} + +/*! + * This function implements IOCTL controls on a PMIC Battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_battery_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_charger_setting *chgr_setting = NULL; + unsigned short c_current; + unsigned int bc_info; + t_eol_setting *eol_setting; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + chgr_setting = kmalloc(sizeof(t_charger_setting), GFP_KERNEL); + eol_setting = kmalloc(sizeof(t_eol_setting), GFP_KERNEL); + switch (cmd) { + case PMIC_BATT_CHARGER_CONTROL: + if (chgr_setting == NULL) + return -ENOMEM; + + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + if (chgr_setting->on != false) { + CHECK_ERROR_KFREE(pmic_batt_enable_charger + (chgr_setting->chgr, + chgr_setting->c_voltage, + chgr_setting->c_current), + (kfree(chgr_setting))); + } else { + CHECK_ERROR(pmic_batt_disable_charger + (chgr_setting->chgr)); + } + + kfree(chgr_setting); + break; + + case PMIC_BATT_SET_CHARGER: + if (chgr_setting == NULL) + return -ENOMEM; + + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_batt_set_charger(chgr_setting->chgr, + chgr_setting->c_voltage, + chgr_setting-> + c_current), + (kfree(chgr_setting))); + + kfree(chgr_setting); + break; + + case PMIC_BATT_GET_CHARGER: + if (chgr_setting == NULL) + return -ENOMEM; + + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_batt_get_charger_setting + (chgr_setting->chgr, &chgr_setting->c_voltage, + &chgr_setting->c_current), + (kfree(chgr_setting))); + if (copy_to_user + ((t_charger_setting *) arg, chgr_setting, + sizeof(t_charger_setting))) { + return -EFAULT; + } + + kfree(chgr_setting); + break; + + case PMIC_BATT_GET_CHARGER_SENSOR: + { + t_sensor_bits sensor; + pmic_get_sensors(&sensor); + if (copy_to_user + ((unsigned int *)arg, &sensor.sense_chgdets, + sizeof(unsigned int))) + return -EFAULT; + + break; + } + case PMIC_BATT_GET_BATTERY_VOLTAGE: + CHECK_ERROR(pmic_batt_get_batt_voltage(&c_current)); + bc_info = (unsigned int)c_current * 2300 / 1023 + 2400; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_BATTERY_CURRENT: + CHECK_ERROR(pmic_batt_get_batt_current(&c_current)); + bc_info = (unsigned int)c_current * 5750 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + break; + + case PMIC_BATT_GET_BATTERY_TEMPERATURE: + CHECK_ERROR(pmic_batt_get_batt_temperature(&c_current)); + bc_info = (unsigned int)c_current; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_CHARGER_VOLTAGE: + CHECK_ERROR(pmic_batt_get_charge_voltage(&c_current)); + bc_info = (unsigned int)c_current * 23000 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_CHARGER_CURRENT: + CHECK_ERROR(pmic_batt_get_charge_current(&c_current)); + bc_info = (unsigned int)c_current * 5750 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_EOL_CONTROL: + if (eol_setting == NULL) + return -ENOMEM; + + if (copy_from_user(eol_setting, (t_eol_setting *) arg, + sizeof(t_eol_setting))) { + kfree(eol_setting); + return -EFAULT; + } + + if (eol_setting->enable != false) { + CHECK_ERROR_KFREE(pmic_batt_bp_enable_eol + (eol_setting->typical), + (kfree(chgr_setting))); + } else { + CHECK_ERROR_KFREE(pmic_batt_disable_eol(), + (kfree(chgr_setting))); + } + + kfree(eol_setting); + break; + + case PMIC_BATT_SET_OUT_CONTROL: + CHECK_ERROR(pmic_batt_set_out_control((t_control) arg)); + break; + + case PMIC_BATT_SET_THRESHOLD: + CHECK_ERROR(pmic_batt_set_threshold((int)arg)); + break; + + case PMIC_BATT_LED_CONTROL: + CHECK_ERROR(pmic_batt_led_control((bool) arg)); + break; + + case PMIC_BATT_REV_SUPP_CONTROL: + CHECK_ERROR(pmic_batt_set_reverse_supply((bool) arg)); + break; + + case PMIC_BATT_UNREG_CONTROL: + CHECK_ERROR(pmic_batt_set_unregulated((bool) arg)); + break; + + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function implements the open method on a Pmic battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_battery_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + /* check open count, if open firstly, register callbacks */ + down(&count_mutex); + if (open_count++ > 0) { + up(&count_mutex); + return 0; + } + + pr_debug("Subscribe the callbacks\n"); + /* register battery event callback */ + if (pmic_batt_event_subscribe(BAT_IT_CHG_DET, callback_chg_detect)) { + pr_debug("Failed to subscribe the charger detect callback\n"); + goto event_err1; + } + if (pmic_power_event_sub(PWR_IT_LOBATLI, callback_power_fail)) { + pr_debug("Failed to subscribe the power failed callback\n"); + goto event_err2; + } + if (pmic_power_event_sub(PWR_IT_LOBATHI, callback_low_battery)) { + pr_debug("Failed to subscribe the low battery callback\n"); + goto event_err3; + } + if (pmic_batt_event_subscribe + (BAT_IT_CHG_OVERVOLT, callback_chg_overvoltage)) { + pr_debug("Failed to subscribe the low battery callback\n"); + goto event_err4; + } + if (pmic_batt_event_subscribe + (BAT_IT_BELOW_THRESHOLD, callback_chg_full)) { + pr_debug("Failed to subscribe the charge full callback\n"); + goto event_err5; + } + + up(&count_mutex); + + return 0; + + /* un-subscribe the event callbacks */ +event_err5: + pmic_batt_event_unsubscribe(BAT_IT_CHG_OVERVOLT, + callback_chg_overvoltage); +event_err4: + pmic_power_event_unsub(PWR_IT_LOBATHI, callback_low_battery); +event_err3: + pmic_power_event_unsub(PWR_IT_LOBATLI, callback_power_fail); +event_err2: + pmic_batt_event_unsubscribe(BAT_IT_CHG_DET, callback_chg_detect); +event_err1: + + open_count--; + up(&count_mutex); + + return -EFAULT; + +} + +/*! + * This function implements the release method on a Pmic battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_battery_release(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + /* check open count, if open firstly, register callbacks */ + down(&count_mutex); + if (--open_count == 0) { + /* unregister these event callback */ + pr_debug("Unsubscribe the callbacks\n"); + pmic_batt_event_unsubscribe(BAT_IT_BELOW_THRESHOLD, + callback_chg_full); + pmic_batt_event_unsubscribe(BAT_IT_CHG_OVERVOLT, + callback_chg_overvoltage); + pmic_power_event_unsub(PWR_IT_LOBATHI, callback_low_battery); + pmic_power_event_unsub(PWR_IT_LOBATLI, callback_power_fail); + pmic_batt_event_unsubscribe(BAT_IT_CHG_DET, + callback_chg_detect); + } + up(&count_mutex); + + return 0; +} + +static struct file_operations pmic_battery_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_battery_ioctl, + .open = pmic_battery_open, + .release = pmic_battery_release, +}; + +static int pmic_battery_remove(struct platform_device *pdev) +{ + device_destroy(pmic_battery_class, MKDEV(pmic_battery_major, 0)); + class_destroy(pmic_battery_class); + unregister_chrdev(pmic_battery_major, PMIC_BATTERY_STRING); + return 0; +} + +static int pmic_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_battery_major = register_chrdev(0, PMIC_BATTERY_STRING, + &pmic_battery_fops); + + if (pmic_battery_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_battery\n"); + return pmic_battery_major; + } + init_waitqueue_head(&suspendq); + + pmic_battery_class = class_create(THIS_MODULE, PMIC_BATTERY_STRING); + if (IS_ERR(pmic_battery_class)) { + printk(KERN_ERR "Error creating PMIC battery class.\n"); + ret = PTR_ERR(pmic_battery_class); + goto err_out1; + } + + temp_class = device_create(pmic_battery_class, NULL, + MKDEV(pmic_battery_major, 0), NULL, + PMIC_BATTERY_STRING); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating PMIC battery class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + pmic_batt_led_control(true); + pmic_batt_set_5k_pull(true); + + printk(KERN_INFO "PMIC Battery successfully probed\n"); + + return ret; + + err_out2: + class_destroy(pmic_battery_class); + err_out1: + unregister_chrdev(pmic_battery_major, PMIC_BATTERY_STRING); + return ret; +} + +static struct platform_driver pmic_battery_driver_ldm = { + .driver = { + .name = "pmic_battery", + .bus = &platform_bus_type, + }, + .suspend = pmic_battery_suspend, + .resume = pmic_battery_resume, + .probe = pmic_battery_probe, + .remove = pmic_battery_remove, +}; + +/* + * Init and Exit + */ + +static int __init pmic_battery_init(void) +{ + pr_debug("PMIC Battery driver loading...\n"); + return platform_driver_register(&pmic_battery_driver_ldm); +} + +static void __exit pmic_battery_exit(void) +{ + platform_driver_unregister(&pmic_battery_driver_ldm); + pr_debug("PMIC Battery driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_battery_init); +module_exit(pmic_battery_exit); + +MODULE_DESCRIPTION("pmic_battery driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_battery_defs.h b/drivers/mxc/pmic/mc13783/pmic_battery_defs.h new file mode 100644 index 000000000000..93742c52e48f --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_battery_defs.h @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_battery_defs.h + * @brief This is the internal header for PMIC(mc13783) Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +#ifndef __PMIC_BATTERY_DEFS_H__ +#define __PMIC_BATTERY_DEFS_H__ + +#define PMIC_BATTERY_STRING "pmic_battery" + +/* REG_CHARGE */ +#define MC13783_BATT_DAC_V_DAC_LSH 0 +#define MC13783_BATT_DAC_V_DAC_WID 3 +#define MC13783_BATT_DAC_DAC_LSH 3 +#define MC13783_BATT_DAC_DAC_WID 4 +#define MC13783_BATT_DAC_TRCKLE_LSH 7 +#define MC13783_BATT_DAC_TRCKLE_WID 3 +#define MC13783_BATT_DAC_FETOVRD_EN_LSH 10 +#define MC13783_BATT_DAC_FETOVRD_EN_WID 1 +#define MC13783_BATT_DAC_FETCTRL_EN_LSH 11 +#define MC13783_BATT_DAC_FETCTRL_EN_WID 1 +#define MC13783_BATT_DAC_REVERSE_SUPPLY_LSH 13 +#define MC13783_BATT_DAC_REVERSE_SUPPLY_WID 1 +#define MC13783_BATT_DAC_OVCTRL_LSH 15 +#define MC13783_BATT_DAC_OVCTRL_WID 2 +#define MC13783_BATT_DAC_UNREGULATED_LSH 17 +#define MC13783_BATT_DAC_UNREGULATED_WID 1 +#define MC13783_BATT_DAC_LED_EN_LSH 18 +#define MC13783_BATT_DAC_LED_EN_WID 1 +#define MC13783_BATT_DAC_5K_LSH 19 +#define MC13783_BATT_DAC_5K_WID 1 + +#define BITS_OUT_VOLTAGE 0 +#define LONG_OUT_VOLTAGE 3 +#define BITS_CURRENT_MAIN 3 +#define LONG_CURRENT_MAIN 4 +#define BITS_CURRENT_TRICKLE 7 +#define LONG_CURRENT_TRICKLE 3 +#define BIT_FETOVRD 10 +#define BIT_FETCTRL 11 +#define BIT_RVRSMODE 13 +#define BITS_OVERVOLTAGE 15 +#define LONG_OVERVOLTAGE 2 +#define BIT_UNREGULATED 17 +#define BIT_CHRG_LED 18 +#define BIT_CHRGRAWPDEN 19 + +/* REG_POWXER_CONTROL_0 */ +#define MC13783_BATT_DAC_V_COIN_LSH 20 +#define MC13783_BATT_DAC_V_COIN_WID 3 +#define MC13783_BATT_DAC_COIN_CH_EN_LSH 23 +#define MC13783_BATT_DAC_COIN_CH_EN_WID 1 +#define MC13783_BATT_DAC_COIN_CH_EN_ENABLED 1 +#define MC13783_BATT_DAC_COIN_CH_EN_DISABLED 0 +#define MC13783_BATT_DAC_EOL_CMP_EN_LSH 18 +#define MC13783_BATT_DAC_EOL_CMP_EN_WID 1 +#define MC13783_BATT_DAC_EOL_CMP_EN_ENABLE 1 +#define MC13783_BATT_DAC_EOL_CMP_EN_DISABLE 0 +#define MC13783_BATT_DAC_EOL_SEL_LSH 16 +#define MC13783_BATT_DAC_EOL_SEL_WID 2 + +#define DEF_VALUE 0 + +#define BAT_THRESHOLD_MAX 3 + +#endif /* __PMIC_BATTERY_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_convity.c b/drivers/mxc/pmic/mc13783/pmic_convity.c new file mode 100644 index 000000000000..5c963bd135af --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_convity.c @@ -0,0 +1,2468 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_convity.c + * @brief Implementation of the PMIC Connectivity driver APIs. + * + * The PMIC connectivity device driver and this API were developed to support + * the external connectivity capabilities of several power management ICs that + * are available from Freescale Semiconductor, Inc. + * + * The following operating modes, in terms of external connectivity, are + * supported: + * + * @verbatim + Operating Mode mc13783 + --------------- ------- + USB (incl. OTG) Yes + RS-232 Yes + CEA-936 Yes + + @endverbatim + * + * @ingroup PMIC_CONNECTIVITY + */ + +#include /* For tasklet interface. */ +#include /* For kernel module interface. */ +#include /* For spinlock interface. */ +#include /* For PMIC ADC driver interface. */ +#include +#include /* For PMIC Connectivity driver interface. */ + +/* + * mc13783 Connectivity API + */ +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_convity_open); +EXPORT_SYMBOL(pmic_convity_close); +EXPORT_SYMBOL(pmic_convity_set_mode); +EXPORT_SYMBOL(pmic_convity_get_mode); +EXPORT_SYMBOL(pmic_convity_reset); +EXPORT_SYMBOL(pmic_convity_set_callback); +EXPORT_SYMBOL(pmic_convity_clear_callback); +EXPORT_SYMBOL(pmic_convity_get_callback); +EXPORT_SYMBOL(pmic_convity_usb_set_speed); +EXPORT_SYMBOL(pmic_convity_usb_get_speed); +EXPORT_SYMBOL(pmic_convity_usb_set_power_source); +EXPORT_SYMBOL(pmic_convity_usb_get_power_source); +EXPORT_SYMBOL(pmic_convity_usb_set_xcvr); +EXPORT_SYMBOL(pmic_convity_usb_get_xcvr); +EXPORT_SYMBOL(pmic_convity_usb_otg_set_dlp_duration); +EXPORT_SYMBOL(pmic_convity_usb_otg_get_dlp_duration); +EXPORT_SYMBOL(pmic_convity_usb_otg_set_config); +EXPORT_SYMBOL(pmic_convity_usb_otg_clear_config); +EXPORT_SYMBOL(pmic_convity_usb_otg_get_config); +EXPORT_SYMBOL(pmic_convity_set_output); +EXPORT_SYMBOL(pmic_convity_rs232_set_config); +EXPORT_SYMBOL(pmic_convity_rs232_get_config); +EXPORT_SYMBOL(pmic_convity_cea936_exit_signal); + +/*! @def SET_BITS + * Set a register field to a given value. + */ + +#define SET_BITS(reg, field, value) (((value) << reg.field.offset) & \ + reg.field.mask) + +/*! @def GET_BITS + * Get the current value of a given register field. + */ +#define GET_BITS(reg, value) (((value) & reg.mask) >> \ + reg.offset) + +/*! + * @brief Define the possible states for a device handle. + * + * This enumeration is used to track the current state of each device handle. + */ +typedef enum { + HANDLE_FREE, /*!< Handle is available for use. */ + HANDLE_IN_USE /*!< Handle is currently in use. */ +} HANDLE_STATE; + +/* + * This structure is used to define a specific hardware register field. + * + * All hardware register fields are defined using an offset to the LSB + * and a mask. The offset is used to right shift a register value before + * applying the mask to actually obtain the value of the field. + */ +typedef struct { + const unsigned char offset; /* Offset of LSB of register field. */ + const unsigned int mask; /* Mask value used to isolate register field. */ +} REGFIELD; + +/*! + * @brief This structure is used to identify the fields in the USBCNTRL_REG_0 hardware register. + * + * This structure lists all of the fields within the USBCNTRL_REG_0 hardware + * register. + */ +typedef struct { + REGFIELD FSENB; /*!< USB Full Speed Enable */ + REGFIELD USB_SUSPEND; /*!< USB Suspend Mode Enable */ + REGFIELD USB_PU; /*!< USB Pullup Enable */ + REGFIELD UDP_PD; /*!< USB Data Plus Pulldown Enable */ + REGFIELD UDM_PD; /*!< USB 150K UDP Pullup Enable */ + REGFIELD DP150K_PU; /*!< USB Pullup/Pulldown Override Enable */ + REGFIELD VBUSPDENB; /*!< USB VBUS Pulldown NMOS Switch Enable */ + REGFIELD CURRENT_LIMIT; /*!< USB Regulator Current Limit Setting-3 bits */ + REGFIELD DLP_SRP; /*!< USB Data Line Pulsing Timer Enable */ + REGFIELD SE0_CONN; /*!< USB Pullup Connect When SE0 Detected */ + REGFIELD USBXCVREN; /*!< USB Transceiver Enabled When INTERFACE_MODE[2:0]=000 and RESETB=high */ + REGFIELD PULLOVR; /*!< 1K5 Pullup and UDP/UDM Pulldown Disable When UTXENB=Low */ + REGFIELD INTERFACE_MODE; /*!< Connectivity Interface Mode Select-3 Bits */ + REGFIELD DATSE0; /*!< USB Single or Differential Mode Select */ + REGFIELD BIDIR; /*!< USB Unidirectional/Bidirectional Transmission */ + REGFIELD USBCNTRL; /*!< USB Mode of Operation controlled By USBEN/SPI Pin */ + REGFIELD IDPD; /*!< USB UID Pulldown Enable */ + REGFIELD IDPULSE; /*!< USB Pulse to Gnd on UID Line Generated */ + REGFIELD IDPUCNTRL; /*!< USB UID Pin pulled high By 5ua Curr Source */ + REGFIELD DMPULSE; /*!< USB Positive pulse on the UDM Line Generated */ +} USBCNTRL_REG_0; + +/*! + * @brief This variable is used to access the USBCNTRL_REG_0 hardware register. + * + * This variable defines how to access all of the fields within the + * USBCNTRL_REG_0 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const USBCNTRL_REG_0 regUSB0 = { + {0, 0x000001}, /*!< FSENB */ + {1, 0x000002}, /*!< USB_SUSPEND */ + {2, 0x000004}, /*!< USB_PU */ + {3, 0x000008}, /*!< UDP_PD */ + {4, 0x000010}, /*!< UDM_PD */ + {5, 0x000020}, /*!< DP150K_PU */ + {6, 0x000040}, /*!< VBUSPDENB */ + {7, 0x000380}, /*!< CURRENT_LIMIT */ + {10, 0x000400}, /*!< DLP_SRP */ + {11, 0x000800}, /*!< SE0_CONN */ + {12, 0x001000}, /*!< USBXCVREN */ + {13, 0x002000}, /*!< PULLOVR */ + {14, 0x01c000}, /*!< INTERFACE_MODE */ + {17, 0x020000}, /*!< DATSE0 */ + {18, 0x040000}, /*!< BIDIR */ + {19, 0x080000}, /*!< USBCNTRL */ + {20, 0x100000}, /*!< IDPD */ + {21, 0x200000}, /*!< IDPULSE */ + {22, 0x400000}, /*!< IDPUCNTRL */ + {23, 0x800000} /*!< DMPULSE */ + +}; + +/*! + * @brief This structure is used to identify the fields in the USBCNTRL_REG_1 hardware register. + * + * This structure lists all of the fields within the USBCNTRL_REG_1 hardware + * register. + */ +typedef struct { + REGFIELD VUSBIN; /*!< Controls The Input Source For VUSB */ + REGFIELD VUSB; /*!< VUSB Output Voltage Select-High=3.3V Low=2.775V */ + REGFIELD VUSBEN; /*!< VUSB Output Enable- */ + REGFIELD VBUSEN; /*!< VBUS Output Enable- */ + REGFIELD RSPOL; /*!< Low=RS232 TX on UDM, RX on UDP + High= RS232 TX on UDP, RX on UDM */ + REGFIELD RSTRI; /*!< TX Forced To Tristate in RS232 Mode Only */ + REGFIELD ID100kPU; /*!< 100k UID Pullup Enabled */ +} USBCNTRL_REG_1; + +/*! + * @brief This variable is used to access the USBCNTRL_REG_1 hardware register. + * + * This variable defines how to access all of the fields within the + * USBCNTRL_REG_1 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const USBCNTRL_REG_1 regUSB1 = { + {0, 0x000003}, /*!< VUSBIN-2 Bits */ + {2, 0x000004}, /*!< VUSB */ + {3, 0x000008}, /*!< VUSBEN */ + /*{4, 0x000010} *//*!< Reserved */ + {5, 0x000020}, /*!< VBUSEN */ + {6, 0x000040}, /*!< RSPOL */ + {7, 0x000080}, /*!< RSTRI */ + {8, 0x000100} /*!< ID100kPU */ + /*!< 9-23 Unused */ +}; + +/*! Define a mask to access the entire hardware register. */ +static const unsigned int REG_FULLMASK = 0xffffff; + +/*! Define the mc13783 USBCNTRL_REG_0 register power on reset state. */ +static const unsigned int RESET_USBCNTRL_REG_0 = 0x080060; + +/*! Define the mc13783 USBCNTRL_REG_1 register power on reset state. */ +static const unsigned int RESET_USBCNTRL_REG_1 = 0x000006; + +static pmic_event_callback_t eventNotify; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the connectivity driver. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_USB_SPEED usbSpeed; /*!< USB connection + speed. */ + PMIC_CONVITY_USB_MODE usbMode; /*!< USB connection + mode. */ + PMIC_CONVITY_USB_POWER_IN usbPowerIn; /*!< USB transceiver + power source. */ + PMIC_CONVITY_USB_POWER_OUT usbPowerOut; /*!< USB transceiver + power output + level. */ + PMIC_CONVITY_USB_TRANSCEIVER_MODE usbXcvrMode; /*!< USB transceiver + mode. */ + unsigned int usbDlpDuration; /*!< USB Data Line + Pulsing duration. */ + PMIC_CONVITY_USB_OTG_CONFIG usbOtgCfg; /*!< USB OTG + configuration + options. */ + PMIC_CONVITY_RS232_INTERNAL rs232CfgInternal; /*!< RS-232 internal + connections. */ + PMIC_CONVITY_RS232_EXTERNAL rs232CfgExternal; /*!< RS-232 external + connections. */ +} pmic_convity_state_struct; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in USB mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_USB_SPEED usbSpeed; /*!< USB connection + speed. */ + PMIC_CONVITY_USB_MODE usbMode; /*!< USB connection + mode. */ + PMIC_CONVITY_USB_POWER_IN usbPowerIn; /*!< USB transceiver + power source. */ + PMIC_CONVITY_USB_POWER_OUT usbPowerOut; /*!< USB transceiver + power output + level. */ + PMIC_CONVITY_USB_TRANSCEIVER_MODE usbXcvrMode; /*!< USB transceiver + mode. */ + unsigned int usbDlpDuration; /*!< USB Data Line + Pulsing duration. */ + PMIC_CONVITY_USB_OTG_CONFIG usbOtgCfg; /*!< USB OTG + configuration + options. */ +} pmic_convity_usb_state; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in RS_232 mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_RS232_INTERNAL rs232CfgInternal; /*!< RS-232 internal + connections. */ + PMIC_CONVITY_RS232_EXTERNAL rs232CfgExternal; /*!< RS-232 external + connections. */ +} pmic_convity_rs232_state; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in cea-936 mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + +} pmic_convity_cea936_state; + +/*! + * @brief Identifies the hardware interrupt source. + * + * This enumeration identifies which of the possible hardware interrupt + * sources actually caused the current interrupt handler to be called. + */ +typedef enum { + CORE_EVENT_4V4 = 1, /*!< Detected USB 4.4 V event. */ + CORE_EVENT_2V0 = 2, /*!< Detected USB 2.0 V event. */ + CORE_EVENT_0V8 = 4, /*!< Detected USB 0.8 V event. */ + CORE_EVENT_ABDET = 8 /*!< Detected USB mini A-B connector event. */ +} PMIC_CORE_EVENT; + +/*! + * @brief This structure defines the reset/power on state for the Connectivity driver. + */ +static const pmic_convity_state_struct reset = { + 0, + HANDLE_FREE, + USB, + NULL, + 0, + USB_FULL_SPEED, + USB_PERIPHERAL, + USB_POWER_INTERNAL, + USB_POWER_3V3, + USB_TRANSCEIVER_OFF, + 0, + USB_PULL_OVERRIDE | USB_VBUS_CURRENT_LIMIT_HIGH, + RS232_TX_USE0VM_RX_UDATVP, + RS232_TX_UDM_RX_UDP +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_usb_state usb = { + 0, + HANDLE_FREE, + USB, + NULL, + 0, + USB_FULL_SPEED, + USB_PERIPHERAL, + USB_POWER_INTERNAL, + USB_POWER_3V3, + USB_TRANSCEIVER_OFF, + 0, + USB_PULL_OVERRIDE | USB_VBUS_CURRENT_LIMIT_HIGH, +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_rs232_state rs_232 = { + 0, + HANDLE_FREE, + RS232_1, + NULL, + 0, + RS232_TX_USE0VM_RX_UDATVP, + RS232_TX_UDM_RX_UDP +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_cea936_state cea_936 = { + 0, + HANDLE_FREE, + CEA936_MONO, + NULL, + 0, +}; + +/*! + * @brief This spinlock is used to provide mutual exclusion. + * + * Create a spinlock that can be used to provide mutually exclusive + * read/write access to the globally accessible "convity" data structure + * that was defined above. Mutually exclusive access is required to + * ensure that the convity data structure is consistent at all times + * when possibly accessed by multiple threads of execution (for example, + * while simultaneously handling a user request and an interrupt event). + * + * We need to use a spinlock sometimes because we need to provide mutual + * exclusion while handling a hardware interrupt. + */ +static DEFINE_SPINLOCK(lock); + +/*! + * @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the Connectivity data structures are consistent at all + * times when possibly accessed by multiple threads of execution. + * + * Note that we use a mutex instead of the spinlock whenever disabling + * interrupts while in the critical section is not required. This helps + * to minimize kernel interrupt handling latency. + */ +static DECLARE_MUTEX(mutex); + +/* Prototype for the connectivity driver tasklet function. */ +static void pmic_convity_tasklet(struct work_struct *work); + +/*! + * @brief Tasklet handler for the connectivity driver. + * + * Declare a tasklet that will do most of the processing for all of the + * connectivity-related interrupt events (USB4.4VI, USB2.0VI, USB0.8VI, + * and AB_DETI). Note that we cannot do all of the required processing + * within the interrupt handler itself because we may need to call the + * ADC driver to measure voltages as well as calling any user-registered + * callback functions. + */ +DECLARE_WORK(convityTasklet, pmic_convity_tasklet); + +/*! + * @brief Global variable to track currently active interrupt events. + * + * This global variable is used to keep track of all of the currently + * active interrupt events for the connectivity driver. Note that access + * to this variable may occur while within an interrupt context and, + * therefore, must be guarded by using a spinlock. + */ +static PMIC_CORE_EVENT eventID; + +/* Prototypes for all static connectivity driver functions. */ +static PMIC_STATUS pmic_convity_set_mode_internal(const PMIC_CONVITY_MODE mode); +static PMIC_STATUS pmic_convity_deregister_all(void); +static void pmic_convity_event_handler(void *param); + +/************************************************************************** + * General setup and configuration functions. + ************************************************************************** + */ + +/*! + * @name General Setup and Configuration Connectivity APIs + * Functions for setting up and configuring the connectivity hardware. + */ +/*@{*/ + +/*! + * Attempt to open and gain exclusive access to the PMIC connectivity + * hardware. An initial operating mode must also be specified. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls. The + * same handle must also be used in the pmic_convity_close() call when use + * of the PMIC connectivity hardware is no longer required. + * + * @param handle device handle from open() call + * @param mode initial connectivity operating mode + * + * @return PMIC_SUCCESS if the open request was successful + */ +PMIC_STATUS pmic_convity_open(PMIC_CONVITY_HANDLE * const handle, + const PMIC_CONVITY_MODE mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + if (handle == (PMIC_CONVITY_HANDLE *) NULL) { + /* Do not dereference a NULL pointer. */ + return PMIC_ERROR; + } + + /* We only need to acquire a mutex here because the interrupt handler + * never modifies the device handle or device handle state. Therefore, + * we don't need to worry about conflicts with the interrupt handler + * or the need to execute in an interrupt context. + * + * But we do need a critical section here to avoid problems in case + * multiple calls to pmic_convity_open() are made since we can only + * allow one of them to succeed. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Check the current device handle state and acquire the handle if + * it is available. + */ + if ((usb.handle_state != HANDLE_FREE) + && (rs_232.handle_state != HANDLE_FREE) + && (cea_936.handle_state != HANDLE_FREE)) { + + /* Cannot open the PMIC connectivity hardware at this time or an invalid + * mode was requested. + */ + *handle = reset.handle; + } else { + + if (mode == USB) { + usb.handle = (PMIC_CONVITY_HANDLE) (&usb); + usb.handle_state = HANDLE_IN_USE; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + rs_232.handle = (PMIC_CONVITY_HANDLE) (&rs_232); + rs_232.handle_state = HANDLE_IN_USE; + } else if ((mode == CEA936_STEREO) || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + cea_936.handle = (PMIC_CONVITY_HANDLE) (&cea_936); + cea_936.handle_state = HANDLE_IN_USE; + + } + /* Let's begin by acquiring the connectivity device handle. */ + /* Then we can try to set the desired operating mode. */ + rc = pmic_convity_set_mode_internal(mode); + + if (rc == PMIC_SUCCESS) { + /* Successfully set the desired operating mode, now return the + * handle to the caller. + */ + if (mode == USB) { + *handle = usb.handle; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + *handle = rs_232.handle; + } else if ((mode == CEA936_STEREO) + || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + *handle = cea_936.handle; + } + } else { + /* Failed to set the desired mode, return the handle to an unused + * state. + */ + if (mode == USB) { + usb.handle = reset.handle; + usb.handle_state = reset.handle_state; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + rs_232.handle = reset.handle; + rs_232.handle_state = reset.handle_state; + } else if ((mode == CEA936_STEREO) + || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + cea_936.handle = reset.handle; + cea_936.handle_state = reset.handle_state; + } + + *handle = reset.handle; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Terminate further access to the PMIC connectivity hardware. Also allows + * another process to call pmic_convity_open() to gain access. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the close request was successful + */ +PMIC_STATUS pmic_convity_close(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Begin a critical section here to avoid the possibility of race + * conditions if multiple threads happen to call this function and + * pmic_convity_open() at the same time. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Confirm that the device handle matches the one assigned in the + * pmic_convity_open() call and then close the connection. + */ + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + rc = PMIC_SUCCESS; + + /* Deregister for all existing callbacks if necessary and make sure + * that the event handling settings are consistent following the + * close operation. + */ + if ((usb.callback != reset.callback) + || (rs_232.callback != reset.callback) + || (cea_936.callback != reset.callback)) { + /* Deregister the existing callback function and all registered + * events before we completely close the handle. + */ + rc = pmic_convity_deregister_all(); + if (rc == PMIC_SUCCESS) { + + } else if (usb.eventMask != reset.eventMask) { + /* Having a non-zero eventMask without a callback function being + * defined should never occur but let's just make sure here that + * we keep things consistent. + */ + usb.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + usb.handle = reset.handle; + usb.handle_state = reset.handle_state; + + } else if (rs_232.eventMask != reset.eventMask) { + + rs_232.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + rs_232.handle = reset.handle; + rs_232.handle_state = reset.handle_state; + + } else if (cea_936.eventMask != reset.eventMask) { + cea_936.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + cea_936.handle = reset.handle; + cea_936.handle_state = reset.handle_state; + + } + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Change the current operating mode of the PMIC connectivity hardware. + * The available connectivity operating modes is hardware dependent and + * consists of one or more of the following: USB (including USB On-the-Go), + * RS-232, and CEA-936. Requesting an operating mode that is not supported + * by the PMIC hardware will return PMIC_NOT_SUPPORTED. + * + * @param handle device handle from + open() call + * @param mode desired operating mode + * + * @return PMIC_SUCCESS if the requested mode was successfully set + */ +PMIC_STATUS pmic_convity_set_mode(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_MODE mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + rc = pmic_convity_set_mode_internal(mode); + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current operating mode for the PMIC connectivity hardware. + * + * @param handle device handle from open() call + * @param mode the current PMIC connectivity operating mode + * + * @return PMIC_SUCCESS if the requested mode was successfully set + */ +PMIC_STATUS pmic_convity_get_mode(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_MODE * const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232. + handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) + && (mode != (PMIC_CONVITY_MODE *) NULL)) { + + *mode = usb.mode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Restore all registers to the initial power-on/reset state. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the reset was successful + */ +PMIC_STATUS pmic_convity_reset(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + + /* Reset the PMIC Connectivity register to it's power on state. */ + rc = pmic_write_reg(REG_USB, RESET_USBCNTRL_REG_0, + REG_FULLMASK); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + RESET_USBCNTRL_REG_1, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + /* Also reset the device driver state data structure. */ + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Register a callback function that will be used to signal PMIC connectivity + * events. For example, the USB subsystem should register a callback function + * in order to be notified of device connect/disconnect events. Note, however, + * that non-USB events may also be signalled depending upon the PMIC hardware + * capabilities. Therefore, the callback function must be able to properly + * handle all of the possible events if support for non-USB peripherals is + * also to be included. + * + * @param handle device handle from open() call + * @param func a pointer to the callback function + * @param eventMask a mask selecting events to be notified + * + * @return PMIC_SUCCESS if the callback was successful registered + */ +PMIC_STATUS pmic_convity_set_callback(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_CALLBACK func, + const PMIC_CONVITY_EVENTS eventMask) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_ERROR; + + /* We need to start a critical section here to ensure a consistent state + * in case simultaneous calls to pmic_convity_set_callback() are made. In + * that case, we must serialize the calls to ensure that the "callback" + * and "eventMask" state variables are always consistent. + * + * Note that we don't actually need to acquire the spinlock until later + * when we are finally ready to update the "callback" and "eventMask" + * state variables which are shared with the interrupt handler. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + /* Return an error if either the callback function or event mask + * is not properly defined. + * + * It is also considered an error if a callback function has already + * been defined. If you wish to register for a new set of events, + * then you must first call pmic_convity_clear_callback() to + * deregister the existing callback function and list of events + * before trying to register a new callback function. + */ + if ((func == NULL) || (eventMask == 0) + || (usb.callback != NULL)) { + rc = PMIC_ERROR; + + /* Register for PMIC events from the core protocol driver. */ + } else { + + if ((eventMask & USB_DETECT_4V4_RISE) || + (eventMask & USB_DETECT_4V4_FALL)) { + /* We need to register for the 4.4V interrupt. + EVENT_USBI or EVENT_USB_44VI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + return rc; + } + } + + if ((eventMask & USB_DETECT_2V0_RISE) || + (eventMask & USB_DETECT_2V0_FALL)) { + /* We need to register for the 2.0V interrupt. + EVENT_USB_20VI or EVENT_USBI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_4V4; + } + } + + if ((eventMask & USB_DETECT_0V8_RISE) || + (eventMask & USB_DETECT_0V8_FALL)) { + /* We need to register for the 0.8V interrupt. + EVENT_USB_08VI or EVENT_USBI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_2V0; + } + } + + if ((eventMask & USB_DETECT_MINI_A) || + (eventMask & USB_DETECT_MINI_B) + || (eventMask & USB_DETECT_NON_USB_ACCESSORY) + || (eventMask & USB_DETECT_FACTORY_MODE)) { + /* We need to register for the AB_DET interrupt. + EVENT_AB_DETI or EVENT_IDI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_ABDET); + rc = pmic_event_subscribe(EVENT_IDI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_0V8; + } + } + + /* Use a critical section to maintain a consistent state. */ + spin_lock_irqsave(&lock, flags); + + /* Successfully registered for all events. */ + usb.callback = func; + usb.eventMask = eventMask; + spin_unlock_irqrestore(&lock, flags); + + goto End; + + /* This section unregisters any already registered events if we should + * encounter an error partway through the registration process. Note + * that we don't check the return status here since it is already set + * to PMIC_ERROR before we get here. + */ + Cleanup_0V8: + + if ((eventMask & USB_DETECT_0V8_RISE) || + (eventMask & USB_DETECT_0V8_FALL)) { + /* EVENT_USB_08VI or EVENT_USBI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + goto End; + } + + Cleanup_2V0: + + if ((eventMask & USB_DETECT_2V0_RISE) || + (eventMask & USB_DETECT_2V0_FALL)) { + /* EVENT_USB_20VI or EVENT_USBI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + goto End; + } + + Cleanup_4V4: + + if ((eventMask & USB_DETECT_4V4_RISE) || + (eventMask & USB_DETECT_4V4_FALL)) { + /* EVENT_USB_44VI or EVENT_USBI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + } + } + /* Exit the critical section. */ + + } + End:up(&mutex); + return rc; + +} + +/*! + * Clears the current callback function. If this function returns successfully + * then all future Connectivity events will only be handled by the default + * handler within the Connectivity driver. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the callback was successful cleared + */ +PMIC_STATUS pmic_convity_clear_callback(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + + rc = pmic_convity_deregister_all(); + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current callback function and event mask. + * + * @param handle device handle from open() call + * @param func the current callback function + * @param eventMask the current event selection mask + * + * @return PMIC_SUCCESS if the callback information was successful + * retrieved + */ +PMIC_STATUS pmic_convity_get_callback(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_CALLBACK * const func, + PMIC_CONVITY_EVENTS * const eventMask) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if ((((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232. + handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) + && (func != (PMIC_CONVITY_CALLBACK *) NULL) + && (eventMask != (PMIC_CONVITY_EVENTS *) NULL)) { + *func = usb.callback; + *eventMask = usb.eventMask; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + + up(&mutex); + + return rc; +} + +/*@*/ + +/************************************************************************** + * USB-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name USB and USB-OTG Connectivity APIs + * Functions for controlling USB and USB-OTG connectivity. + */ +/*@{*/ + +/*! + * Set the USB transceiver speed. + * + * @param handle device handle from open() call + * @param speed the desired USB transceiver speed + * + * @return PMIC_SUCCESS if the transceiver speed was successfully set + */ +PMIC_STATUS pmic_convity_usb_set_speed(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_SPEED speed) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = SET_BITS(regUSB0, FSENB, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (handle == (rs_232.handle || cea_936.handle)) { + return PMIC_PARAMETER_ERROR; + } else { + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) { + /* Validate the function parameters and if they are valid, then + * configure the pull-up and pull-down resistors as required for + * the desired operating mode. + */ + if ((speed == USB_HIGH_SPEED)) { + /* + * The USB transceiver also does not support the high speed mode + * (which is also optional under the USB OTG specification). + */ + rc = PMIC_NOT_SUPPORTED; + } else if ((speed != USB_LOW_SPEED) + && (speed != USB_FULL_SPEED)) { + /* Final validity check on the speed parameter. */ + rc = PMIC_ERROR;; + } else { + /* First configure the D+ and D- pull-up/pull-down resistors as + * per the USB OTG specification. + */ + if (speed == USB_FULL_SPEED) { + /* Activate pull-up on D+ and pull-down on D-. */ + reg_value = + SET_BITS(regUSB0, UDM_PD, 1); + } else if (speed == USB_LOW_SPEED) { + /* Activate pull-up on D+ and pull-down on D-. */ + reg_value = SET_BITS(regUSB0, FSENB, 1); + } + + /* Now set the desired USB transceiver speed. Note that + * USB_FULL_SPEED simply requires FSENB=0 (which it + * already is). + */ + + rc = pmic_write_reg(REG_USB, reg_value, + reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbSpeed = speed; + } + } + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver speed. + * + * @param handle device handle from open() call + * @param speed the current USB transceiver speed + * @param mode the current USB transceiver mode + * + * @return PMIC_SUCCESS if the transceiver speed was successfully + * obtained + */ +PMIC_STATUS pmic_convity_usb_get_speed(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_SPEED * const speed, + PMIC_CONVITY_USB_MODE * const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (speed != (PMIC_CONVITY_USB_SPEED *) NULL) && + (mode != (PMIC_CONVITY_USB_MODE *) NULL)) { + *speed = usb.usbSpeed; + *mode = usb.usbMode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * This function enables/disables VUSB and VBUS output. + * This API configures the VUSBEN and VBUSEN bits of USB register + * + * @param handle device handle from open() call + * @param out_type true, for VBUS + * false, for VUSB + * @param out if true, output is enabled + * if false, output is disabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_convity_set_output(const PMIC_CONVITY_HANDLE handle, + bool out_type, bool out) +{ + + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if ((out_type == 0) && (out == 1)) { + + reg_value = SET_BITS(regUSB1, VUSBEN, 1); + reg_mask = SET_BITS(regUSB1, VUSBEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } else if (out_type == 0 && out == 0) { + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } else if (out_type == 1 && out == 1) { + + reg_value = SET_BITS(regUSB1, VBUSEN, 1); + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } + + else if (out_type == 1 && out == 0) { + + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } + + /*else { + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } */ + } + + up(&mutex); + + return rc; +} + +/*! + * Set the USB transceiver's power supply configuration. + * + * @param handle device handle from open() call + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @return PMIC_SUCCESS if the USB transceiver's power supply + * configuration was successfully set + */ +PMIC_STATUS pmic_convity_usb_set_power_source(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_POWER_IN + pwrin, + const PMIC_CONVITY_USB_POWER_OUT + pwrout) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if (pwrin == USB_POWER_INTERNAL_BOOST) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 0); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } else if (pwrin == USB_POWER_VBUS) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 1); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } + + else if (pwrin == USB_POWER_INTERNAL) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 2); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } + + if (pwrout == USB_POWER_3V3) { + reg_value |= SET_BITS(regUSB1, VUSB, 1); + reg_mask |= SET_BITS(regUSB1, VUSB, 1); + } + + else if (pwrout == USB_POWER_2V775) { + reg_value |= SET_BITS(regUSB1, VUSB, 0); + reg_mask |= SET_BITS(regUSB1, VUSB, 1); + } + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbPowerIn = pwrin; + usb.usbPowerOut = pwrout; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver's current power supply configuration. + * + * @param handle device handle from open() call + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @return PMIC_SUCCESS if the USB transceiver's power supply + * configuration was successfully retrieved + */ +PMIC_STATUS pmic_convity_usb_get_power_source(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_POWER_IN * + const pwrin, + PMIC_CONVITY_USB_POWER_OUT * + const pwrout) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (pwrin != (PMIC_CONVITY_USB_POWER_IN *) NULL) && + (pwrout != (PMIC_CONVITY_USB_POWER_OUT *) NULL)) { + *pwrin = usb.usbPowerIn; + *pwrout = usb.usbPowerOut; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Set the USB transceiver's operating mode. + * + * @param handle device handle from open() call + * @param mode desired operating mode + * + * @return PMIC_SUCCESS if the USB transceiver's operating mode + * was successfully configured + */ +PMIC_STATUS pmic_convity_usb_set_xcvr(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_TRANSCEIVER_MODE + mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if (mode == USB_TRANSCEIVER_OFF) { + reg_value = SET_BITS(regUSB0, USBXCVREN, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + } + + if (mode == USB_SINGLE_ENDED_UNIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 1) | SET_BITS(regUSB0, + BIDIR, 0); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_SINGLE_ENDED_BIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 1) | SET_BITS(regUSB0, + BIDIR, 1); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_DIFFERENTIAL_UNIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 0) | SET_BITS(regUSB0, + BIDIR, 0); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_DIFFERENTIAL_BIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 0) | SET_BITS(regUSB0, + BIDIR, 1); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } + + if (mode == USB_SUSPEND_ON) { + reg_value |= SET_BITS(regUSB0, USB_SUSPEND, 1); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } else if (mode == USB_SUSPEND_OFF) { + reg_value |= SET_BITS(regUSB0, USB_SUSPEND, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } + + if (mode == USB_OTG_SRP_DLP_START) { + reg_value |= SET_BITS(regUSB0, USB_PU, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } else if (mode == USB_OTG_SRP_DLP_STOP) { + reg_value &= SET_BITS(regUSB0, USB_PU, 1); + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbXcvrMode = mode; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver's current operating mode. + * + * @param handle device handle from open() call + * @param mode current operating mode + * + * @return PMIC_SUCCESS if the USB transceiver's operating mode + * was successfully retrieved + */ +PMIC_STATUS pmic_convity_usb_get_xcvr(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_TRANSCEIVER_MODE * + const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (mode != (PMIC_CONVITY_USB_TRANSCEIVER_MODE *) NULL)) { + *mode = usb.usbXcvrMode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Set the Data Line Pulse duration (in milliseconds) for the USB OTG + * Session Request Protocol. + * + * For mc13783, this feature is not supported.So return PMIC_NOT_SUPPORTED + * + * @param handle device handle from open() call + * @param duration the data line pulse duration (ms) + * + * @return PMIC_SUCCESS if the pulse duration was successfully set + */ +PMIC_STATUS pmic_convity_usb_otg_set_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + const unsigned int duration) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + /* The setting of the dlp duration is not supported by the mc13783 PMIC hardware. */ + + /* No critical section is required. */ + + if ((handle != usb.handle) + || (usb.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } + + return rc; +} + +/*! + * Get the current Data Line Pulse duration (in milliseconds) for the USB + * OTG Session Request Protocol. + * + * @param handle device handle from open() call + * @param duration the data line pulse duration (ms) + * + * @return PMIC_SUCCESS if the pulse duration was successfully obtained + */ +PMIC_STATUS pmic_convity_usb_otg_get_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + unsigned int *const duration) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + /* The setting of dlp duration is not supported by the mc13783 PMIC hardware. */ + + /* No critical section is required. */ + + if ((handle != usb.handle) + || (usb.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } + + return rc; +} + +/*! + * Set the USB On-The-Go (OTG) configuration. + * + * @param handle device handle from open() call + * @param cfg desired USB OTG configuration + * + * @return PMIC_SUCCESS if the OTG configuration was successfully set + */ +PMIC_STATUS pmic_convity_usb_otg_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + if (cfg & USB_OTG_SE0CONN) { + reg_value = SET_BITS(regUSB0, SE0_CONN, 1); + reg_mask = SET_BITS(regUSB0, SE0_CONN, 1); + } + if (cfg & USBXCVREN) { + reg_value |= SET_BITS(regUSB0, USBXCVREN, 1); + reg_mask |= SET_BITS(regUSB0, USBXCVREN, 1); + } + + if (cfg & USB_OTG_DLP_SRP) { + reg_value |= SET_BITS(regUSB0, DLP_SRP, 1); + reg_mask |= SET_BITS(regUSB0, DLP_SRP, 1); + } + + if (cfg & USB_PULL_OVERRIDE) { + reg_value |= SET_BITS(regUSB0, PULLOVR, 1); + reg_mask |= SET_BITS(regUSB0, PULLOVR, 1); + } + + if (cfg & USB_PU) { + reg_value |= SET_BITS(regUSB0, USB_PU, 1); + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + if (cfg & USB_UDM_PD) { + reg_value |= SET_BITS(regUSB0, UDM_PD, 1); + reg_mask |= SET_BITS(regUSB0, UDM_PD, 1); + } + + if (cfg & USB_UDP_PD) { + reg_value |= SET_BITS(regUSB0, UDP_PD, 1); + reg_mask |= SET_BITS(regUSB0, UDP_PD, 1); + } + + if (cfg & USB_DP150K_PU) { + reg_value |= SET_BITS(regUSB0, DP150K_PU, 1); + reg_mask |= SET_BITS(regUSB0, DP150K_PU, 1); + } + + if (cfg & USB_USBCNTRL) { + reg_value |= SET_BITS(regUSB0, USBCNTRL, 1); + reg_mask |= SET_BITS(regUSB0, USBCNTRL, 1); + } + + if (cfg & USB_VBUS_CURRENT_LIMIT_HIGH) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 0); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 1); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 2); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 3); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 4); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 5); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 6); + } + if (cfg & USB_VBUS_CURRENT_LIMIT_LOW) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 7); + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 7); + } + + if (cfg & USB_VBUS_PULLDOWN) { + reg_value |= SET_BITS(regUSB0, VBUSPDENB, 1); + reg_mask |= SET_BITS(regUSB0, VBUSPDENB, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + if ((cfg & USB_VBUS_CURRENT_LIMIT_HIGH) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS)) { + /* Make sure that the VBUS current limit state is + * correctly set to either USB_VBUS_CURRENT_LIMIT_HIGH + * or USB_VBUS_CURRENT_LIMIT_LOW but never both at the + * same time. + * + * We guarantee this by first clearing both of the + * status bits and then resetting the correct one. + */ + usb.usbOtgCfg &= + ~(USB_VBUS_CURRENT_LIMIT_HIGH | + USB_VBUS_CURRENT_LIMIT_LOW | + USB_VBUS_CURRENT_LIMIT_LOW_10MS | + USB_VBUS_CURRENT_LIMIT_LOW_20MS | + USB_VBUS_CURRENT_LIMIT_LOW_30MS | + USB_VBUS_CURRENT_LIMIT_LOW_40MS | + USB_VBUS_CURRENT_LIMIT_LOW_50MS | + USB_VBUS_CURRENT_LIMIT_LOW_60MS); + } + + usb.usbOtgCfg |= cfg; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Clears the USB On-The-Go (OTG) configuration. Multiple configuration settings + * may be OR'd together in a single call. However, selecting conflicting + * settings (e.g., multiple VBUS current limits) will result in undefined + * behavior. + * + * @param handle Device handle from open() call. + * @param cfg USB OTG configuration settings to be cleared. + * + * @retval PMIC_SUCCESS If the OTG configuration was successfully + * cleared. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired USB OTG configuration is + * not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_usb_otg_clear_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + /* if ((cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS)) + { + rc = PMIC_NOT_SUPPORTED; + } */ + + if (cfg & USB_OTG_SE0CONN) { + reg_mask = SET_BITS(regUSB0, SE0_CONN, 1); + } + + if (cfg & USB_OTG_DLP_SRP) { + reg_mask |= SET_BITS(regUSB0, DLP_SRP, 1); + } + + if (cfg & USB_DP150K_PU) { + reg_mask |= SET_BITS(regUSB0, DP150K_PU, 1); + } + + if (cfg & USB_PULL_OVERRIDE) { + reg_mask |= SET_BITS(regUSB0, PULLOVR, 1); + } + + if (cfg & USB_PU) { + + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + if (cfg & USB_UDM_PD) { + + reg_mask |= SET_BITS(regUSB0, UDM_PD, 1); + } + + if (cfg & USB_UDP_PD) { + + reg_mask |= SET_BITS(regUSB0, UDP_PD, 1); + } + + if (cfg & USB_USBCNTRL) { + reg_mask |= SET_BITS(regUSB0, USBCNTRL, 1); + } + + if (cfg & USB_VBUS_CURRENT_LIMIT_HIGH) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 0); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 1); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 2); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 3); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 4); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 5); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 6); + } + + if (cfg & USB_VBUS_PULLDOWN) { + reg_mask |= SET_BITS(regUSB0, VBUSPDENB, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbOtgCfg &= ~cfg; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current USB On-The-Go (OTG) configuration. + * + * @param handle device handle from open() call + * @param cfg the current USB OTG configuration + * + * @return PMIC_SUCCESS if the OTG configuration was successfully + * retrieved + */ +PMIC_STATUS pmic_convity_usb_otg_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_OTG_CONFIG * + const cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (cfg != (PMIC_CONVITY_USB_OTG_CONFIG *) NULL)) { + *cfg = usb.usbOtgCfg; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * RS-232-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name RS-232 Serial Connectivity APIs + * Functions for controlling RS-232 serial connectivity. + */ +/*@{*/ + +/*! + * Set the connectivity interface to the selected RS-232 operating + * configuration. Note that the RS-232 operating mode will be automatically + * overridden if the USB_EN is asserted at any time (e.g., when a USB device + * is attached). However, we will also automatically return to the RS-232 + * mode once the USB device is detached. + * + * @param handle device handle from open() call + * @param cfgInternal RS-232 transceiver internal connections + * @param cfgExternal RS-232 transceiver external connections + * + * @return PMIC_SUCCESS if the requested mode was set + */ +PMIC_STATUS pmic_convity_rs232_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_RS232_INTERNAL + cfgInternal, + const PMIC_CONVITY_RS232_EXTERNAL + cfgExternal) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value0 = 0, reg_value1 = 0; + unsigned int reg_mask = SET_BITS(regUSB1, RSPOL, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == rs_232.handle) && (rs_232.handle_state == HANDLE_IN_USE)) { + rc = PMIC_SUCCESS; + + /* Validate the calling parameters. */ + /*if ((cfgInternal != RS232_TX_USE0VM_RX_UDATVP) && + (cfgInternal != RS232_TX_RX_INTERNAL_DEFAULT) && (cfgInternal != RS232_TX_UDATVP_RX_URXVM)) + { + + rc = PMIC_NOT_SUPPORTED; + } */ + if (cfgInternal == RS232_TX_USE0VM_RX_UDATVP) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 1); + + } else if (cfgInternal == RS232_TX_RX_INTERNAL_DEFAULT) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 1); + reg_mask |= SET_BITS(regUSB1, RSPOL, 1); + + } else if (cfgInternal == RS232_TX_UDATVP_RX_URXVM) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 2); + reg_value1 |= SET_BITS(regUSB1, RSPOL, 1); + + } else if ((cfgExternal == RS232_TX_UDM_RX_UDP) || + (cfgExternal == RS232_TX_RX_EXTERNAL_DEFAULT)) { + /* Configure for TX on D+ and RX on D-. */ + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 1); + reg_value1 |= SET_BITS(regUSB1, RSPOL, 0); + } else if (cfgExternal != RS232_TX_UDM_RX_UDP) { + /* Any other RS-232 configuration is an error. */ + rc = PMIC_ERROR; + } + + if (rc == PMIC_SUCCESS) { + /* Configure for TX on D- and RX on D+. */ + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value1, reg_mask); + + if (rc == PMIC_SUCCESS) { + rs_232.rs232CfgInternal = cfgInternal; + rs_232.rs232CfgExternal = cfgExternal; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the connectivity interface's current RS-232 operating configuration. + * + * @param handle device handle from open() call + * @param cfgInternal RS-232 transceiver internal connections + * @param cfgExternal RS-232 transceiver external connections + * + * @return PMIC_SUCCESS if the requested mode was retrieved + */ +PMIC_STATUS pmic_convity_rs232_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_RS232_INTERNAL * + const cfgInternal, + PMIC_CONVITY_RS232_EXTERNAL * + const cfgExternal) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == rs_232.handle) && + (rs_232.handle_state == HANDLE_IN_USE) && + (cfgInternal != (PMIC_CONVITY_RS232_INTERNAL *) NULL) && + (cfgExternal != (PMIC_CONVITY_RS232_EXTERNAL *) NULL)) { + *cfgInternal = rs_232.rs232CfgInternal; + *cfgExternal = rs_232.rs232CfgExternal; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * CEA-936-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name CEA-936 Connectivity APIs + * Functions for controlling CEA-936 connectivity. + */ +/*@{*/ + +/*! + * Signal the attached device to exit the current CEA-936 operating mode. + * Returns an error if the current operating mode is not CEA-936. + * + * @param handle device handle from open() call + * @param signal type of exit signal to be sent + * + * @return PMIC_SUCCESS if exit signal was sent + */ +PMIC_STATUS pmic_convity_cea936_exit_signal(const PMIC_CONVITY_HANDLE handle, + const + PMIC_CONVITY_CEA936_EXIT_SIGNAL + signal) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = + SET_BITS(regUSB0, IDPD, 1) | SET_BITS(regUSB0, IDPULSE, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle != cea_936.handle) + || (cea_936.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } else if (signal == CEA936_UID_PULLDOWN_6MS) { + reg_value = + SET_BITS(regUSB0, IDPULSE, 0) | SET_BITS(regUSB0, IDPD, 0); + } else if (signal == CEA936_UID_PULLDOWN_6MS) { + reg_value = SET_BITS(regUSB0, IDPULSE, 1); + } else if (signal == CEA936_UID_PULLDOWN) { + reg_value = SET_BITS(regUSB0, IDPD, 1); + } else if (signal == CEA936_UDMPULSE) { + reg_value = SET_BITS(regUSB0, DMPULSE, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * Static functions. + ************************************************************************** + */ + +/*! + * @name Connectivity Driver Internal Support Functions + * These non-exported internal functions are used to support the functionality + * of the exported connectivity APIs. + */ +/*@{*/ + +/*! + * This internal helper function sets the desired operating mode (either USB + * OTG or RS-232). It must be called with the mutex already acquired. + * + * @param mode the desired operating mode (USB or RS232) + * + * @return PMIC_SUCCESS if the desired operating mode was set + * @return PMIC_NOT_SUPPORTED if the desired operating mode is invalid + */ +static PMIC_STATUS pmic_convity_set_mode_internal(const PMIC_CONVITY_MODE mode) +{ + unsigned int reg_value0 = 0, reg_value1 = 0; + unsigned int reg_mask0 = 0, reg_mask1 = 0; + + PMIC_STATUS rc = PMIC_SUCCESS; + + switch (mode) { + case USB: + /* For the USB mode, we start by tri-stating the USB bus (by + * setting VBUSEN = 0) until a device is connected (i.e., + * until we receive a 4.4V rising edge event). All pull-up + * and pull-down resistors are also disabled until a USB + * device is actually connected and we have determined which + * device is the host and the desired USB bus speed. + * + * Also tri-state the RS-232 buffers (by setting RSTRI = 1). + * This prevents the hardware from automatically returning to + * the RS-232 mode when the USB device is detached. + */ + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 0); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + /*reg_value1 = SET_BITS(regUSB1, RSTRI, 1); */ + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + /* if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } */ + + break; + + case RS232_1: + /* For the RS-232 mode, we tri-state the USB bus (by setting + * VBUSEN = 0) and enable the RS-232 transceiver (by setting + * RS232ENB = 0). + * + * Note that even in the RS-232 mode, if a USB device is + * plugged in, we will receive a 4.4V rising edge event which + * will automatically disable the RS-232 transceiver and + * tri-state the RS-232 buffers. This allows us to temporarily + * switch over to USB mode while the USB device is attached. + * The RS-232 transceiver and buffers will be automatically + * re-enabled when the USB device is detached. + */ + + /* Explicitly disconnect all of the USB pull-down resistors + * and the VUSB power regulator here just to be safe. + * + * But we do connect the internal pull-up resistor on USB_D+ + * to avoid having an extra load on the USB_D+ line when in + * RS-232 mode. + */ + + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 1) | + SET_BITS(regUSB0, VBUSPDENB, 1) | + SET_BITS(regUSB0, USB_PU, 1); + reg_mask0 = + SET_BITS(regUSB0, INTERFACE_MODE, 7) | SET_BITS(regUSB0, + VBUSPDENB, + 1) | + SET_BITS(regUSB0, USB_PU, 1); + + reg_value1 = SET_BITS(regUSB1, RSPOL, 0); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + + if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } + break; + + case RS232_2: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 2) | + SET_BITS(regUSB0, VBUSPDENB, 1) | + SET_BITS(regUSB0, USB_PU, 1); + reg_mask0 = + SET_BITS(regUSB0, INTERFACE_MODE, 7) | SET_BITS(regUSB0, + VBUSPDENB, + 1) | + SET_BITS(regUSB0, USB_PU, 1); + + reg_value1 = SET_BITS(regUSB1, RSPOL, 1); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + + if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } + break; + + case CEA936_MONO: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 4); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_STEREO: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 5); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_TEST_RIGHT: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 6); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_TEST_LEFT: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 7); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + default: + rc = PMIC_NOT_SUPPORTED; + } + + if (rc == PMIC_SUCCESS) { + if (mode == USB) { + usb.mode = mode; + } else if ((mode == RS232_1) || (mode == RS232_1)) { + rs_232.mode = mode; + } else if ((mode == CEA936_MONO) || (mode == CEA936_STEREO) || + (mode == CEA936_TEST_RIGHT) + || (mode == CEA936_TEST_LEFT)) { + cea_936.mode = mode; + } + } + + return rc; +} + +/*! + * This internal helper function deregisters all of the currently registered + * callback events. It must be called with the mutual exclusion spinlock + * already acquired. + * + * We've defined the event and callback deregistration code here as a separate + * function because it can be called by either the pmic_convity_close() or the + * pmic_convity_clear_callback() APIs. We also wanted to avoid any possible + * issues with having the same thread calling spin_lock_irq() twice. + * + * Note that the mutex must have already been acquired. We will also acquire + * the spinlock here to avoid any possible race conditions with the interrupt + * handler. + * + * @return PMIC_SUCCESS if all of the callback events were cleared + */ +static PMIC_STATUS pmic_convity_deregister_all(void) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_SUCCESS; + + /* Deregister each of the PMIC events that we had previously + * registered for by using pmic_event_subscribe(). + */ + + if ((usb.eventMask & USB_DETECT_MINI_A) || + (usb.eventMask & USB_DETECT_MINI_B) || + (usb.eventMask & USB_DETECT_NON_USB_ACCESSORY) || + (usb.eventMask & USB_DETECT_FACTORY_MODE)) { + /* EVENT_AB_DETI or EVENT_IDI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_ABDET); + + if (pmic_event_unsubscribe(EVENT_IDI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_MINI_A | + USB_DETECT_MINI_B | + USB_DETECT_NON_USB_ACCESSORY | + USB_DETECT_FACTORY_MODE); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_AB_DETI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + else if ((usb.eventMask & USB_DETECT_0V8_RISE) || + (usb.eventMask & USB_DETECT_0V8_FALL)) { + /* EVENT_USB_08VI or EVENT_USBI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_0V8_RISE | + USB_DETECT_0V8_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_08VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + + } + + else if ((usb.eventMask & USB_DETECT_2V0_RISE) || + (usb.eventMask & USB_DETECT_2V0_FALL)) { + /* EVENT_USB_20VI or EVENT_USBI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_2V0_RISE | + USB_DETECT_2V0_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_20VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + else if ((usb.eventMask & USB_DETECT_4V4_RISE) || + (usb.eventMask & USB_DETECT_4V4_FALL)) { + + /* EVENT_USB_44VI or EVENT_USBI */ + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_4V4_RISE | + USB_DETECT_4V4_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_44VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + if (rc == PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + /* Restore the initial reset values for the callback function + * and event mask parameters. This should be NULL and zero, + * respectively. + * + * Note that we wait until the end here to fully reset everything + * just in case some of the pmic_event_unsubscribe() calls above + * failed for some reason (which normally shouldn't happen). + */ + usb.callback = reset.callback; + usb.eventMask = reset.eventMask; + + spin_unlock_irqrestore(&lock, flags); + } + return rc; +} + +/*! + * This is the default event handler for all connectivity-related events + * and hardware interrupts. + * + * @param param event ID + */ +static void pmic_convity_event_handler(void *param) +{ + unsigned long flags; + + /* Update the global list of active interrupt events. */ + spin_lock_irqsave(&lock, flags); + eventID |= (PMIC_CORE_EVENT) (param); + spin_unlock_irqrestore(&lock, flags); + + /* Schedule the tasklet to be run as soon as it is convenient to do so. */ + schedule_work(&convityTasklet); +} + +/*! + * @brief This is the connectivity driver tasklet that handles interrupt events. + * + * This function is scheduled by the connectivity driver interrupt handler + * pmic_convity_event_handler() to complete the processing of all of the + * connectivity-related interrupt events. + * + * Since this tasklet runs with interrupts enabled, we can safely call + * the ADC driver, if necessary, to properly detect the type of USB connection + * that is being made and to call any user-registered callback functions. + * + * @param arg The parameter that was provided above in + * the DECLARE_TASKLET() macro (unused). + */ +static void pmic_convity_tasklet(struct work_struct *work) +{ + + PMIC_CONVITY_EVENTS activeEvents = 0; + unsigned long flags = 0; + + /* Check the interrupt sense bits to determine exactly what + * event just occurred. + */ + if (eventID & CORE_EVENT_4V4) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_4V4; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB4V4S) ? + USB_DETECT_4V4_RISE : USB_DETECT_4V4_FALL; + + if (activeEvents & ~usb.eventMask) { + /* The default handler for 4.4 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_2V0) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_2V0; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB2V0S) ? + USB_DETECT_2V0_RISE : USB_DETECT_2V0_FALL; + if (activeEvents & ~usb.eventMask) { + /* The default handler for 2.0 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_0V8) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_0V8; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB0V8S) ? + USB_DETECT_0V8_RISE : USB_DETECT_0V8_FALL; + + if (activeEvents & ~usb.eventMask) { + /* The default handler for 0.8 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_ABDET) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_ABDET; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_ID_GNDS) ? + USB_DETECT_MINI_A : 0; + + activeEvents |= pmic_check_sensor(SENSE_ID_FLOATS) ? + USB_DETECT_MINI_B : 0; + } + + /* Begin a critical section here so that we don't register/deregister + * for events or open/close the connectivity driver while the existing + * event handler (if it is currently defined) is in the middle of handling + * the current event. + */ + spin_lock_irqsave(&lock, flags); + + /* Finally, call the user-defined callback function if required. */ + if ((usb.handle_state == HANDLE_IN_USE) && + (usb.callback != NULL) && (activeEvents & usb.eventMask)) { + (*usb.callback) (activeEvents); + } + + spin_unlock_irqrestore(&lock, flags); +} + +/*@}*/ + +/************************************************************************** + * Module initialization and termination functions. + * + * Note that if this code is compiled into the kernel, then the + * module_init() function will be called within the device_initcall() + * group. + ************************************************************************** + */ + +/*! + * @name Connectivity Driver Loading/Unloading Functions + * These non-exported internal functions are used to support the connectivity + * device driver initialization and de-initialization operations. + */ +/*@{*/ + +/*! + * @brief This is the connectivity device driver initialization function. + * + * This function is called by the kernel when this device driver is first + * loaded. + */ +static int __init mc13783_pmic_convity_init(void) +{ + printk(KERN_INFO "PMIC Connectivity driver loading..\n"); + + return 0; +} + +/*! + * @brief This is the Connectivity device driver de-initialization function. + * + * This function is called by the kernel when this device driver is about + * to be unloaded. + */ +static void __exit mc13783_pmic_convity_exit(void) +{ + printk(KERN_INFO "PMIC Connectivity driver unloading\n"); + + /* Close the device handle if it is still open. This will also + * deregister any callbacks that may still be active. + */ + if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(usb.handle); + } else if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(rs_232.handle); + } else if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(cea_936.handle); + } + + /* Reset the PMIC Connectivity register to it's power on state. + * We should do this when unloading the module so that we don't + * leave the hardware in a state which could cause problems when + * no device driver is loaded. + */ + pmic_write_reg(REG_USB, RESET_USBCNTRL_REG_0, REG_FULLMASK); + pmic_write_reg(REG_CHARGE_USB_SPARE, RESET_USBCNTRL_REG_1, + REG_FULLMASK); + /* Note that there is no need to reset the "convity" device driver + * state structure to the reset state since we are in the final + * stage of unloading the device driver. The device driver state + * structure will be automatically and properly reinitialized if + * this device driver is reloaded. + */ +} + +/*@}*/ + +/* + * Module entry points and description information. + */ + +module_init(mc13783_pmic_convity_init); +module_exit(mc13783_pmic_convity_exit); + +MODULE_DESCRIPTION("mc13783 Connectivity device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_light.c b/drivers/mxc/pmic/mc13783/pmic_light.c new file mode 100644 index 000000000000..0eb52be9335d --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_light.c @@ -0,0 +1,2764 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_light.c + * @brief This is the main file of PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include "pmic_light_defs.h" + +#define NB_LIGHT_REG 6 + +static int pmic_light_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait; + +/*! + * To indicate whether any of the light devices are suspending + */ +static int suspend_flag; + +/*! + * The suspendq is used to block application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_light_class; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_bklit_tcled_master_enable); +EXPORT_SYMBOL(pmic_bklit_tcled_master_disable); +EXPORT_SYMBOL(pmic_bklit_master_enable); +EXPORT_SYMBOL(pmic_bklit_master_disable); +EXPORT_SYMBOL(pmic_bklit_set_current); +EXPORT_SYMBOL(pmic_bklit_get_current); +EXPORT_SYMBOL(pmic_bklit_set_dutycycle); +EXPORT_SYMBOL(pmic_bklit_get_dutycycle); +EXPORT_SYMBOL(pmic_bklit_set_cycle_time); +EXPORT_SYMBOL(pmic_bklit_get_cycle_time); +EXPORT_SYMBOL(pmic_bklit_set_mode); +EXPORT_SYMBOL(pmic_bklit_get_mode); +EXPORT_SYMBOL(pmic_bklit_rampup); +EXPORT_SYMBOL(pmic_bklit_off_rampup); +EXPORT_SYMBOL(pmic_bklit_rampdown); +EXPORT_SYMBOL(pmic_bklit_off_rampdown); +EXPORT_SYMBOL(pmic_bklit_enable_edge_slow); +EXPORT_SYMBOL(pmic_bklit_disable_edge_slow); +EXPORT_SYMBOL(pmic_bklit_get_edge_slow); +EXPORT_SYMBOL(pmic_bklit_set_strobemode); +EXPORT_SYMBOL(pmic_tcled_enable); +EXPORT_SYMBOL(pmic_tcled_disable); +EXPORT_SYMBOL(pmic_tcled_get_mode); +EXPORT_SYMBOL(pmic_tcled_ind_set_current); +EXPORT_SYMBOL(pmic_tcled_ind_get_current); +EXPORT_SYMBOL(pmic_tcled_ind_set_blink_pattern); +EXPORT_SYMBOL(pmic_tcled_ind_get_blink_pattern); +EXPORT_SYMBOL(pmic_tcled_fun_set_current); +EXPORT_SYMBOL(pmic_tcled_fun_get_current); +EXPORT_SYMBOL(pmic_tcled_fun_set_cycletime); +EXPORT_SYMBOL(pmic_tcled_fun_get_cycletime); +EXPORT_SYMBOL(pmic_tcled_fun_set_dutycycle); +EXPORT_SYMBOL(pmic_tcled_fun_get_dutycycle); +EXPORT_SYMBOL(pmic_tcled_fun_blendedramps); +EXPORT_SYMBOL(pmic_tcled_fun_sawramps); +EXPORT_SYMBOL(pmic_tcled_fun_blendedbowtie); +EXPORT_SYMBOL(pmic_tcled_fun_chasinglightspattern); +EXPORT_SYMBOL(pmic_tcled_fun_strobe); +EXPORT_SYMBOL(pmic_tcled_fun_rampup); +EXPORT_SYMBOL(pmic_tcled_get_fun_rampup); +EXPORT_SYMBOL(pmic_tcled_fun_rampdown); +EXPORT_SYMBOL(pmic_tcled_get_fun_rampdown); +EXPORT_SYMBOL(pmic_tcled_fun_triode_on); +EXPORT_SYMBOL(pmic_tcled_fun_triode_off); +EXPORT_SYMBOL(pmic_tcled_enable_edge_slow); +EXPORT_SYMBOL(pmic_tcled_disable_edge_slow); +EXPORT_SYMBOL(pmic_tcled_enable_half_current); +EXPORT_SYMBOL(pmic_tcled_disable_half_current); +EXPORT_SYMBOL(pmic_tcled_enable_audio_modulation); +EXPORT_SYMBOL(pmic_tcled_disable_audio_modulation); +EXPORT_SYMBOL(pmic_bklit_set_boost_mode); +EXPORT_SYMBOL(pmic_bklit_get_boost_mode); +EXPORT_SYMBOL(pmic_bklit_config_boost_mode); +EXPORT_SYMBOL(pmic_bklit_gets_boost_mode); + +/*! + * This is the suspend of power management for the pmic light API. + * It suports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_light_suspend(struct platform_device *dev, pm_message_t state) +{ + suspend_flag = 1; + /* switch off all leds and backlights */ + CHECK_ERROR(pmic_light_init_reg()); + + return 0; +}; + +/*! + * This is the resume of power management for the pmic light API. + * It suports RESTORE state. + * + * @param dev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_light_resume(struct platform_device *pdev) +{ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/*! + * This function enables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_tcled_master_enable(void) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + reg_value = BITFVAL(BIT_LEDEN, 1); + mask = BITFMASK(BIT_LEDEN); + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful + */ +PMIC_STATUS pmic_bklit_tcled_master_disable(void) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + reg_value = BITFVAL(BIT_LEDEN, 0); + mask = BITFMASK(BIT_LEDEN); + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight. Not supported on mc13783 + * Use pmic_bklit_tcled_master_enable. + * + * @return This function returns PMIC_NOT_SUPPORTED + */ +PMIC_STATUS pmic_bklit_master_enable(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function disables backlight. Not supported on mc13783 + * Use pmic_bklit_tcled_master_enable. + * + * @return This function returns PMIC_NOT_SUPPORTED + */ +PMIC_STATUS pmic_bklit_master_disable(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function sets backlight current level. + * + * @param channel Backlight channel + * @param level Backlight current level, as the following table. + * @verbatim + * level main & aux keyboard + * ------ ----------- -------- + * 0 0 mA 0 mA + * 1 3 mA 12 mA + * 2 6 mA 24 mA + * 3 9 mA 36 mA + * 4 12 mA 48 mA + * 5 15 mA 60 mA + * 6 18 mA 72 mA + * 7 21 mA 84 mA + * @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_current(t_bklit_channel channel, unsigned char level) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + value = BITFVAL(BIT_CL_MAIN, level); + mask = BITFMASK(BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + value = BITFVAL(BIT_CL_AUX, level); + mask = BITFMASK(BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + value = BITFVAL(BIT_CL_KEY, level); + mask = BITFMASK(BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives backlight current level. + * The channels are not individually adjustable, hence + * the channel parameter is ignored. + * + * @param channel Backlight channel (Ignored because the + * channels are not individually adjustable) + * @param level Pointer to store backlight current level result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_current(t_bklit_channel channel, + unsigned char *level) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, mask)); + + switch (channel) { + case BACKLIGHT_LED1: + *level = BITFEXT(reg_value, BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + *level = BITFEXT(reg_value, BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + *level = BITFEXT(reg_value, BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a backlight channel duty cycle. + * LED perceived brightness for each zone may be individually set by setting + * duty cycle. The default setting is for 0% duty cycle; this keeps all zone + * drivers turned off even after the master enable command. Each LED current + * sink can be turned on and adjusted for brightness with an independent 4 bit + * word for a duty cycle ranging from 0% to 100% in approximately 6.7% steps. + * + * @param channel Backlight channel. + * @param dc Backlight duty cycle, as the following table. + * @verbatim + * dc Duty Cycle (% On-time over Cycle Time) + * ------ --------------------------------------- + * 0 0% + * 1 6.7% + * 2 13.3% + * 3 20% + * 4 26.7% + * 5 33.3% + * 6 40% + * 7 46.7% + * 8 53.3% + * 9 60% + * 10 66.7% + * 11 73.3% + * 12 80% + * 13 86.7% + * 14 93.3% + * 15 100% + * @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_dutycycle(t_bklit_channel channel, unsigned char dc) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (dc > 15) { + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + reg_value = reg_value & (~MASK_DUTY_CYCLE); + reg_value = reg_value | (dc << BIT_DUTY_CYCLE); + break; + case BACKLIGHT_LED2: + reg_value = reg_value & (~(MASK_DUTY_CYCLE << INDEX_AUX)); + reg_value = reg_value | (dc << (BIT_DUTY_CYCLE + INDEX_AUX)); + break; + case BACKLIGHT_LED3: + reg_value = reg_value & (~(MASK_DUTY_CYCLE << INDEX_KYD)); + reg_value = reg_value | (dc << (BIT_DUTY_CYCLE + INDEX_KYD)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(LREG_2, reg_value, PMIC_ALL_BITS)); + return PMIC_SUCCESS; + +} + +/*! + * This function retrives a backlight channel duty cycle. + * + * @param channel Backlight channel. + * @param dc Pointer to backlight duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_dutycycle(t_bklit_channel channel, unsigned char *dc) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE)) + >> BIT_DUTY_CYCLE); + + break; + case BACKLIGHT_LED2: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE << INDEX_AUX)) + >> (BIT_DUTY_CYCLE + INDEX_AUX)); + break; + case BACKLIGHT_LED3: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE << + INDEX_KYD)) >> (BIT_DUTY_CYCLE + + INDEX_KYD)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a backlight channel cycle time. + * Cycle Time is defined as the period of a complete cycle of + * Time_on + Time_off. The default Cycle Time is set to 0.01 seconds such that + * the 100 Hz on-off cycling is averaged out by the eye to eliminate + * flickering. Additionally, the Cycle Time can be programmed to intentionally + * extend the period of on-off cycles for a visual pulsating or blinking effect. + * + * @param period Backlight cycle time, as the following table. + * @verbatim + * period Cycle Time + * -------- ------------ + * 0 0.01 seconds + * 1 0.1 seconds + * 2 0.5 seconds + * 3 2 seconds + * @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_cycle_time(unsigned char period) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (period > 3) { + return PMIC_PARAMETER_ERROR; + } + mask = BITFMASK(BIT_PERIOD); + value = BITFVAL(BIT_PERIOD, period); + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function retrives a backlight channel cycle time setting. + * + * @param period Pointer to save backlight cycle time setting result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_cycle_time(unsigned char *period) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_PERIOD); + CHECK_ERROR(pmic_read_reg(LREG_2, &value, mask)); + *period = BITFEXT(value, BIT_PERIOD); + return PMIC_SUCCESS; +} + +/*! + * This function sets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_mode(t_bklit_channel channel, t_bklit_mode mode) +{ + unsigned int reg_value = 0; + unsigned int clear_val = 0; + unsigned int triode_val = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + clear_val = ~(MASK_TRIODE_MAIN_BL); + triode_val = MASK_TRIODE_MAIN_BL; + break; + case BACKLIGHT_LED2: + clear_val = ~(MASK_TRIODE_MAIN_BL << INDEX_AUXILIARY); + triode_val = (MASK_TRIODE_MAIN_BL << INDEX_AUXILIARY); + break; + case BACKLIGHT_LED3: + clear_val = ~(MASK_TRIODE_MAIN_BL << INDEX_KEYPAD); + triode_val = (MASK_TRIODE_MAIN_BL << INDEX_KEYPAD); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + reg_value = (reg_value & clear_val); + + if (mode == BACKLIGHT_TRIODE_MODE) { + reg_value = (reg_value | triode_val); + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, PMIC_ALL_BITS)); + return PMIC_SUCCESS; +} + +/*! + * This function gets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_mode(t_bklit_channel channel, t_bklit_mode *mode) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_TRIODE_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_TRIODE_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_TRIODE_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, ®_value, mask)); + + switch (channel) { + case BACKLIGHT_LED1: + *mode = BITFEXT(reg_value, BIT_TRIODE_MAIN_BL); + break; + case BACKLIGHT_LED2: + *mode = BITFEXT(reg_value, BIT_TRIODE_AUX_BL); + break; + case BACKLIGHT_LED3: + *mode = BITFEXT(reg_value, BIT_TRIODE_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function starts backlight brightness ramp up function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampup(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_UP_MAIN_BL); + reg_value = BITFVAL(BIT_UP_MAIN_BL, 1); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_UP_AUX_BL); + reg_value = BITFVAL(BIT_UP_AUX_BL, 1); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_UP_KEY_BL); + reg_value = BITFVAL(BIT_UP_KEY_BL, 1); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function stops backlight brightness ramp up function; + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampup(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_UP_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_UP_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_UP_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function starts backlight brightness ramp down function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampdown(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_DOWN_MAIN_BL); + reg_value = BITFVAL(BIT_DOWN_MAIN_BL, 1); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_DOWN_AUX_BL); + reg_value = BITFVAL(BIT_DOWN_AUX_BL, 1); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_DOWN_KEY_BL); + reg_value = BITFVAL(BIT_DOWN_KEY_BL, 1); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function stops backlight brightness ramp down function. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampdown(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_DOWN_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_DOWN_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_DOWN_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight analog edge slowing mode. Analog Edge + * Slowing slows down the transient edges to reduce the chance of coupling LED + * modulation activity into other circuits. Rise and fall times will be targeted + * for approximately 50usec. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_enable_edge_slow(void) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + value = BITFVAL(BIT_SLEWLIMBL, 1); + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables backlight analog edge slowing mode. The backlight + * drivers will default to an <93>Instant On<94> mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_disable_edge_slow(void) +{ + unsigned int mask; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + CHECK_ERROR(pmic_write_reg(LREG_2, 0, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets backlight analog edge slowing mode. DThe backlight + * + * @param edge Edge slowing mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_edge_slow(bool *edge) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + CHECK_ERROR(pmic_read_reg(LREG_2, &value, mask)); + *edge = (bool) BITFEXT(value, BIT_SLEWLIMBL); + + return PMIC_SUCCESS; +} + +/*! + * This function sets backlight Strobe Light Pulsing mode. + * + * @param channel Backlight channel. + * @param mode Strobe Light Pulsing mode. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_bklit_set_strobemode(t_bklit_channel channel, + t_bklit_strobe_mode mode) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables tri-color LED. + * + * @param mode Tri-color LED operation mode. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable(t_tcled_mode mode, t_funlight_bank bank) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (mode) { + case TCLED_FUN_MODE: + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + value = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + value = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + value = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + break; + case TCLED_IND_MODE: + mask = MASK_BK1_FL | MASK_BK2_FL | MASK_BK3_FL; + break; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables tri-color LED. + * + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + * + */ +PMIC_STATUS pmic_tcled_disable(t_funlight_bank bank) +{ + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, 0, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives tri-color LED operation mode. + * + * @param mode Pointer to Tri-color LED operation mode. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_mode(t_tcled_mode *mode, t_funlight_bank bank) +{ + unsigned int val; + unsigned int mask; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, &val, mask)); + + if (val) { + *mode = TCLED_FUN_MODE; + } else { + *mode = TCLED_IND_MODE; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel current level in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Current level. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_current(t_ind_channel channel, + t_tcled_cur_level level, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (level > TCLED_CUR_LEVEL_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + value = BITFVAL(BITS_CL_RED, level); + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_IND_GREEN: + value = BITFVAL(BITS_CL_GREEN, level); + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + value = BITFVAL(BITS_CL_BLUE, level); + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel current level + * in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_current(t_ind_channel channel, + t_tcled_cur_level *level, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_IND_GREEN: + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_IND_RED: + *level = BITFEXT(value, BITS_CL_RED); + break; + case TCLED_IND_GREEN: + *level = BITFEXT(value, BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + *level = BITFEXT(value, BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel blinking pattern in indication + * mode. + * + * @param channel Tri-color LED channel. + * @param pattern Blinking pattern. + * @param skip If true, skip a cycle after each cycle. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern pattern, + bool skip, t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (skip == true) { + return PMIC_NOT_SUPPORTED; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + value = BITFVAL(BITS_DC_RED, pattern); + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_IND_GREEN: + value = BITFVAL(BITS_DC_GREEN, pattern); + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + value = BITFVAL(BITS_DC_BLUE, pattern); + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel blinking pattern in + * indication mode. + * + * @param channel Tri-color LED channel. + * @param pattern Pointer to Blinking pattern. + * @param skip Pointer to a boolean varible indicating if skip + * @param bank Selected tri-color bank + * a cycle after each cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern * + pattern, bool *skip, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_IND_GREEN: + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_IND_RED: + *pattern = BITFEXT(value, BITS_DC_RED); + break; + case TCLED_IND_GREEN: + *pattern = BITFEXT(value, BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + *pattern = BITFEXT(value, BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel current level in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level level) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (level > TCLED_CUR_LEVEL_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + value = BITFVAL(BITS_CL_RED, level); + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + value = BITFVAL(BITS_CL_GREEN, level); + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + value = BITFVAL(BITS_CL_BLUE, level); + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel current level + * in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level *level) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_FUN_CHANNEL1: + *level = BITFEXT(value, BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + *level = BITFEXT(value, BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + *level = BITFEXT(value, BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets tri-color LED cycle time. + * + * @param bank Tri-color LED bank + * @param ct Cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time ct) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (ct > TC_CYCLE_TIME_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + value = BITFVAL(BIT_PERIOD, ct); + mask = BITFMASK(BIT_PERIOD); + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function retrives tri-color LED cycle time in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param ct Pointer to cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time *ct) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (*ct > TC_CYCLE_TIME_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BIT_PERIOD); + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + *ct = BITFVAL(BIT_PERIOD, value); + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char dc) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + value = BITFVAL(BITS_DC_RED, dc); + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + value = BITFVAL(BITS_DC_GREEN, dc); + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + value = BITFVAL(BITS_DC_BLUE, dc); + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Pointer to duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char *dc) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_FUN_CHANNEL1: + *dc = BITFEXT(value, BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + *dc = BITFEXT(value, BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + *dc = BITFEXT(value, BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Blended Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedramps(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Saw Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_sawramps(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, SAW_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, SAW_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Blended Bowtie fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedbowtie(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_INVERSE_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_INVERSE_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Chasing Lights fun light pattern. + * + * @param bank Tri-color LED bank + * @param pattern Chasing light pattern mode. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_chasinglightspattern(t_funlight_bank bank, + t_chaselight_pattern pattern, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (pattern > BGR) { + return PMIC_PARAMETER_ERROR; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + if (pattern == PMIC_RGB) { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_RGB_SLOW); + } else { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_BGR_SLOW); + } + break; + case TC_FAST: + if (pattern == PMIC_RGB) { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_RGB_FAST); + } else { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_BGR_FAST); + } + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Strobe Mode fun light pattern. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_strobe(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_fun_strobe_speed speed) +{ + /* not supported on mc13783 */ + + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool rampup) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPUP; + value = LEDR1RAMPUP; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPUP; + value = LEDR2RAMPUP; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPUP; + value = LEDR3RAMPUP; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + value = value; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + value = value * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + value = value * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (!rampup) { + value = 0; + } + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool *rampup) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPUP; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPUP; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPUP; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_1, &value, mask)); + if (value) { + *rampup = true; + } else { + *rampup = false; + } + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, bool rampdown) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPDOWN; + value = LEDR1RAMPDOWN; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPDOWN; + value = LEDR2RAMPDOWN; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPDOWN; + value = LEDR3RAMPDOWN; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + value = value; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + value = value * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + value = value * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (!rampdown) { + value = 0; + } + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, + bool *rampdown) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPDOWN; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPDOWN; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPDOWN; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_1, &value, mask)); + if (value) { + *rampdown = true; + } else { + *rampdown = false; + } + return PMIC_SUCCESS; +} + +/*! + * This function enables a Tri-color channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_on(t_funlight_bank bank, + t_funlight_channel channel) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + value = ENABLE_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + value = ENABLE_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + value = ENABLE_BK2_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables a Tri-color LED channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_off(t_funlight_bank bank, + t_funlight_channel channel) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables Tri-color LED edge slowing. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable_edge_slow(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_SLEWLIMTC, 1); + mask = BITFMASK(BIT_SLEWLIMTC); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables Tri-color LED edge slowing. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable_edge_slow(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_SLEWLIMTC, 0); + mask = BITFMASK(BIT_SLEWLIMTC); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables Tri-color LED half current mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable_half_current(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_TC1HALF, 1); + mask = BITFMASK(BIT_TC1HALF); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables Tri-color LED half current mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable_half_current(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_TC1HALF, 0); + mask = BITFMASK(BIT_TC1HALF); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_enable_audio_modulation(t_led_channel channel, + t_aud_path path, + t_aud_gain gain, bool lpf_bypass) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function disables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_disable_audio_modulation(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_set_boost_mode(bool en_dis) +{ + + pmic_version_t mc13783_ver; + unsigned int mask; + unsigned int value; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_BOOSTEN, en_dis); + mask = BITFMASK(BIT_BOOSTEN); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_get_boost_mode(bool *en_dis) +{ + pmic_version_t mc13783_ver; + unsigned int mask; + unsigned int value; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_BOOSTEN); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *en_dis = BITFEXT(value, BIT_BOOSTEN); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function sets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_config_boost_mode(unsigned int abms, unsigned int abr) +{ + unsigned int conf_boost = 0; + unsigned int mask; + unsigned int value; + pmic_version_t mc13783_ver; + + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (suspend_flag == 1) { + return -EBUSY; + } + + if (abms > MAX_BOOST_ABMS) { + return PMIC_PARAMETER_ERROR; + } + + if (abr > MAX_BOOST_ABR) { + return PMIC_PARAMETER_ERROR; + } + + conf_boost = abms | (abr << 3); + + value = BITFVAL(BITS_BOOST, conf_boost); + mask = BITFMASK(BITS_BOOST); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_gets_boost_mode(unsigned int *abms, unsigned int *abr) +{ + unsigned int mask; + unsigned int value; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (suspend_flag == 1) { + return -EBUSY; + } + + mask = BITFMASK(BITS_BOOST_ABMS); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *abms = BITFEXT(value, BITS_BOOST_ABMS); + + mask = BITFMASK(BITS_BOOST_ABR); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *abr = BITFEXT(value, BITS_BOOST_ABR); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function implements IOCTL controls on a PMIC Light device. + * + + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_light_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_bklit_setting_param *bklit_setting = NULL; + t_tcled_enable_param *tcled_setting; + t_fun_param *fun_param; + t_tcled_ind_param *tcled_ind; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + bklit_setting = kmalloc(sizeof(t_bklit_setting_param), GFP_KERNEL); + tcled_setting = kmalloc(sizeof(t_tcled_enable_param), GFP_KERNEL); + fun_param = kmalloc(sizeof(t_fun_param), GFP_KERNEL); + tcled_ind = kmalloc(sizeof(t_tcled_ind_param), GFP_KERNEL); + switch (cmd) { + case PMIC_BKLIT_TCLED_ENABLE: + pmic_bklit_tcled_master_enable(); + break; + + case PMIC_BKLIT_TCLED_DISABLE: + pmic_bklit_tcled_master_disable(); + break; + + case PMIC_BKLIT_ENABLE: + pmic_bklit_master_enable(); + break; + + case PMIC_BKLIT_DISABLE: + pmic_bklit_master_disable(); + break; + + case PMIC_SET_BKLIT: + if (bklit_setting == NULL) + return -ENOMEM; + + if (copy_from_user(bklit_setting, (t_bklit_setting_param *) arg, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_bklit_set_mode(bklit_setting->channel, + bklit_setting->mode), + (kfree(bklit_setting))); + + CHECK_ERROR_KFREE(pmic_bklit_set_current(bklit_setting->channel, + bklit_setting-> + current_level), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_dutycycle + (bklit_setting->channel, + bklit_setting->duty_cycle), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_cycle_time + (bklit_setting->cycle_time), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_boost_mode + (bklit_setting->en_dis), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_config_boost_mode + (bklit_setting->abms, bklit_setting->abr), + (kfree(bklit_setting))); + if (bklit_setting->edge_slow != false) { + CHECK_ERROR_KFREE(pmic_bklit_enable_edge_slow(), + (kfree(bklit_setting))); + } else { + CHECK_ERROR_KFREE(pmic_bklit_disable_edge_slow(), + (kfree(bklit_setting))); + } + + kfree(bklit_setting); + break; + + case PMIC_GET_BKLIT: + if (bklit_setting == NULL) + return -ENOMEM; + + if (copy_from_user(bklit_setting, (t_bklit_setting_param *) arg, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_bklit_get_current(bklit_setting->channel, + &bklit_setting-> + current_level), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_cycle_time + (&bklit_setting->cycle_time), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_dutycycle + (bklit_setting->channel, + &bklit_setting->duty_cycle), + (kfree(bklit_setting))); + bklit_setting->strobe = BACKLIGHT_STROBE_NONE; + CHECK_ERROR_KFREE(pmic_bklit_get_mode(bklit_setting->channel, + &bklit_setting->mode), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_edge_slow + (&bklit_setting->edge_slow), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_boost_mode + (&bklit_setting->en_dis), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_gets_boost_mode + (&bklit_setting->abms, &bklit_setting->abr), + (kfree(bklit_setting))); + + if (copy_to_user((t_bklit_setting_param *) arg, bklit_setting, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + kfree(bklit_setting); + break; + + case PMIC_RAMPUP_BKLIT: + CHECK_ERROR(pmic_bklit_rampup((t_bklit_channel) arg)); + break; + + case PMIC_RAMPDOWN_BKLIT: + CHECK_ERROR(pmic_bklit_rampdown((t_bklit_channel) arg)); + break; + + case PMIC_OFF_RAMPUP_BKLIT: + CHECK_ERROR(pmic_bklit_off_rampup((t_bklit_channel) arg)); + break; + + case PMIC_OFF_RAMPDOWN_BKLIT: + CHECK_ERROR(pmic_bklit_off_rampdown((t_bklit_channel) arg)); + break; + + case PMIC_TCLED_ENABLE: + if (tcled_setting == NULL) + return -ENOMEM; + + if (copy_from_user(tcled_setting, (t_tcled_enable_param *) arg, + sizeof(t_tcled_enable_param))) { + kfree(tcled_setting); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_enable(tcled_setting->mode, + tcled_setting->bank), + (kfree(bklit_setting))); + break; + + case PMIC_TCLED_DISABLE: + CHECK_ERROR(pmic_tcled_disable((t_funlight_bank) arg)); + break; + + case PMIC_TCLED_PATTERN: + if (fun_param == NULL) + return -ENOMEM; + + if (copy_from_user(fun_param, + (t_fun_param *) arg, sizeof(t_fun_param))) { + kfree(fun_param); + return -EFAULT; + } + + switch (fun_param->pattern) { + case BLENDED_RAMPS_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedramps + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case BLENDED_RAMPS_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedramps + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case SAW_RAMPS_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_sawramps + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case SAW_RAMPS_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_sawramps + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case BLENDED_BOWTIE_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedbowtie + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case BLENDED_BOWTIE_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedbowtie + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case STROBE_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_strobe + (fun_param->bank, fun_param->channel, + TC_STROBE_SLOW), (kfree(fun_param))); + break; + + case STROBE_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_strobe + (fun_param->bank, + fun_param->channel, TC_STROBE_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_RGB_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, PMIC_RGB, TC_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_RGB_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, PMIC_RGB, TC_FAST), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_BGR_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, BGR, TC_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_BGR_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, BGR, TC_FAST), + (kfree(fun_param))); + break; + } + + kfree(fun_param); + break; + + case PMIC_SET_TCLED: + if (tcled_ind == NULL) + return -ENOMEM; + + if (copy_from_user(tcled_ind, (t_tcled_ind_param *) arg, + sizeof(t_tcled_ind_param))) { + kfree(tcled_ind); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_ind_set_current(tcled_ind->channel, + tcled_ind->level, + tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_ind_set_blink_pattern + (tcled_ind->channel, tcled_ind->pattern, + tcled_ind->skip, tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_fun_rampup + (tcled_ind->bank, tcled_ind->channel, + tcled_ind->rampup), (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_fun_rampdown + (tcled_ind->bank, tcled_ind->channel, + tcled_ind->rampdown), (kfree(tcled_ind))); + if (tcled_ind->half_current) { + CHECK_ERROR_KFREE(pmic_tcled_enable_half_current(), + (kfree(tcled_ind))); + } else { + CHECK_ERROR_KFREE(pmic_tcled_disable_half_current(), + (kfree(tcled_ind))); + } + + kfree(tcled_ind); + break; + + case PMIC_GET_TCLED: + if (tcled_ind == NULL) + return -ENOMEM; + + if (copy_from_user(tcled_ind, (t_tcled_ind_param *) arg, + sizeof(t_tcled_ind_param))) { + kfree(tcled_ind); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_ind_get_current(tcled_ind->channel, + &tcled_ind->level, + tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_ind_get_blink_pattern + (tcled_ind->channel, &tcled_ind->pattern, + &tcled_ind->skip, tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_get_fun_rampup + (tcled_ind->bank, tcled_ind->channel, + &tcled_ind->rampup), (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_get_fun_rampdown + (tcled_ind->bank, tcled_ind->channel, + &tcled_ind->rampdown), (kfree(tcled_ind))); + if (copy_to_user + ((t_tcled_ind_param *) arg, tcled_ind, + sizeof(t_tcled_ind_param))) { + return -EFAULT; + } + kfree(tcled_ind); + + break; + + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function initialize Light registers of mc13783 with 0. + * + * @return This function returns 0 if successful. + */ +int pmic_light_init_reg(void) +{ + CHECK_ERROR(pmic_write_reg(LREG_0, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_3, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_4, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_5, 0, PMIC_ALL_BITS)); + return 0; +} + +/*! + * This function implements the open method on a mc13783 light device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_light_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +/*! + * This function implements the release method on a mc13783 light device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_light_release(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +static struct file_operations pmic_light_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_light_ioctl, + .open = pmic_light_open, + .release = pmic_light_release, +}; + +static int pmic_light_remove(struct platform_device *pdev) +{ + device_destroy(pmic_light_class, MKDEV(pmic_light_major, 0)); + class_destroy(pmic_light_class); + unregister_chrdev(pmic_light_major, "pmic_light"); + return 0; +} + +static int pmic_light_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + pmic_light_major = register_chrdev(0, "pmic_light", &pmic_light_fops); + + if (pmic_light_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_light\n"); + return pmic_light_major; + } + init_waitqueue_head(&suspendq); + + pmic_light_class = class_create(THIS_MODULE, "pmic_light"); + if (IS_ERR(pmic_light_class)) { + printk(KERN_ERR "Error creating pmic_light class.\n"); + ret = PTR_ERR(pmic_light_class); + goto err_out1; + } + + temp_class = device_create(pmic_light_class, NULL, + MKDEV(pmic_light_major, 0), NULL, + "pmic_light"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic_light class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + ret = pmic_light_init_reg(); + if (ret != PMIC_SUCCESS) { + goto err_out3; + } + + printk(KERN_INFO "PMIC Light successfully loaded\n"); + return ret; + + err_out3: + device_destroy(pmic_light_class, MKDEV(pmic_light_major, 0)); + err_out2: + class_destroy(pmic_light_class); + err_out1: + unregister_chrdev(pmic_light_major, "pmic_light"); + return ret; +} + +static struct platform_driver pmic_light_driver_ldm = { + .driver = { + .name = "pmic_light", + }, + .suspend = pmic_light_suspend, + .resume = pmic_light_resume, + .probe = pmic_light_probe, + .remove = pmic_light_remove, +}; + +/* + * Initialization and Exit + */ + +static int __init pmic_light_init(void) +{ + pr_debug("PMIC Light driver loading...\n"); + return platform_driver_register(&pmic_light_driver_ldm); +} +static void __exit pmic_light_exit(void) +{ + platform_driver_unregister(&pmic_light_driver_ldm); + pr_debug("PMIC Light driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_light_init); +module_exit(pmic_light_exit); + +MODULE_DESCRIPTION("PMIC_LIGHT"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_light_defs.h b/drivers/mxc/pmic/mc13783/pmic_light_defs.h new file mode 100644 index 000000000000..2c76843264e6 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_light_defs.h @@ -0,0 +1,144 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_light_defs.h + * @brief This is the internal header PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +#ifndef __MC13783_LIGHT_DEFS_H__ +#define __MC13783_LIGHT_DEFS_H__ + +#define LREG_0 REG_LED_CONTROL_0 +#define LREG_1 REG_LED_CONTROL_1 +#define LREG_2 REG_LED_CONTROL_2 +#define LREG_3 REG_LED_CONTROL_3 +#define LREG_4 REG_LED_CONTROL_4 +#define LREG_5 REG_LED_CONTROL_5 + +/* REG_LED_CONTROL_0 */ + +#define BIT_LEDEN_LSH 0 +#define BIT_LEDEN_WID 1 +#define MASK_TRIODE_MAIN_BL 0x080 +#define INDEX_AUXILIARY 1 +#define INDEX_KEYPAD 2 +#define BITS_FUN_LIGHT_LSH 17 +#define BITS_FUN_LIGHT_WID 4 +#define MASK_FUN_LIGHT 0x1E0000 +#define MASK_BK1_FL 0x200000 +#define ENABLE_BK1_FL 0x200000 +#define MASK_BK2_FL 0x400000 +#define ENABLE_BK2_FL 0x400000 +#define MASK_BK3_FL 0x800000 +#define ENABLE_BK3_FL 0x800000 +#define BIT_UP_MAIN_BL_LSH 1 +#define BIT_UP_MAIN_BL_WID 1 +#define BIT_UP_AUX_BL_LSH 2 +#define BIT_UP_AUX_BL_WID 1 +#define BIT_UP_KEY_BL_LSH 3 +#define BIT_UP_KEY_BL_WID 1 +#define BIT_DOWN_MAIN_BL_LSH 4 +#define BIT_DOWN_MAIN_BL_WID 1 +#define BIT_DOWN_AUX_BL_LSH 5 +#define BIT_DOWN_AUX_BL_WID 1 +#define BIT_DOWN_KEY_BL_LSH 6 +#define BIT_DOWN_KEY_BL_WID 1 +#define BIT_TRIODE_MAIN_BL_LSH 7 +#define BIT_TRIODE_MAIN_BL_WID 1 +#define BIT_TRIODE_AUX_BL_LSH 8 +#define BIT_TRIODE_AUX_BL_WID 1 +#define BIT_TRIODE_KEY_BL_LSH 9 +#define BIT_TRIODE_KEY_BL_WID 1 + +#define BIT_BOOSTEN_LSH 10 +#define BIT_BOOSTEN_WID 1 +#define BITS_BOOST_LSH 11 +#define BITS_BOOST_WID 5 +#define BITS_BOOST_ABMS_LSH 11 +#define BITS_BOOST_ABMS_WID 3 +#define BITS_BOOST_ABR_LSH 14 +#define BITS_BOOST_ABR_WID 2 + +#define MAX_BOOST_ABMS 7 +#define MAX_BOOST_ABR 3 + +/* REG_LED_CONTROL_1 */ + +#define BIT_SLEWLIMTC_LSH 23 +#define BIT_SLEWLIMTC_WID 1 +#define BIT_TC1HALF_LSH 18 +#define BIT_TC1HALF_WID 1 +#define LEDR1RAMPUP 0x000001 +#define LEDR2RAMPUP 0x000040 +#define LEDR3RAMPUP 0x001000 +#define LEDR1RAMPDOWN 0x000008 +#define LEDR2RAMPDOWN 0x000200 +#define LEDR3RAMPDOWN 0x008000 + +/* REG_LED_CONTROL_2 */ + +#define BIT_SLEWLIMBL_LSH 23 +#define BIT_SLEWLIMBL_WID 1 +#define BIT_DUTY_CYCLE 9 +#define MASK_DUTY_CYCLE 0x001E00 +#define INDEX_AUX 4 +#define INDEX_KYD 8 +#define BIT_CL_MAIN_LSH 0 +#define BIT_CL_MAIN_WID 3 +#define BIT_CL_AUX_LSH 3 +#define BIT_CL_AUX_WID 3 +#define BIT_CL_KEY_LSH 6 +#define BIT_CL_KEY_WID 3 + +/* REG_LED_CONTROL_3 4 5 */ +#define BITS_CL_RED_LSH 0 +#define BITS_CL_RED_WID 2 +#define BITS_CL_GREEN_LSH 2 +#define BITS_CL_GREEN_WID 2 +#define BITS_CL_BLUE_LSH 4 +#define BITS_CL_BLUE_WID 2 +#define BITS_DC_RED_LSH 6 +#define BITS_DC_RED_WID 5 +#define BITS_DC_GREEN_LSH 11 +#define BITS_DC_GREEN_WID 5 +#define BITS_DC_BLUE_LSH 16 +#define BITS_DC_BLUE_WID 5 +#define BIT_PERIOD_LSH 21 +#define BIT_PERIOD_WID 2 + +#define DUTY_CYCLE_MAX 31 + +/* Fun light pattern */ +#define BLENDED_RAMPS_SLOW 0 +#define BLENDED_RAMPS_FAST 1 +#define SAW_RAMPS_SLOW 2 +#define SAW_RAMPS_FAST 3 +#define BLENDED_INVERSE_RAMPS_SLOW 4 +#define BLENDED_INVERSE_RAMPS_FAST 5 +#define CHASING_LIGHTS_RGB_SLOW 6 +#define CHASING_LIGHTS_RGB_FAST 7 +#define CHASING_LIGHTS_BGR_SLOW 8 +#define CHASING_LIGHTS_BGR_FAST 9 +#define FUN_LIGHTS_OFF 15 + +/*! + * This function initialize Light registers of mc13783 with 0. + * + * @return This function returns 0 if successful. + */ +int pmic_light_init_reg(void); + +#endif /* __MC13783_LIGHT_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_power.c b/drivers/mxc/pmic/mc13783/pmic_power.c new file mode 100644 index 000000000000..7a17d2afb88a --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_power.c @@ -0,0 +1,3146 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_power.c + * @brief This is the main file of PMIC(mc13783) Power driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "pmic_power_defs.h" + +#ifdef CONFIG_MXC_HWEVENT +#include +#endif + +#include + +#define MC13783_REGCTRL_GPOx_MASK 0x18000 + +static bool VBKUP1_EN; +static bool VBKUP2_EN; + +/* + * Power Pmic API + */ + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_power_off); +EXPORT_SYMBOL(pmic_power_set_pc_config); +EXPORT_SYMBOL(pmic_power_get_pc_config); +EXPORT_SYMBOL(pmic_power_regulator_on); +EXPORT_SYMBOL(pmic_power_regulator_off); +EXPORT_SYMBOL(pmic_power_regulator_set_voltage); +EXPORT_SYMBOL(pmic_power_regulator_get_voltage); +EXPORT_SYMBOL(pmic_power_regulator_set_config); +EXPORT_SYMBOL(pmic_power_regulator_get_config); +EXPORT_SYMBOL(pmic_power_vbkup2_auto_en); +EXPORT_SYMBOL(pmic_power_get_vbkup2_auto_state); +EXPORT_SYMBOL(pmic_power_bat_det_en); +EXPORT_SYMBOL(pmic_power_get_bat_det_state); +EXPORT_SYMBOL(pmic_power_vib_pin_en); +EXPORT_SYMBOL(pmic_power_gets_vib_pin_state); +EXPORT_SYMBOL(pmic_power_get_power_mode_sense); +EXPORT_SYMBOL(pmic_power_set_regen_assig); +EXPORT_SYMBOL(pmic_power_get_regen_assig); +EXPORT_SYMBOL(pmic_power_set_regen_inv); +EXPORT_SYMBOL(pmic_power_get_regen_inv); +EXPORT_SYMBOL(pmic_power_esim_v_en); +EXPORT_SYMBOL(pmic_power_gets_esim_v_state); +EXPORT_SYMBOL(pmic_power_set_auto_reset_en); +EXPORT_SYMBOL(pmic_power_get_auto_reset_en); +EXPORT_SYMBOL(pmic_power_set_conf_button); +EXPORT_SYMBOL(pmic_power_get_conf_button); +EXPORT_SYMBOL(pmic_power_event_sub); +EXPORT_SYMBOL(pmic_power_event_unsub); + +/*! + * This function is called to put the power in a low power state. + * Switching off the platform cannot be decided by + * the power module. It has to be handled by the + * client application. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_power_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +}; + +/*! + * This function is called to resume the power from a low power state. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_power_resume(struct platform_device *pdev) +{ + return 0; +}; + +/*! + * This function sets user power off in power control register and thus powers + * off the phone. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +void pmic_power_off(void) +{ + unsigned int mask, value; + + mask = BITFMASK(MC13783_PWRCTRL_USER_OFF_SPI); + value = BITFVAL(MC13783_PWRCTRL_USER_OFF_SPI, + MC13783_PWRCTRL_USER_OFF_SPI_ENABLE); + + pmic_write_reg(REG_POWER_CONTROL_0, value, mask); +} + +/*! + * This function sets the power control configuration. + * + * @param pc_config power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_set_pc_config(t_pc_config *pc_config) +{ + unsigned int pwrctrl_val_reg0 = 0; + unsigned int pwrctrl_val_reg1 = 0; + unsigned int pwrctrl_mask_reg0 = 0; + unsigned int pwrctrl_mask_reg1 = 0; + + if (pc_config == NULL) { + return PMIC_PARAMETER_ERROR; + } + + if (pc_config->pc_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PCEN, + MC13783_PWRCTRL_PCEN_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PCT, + pc_config->pc_timer); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PCEN, + MC13783_PWRCTRL_PCEN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_PCEN); + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_PCT); + + if (pc_config->pc_count_enable != false) { + + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT_EN, + MC13783_PWRCTRL_PC_COUNT_EN_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT, + pc_config->pc_count); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PC_MAX_CNT, + pc_config->pc_max_count); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT_EN, + MC13783_PWRCTRL_PC_COUNT_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_PC_COUNT_EN); + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_PC_MAX_CNT) | + BITFMASK(MC13783_PWRCTRL_PC_COUNT); + + if (pc_config->warm_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_WARM_EN_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_WARM_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_WARM_EN); + + if (pc_config->user_off_pc != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_USER_OFF_PC, + MC13783_PWRCTRL_USER_OFF_PC_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_USER_OFF_PC_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_USER_OFF_PC); + + if (pc_config->clk_32k_user_off != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_USER_OFF, + MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_USER_OFF, + MC13783_PWRCTRL_32OUT_USER_OFF_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_32OUT_USER_OFF); + + if (pc_config->clk_32k_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_EN, + MC13783_PWRCTRL_32OUT_EN_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_EN, + MC13783_PWRCTRL_32OUT_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_32OUT_EN); + + if (pc_config->en_vbkup1 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + VBKUP1_EN = true; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + VBKUP1_EN = false; + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1_EN); + + if (pc_config->en_vbkup2 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + VBKUP2_EN = true; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + VBKUP2_EN = false; + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP2_EN); + + if (pc_config->auto_en_vbkup1 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_AUTO_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_AUTO_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1_AUTO_EN); + + if (pc_config->auto_en_vbkup2 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_AUTO_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_AUTO_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP2_AUTO_EN); + + if (VBKUP1_EN != false) { + if (pc_config->vhold_voltage > 3 + || pc_config->vhold_voltage < 0) { + return PMIC_PARAMETER_ERROR; + } else { + + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1, + pc_config->vhold_voltage); + } + } + if (VBKUP2_EN != false) { + if (pc_config->vhold_voltage > 3 + || pc_config->vhold_voltage < 0) { + return PMIC_PARAMETER_ERROR; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2, + pc_config->vhold_voltage2); + } + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1) | + BITFMASK(MC13783_PWRCTRL_VBKUP2); + + if (pc_config->mem_allon != false) { + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_ALLON, + MC13783_PWRCTRL_MEM_ALLON_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_TMR, + pc_config->mem_timer); + } else { + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_ALLON, + MC13783_PWRCTRL_MEM_ALLON_DISABLE); + } + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_MEM_ALLON) | + BITFMASK(MC13783_PWRCTRL_MEM_TMR); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + pwrctrl_val_reg0, pwrctrl_mask_reg0)); + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_1, + pwrctrl_val_reg1, pwrctrl_mask_reg1)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the power control configuration. + * + * @param pc_config pointer to power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_pc_config(t_pc_config *pc_config) +{ + unsigned int pwrctrl_val_reg0 = 0; + unsigned int pwrctrl_val_reg1 = 0; + + if (pc_config == NULL) { + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + &pwrctrl_val_reg0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_1, + &pwrctrl_val_reg1, PMIC_ALL_BITS)); + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_PCEN) + == MC13783_PWRCTRL_PCEN_ENABLE) { + pc_config->pc_enable = true; + pc_config->pc_timer = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PCT); + + } else { + pc_config->pc_enable = false; + pc_config->pc_timer = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_PC_COUNT_EN) + == MC13783_PWRCTRL_PCEN_ENABLE) { + pc_config->pc_count_enable = true; + pc_config->pc_count = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PC_COUNT); + pc_config->pc_max_count = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PC_MAX_CNT); + } else { + pc_config->pc_count_enable = false; + pc_config->pc_count = 0; + pc_config->pc_max_count = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_WARM_EN) + == MC13783_PWRCTRL_WARM_EN_ENABLE) { + pc_config->warm_enable = true; + } else { + pc_config->warm_enable = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_USER_OFF_PC) + == MC13783_PWRCTRL_USER_OFF_PC_ENABLE) { + pc_config->user_off_pc = true; + } else { + pc_config->user_off_pc = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_32OUT_USER_OFF) + == MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE) { + pc_config->clk_32k_user_off = true; + } else { + pc_config->clk_32k_user_off = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_32OUT_EN) + == MC13783_PWRCTRL_32OUT_EN_ENABLE) { + pc_config->clk_32k_enable = true; + } else { + pc_config->clk_32k_enable = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP1_AUTO_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->auto_en_vbkup1 = true; + } else { + pc_config->auto_en_vbkup1 = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP2_AUTO_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->auto_en_vbkup2 = true; + } else { + pc_config->auto_en_vbkup2 = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP1_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->en_vbkup1 = true; + pc_config->vhold_voltage = BITFEXT(pwrctrl_val_reg0, + MC13783_PWRCTRL_VBKUP1); + } else { + pc_config->en_vbkup1 = false; + pc_config->vhold_voltage = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP2_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->en_vbkup2 = true; + pc_config->vhold_voltage2 = BITFEXT(pwrctrl_val_reg0, + MC13783_PWRCTRL_VBKUP2); + } else { + pc_config->en_vbkup2 = false; + pc_config->vhold_voltage2 = 0; + } + + if (BITFEXT(pwrctrl_val_reg1, MC13783_PWRCTRL_MEM_ALLON) == + MC13783_PWRCTRL_MEM_ALLON_ENABLE) { + pc_config->mem_allon = true; + pc_config->mem_timer = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_MEM_TMR); + } else { + pc_config->mem_allon = false; + pc_config->mem_timer = 0; + } + + return PMIC_SUCCESS; +} + +/*! + * This function turns on a regulator. + * + * @param regulator The regulator to be truned on. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_on(t_pmic_regulator regulator) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_PLL: + reg_val = BITFVAL(MC13783_SWCTRL_PLL_EN, + MC13783_SWCTRL_PLL_EN_ENABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_EN); + reg = REG_SWITCHERS_4; + break; + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_EN, + MC13783_SWCTRL_SW3_EN_ENABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_EN); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_EN, + MC13783_REGCTRL_VAUDIO_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_EN, + MC13783_REGCTRL_VIOHI_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_EN, + MC13783_REGCTRL_VIOLO_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_EN, + MC13783_REGCTRL_VDIG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_EN, + MC13783_REGCTRL_VGEN_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_EN, + MC13783_REGCTRL_VRFDIG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_EN, + MC13783_REGCTRL_VRFREF_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_EN, + MC13783_REGCTRL_VRFCP_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_EN, + MC13783_REGCTRL_VSIM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_EN, + MC13783_REGCTRL_VESIM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_EN, + MC13783_REGCTRL_VCAM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_EN, + MC13783_REGCTRL_VRFBG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VVIB: + reg_val = BITFVAL(MC13783_REGCTRL_VVIB_EN, + MC13783_REGCTRL_VVIB_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VVIB_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_EN, + MC13783_REGCTRL_VRF1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_EN, + MC13783_REGCTRL_VRF2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_EN, + MC13783_REGCTRL_VMMC1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_EN, + MC13783_REGCTRL_VMMC2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGCTRL_GPO1_EN, + MC13783_REGCTRL_GPO1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO1_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGCTRL_GPO2_EN, + MC13783_REGCTRL_GPO2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO2_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGCTRL_GPO3_EN, + MC13783_REGCTRL_GPO3_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO3_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGCTRL_GPO4_EN, + MC13783_REGCTRL_GPO4_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO4_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function turns off a regulator. + * + * @param regulator The regulator to be truned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_off(t_pmic_regulator regulator) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_PLL: + reg_val = BITFVAL(MC13783_SWCTRL_PLL_EN, + MC13783_SWCTRL_PLL_EN_DISABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_EN); + reg = REG_SWITCHERS_4; + break; + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_EN, + MC13783_SWCTRL_SW3_EN_DISABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_EN); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_EN, + MC13783_REGCTRL_VAUDIO_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_EN, + MC13783_REGCTRL_VIOHI_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_EN, + MC13783_REGCTRL_VIOLO_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_EN, + MC13783_REGCTRL_VDIG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_EN, + MC13783_REGCTRL_VGEN_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_EN, + MC13783_REGCTRL_VRFDIG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_EN, + MC13783_REGCTRL_VRFREF_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_EN, + MC13783_REGCTRL_VRFCP_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_EN, + MC13783_REGCTRL_VSIM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_EN, + MC13783_REGCTRL_VESIM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_EN, + MC13783_REGCTRL_VCAM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_EN, + MC13783_REGCTRL_VRFBG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VVIB: + reg_val = BITFVAL(MC13783_REGCTRL_VVIB_EN, + MC13783_REGCTRL_VVIB_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VVIB_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_EN, + MC13783_REGCTRL_VRF1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_EN, + MC13783_REGCTRL_VRF2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_EN, + MC13783_REGCTRL_VMMC1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_EN, + MC13783_REGCTRL_VMMC2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGCTRL_GPO1_EN, + MC13783_REGCTRL_GPO1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO1_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGCTRL_GPO2_EN, + MC13783_REGCTRL_GPO2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO2_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGCTRL_GPO3_EN, + MC13783_REGCTRL_GPO3_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO3_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGCTRL_GPO4_EN, + MC13783_REGCTRL_GPO4_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO4_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the regulator output voltage. + * + * @param regulator The regulator to be configured. + * @param voltage The regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_voltage(t_pmic_regulator regulator, + t_regulator_voltage voltage) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((voltage.sw1a < SW1A_0_9V) || (voltage.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A, voltage.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((voltage.sw1b < SW1B_0_9V) || (voltage.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B, voltage.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((voltage.sw2a < SW2A_0_9V) || (voltage.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A, voltage.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((voltage.sw2b < SW2B_0_9V) || (voltage.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B, voltage.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW1A); + reg = REG_SWITCHERS_3; + break; + case SW_SW3: + if (voltage.sw3 != SW3_5V) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW3, voltage.sw3); + reg_mask = BITFMASK(MC13783_SWSET_SW3); + reg = REG_SWITCHERS_5; + break; + case REGU_VIOLO: + if ((voltage.violo < VIOLO_1_2V) || + (voltage.violo > VIOLO_1_8V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VIOLO, voltage.violo); + reg_mask = BITFMASK(MC13783_REGSET_VIOLO); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VDIG: + if ((voltage.vdig < VDIG_1_2V) || (voltage.vdig > VDIG_1_8V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VDIG, voltage.vdig); + reg_mask = BITFMASK(MC13783_REGSET_VDIG); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VGEN: + if ((voltage.vgen < VGEN_1_2V) || (voltage.vgen > VGEN_2_4V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VGEN, voltage.vgen); + reg_mask = BITFMASK(MC13783_REGSET_VGEN); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFDIG: + if ((voltage.vrfdig < VRFDIG_1_2V) || + (voltage.vrfdig > VRFDIG_1_875V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFDIG, voltage.vrfdig); + reg_mask = BITFMASK(MC13783_REGSET_VRFDIG); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFREF: + if ((voltage.vrfref < VRFREF_2_475V) || + (voltage.vrfref > VRFREF_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFREF, voltage.vrfref); + reg_mask = BITFMASK(MC13783_REGSET_VRFREF); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFCP: + if ((voltage.vrfcp < VRFCP_2_7V) || + (voltage.vrfcp > VRFCP_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFCP, voltage.vrfcp); + reg_mask = BITFMASK(MC13783_REGSET_VRFCP); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VSIM: + if ((voltage.vsim < VSIM_1_8V) || (voltage.vsim > VSIM_2_9V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VSIM, voltage.vsim); + reg_mask = BITFMASK(MC13783_REGSET_VSIM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VESIM: + if ((voltage.vesim < VESIM_1_8V) || + (voltage.vesim > VESIM_2_9V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VESIM, voltage.vesim); + reg_mask = BITFMASK(MC13783_REGSET_VESIM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VCAM: + if ((voltage.vcam < VCAM_1_5V) || (voltage.vcam > VCAM_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VCAM, voltage.vcam); + reg_mask = BITFMASK(MC13783_REGSET_VCAM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VVIB: + if ((voltage.vvib < VVIB_1_3V) || (voltage.vvib > VVIB_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VVIB, voltage.vvib); + reg_mask = BITFMASK(MC13783_REGSET_VVIB); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VRF1: + if ((voltage.vrf1 < VRF1_1_5V) || (voltage.vrf1 > VRF1_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRF1, voltage.vrf1); + reg_mask = BITFMASK(MC13783_REGSET_VRF1); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VRF2: + if ((voltage.vrf2 < VRF2_1_5V) || (voltage.vrf2 > VRF2_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRF2, voltage.vrf2); + reg_mask = BITFMASK(MC13783_REGSET_VRF2); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VMMC1: + if ((voltage.vmmc1 < VMMC1_1_6V) || (voltage.vmmc1 > VMMC1_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VMMC1, voltage.vmmc1); + reg_mask = BITFMASK(MC13783_REGSET_VMMC1); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VMMC2: + if ((voltage.vmmc2 < VMMC2_1_6V) || (voltage.vmmc2 > VMMC2_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VMMC2, voltage.vmmc2); + reg_mask = BITFMASK(MC13783_REGSET_VMMC2); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + case REGU_GPO1: + case REGU_GPO2: + case REGU_GPO3: + case REGU_GPO4: + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the regulator output voltage. + * + * @param regulator The regulator to be truned off. + * @param voltage Pointer to regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_voltage(t_pmic_regulator regulator, + t_regulator_voltage *voltage) +{ + unsigned int reg_val = 0; + + if (regulator == SW_SW1A) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_0, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW1B) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_1, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW2A) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_2, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW2B) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_3, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW3) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_5, + ®_val, PMIC_ALL_BITS)); + } else if ((regulator == REGU_VIOLO) || (regulator == REGU_VDIG) || + (regulator == REGU_VGEN) || + (regulator == REGU_VRFDIG) || + (regulator == REGU_VRFREF) || + (regulator == REGU_VRFCP) || + (regulator == REGU_VSIM) || + (regulator == REGU_VESIM) || (regulator == REGU_VCAM)) { + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®_val, PMIC_ALL_BITS)); + } else if ((regulator == REGU_VVIB) || (regulator == REGU_VRF1) || + (regulator == REGU_VRF2) || + (regulator == REGU_VMMC1) || (regulator == REGU_VMMC2)) { + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_1, + ®_val, PMIC_ALL_BITS)); + } + + switch (regulator) { + case SW_SW1A: + voltage->sw1a = BITFEXT(reg_val, MC13783_SWSET_SW1A); + break; + case SW_SW1B: + voltage->sw1b = BITFEXT(reg_val, MC13783_SWSET_SW1B); + break; + case SW_SW2A: + voltage->sw2a = BITFEXT(reg_val, MC13783_SWSET_SW2A); + break; + case SW_SW2B: + voltage->sw2b = BITFEXT(reg_val, MC13783_SWSET_SW2B); + break; + case SW_SW3: + voltage->sw3 = BITFEXT(reg_val, MC13783_SWSET_SW3); + break; + case REGU_VIOLO: + voltage->violo = BITFEXT(reg_val, MC13783_REGSET_VIOLO); + break; + case REGU_VDIG: + voltage->vdig = BITFEXT(reg_val, MC13783_REGSET_VDIG); + break; + case REGU_VGEN: + voltage->vgen = BITFEXT(reg_val, MC13783_REGSET_VGEN); + break; + case REGU_VRFDIG: + voltage->vrfdig = BITFEXT(reg_val, MC13783_REGSET_VRFDIG); + break; + case REGU_VRFREF: + voltage->vrfref = BITFEXT(reg_val, MC13783_REGSET_VRFREF); + break; + case REGU_VRFCP: + voltage->vrfcp = BITFEXT(reg_val, MC13783_REGSET_VRFCP); + break; + case REGU_VSIM: + voltage->vsim = BITFEXT(reg_val, MC13783_REGSET_VSIM); + break; + case REGU_VESIM: + voltage->vesim = BITFEXT(reg_val, MC13783_REGSET_VESIM); + break; + case REGU_VCAM: + voltage->vcam = BITFEXT(reg_val, MC13783_REGSET_VCAM); + break; + case REGU_VVIB: + voltage->vvib = BITFEXT(reg_val, MC13783_REGSET_VVIB); + break; + case REGU_VRF1: + voltage->vrf1 = BITFEXT(reg_val, MC13783_REGSET_VRF1); + break; + case REGU_VRF2: + voltage->vrf2 = BITFEXT(reg_val, MC13783_REGSET_VRF2); + break; + case REGU_VMMC1: + voltage->vmmc1 = BITFEXT(reg_val, MC13783_REGSET_VMMC1); + break; + case REGU_VMMC2: + voltage->vmmc2 = BITFEXT(reg_val, MC13783_REGSET_VMMC2); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the DVS voltage + * + * @param regulator The regulator to be configured. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs(t_pmic_regulator regulator, + t_regulator_voltage dvs) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((dvs.sw1a < SW1A_0_9V) || (dvs.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A_DVS, dvs.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A_DVS); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((dvs.sw1b < SW1B_0_9V) || (dvs.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B_DVS, dvs.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B_DVS); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((dvs.sw2a < SW2A_0_9V) || (dvs.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A_DVS, dvs.sw2a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A_DVS); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((dvs.sw2b < SW2B_0_9V) || (dvs.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B_DVS, dvs.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW2B_DVS); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the DVS voltage + * + * @param regulator The regulator to be handled. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs(t_pmic_regulator regulator, + t_regulator_voltage *dvs) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWSET_SW1A_DVS); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWSET_SW1B_DVS); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWSET_SW2A_DVS); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWSET_SW2B_DVS); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1A_DVS); + break; + case SW_SW1B: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1B_DVS); + break; + case SW_SW2A: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2A_DVS); + break; + case SW_SW2B: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2B_DVS); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the standiby voltage + * + * @param regulator The regulator to be configured. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_stby(t_pmic_regulator regulator, + t_regulator_voltage stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((stby.sw1a < SW1A_0_9V) || (stby.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A_STDBY, stby.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A_STDBY); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((stby.sw1b < SW1B_0_9V) || (stby.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B_STDBY, stby.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B_STDBY); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((stby.sw2a < SW2A_0_9V) || (stby.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A_STDBY, stby.sw2a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A_STDBY); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((stby.sw2b < SW2B_0_9V) || (stby.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B_STDBY, stby.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW2B_STDBY); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the standiby voltage + * + * @param regulator The regulator to be handled. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_stby(t_pmic_regulator regulator, + t_regulator_voltage *stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWSET_SW1A_STDBY); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWSET_SW1B_STDBY); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWSET_SW2A_STDBY); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWSET_SW2B_STDBY); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1A_STDBY); + break; + case SW_SW1B: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1B_STDBY); + break; + case SW_SW2A: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2A_STDBY); + break; + case SW_SW2B: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2B_STDBY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switchers mode. + * + * @param regulator The regulator to be configured. + * @param mode The switcher mode + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_mode(t_pmic_regulator regulator, + t_regulator_sw_mode mode, bool stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode; + + if (mode == SYNC_RECT) { + l_mode = MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN; + } else if (mode == NO_PULSE_SKIP) { + l_mode = MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN; + } else if (mode == PULSE_SKIP) { + l_mode = MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN; + } else if (mode == LOW_POWER) { + l_mode = MC13783_SWCTRL_SW_MODE_LOW_POWER_EN; + } else { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW1A: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW1A_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW1B_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW1B_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW2A_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_MODE); + } + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW2B_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_MODE); + } + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switchers mode. + * + * @param regulator The regulator to be handled. + * @param mode The switcher mode. + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_mode(t_pmic_regulator regulator, + t_regulator_sw_mode *mode, bool stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg = 0; + unsigned int l_mode = 0; + + switch (regulator) { + case SW_SW1A: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_MODE); + } + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_MODE); + } + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_MODE); + } + break; + case SW_SW1B: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_MODE); + } + break; + case SW_SW2A: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_MODE); + } + break; + case SW_SW2B: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_MODE); + } + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (l_mode == MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN) { + *mode = SYNC_RECT; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN) { + *mode = NO_PULSE_SKIP; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN) { + *mode = PULSE_SKIP; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_LOW_POWER_EN) { + *mode = LOW_POWER; + } else { + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch dvs speed + * + * @param regulator The regulator to be configured. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed speed) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + if (speed > 3 || speed < 0) { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch dvs speed + * + * @param regulator The regulator to be handled. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed *speed) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_DVS_SPEED); + break; + case SW_SW1B: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_DVS_SPEED); + break; + case SW_SW2A: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_DVS_SPEED); + break; + case SW_SW2B: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_DVS_SPEED); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch panic mode + * + * @param regulator The regulator to be configured. + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_panic_mode(t_pmic_regulator regulator, + bool panic_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch panic mode + * + * @param regulator The regulator to be handled + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_panic_mode(t_pmic_regulator regulator, + bool *panic_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_PANIC_MODE); + break; + case SW_SW1B: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_PANIC_MODE); + break; + case SW_SW2A: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_PANIC_MODE); + break; + case SW_SW2B: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_PANIC_MODE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch softstart mode + * + * @param regulator The regulator to be configured. + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_softstart(t_pmic_regulator regulator, + bool softstart) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch softstart mode + * + * @param regulator The regulator to be handled + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_softstart(t_pmic_regulator regulator, + bool *softstart) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_SOFTSTART); + break; + case SW_SW1B: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_SOFTSTART); + break; + case SW_SW2A: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_SOFTSTART); + break; + case SW_SW2B: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_SOFTSTART); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the PLL multiplication factor + * + * @param regulator The regulator to be configured. + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_factor(t_pmic_regulator regulator, + t_switcher_factor factor) +{ + unsigned int reg_val = 0, reg_mask = 0; + + if (regulator != SW_PLL) { + return PMIC_PARAMETER_ERROR; + } + if (factor < FACTOR_28 || factor > FACTOR_35) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWCTRL_PLL_FACTOR, factor); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_FACTOR); + + CHECK_ERROR(pmic_write_reg(REG_SWITCHERS_4, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the PLL multiplication factor + * + * @param regulator The regulator to be handled + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_factor(t_pmic_regulator regulator, + t_switcher_factor *factor) +{ + unsigned int reg_val = 0, reg_mask = 0; + + if (regulator != SW_PLL) { + return PMIC_PARAMETER_ERROR; + } + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_FACTOR); + + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_4, ®_val, reg_mask)); + + *factor = BITFEXT(reg_val, MC13783_SWCTRL_PLL_FACTOR); + + return PMIC_SUCCESS; +} + +/*! + * This function enables or disables low power mode. + * + * @param regulator The regulator to be configured. + * @param lp_mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode lp_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode, l_stby; + + if (lp_mode == LOW_POWER_DISABLED) { + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = MC13783_REGTRL_STBY_MODE_DISABLE; + } else if (lp_mode == LOW_POWER_CTRL_BY_PIN) { + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = MC13783_REGTRL_STBY_MODE_ENABLE; + } else if (lp_mode == LOW_POWER_EN) { + l_mode = MC13783_REGTRL_LP_MODE_ENABLE; + l_stby = MC13783_REGTRL_STBY_MODE_DISABLE; + } else if (lp_mode == LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN) { + l_mode = MC13783_REGTRL_LP_MODE_ENABLE; + l_stby = MC13783_REGTRL_STBY_MODE_ENABLE; + } else { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_MODE, l_mode) | + BITFVAL(MC13783_SWCTRL_SW3_STBY, l_stby); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_MODE) | + BITFMASK(MC13783_SWCTRL_SW3_STBY); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VAUDIO_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_MODE) | + BITFMASK(MC13783_REGCTRL_VAUDIO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VIOHI_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_MODE) | + BITFMASK(MC13783_REGCTRL_VIOHI_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VIOLO_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_MODE) | + BITFMASK(MC13783_REGCTRL_VIOLO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VDIG_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VGEN_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_MODE) | + BITFMASK(MC13783_REGCTRL_VGEN_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFDIG_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VRFDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFREF_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_MODE) | + BITFMASK(MC13783_REGCTRL_VRFREF_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFCP_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_MODE) | + BITFMASK(MC13783_REGCTRL_VRFCP_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VSIM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_MODE) | + BITFMASK(MC13783_REGCTRL_VSIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VESIM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_MODE) | + BITFMASK(MC13783_REGCTRL_VESIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VCAM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_MODE) | + BITFMASK(MC13783_REGCTRL_VCAM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + if ((lp_mode == LOW_POWER) || + (lp_mode == LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_STBY, l_mode); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRF1_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_MODE) | + BITFMASK(MC13783_REGCTRL_VRF1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRF2_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_MODE) | + BITFMASK(MC13783_REGCTRL_VRF2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VMMC1_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VMMC2_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets low power mode. + * + * @param regulator The regulator to be handled + * @param lp_mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode *lp_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode, l_stby; + + switch (regulator) { + case SW_SW3: + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_MODE) | + BITFMASK(MC13783_SWCTRL_SW3_STBY); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_MODE) | + BITFMASK(MC13783_REGCTRL_VAUDIO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_MODE) | + BITFMASK(MC13783_REGCTRL_VIOHI_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_MODE) | + BITFMASK(MC13783_REGCTRL_VIOLO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_MODE) | + BITFMASK(MC13783_REGCTRL_VGEN_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VRFDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_MODE) | + BITFMASK(MC13783_REGCTRL_VRFREF_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_MODE) | + BITFMASK(MC13783_REGCTRL_VRFCP_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_MODE) | + BITFMASK(MC13783_REGCTRL_VSIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_MODE) | + BITFMASK(MC13783_REGCTRL_VESIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_MODE) | + BITFMASK(MC13783_REGCTRL_VCAM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_MODE) | + BITFMASK(MC13783_REGCTRL_VRF1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_MODE) | + BITFMASK(MC13783_REGCTRL_VRF2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW3: + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW3_MODE); + l_stby = BITFEXT(reg_val, MC13783_SWCTRL_SW3_STBY); + break; + case REGU_VAUDIO: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VAUDIO_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VAUDIO_STBY); + break; + case REGU_VIOHI: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VIOHI_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VIOHI_STBY); + break; + case REGU_VIOLO: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VIOLO_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VIOLO_STBY); + break; + case REGU_VDIG: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VDIG_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VDIG_STBY); + break; + case REGU_VGEN: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VGEN_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VGEN_STBY); + break; + case REGU_VRFDIG: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFDIG_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFDIG_STBY); + break; + case REGU_VRFREF: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFREF_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFREF_STBY); + break; + case REGU_VRFCP: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFCP_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFCP_STBY); + break; + case REGU_VSIM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VSIM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VSIM_STBY); + break; + case REGU_VESIM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VESIM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VESIM_STBY); + break; + case REGU_VCAM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VCAM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VCAM_STBY); + break; + case REGU_VRFBG: + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFBG_STBY); + break; + case REGU_VRF1: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRF1_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRF1_STBY); + break; + case REGU_VRF2: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRF2_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRF2_STBY); + break; + case REGU_VMMC1: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VMMC1_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VMMC1_STBY); + break; + case REGU_VMMC2: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VMMC2_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VMMC2_STBY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if ((l_mode == MC13783_REGTRL_LP_MODE_DISABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_DISABLE)) { + *lp_mode = LOW_POWER_DISABLED; + } else if ((l_mode == MC13783_REGTRL_LP_MODE_DISABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_ENABLE)) { + *lp_mode = LOW_POWER_CTRL_BY_PIN; + } else if ((l_mode == MC13783_REGTRL_LP_MODE_ENABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_DISABLE)) { + *lp_mode = LOW_POWER_EN; + } else { + *lp_mode = LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the regulator configuration. + * + * @param regulator The regulator to be configured. + * @param config The regulator output configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_config(t_pmic_regulator regulator, + t_regulator_config *config) +{ + if (config == NULL) { + return PMIC_ERROR; + } + + switch (regulator) { + case SW_SW1A: + case SW_SW1B: + case SW_SW2A: + case SW_SW2B: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + CHECK_ERROR(pmic_power_switcher_set_dvs + (regulator, config->voltage_lvs)); + CHECK_ERROR(pmic_power_switcher_set_stby + (regulator, config->voltage_stby)); + CHECK_ERROR(pmic_power_switcher_set_mode + (regulator, config->mode, false)); + CHECK_ERROR(pmic_power_switcher_set_mode + (regulator, config->stby_mode, true)); + CHECK_ERROR(pmic_power_switcher_set_dvs_speed + (regulator, config->dvs_speed)); + CHECK_ERROR(pmic_power_switcher_set_panic_mode + (regulator, config->panic_mode)); + CHECK_ERROR(pmic_power_switcher_set_softstart + (regulator, config->softstart)); + break; + case SW_PLL: + CHECK_ERROR(pmic_power_switcher_set_factor + (regulator, config->factor)); + break; + case SW_SW3: + case REGU_VIOLO: + case REGU_VDIG: + case REGU_VGEN: + case REGU_VRFDIG: + case REGU_VRFREF: + case REGU_VRFCP: + case REGU_VSIM: + case REGU_VESIM: + case REGU_VCAM: + case REGU_VRF1: + case REGU_VRF2: + case REGU_VMMC1: + case REGU_VMMC2: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + CHECK_ERROR(pmic_power_regulator_set_lp_mode + (regulator, config->lp_mode)); + break; + case REGU_VVIB: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + CHECK_ERROR(pmic_power_regulator_set_lp_mode + (regulator, config->lp_mode)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the regulator output configuration. + * + * @param regulator The regulator to be truned off. + * @param config Pointer to regulator configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_config(t_pmic_regulator regulator, + t_regulator_config *config) +{ + if (config == NULL) { + return PMIC_ERROR; + } + + switch (regulator) { + case SW_SW1A: + case SW_SW1B: + case SW_SW2A: + case SW_SW2B: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + CHECK_ERROR(pmic_power_switcher_get_dvs + (regulator, &config->voltage_lvs)); + CHECK_ERROR(pmic_power_switcher_get_stby + (regulator, &config->voltage_stby)); + CHECK_ERROR(pmic_power_switcher_get_mode + (regulator, &config->mode, false)); + CHECK_ERROR(pmic_power_switcher_get_mode + (regulator, &config->stby_mode, true)); + CHECK_ERROR(pmic_power_switcher_get_dvs_speed + (regulator, &config->dvs_speed)); + CHECK_ERROR(pmic_power_switcher_get_panic_mode + (regulator, &config->panic_mode)); + CHECK_ERROR(pmic_power_switcher_get_softstart + (regulator, &config->softstart)); + break; + case SW_PLL: + CHECK_ERROR(pmic_power_switcher_get_factor + (regulator, &config->factor)); + break; + case SW_SW3: + case REGU_VIOLO: + case REGU_VDIG: + case REGU_VGEN: + case REGU_VRFDIG: + case REGU_VRFREF: + case REGU_VRFCP: + case REGU_VSIM: + case REGU_VESIM: + case REGU_VCAM: + case REGU_VRF1: + case REGU_VRF2: + case REGU_VMMC1: + case REGU_VMMC2: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + CHECK_ERROR(pmic_power_regulator_get_lp_mode + (regulator, &config->lp_mode)); + break; + case REGU_VVIB: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + CHECK_ERROR(pmic_power_regulator_get_lp_mode + (regulator, &config->lp_mode)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function enables automatically VBKUP2 in the memory hold modes. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable VBKUP2AUTOMH + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vbkup2_auto_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_VBKUP2AUTOMH, en); + reg_mask = BITFMASK(MC13783_REGCTRL_VBKUP2AUTOMH); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of automatically VBKUP2. + * Only on mc13783 2.0 or higher + * + * @param en if true, VBKUP2AUTOMH is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_vbkup2_auto_state(bool *en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_VBKUP2AUTOMH); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_VBKUP2AUTOMH); + + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables battery detect function. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable BATTDETEN + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_bat_det_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_BATTDETEN, en); + reg_mask = BITFMASK(MC13783_REGCTRL_BATTDETEN); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of battery detect function. + * Only on mc13783 2.0 or higher + * + * @param en if true, BATTDETEN is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_bat_det_state(bool *en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_BATTDETEN); + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_BATTDETEN); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables control of VVIB by VIBEN pin. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable VIBPINCTRL + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vib_pin_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_VIBPINCTRL, en); + reg_mask = BITFMASK(MC13783_REGCTRL_VIBPINCTRL); + + CHECK_ERROR(pmic_write_reg(REG_POWER_MISCELLANEOUS, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of control of VVIB by VIBEN pin. + * Only on mc13783 2.0 or higher + * @param en if true, VIBPINCTRL is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_gets_vib_pin_state(bool *en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_VIBPINCTRL); + CHECK_ERROR(pmic_read_reg(REG_POWER_MISCELLANEOUS, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_VIBPINCTRL); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function returns power up sense value + * + * @param p_up_sense value of power up sense + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_power_mode_sense(struct t_p_up_sense *p_up_sense) +{ + unsigned int reg_value = 0; + CHECK_ERROR(pmic_read_reg(REG_POWER_UP_MODE_SENSE, + ®_value, PMIC_ALL_BITS)); + p_up_sense->state_ictest = (STATE_ICTEST_MASK & reg_value); + p_up_sense->state_clksel = ((STATE_CLKSEL_MASK & reg_value) + >> STATE_CLKSEL_BIT); + p_up_sense->state_pums1 = ((STATE_PUMS1_MASK & reg_value) + >> STATE_PUMS1_BITS); + p_up_sense->state_pums2 = ((STATE_PUMS2_MASK & reg_value) + >> STATE_PUMS2_BITS); + p_up_sense->state_pums3 = ((STATE_PUMS3_MASK & reg_value) + >> STATE_PUMS3_BITS); + p_up_sense->state_chrgmode0 = ((STATE_CHRGM1_MASK & reg_value) + >> STATE_CHRGM1_BITS); + p_up_sense->state_chrgmode1 = ((STATE_CHRGM2_MASK & reg_value) + >> STATE_CHRGM2_BITS); + p_up_sense->state_umod = ((STATE_UMOD_MASK & reg_value) + >> STATE_UMOD_BITS); + p_up_sense->state_usben = ((STATE_USBEN_MASK & reg_value) + >> STATE_USBEN_BIT); + p_up_sense->state_sw_1a1b_joined = ((STATE_SW1A_J_B_MASK & reg_value) + >> STATE_SW1A_J_B_BIT); + p_up_sense->state_sw_2a2b_joined = ((STATE_SW2A_J_B_MASK & reg_value) + >> STATE_SW2A_J_B_BIT); + return PMIC_SUCCESS; +} + +/*! + * This function configures the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis if true, the regulator is enabled by regen. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_assig(t_pmic_regulator regulator, bool en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (regulator) { + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGGEN_VAUDIO, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGGEN_VIOHI, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGGEN_VIOLO, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGGEN_VDIG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGGEN_VGEN, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGGEN_VRFDIG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGGEN_VRFREF, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGGEN_VRFCP, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGGEN_VCAM, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGGEN_VRFBG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGGEN_VRF1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGGEN_VRF2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGGEN_VMMC1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGGEN_VMMC2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGGEN_GPO1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGGEN_GPO2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGGEN_GPO3, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGGEN_GPO4, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO4); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis return value, if true : + * the regulator is enabled by regen. + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_assig(t_pmic_regulator regulator, + bool *en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (regulator) { + case REGU_VAUDIO: + reg_mask = BITFMASK(MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + reg_mask = BITFMASK(MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + reg_mask = BITFMASK(MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + reg_mask = BITFMASK(MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + reg_mask = BITFMASK(MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + reg_mask = BITFMASK(MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + reg_mask = BITFMASK(MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + reg_mask = BITFMASK(MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + reg_mask = BITFMASK(MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + reg_mask = BITFMASK(MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + reg_mask = BITFMASK(MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + reg_mask = BITFMASK(MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + reg_mask = BITFMASK(MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + reg_mask = BITFMASK(MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + reg_mask = BITFMASK(MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + reg_mask = BITFMASK(MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + reg_mask = BITFMASK(MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + reg_mask = BITFMASK(MC13783_REGGEN_GPO4); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, ®_val, reg_mask)); + + switch (regulator) { + case REGU_VAUDIO: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO4); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_inv(bool en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_val = BITFVAL(MC13783_REGGEN_INV, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_INV); + + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, reg_val, reg_mask)); + return PMIC_SUCCESS; +} + +/*! + * This function gets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_inv(bool *en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_mask = BITFMASK(MC13783_REGGEN_INV); + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, ®_val, reg_mask)); + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_INV); + return PMIC_SUCCESS; +} + +/*! + * This function enables esim control voltage. + * Only on mc13783 2.0 or higher + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_esim_v_en(bool vesim, bool vmmc1, bool vmmc2) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGGEN_VESIMESIM, vesim) | + BITFVAL(MC13783_REGGEN_VMMC1ESIM, vesim) | + BITFVAL(MC13783_REGGEN_VMMC2ESIM, vesim); + reg_mask = BITFMASK(MC13783_REGGEN_VESIMESIM) | + BITFMASK(MC13783_REGGEN_VMMC1ESIM) | + BITFMASK(MC13783_REGGEN_VMMC2ESIM); + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets esim control voltage values. + * Only on mc13783 2.0 or higher + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_gets_esim_v_state(bool *vesim, bool *vmmc1, + bool *vmmc2) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGGEN_VESIMESIM) | + BITFMASK(MC13783_REGGEN_VMMC1ESIM) | + BITFMASK(MC13783_REGGEN_VMMC2ESIM); + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, + ®_val, reg_mask)); + *vesim = BITFEXT(reg_val, MC13783_REGGEN_VESIMESIM); + *vmmc1 = BITFEXT(reg_val, MC13783_REGGEN_VMMC1ESIM); + *vmmc2 = BITFEXT(reg_val, MC13783_REGGEN_VMMC2ESIM); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables auto reset after a system reset. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_auto_reset_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_val = BITFVAL(MC13783_AUTO_RESTART, en); + reg_mask = BITFMASK(MC13783_AUTO_RESTART); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_2, reg_val, reg_mask)); + return PMIC_SUCCESS; +} + +/*! + * This function gets auto reset configuration. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_auto_reset_en(bool *en) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_mask = BITFMASK(MC13783_AUTO_RESTART); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_2, ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_AUTO_RESTART); + return PMIC_SUCCESS; +} + +/*! + * This function configures a system reset on a button. + * + * @param bt type of button. + * @param sys_rst if true, enable the system reset on this button + * @param deb_time sets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_conf_button(t_button bt, bool sys_rst, int deb_time) +{ + int max_val = 0; + unsigned int reg_val = 0, reg_mask = 0; + + max_val = (1 << MC13783_DEB_BT_ON1B_WID) - 1; + if (deb_time > max_val) { + return PMIC_PARAMETER_ERROR; + } + + switch (bt) { + case BT_ON1B: + reg_val = BITFVAL(MC13783_EN_BT_ON1B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON1B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON1B) | + BITFMASK(MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + reg_val = BITFVAL(MC13783_EN_BT_ON2B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON2B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON2B) | + BITFMASK(MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + reg_val = BITFVAL(MC13783_EN_BT_ON3B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON3B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON3B) | + BITFMASK(MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_2, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets configuration of a button. + * + * @param bt type of button. + * @param sys_rst if true, the system reset is enabled on this button + * @param deb_time gets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_conf_button(t_button bt, + bool *sys_rst, int *deb_time) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (bt) { + case BT_ON1B: + reg_mask = BITFMASK(MC13783_EN_BT_ON1B) | + BITFMASK(MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + reg_mask = BITFMASK(MC13783_EN_BT_ON2B) | + BITFMASK(MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + reg_mask = BITFMASK(MC13783_EN_BT_ON3B) | + BITFMASK(MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_2, ®_val, reg_mask)); + + switch (bt) { + case BT_ON1B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON1B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON2B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON3B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event(t_pwr_int event, void *callback, bool sub) +{ + pmic_event_callback_t power_callback; + type_event power_event; + + power_callback.func = callback; + power_callback.param = NULL; + switch (event) { + case PWR_IT_BPONI: + power_event = EVENT_BPONI; + break; + case PWR_IT_LOBATLI: + power_event = EVENT_LOBATLI; + break; + case PWR_IT_LOBATHI: + power_event = EVENT_LOBATHI; + break; + case PWR_IT_ONOFD1I: + power_event = EVENT_ONOFD1I; + break; + case PWR_IT_ONOFD2I: + power_event = EVENT_ONOFD2I; + break; + case PWR_IT_ONOFD3I: + power_event = EVENT_ONOFD3I; + break; + case PWR_IT_SYSRSTI: + power_event = EVENT_SYSRSTI; + break; + case PWR_IT_PWRRDYI: + power_event = EVENT_PWRRDYI; + break; + case PWR_IT_PCI: + power_event = EVENT_PCI; + break; + case PWR_IT_WARMI: + power_event = EVENT_WARMI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(power_event, power_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe + (power_event, power_callback)); + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_sub(t_pwr_int event, void *callback) +{ + return pmic_power_event(event, callback, true); +} + +/*! + * This function is used to un subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_unsub(t_pwr_int event, void *callback) +{ + return pmic_power_event(event, callback, false); +} + +void pmic_power_key_callback(void) +{ +#ifdef CONFIG_MXC_HWEVENT + /*read the power key is pressed or up */ + t_sensor_bits sense; + struct mxc_hw_event event = { HWE_POWER_KEY, 0 }; + + pmic_get_sensors(&sense); + if (sense.sense_onofd1s) { + pr_debug("PMIC Power key up\n"); + event.args = PWRK_UNPRESS; + } else { + pr_debug("PMIC Power key pressed\n"); + event.args = PWRK_PRESS; + } + /* send hw event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static irqreturn_t power_key_int(int irq, void *dev_id) +{ + pr_info(KERN_INFO "on-off key pressed\n"); + + return 0; +} + +extern void gpio_power_key_active(void); + +/* + * Init and Exit + */ + +static int pmic_power_probe(struct platform_device *pdev) +{ + int irq, ret; + struct pmic_platform_data *ppd; + + /* configure on/off button */ + gpio_power_key_active(); + + ppd = pdev->dev.platform_data; + if (ppd) + irq = ppd->power_key_irq; + else + goto done; + + if (irq == 0) { + pr_info(KERN_INFO "PMIC Power has no platform data\n"); + goto done; + } + set_irq_type(irq, IRQF_TRIGGER_RISING); + + ret = request_irq(irq, power_key_int, 0, "power_key", 0); + if (ret) + pr_info(KERN_ERR "register on-off key interrupt failed\n"); + + set_irq_wake(irq, 1); + + done: + pr_info(KERN_INFO "PMIC Power successfully probed\n"); + return 0; +} + +static struct platform_driver pmic_power_driver_ldm = { + .driver = { + .name = "pmic_power", + }, + .suspend = pmic_power_suspend, + .resume = pmic_power_resume, + .probe = pmic_power_probe, + .remove = NULL, +}; + +static int __init pmic_power_init(void) +{ + pr_debug("PMIC Power driver loading..\n"); + pmic_power_event_sub(PWR_IT_ONOFD1I, pmic_power_key_callback); + /* set power off hook to mc13783 power off */ + pm_power_off = pmic_power_off; + return platform_driver_register(&pmic_power_driver_ldm); +} +static void __exit pmic_power_exit(void) +{ + pmic_power_event_unsub(PWR_IT_ONOFD1I, pmic_power_key_callback); + platform_driver_unregister(&pmic_power_driver_ldm); + pr_debug("PMIC Power driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall_sync(pmic_power_init); +module_exit(pmic_power_exit); + +MODULE_DESCRIPTION("pmic_power driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_power_defs.h b/drivers/mxc/pmic/mc13783/pmic_power_defs.h new file mode 100644 index 000000000000..bdca67589837 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_power_defs.h @@ -0,0 +1,509 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_power_defs.h + * @brief This is the internal header define of PMIC(mc13783) Power driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ + +#ifndef __MC13783_POWER_DEFS_H__ +#define __MC13783_POWER_DEFS_H__ + +/* + * Power Up Mode Sense bits + */ + +#define STATE_ICTEST_MASK 0x000001 + +#define STATE_CLKSEL_BIT 1 +#define STATE_CLKSEL_MASK 0x000002 + +#define STATE_PUMS1_BITS 2 +#define STATE_PUMS1_MASK 0x00000C + +#define STATE_PUMS2_BITS 4 +#define STATE_PUMS2_MASK 0x000030 + +#define STATE_PUMS3_BITS 6 +#define STATE_PUMS3_MASK 0x0000C0 + +#define STATE_CHRGM1_BITS 8 +#define STATE_CHRGM1_MASK 0x000300 + +#define STATE_CHRGM2_BITS 10 +#define STATE_CHRGM2_MASK 0x000C00 + +#define STATE_UMOD_BITS 12 +#define STATE_UMOD_MASK 0x003000 + +#define STATE_USBEN_BIT 14 +#define STATE_USBEN_MASK 0x004000 + +#define STATE_SW1A_J_B_BIT 15 +#define STATE_SW1A_J_B_MASK 0x008000 + +#define STATE_SW2A_J_B_BIT 16 +#define STATE_SW2A_J_B_MASK 0x010000 + +#define PC_COUNT_MAX 3 +#define PC_COUNT_MIN 0 +/* + * Reg Regen + */ +#define MC13783_REGGEN_VAUDIO_LSH 0 +#define MC13783_REGGEN_VAUDIO_WID 1 +#define MC13783_REGGEN_VIOHI_LSH 1 +#define MC13783_REGGEN_VIOHI_WID 1 +#define MC13783_REGGEN_VIOLO_LSH 2 +#define MC13783_REGGEN_VIOLO_WID 1 +#define MC13783_REGGEN_VDIG_LSH 3 +#define MC13783_REGGEN_VDIG_WID 1 +#define MC13783_REGGEN_VGEN_LSH 4 +#define MC13783_REGGEN_VGEN_WID 1 +#define MC13783_REGGEN_VRFDIG_LSH 5 +#define MC13783_REGGEN_VRFDIG_WID 1 +#define MC13783_REGGEN_VRFREF_LSH 6 +#define MC13783_REGGEN_VRFREF_WID 1 +#define MC13783_REGGEN_VRFCP_LSH 7 +#define MC13783_REGGEN_VRFCP_WID 1 +#define MC13783_REGGEN_VCAM_LSH 8 +#define MC13783_REGGEN_VCAM_WID 1 +#define MC13783_REGGEN_VRFBG_LSH 9 +#define MC13783_REGGEN_VRFBG_WID 1 +#define MC13783_REGGEN_VRF1_LSH 10 +#define MC13783_REGGEN_VRF1_WID 1 +#define MC13783_REGGEN_VRF2_LSH 11 +#define MC13783_REGGEN_VRF2_WID 1 +#define MC13783_REGGEN_VMMC1_LSH 12 +#define MC13783_REGGEN_VMMC1_WID 1 +#define MC13783_REGGEN_VMMC2_LSH 13 +#define MC13783_REGGEN_VMMC2_WID 1 +#define MC13783_REGGEN_GPO1_LSH 16 +#define MC13783_REGGEN_GPO1_WID 1 +#define MC13783_REGGEN_GPO2_LSH 17 +#define MC13783_REGGEN_GPO2_WID 1 +#define MC13783_REGGEN_GPO3_LSH 18 +#define MC13783_REGGEN_GPO3_WID 1 +#define MC13783_REGGEN_GPO4_LSH 19 +#define MC13783_REGGEN_GPO4_WID 1 +#define MC13783_REGGEN_INV_LSH 20 +#define MC13783_REGGEN_INV_WID 1 +#define MC13783_REGGEN_VESIMESIM_LSH 21 +#define MC13783_REGGEN_VESIMESIM_WID 1 +#define MC13783_REGGEN_VMMC1ESIM_LSH 22 +#define MC13783_REGGEN_VMMC1ESIM_WID 1 +#define MC13783_REGGEN_VMMC2ESIM_LSH 23 +#define MC13783_REGGEN_VMMC2ESIM_WID 1 + +/* + * Reg Power Control 0 + */ +#define MC13783_PWRCTRL_PCEN_LSH 0 +#define MC13783_PWRCTRL_PCEN_WID 1 +#define MC13783_PWRCTRL_PCEN_ENABLE 1 +#define MC13783_PWRCTRL_PCEN_DISABLE 0 +#define MC13783_PWRCTRL_PC_COUNT_EN_LSH 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_WID 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_ENABLE 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_DISABLE 0 +#define MC13783_PWRCTRL_WARM_EN_LSH 2 +#define MC13783_PWRCTRL_WARM_EN_WID 1 +#define MC13783_PWRCTRL_WARM_EN_ENABLE 1 +#define MC13783_PWRCTRL_WARM_EN_DISABLE 0 +#define MC13783_PWRCTRL_USER_OFF_SPI_LSH 3 +#define MC13783_PWRCTRL_USER_OFF_SPI_WID 1 +#define MC13783_PWRCTRL_USER_OFF_SPI_ENABLE 1 +#define MC13783_PWRCTRL_USER_OFF_PC_LSH 4 +#define MC13783_PWRCTRL_USER_OFF_PC_WID 1 +#define MC13783_PWRCTRL_USER_OFF_PC_ENABLE 1 +#define MC13783_PWRCTRL_USER_OFF_PC_DISABLE 0 +#define MC13783_PWRCTRL_32OUT_USER_OFF_LSH 5 +#define MC13783_PWRCTRL_32OUT_USER_OFF_WID 1 +#define MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE 1 +#define MC13783_PWRCTRL_32OUT_USER_OFF_DISABLE 0 +#define MC13783_PWRCTRL_32OUT_EN_LSH 6 +#define MC13783_PWRCTRL_32OUT_EN_WID 1 +#define MC13783_PWRCTRL_32OUT_EN_ENABLE 1 +#define MC13783_PWRCTRL_32OUT_EN_DISABLE 0 +#define MC13783_REGCTRL_VBKUP2AUTOMH_LSH 7 +#define MC13783_REGCTRL_VBKUP2AUTOMH_WID 1 +#define MC13783_PWRCTRL_VBKUP1_EN_LSH 8 +#define MC13783_PWRCTRL_VBKUP1_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP_ENABLE 1 +#define MC13783_PWRCTRL_VBKUP_DISABLE 0 +#define MC13783_PWRCTRL_VBKUP1_AUTO_EN_LSH 9 +#define MC13783_PWRCTRL_VBKUP1_AUTO_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP1_LSH 10 +#define MC13783_PWRCTRL_VBKUP1_WID 2 +#define MC13783_PWRCTRL_VBKUP2_EN_LSH 12 +#define MC13783_PWRCTRL_VBKUP2_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP2_AUTO_EN_LSH 13 +#define MC13783_PWRCTRL_VBKUP2_AUTO_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP2_LSH 14 +#define MC13783_PWRCTRL_VBKUP2_WID 2 +#define MC13783_REGCTRL_BATTDETEN_LSH 19 +#define MC13783_REGCTRL_BATTDETEN_WID 1 + +/* + * Reg Power Control 1 + */ +#define MC13783_PWRCTRL_PCT_LSH 0 +#define MC13783_PWRCTRL_PCT_WID 8 +#define MC13783_PWRCTRL_PC_COUNT_LSH 8 +#define MC13783_PWRCTRL_PC_COUNT_WID 4 +#define MC13783_PWRCTRL_PC_MAX_CNT_LSH 12 +#define MC13783_PWRCTRL_PC_MAX_CNT_WID 4 +#define MC13783_PWRCTRL_MEM_TMR_LSH 16 +#define MC13783_PWRCTRL_MEM_TMR_WID 4 +#define MC13783_PWRCTRL_MEM_ALLON_LSH 20 +#define MC13783_PWRCTRL_MEM_ALLON_WID 1 +#define MC13783_PWRCTRL_MEM_ALLON_ENABLE 1 +#define MC13783_PWRCTRL_MEM_ALLON_DISABLE 0 + +/* + * Reg Power Control 2 + */ +#define MC13783_AUTO_RESTART_LSH 0 +#define MC13783_AUTO_RESTART_WID 1 +#define MC13783_EN_BT_ON1B_LSH 1 +#define MC13783_EN_BT_ON1B_WID 1 +#define MC13783_EN_BT_ON2B_LSH 2 +#define MC13783_EN_BT_ON2B_WID 1 +#define MC13783_EN_BT_ON3B_LSH 3 +#define MC13783_EN_BT_ON3B_WID 1 +#define MC13783_DEB_BT_ON1B_LSH 4 +#define MC13783_DEB_BT_ON1B_WID 2 +#define MC13783_DEB_BT_ON2B_LSH 6 +#define MC13783_DEB_BT_ON2B_WID 2 +#define MC13783_DEB_BT_ON3B_LSH 8 +#define MC13783_DEB_BT_ON3B_WID 2 + +/* + * Reg Regulator Mode 0 + */ +#define MC13783_REGCTRL_VAUDIO_EN_LSH 0 +#define MC13783_REGCTRL_VAUDIO_EN_WID 1 +#define MC13783_REGCTRL_VAUDIO_EN_ENABLE 1 +#define MC13783_REGCTRL_VAUDIO_EN_DISABLE 0 +#define MC13783_REGCTRL_VAUDIO_STBY_LSH 1 +#define MC13783_REGCTRL_VAUDIO_STBY_WID 1 +#define MC13783_REGCTRL_VAUDIO_MODE_LSH 2 +#define MC13783_REGCTRL_VAUDIO_MODE_WID 1 +#define MC13783_REGCTRL_VIOHI_EN_LSH 3 +#define MC13783_REGCTRL_VIOHI_EN_WID 1 +#define MC13783_REGCTRL_VIOHI_EN_ENABLE 1 +#define MC13783_REGCTRL_VIOHI_EN_DISABLE 0 +#define MC13783_REGCTRL_VIOHI_STBY_LSH 4 +#define MC13783_REGCTRL_VIOHI_STBY_WID 1 +#define MC13783_REGCTRL_VIOHI_MODE_LSH 5 +#define MC13783_REGCTRL_VIOHI_MODE_WID 1 +#define MC13783_REGCTRL_VIOLO_EN_LSH 6 +#define MC13783_REGCTRL_VIOLO_EN_WID 1 +#define MC13783_REGCTRL_VIOLO_EN_ENABLE 1 +#define MC13783_REGCTRL_VIOLO_EN_DISABLE 0 +#define MC13783_REGCTRL_VIOLO_STBY_LSH 7 +#define MC13783_REGCTRL_VIOLO_STBY_WID 1 +#define MC13783_REGCTRL_VIOLO_MODE_LSH 8 +#define MC13783_REGCTRL_VIOLO_MODE_WID 1 +#define MC13783_REGCTRL_VDIG_EN_LSH 9 +#define MC13783_REGCTRL_VDIG_EN_WID 1 +#define MC13783_REGCTRL_VDIG_EN_ENABLE 1 +#define MC13783_REGCTRL_VDIG_EN_DISABLE 0 +#define MC13783_REGCTRL_VDIG_STBY_LSH 10 +#define MC13783_REGCTRL_VDIG_STBY_WID 1 +#define MC13783_REGCTRL_VDIG_MODE_LSH 11 +#define MC13783_REGCTRL_VDIG_MODE_WID 1 +#define MC13783_REGCTRL_VGEN_EN_LSH 12 +#define MC13783_REGCTRL_VGEN_EN_WID 1 +#define MC13783_REGCTRL_VGEN_EN_ENABLE 1 +#define MC13783_REGCTRL_VGEN_EN_DISABLE 0 +#define MC13783_REGCTRL_VGEN_STBY_LSH 13 +#define MC13783_REGCTRL_VGEN_STBY_WID 1 +#define MC13783_REGCTRL_VGEN_MODE_LSH 14 +#define MC13783_REGCTRL_VGEN_MODE_WID 1 +#define MC13783_REGCTRL_VRFDIG_EN_LSH 15 +#define MC13783_REGCTRL_VRFDIG_EN_WID 1 +#define MC13783_REGCTRL_VRFDIG_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFDIG_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFDIG_STBY_LSH 16 +#define MC13783_REGCTRL_VRFDIG_STBY_WID 1 +#define MC13783_REGCTRL_VRFDIG_MODE_LSH 17 +#define MC13783_REGCTRL_VRFDIG_MODE_WID 1 +#define MC13783_REGCTRL_VRFREF_EN_LSH 18 +#define MC13783_REGCTRL_VRFREF_EN_WID 1 +#define MC13783_REGCTRL_VRFREF_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFREF_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFREF_STBY_LSH 19 +#define MC13783_REGCTRL_VRFREF_STBY_WID 1 +#define MC13783_REGCTRL_VRFREF_MODE_LSH 20 +#define MC13783_REGCTRL_VRFREF_MODE_WID 1 +#define MC13783_REGCTRL_VRFCP_EN_LSH 21 +#define MC13783_REGCTRL_VRFCP_EN_WID 1 +#define MC13783_REGCTRL_VRFCP_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFCP_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFCP_STBY_LSH 22 +#define MC13783_REGCTRL_VRFCP_STBY_WID 1 +#define MC13783_REGCTRL_VRFCP_MODE_LSH 23 +#define MC13783_REGCTRL_VRFCP_MODE_WID 1 + +/* + * Reg Regulator Mode 1 + */ +#define MC13783_REGCTRL_VSIM_EN_LSH 0 +#define MC13783_REGCTRL_VSIM_EN_WID 1 +#define MC13783_REGCTRL_VSIM_EN_ENABLE 1 +#define MC13783_REGCTRL_VSIM_EN_DISABLE 0 +#define MC13783_REGCTRL_VSIM_STBY_LSH 1 +#define MC13783_REGCTRL_VSIM_STBY_WID 1 +#define MC13783_REGCTRL_VSIM_MODE_LSH 2 +#define MC13783_REGCTRL_VSIM_MODE_WID 1 +#define MC13783_REGCTRL_VESIM_EN_LSH 3 +#define MC13783_REGCTRL_VESIM_EN_WID 1 +#define MC13783_REGCTRL_VESIM_EN_ENABLE 1 +#define MC13783_REGCTRL_VESIM_EN_DISABLE 0 +#define MC13783_REGCTRL_VESIM_STBY_LSH 4 +#define MC13783_REGCTRL_VESIM_STBY_WID 1 +#define MC13783_REGCTRL_VESIM_MODE_LSH 5 +#define MC13783_REGCTRL_VESIM_MODE_WID 1 +#define MC13783_REGCTRL_VCAM_EN_LSH 6 +#define MC13783_REGCTRL_VCAM_EN_WID 1 +#define MC13783_REGCTRL_VCAM_EN_ENABLE 1 +#define MC13783_REGCTRL_VCAM_EN_DISABLE 0 +#define MC13783_REGCTRL_VCAM_STBY_LSH 7 +#define MC13783_REGCTRL_VCAM_STBY_WID 1 +#define MC13783_REGCTRL_VCAM_MODE_LSH 8 +#define MC13783_REGCTRL_VCAM_MODE_WID 1 +#define MC13783_REGCTRL_VRFBG_EN_LSH 9 +#define MC13783_REGCTRL_VRFBG_EN_WID 1 +#define MC13783_REGCTRL_VRFBG_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFBG_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFBG_STBY_LSH 10 +#define MC13783_REGCTRL_VRFBG_STBY_WID 1 +#define MC13783_REGCTRL_VVIB_EN_LSH 11 +#define MC13783_REGCTRL_VVIB_EN_WID 1 +#define MC13783_REGCTRL_VVIB_EN_ENABLE 1 +#define MC13783_REGCTRL_VVIB_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF1_EN_LSH 12 +#define MC13783_REGCTRL_VRF1_EN_WID 1 +#define MC13783_REGCTRL_VRF1_EN_ENABLE 1 +#define MC13783_REGCTRL_VRF1_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF1_STBY_LSH 13 +#define MC13783_REGCTRL_VRF1_STBY_WID 1 +#define MC13783_REGCTRL_VRF1_MODE_LSH 14 +#define MC13783_REGCTRL_VRF1_MODE_WID 1 +#define MC13783_REGCTRL_VRF2_EN_LSH 15 +#define MC13783_REGCTRL_VRF2_EN_WID 1 +#define MC13783_REGCTRL_VRF2_EN_ENABLE 1 +#define MC13783_REGCTRL_VRF2_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF2_STBY_LSH 16 +#define MC13783_REGCTRL_VRF2_STBY_WID 1 +#define MC13783_REGCTRL_VRF2_MODE_LSH 17 +#define MC13783_REGCTRL_VRF2_MODE_WID 1 +#define MC13783_REGCTRL_VMMC1_EN_LSH 18 +#define MC13783_REGCTRL_VMMC1_EN_WID 1 +#define MC13783_REGCTRL_VMMC1_EN_ENABLE 1 +#define MC13783_REGCTRL_VMMC1_EN_DISABLE 0 +#define MC13783_REGCTRL_VMMC1_STBY_LSH 19 +#define MC13783_REGCTRL_VMMC1_STBY_WID 1 +#define MC13783_REGCTRL_VMMC1_MODE_LSH 20 +#define MC13783_REGCTRL_VMMC1_MODE_WID 1 +#define MC13783_REGCTRL_VMMC2_EN_LSH 21 +#define MC13783_REGCTRL_VMMC2_EN_WID 1 +#define MC13783_REGCTRL_VMMC2_EN_ENABLE 1 +#define MC13783_REGCTRL_VMMC2_EN_DISABLE 0 +#define MC13783_REGCTRL_VMMC2_STBY_LSH 22 +#define MC13783_REGCTRL_VMMC2_STBY_WID 1 +#define MC13783_REGCTRL_VMMC2_MODE_LSH 23 +#define MC13783_REGCTRL_VMMC2_MODE_WID 1 + +/* + * Reg Regulator Misc. + */ +#define MC13783_REGCTRL_GPO1_EN_LSH 6 +#define MC13783_REGCTRL_GPO1_EN_WID 1 +#define MC13783_REGCTRL_GPO1_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO1_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO2_EN_LSH 8 +#define MC13783_REGCTRL_GPO2_EN_WID 1 +#define MC13783_REGCTRL_GPO2_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO2_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO3_EN_LSH 10 +#define MC13783_REGCTRL_GPO3_EN_WID 1 +#define MC13783_REGCTRL_GPO3_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO3_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO4_EN_LSH 12 +#define MC13783_REGCTRL_GPO4_EN_WID 1 +#define MC13783_REGCTRL_GPO4_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO4_EN_DISABLE 0 +#define MC13783_REGCTRL_VIBPINCTRL_LSH 14 +#define MC13783_REGCTRL_VIBPINCTRL_WID 1 + +/* + * Reg Regulator Setting 0 + */ +#define MC13783_REGSET_VIOLO_LSH 2 +#define MC13783_REGSET_VIOLO_WID 2 +#define MC13783_REGSET_VDIG_LSH 4 +#define MC13783_REGSET_VDIG_WID 2 +#define MC13783_REGSET_VGEN_LSH 6 +#define MC13783_REGSET_VGEN_WID 3 +#define MC13783_REGSET_VRFDIG_LSH 9 +#define MC13783_REGSET_VRFDIG_WID 2 +#define MC13783_REGSET_VRFREF_LSH 11 +#define MC13783_REGSET_VRFREF_WID 2 +#define MC13783_REGSET_VRFCP_LSH 13 +#define MC13783_REGSET_VRFCP_WID 1 +#define MC13783_REGSET_VSIM_LSH 14 +#define MC13783_REGSET_VSIM_WID 1 +#define MC13783_REGSET_VESIM_LSH 15 +#define MC13783_REGSET_VESIM_WID 1 +#define MC13783_REGSET_VCAM_LSH 16 +#define MC13783_REGSET_VCAM_WID 3 + +/* + * Reg Regulator Setting 1 + */ +#define MC13783_REGSET_VVIB_LSH 0 +#define MC13783_REGSET_VVIB_WID 2 +#define MC13783_REGSET_VRF1_LSH 2 +#define MC13783_REGSET_VRF1_WID 2 +#define MC13783_REGSET_VRF2_LSH 4 +#define MC13783_REGSET_VRF2_WID 2 +#define MC13783_REGSET_VMMC1_LSH 6 +#define MC13783_REGSET_VMMC1_WID 3 +#define MC13783_REGSET_VMMC2_LSH 9 +#define MC13783_REGSET_VMMC2_WID 3 + +/* + * Reg Switcher 0 + */ +#define MC13783_SWSET_SW1A_LSH 0 +#define MC13783_SWSET_SW1A_WID 6 +#define MC13783_SWSET_SW1A_DVS_LSH 6 +#define MC13783_SWSET_SW1A_DVS_WID 6 +#define MC13783_SWSET_SW1A_STDBY_LSH 12 +#define MC13783_SWSET_SW1A_STDBY_WID 6 + +/* + * Reg Switcher 1 + */ +#define MC13783_SWSET_SW1B_LSH 0 +#define MC13783_SWSET_SW1B_WID 6 +#define MC13783_SWSET_SW1B_DVS_LSH 6 +#define MC13783_SWSET_SW1B_DVS_WID 6 +#define MC13783_SWSET_SW1B_STDBY_LSH 12 +#define MC13783_SWSET_SW1B_STDBY_WID 6 + +/* + * Reg Switcher 2 + */ +#define MC13783_SWSET_SW2A_LSH 0 +#define MC13783_SWSET_SW2A_WID 6 +#define MC13783_SWSET_SW2A_DVS_LSH 6 +#define MC13783_SWSET_SW2A_DVS_WID 6 +#define MC13783_SWSET_SW2A_STDBY_LSH 12 +#define MC13783_SWSET_SW2A_STDBY_WID 6 + +/* + * Reg Switcher 3 + */ +#define MC13783_SWSET_SW2B_LSH 0 +#define MC13783_SWSET_SW2B_WID 6 +#define MC13783_SWSET_SW2B_DVS_LSH 6 +#define MC13783_SWSET_SW2B_DVS_WID 6 +#define MC13783_SWSET_SW2B_STDBY_LSH 12 +#define MC13783_SWSET_SW2B_STDBY_WID 6 + +/* + * Reg Switcher 4 + */ +#define MC13783_SWCTRL_SW1A_MODE_LSH 0 +#define MC13783_SWCTRL_SW1A_MODE_WID 2 +#define MC13783_SWCTRL_SW1A_STBY_MODE_LSH 2 +#define MC13783_SWCTRL_SW1A_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW1A_DVS_SPEED_LSH 6 +#define MC13783_SWCTRL_SW1A_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW1A_PANIC_MODE_LSH 8 +#define MC13783_SWCTRL_SW1A_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW1A_SOFTSTART_LSH 9 +#define MC13783_SWCTRL_SW1A_SOFTSTART_WID 1 +#define MC13783_SWCTRL_SW1B_MODE_LSH 10 +#define MC13783_SWCTRL_SW1B_MODE_WID 2 +#define MC13783_SWCTRL_SW1B_STBY_MODE_LSH 12 +#define MC13783_SWCTRL_SW1B_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW1B_DVS_SPEED_LSH 14 +#define MC13783_SWCTRL_SW1B_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW1B_PANIC_MODE_LSH 16 +#define MC13783_SWCTRL_SW1B_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW1B_SOFTSTART_LSH 17 +#define MC13783_SWCTRL_SW1B_SOFTSTART_WID 1 +#define MC13783_SWCTRL_PLL_EN_LSH 18 +#define MC13783_SWCTRL_PLL_EN_WID 1 +#define MC13783_SWCTRL_PLL_EN_ENABLE 1 +#define MC13783_SWCTRL_PLL_EN_DISABLE 0 +#define MC13783_SWCTRL_PLL_FACTOR_LSH 19 +#define MC13783_SWCTRL_PLL_FACTOR_WID 3 + +/* + * Reg Switcher 5 + */ +#define MC13783_SWCTRL_SW2A_MODE_LSH 0 +#define MC13783_SWCTRL_SW2A_MODE_WID 2 +#define MC13783_SWCTRL_SW2A_STBY_MODE_LSH 2 +#define MC13783_SWCTRL_SW2A_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW2A_DVS_SPEED_LSH 6 +#define MC13783_SWCTRL_SW2A_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW2A_PANIC_MODE_LSH 8 +#define MC13783_SWCTRL_SW2A_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW2A_SOFTSTART_LSH 9 +#define MC13783_SWCTRL_SW2A_SOFTSTART_WID 1 +#define MC13783_SWCTRL_SW2B_MODE_LSH 10 +#define MC13783_SWCTRL_SW2B_MODE_WID 2 +#define MC13783_SWCTRL_SW2B_STBY_MODE_LSH 12 +#define MC13783_SWCTRL_SW2B_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW2B_DVS_SPEED_LSH 14 +#define MC13783_SWCTRL_SW2B_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW2B_PANIC_MODE_LSH 16 +#define MC13783_SWCTRL_SW2B_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW2B_SOFTSTART_LSH 17 +#define MC13783_SWCTRL_SW2B_SOFTSTART_WID 1 +#define MC13783_SWSET_SW3_LSH 18 +#define MC13783_SWSET_SW3_WID 2 +#define MC13783_SWCTRL_SW3_EN_LSH 20 +#define MC13783_SWCTRL_SW3_EN_WID 2 +#define MC13783_SWCTRL_SW3_EN_ENABLE 1 +#define MC13783_SWCTRL_SW3_EN_DISABLE 0 +#define MC13783_SWCTRL_SW3_STBY_LSH 21 +#define MC13783_SWCTRL_SW3_STBY_WID 1 +#define MC13783_SWCTRL_SW3_MODE_LSH 22 +#define MC13783_SWCTRL_SW3_MODE_WID 1 + +/* + * Switcher configuration + */ +#define MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN 0 +#define MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN 1 +#define MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN 2 +#define MC13783_SWCTRL_SW_MODE_LOW_POWER_EN 3 +#define MC13783_REGTRL_LP_MODE_ENABLE 1 +#define MC13783_REGTRL_LP_MODE_DISABLE 0 +#define MC13783_REGTRL_STBY_MODE_ENABLE 1 +#define MC13783_REGTRL_STBY_MODE_DISABLE 0 + +#endif /* __MC13783_POWER_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_rtc.c b/drivers/mxc/pmic/mc13783/pmic_rtc.c new file mode 100644 index 000000000000..dd6dd51b7d93 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_rtc.c @@ -0,0 +1,544 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_rtc.c + * @brief This is the main file of PMIC(mc13783) RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include + +#include "pmic_rtc_defs.h" + +#define PMIC_LOAD_ERROR_MSG \ +"PMIC card was not correctly detected. Stop loading PMIC RTC driver\n" + +/* + * Global variables + */ +static int pmic_rtc_major; +static void callback_alarm_asynchronous(void *); +static void callback_alarm_synchronous(void *); +static unsigned int pmic_rtc_poll(struct file *file, poll_table * wait); +static DECLARE_WAIT_QUEUE_HEAD(queue_alarm); +static DECLARE_WAIT_QUEUE_HEAD(pmic_rtc_wait); +static pmic_event_callback_t alarm_callback; +static pmic_event_callback_t rtc_callback; +static bool pmic_rtc_done; +static struct class *pmic_rtc_class; + +static DECLARE_MUTEX(mutex); + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_rtc_set_time); +EXPORT_SYMBOL(pmic_rtc_get_time); +EXPORT_SYMBOL(pmic_rtc_set_time_alarm); +EXPORT_SYMBOL(pmic_rtc_get_time_alarm); +EXPORT_SYMBOL(pmic_rtc_wait_alarm); +EXPORT_SYMBOL(pmic_rtc_event_sub); +EXPORT_SYMBOL(pmic_rtc_event_unsub); + +/* + * Real Time Clock Pmic API + */ + +/*! + * This is the callback function called on TSI Pmic event, used in asynchronous + * call. + */ +static void callback_alarm_asynchronous(void *unused) +{ + pmic_rtc_done = true; +} + +/*! + * This is the callback function is used in test code for (un)sub. + */ +static void callback_test_sub(void) +{ + printk(KERN_INFO "*****************************************\n"); + printk(KERN_INFO "***** PMIC RTC 'Alarm IT CallBack' ******\n"); + printk(KERN_INFO "*****************************************\n"); +} + +/*! + * This is the callback function called on TSI Pmic event, used in synchronous + * call. + */ +static void callback_alarm_synchronous(void *unused) +{ + printk(KERN_INFO "*** Alarm IT Pmic ***\n"); + wake_up(&queue_alarm); +} + +/*! + * This function wait the Alarm event + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_wait_alarm(void) +{ + DEFINE_WAIT(wait); + alarm_callback.func = callback_alarm_synchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, alarm_callback)); + prepare_to_wait(&queue_alarm, &wait, TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&queue_alarm, &wait); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TODAI, alarm_callback)); + return PMIC_SUCCESS; +} + +/*! + * This function set the real time clock of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time(struct timeval *pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + tod_reg_val = pmic_time->tv_sec % 86400; + day_reg_val = pmic_time->tv_sec / 86400; + + mask = BITFMASK(MC13783_RTCTIME_TIME); + value = BITFVAL(MC13783_RTCTIME_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_TIME, value, mask)); + + mask = BITFMASK(MC13783_RTCDAY_DAY); + value = BITFVAL(MC13783_RTCDAY_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function get the real time clock of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time(struct timeval *pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + mask = BITFMASK(MC13783_RTCTIME_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_TIME, &value, mask)); + tod_reg_val = BITFEXT(value, MC13783_RTCTIME_TIME); + + mask = BITFMASK(MC13783_RTCDAY_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val = BITFEXT(value, MC13783_RTCDAY_DAY); + + pmic_time->tv_sec = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * + 86400)); + return PMIC_SUCCESS; +} + +/*! + * This function set the real time clock alarm of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time_alarm(struct timeval *pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + if (down_interruptible(&mutex) < 0) + return ret; + + tod_reg_val = pmic_time->tv_sec % 86400; + day_reg_val = pmic_time->tv_sec / 86400; + + mask = BITFMASK(MC13783_RTCALARM_TIME); + value = BITFVAL(MC13783_RTCALARM_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, value, mask)); + + mask = BITFMASK(MC13783_RTCALARM_DAY); + value = BITFVAL(MC13783_RTCALARM_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY_ALARM, value, mask)); + up(&mutex); + return PMIC_SUCCESS; +} + +/*! + * This function get the real time clock alarm of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time_alarm(struct timeval *pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + mask = BITFMASK(MC13783_RTCALARM_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_ALARM, &value, mask)); + tod_reg_val = BITFEXT(value, MC13783_RTCALARM_TIME); + + mask = BITFMASK(MC13783_RTCALARM_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY_ALARM, &value, mask)); + day_reg_val = BITFEXT(value, MC13783_RTCALARM_DAY); + + pmic_time->tv_sec = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * + 86400)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event(t_rtc_int event, void *callback, bool sub) +{ + type_event rtc_event; + if (callback == NULL) { + return PMIC_ERROR; + } else { + rtc_callback.func = callback; + rtc_callback.param = NULL; + } + switch (event) { + case RTC_IT_ALARM: + rtc_event = EVENT_TODAI; + break; + case RTC_IT_1HZ: + rtc_event = EVENT_E1HZI; + break; + case RTC_IT_RST: + rtc_event = EVENT_RTCRSTI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(rtc_event, rtc_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe(rtc_event, rtc_callback)); + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_sub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, true)); + return PMIC_SUCCESS; +} + +/*! + * This function is used to un subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_unsub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, false)); + return PMIC_SUCCESS; +} + +/* Called without the kernel lock - fine */ +static unsigned int pmic_rtc_poll(struct file *file, poll_table * wait) +{ + /*poll_wait(file, &pmic_rtc_wait, wait); */ + + if (pmic_rtc_done) + return POLLIN | POLLRDNORM; + return 0; +} + +/*! + * This function implements IOCTL controls on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct timeval *pmic_time = NULL; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + if (arg) { + pmic_time = kmalloc(sizeof(struct timeval), GFP_KERNEL); + if (pmic_time == NULL) + return -ENOMEM; + + /* if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } */ + } + + switch (cmd) { + case PMIC_RTC_SET_TIME: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("SET RTC\n"); + CHECK_ERROR(pmic_rtc_set_time(pmic_time)); + break; + case PMIC_RTC_GET_TIME: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("GET RTC\n"); + CHECK_ERROR(pmic_rtc_get_time(pmic_time)); + break; + case PMIC_RTC_SET_ALARM: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("SET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_set_time_alarm(pmic_time)); + break; + case PMIC_RTC_GET_ALARM: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("GET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_get_time_alarm(pmic_time)); + break; + case PMIC_RTC_WAIT_ALARM: + printk(KERN_INFO "WAIT ALARM...\n"); + CHECK_ERROR(pmic_rtc_event_sub(RTC_IT_ALARM, + callback_test_sub)); + CHECK_ERROR(pmic_rtc_wait_alarm()); + printk(KERN_INFO "ALARM DONE\n"); + CHECK_ERROR(pmic_rtc_event_unsub(RTC_IT_ALARM, + callback_test_sub)); + break; + case PMIC_RTC_ALARM_REGISTER: + printk(KERN_INFO "PMIC RTC ALARM REGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, alarm_callback)); + break; + case PMIC_RTC_ALARM_UNREGISTER: + printk(KERN_INFO "PMIC RTC ALARM UNREGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_unsubscribe + (EVENT_TODAI, alarm_callback)); + pmic_rtc_done = false; + break; + default: + pr_debug("%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + if (arg) { + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + kfree(pmic_time); + } + + return 0; +} + +/*! + * This function implements the open method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_open(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function implements the release method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function is called to put the RTC in a low power state. + * There is no need for power handlers for the RTC device. + * The RTC cannot be suspended. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +/*! + * This function is called to resume the RTC from a low power state. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_rtc_resume(struct platform_device *pdev) +{ + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ + +static struct file_operations pmic_rtc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_rtc_ioctl, + .poll = pmic_rtc_poll, + .open = pmic_rtc_open, + .release = pmic_rtc_release, +}; + +static int pmic_rtc_remove(struct platform_device *pdev) +{ + device_destroy(pmic_rtc_class, MKDEV(pmic_rtc_major, 0)); + class_destroy(pmic_rtc_class); + unregister_chrdev(pmic_rtc_major, "pmic_rtc"); + return 0; +} + +static int pmic_rtc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_rtc_major = register_chrdev(0, "pmic_rtc", &pmic_rtc_fops); + if (pmic_rtc_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_rtc\n"); + return pmic_rtc_major; + } + + pmic_rtc_class = class_create(THIS_MODULE, "pmic_rtc"); + if (IS_ERR(pmic_rtc_class)) { + printk(KERN_ERR "Error creating pmic rtc class.\n"); + ret = PTR_ERR(pmic_rtc_class); + goto err_out1; + } + + temp_class = device_create(pmic_rtc_class, NULL, + MKDEV(pmic_rtc_major, 0), NULL, + "pmic_rtc"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic rtc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + printk(KERN_INFO "PMIC RTC successfully probed\n"); + return ret; + + err_out2: + class_destroy(pmic_rtc_class); + err_out1: + unregister_chrdev(pmic_rtc_major, "pmic_rtc"); + return ret; +} + +static struct platform_driver pmic_rtc_driver_ldm = { + .driver = { + .name = "pmic_rtc", + .owner = THIS_MODULE, + }, + .suspend = pmic_rtc_suspend, + .resume = pmic_rtc_resume, + .probe = pmic_rtc_probe, + .remove = pmic_rtc_remove, +}; + +static int __init pmic_rtc_init(void) +{ + pr_debug("PMIC RTC driver loading...\n"); + return platform_driver_register(&pmic_rtc_driver_ldm); +} +static void __exit pmic_rtc_exit(void) +{ + platform_driver_unregister(&pmic_rtc_driver_ldm); + pr_debug("PMIC RTC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_rtc_init); +module_exit(pmic_rtc_exit); + +MODULE_DESCRIPTION("Pmic_rtc driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h b/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h new file mode 100644 index 000000000000..0164a5154d98 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h @@ -0,0 +1,47 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MC13783_RTC_DEFS_H__ +#define __MC13783_RTC_DEFS_H__ + +/*! + * @file mc13783/pmic_rtc_defs.h + * @brief This is the internal header of PMIC(mc13783) RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * RTC Time + */ +#define MC13783_RTCTIME_TIME_LSH 0 +#define MC13783_RTCTIME_TIME_WID 17 + +/* + * RTC Alarm + */ +#define MC13783_RTCALARM_TIME_LSH 0 +#define MC13783_RTCALARM_TIME_WID 17 + +/* + * RTC Day + */ +#define MC13783_RTCDAY_DAY_LSH 0 +#define MC13783_RTCDAY_DAY_WID 15 + +/* + * RTC Day alarm + */ +#define MC13783_RTCALARM_DAY_LSH 0 +#define MC13783_RTCALARM_DAY_WID 15 + +#endif /* __MC13783_RTC_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13892/Kconfig b/drivers/mxc/pmic/mc13892/Kconfig new file mode 100644 index 000000000000..930e06ab2282 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/Kconfig @@ -0,0 +1,48 @@ +# +# PMIC Modules configuration +# + +config MXC_MC13892_ADC + tristate "MC13892 ADC support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 ADC module driver. This module provides kernel API + for the ADC system of MC13892. + It controls also the touch screen interface. + If you want MC13892 ADC support, you should say Y here + +config MXC_MC13892_RTC + tristate "MC13892 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 RTC module driver. This module provides kernel API + for RTC part of MC13892. + If you want MC13892 RTC support, you should say Y here +config MXC_MC13892_LIGHT + tristate "MC13892 Light and Backlight support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 Light module driver. This module provides kernel API + for led and backlight control part of MC13892. + If you want MC13892 Light support, you should say Y here +config MXC_MC13892_BATTERY + tristate "MC13892 Battery API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 battery module driver. This module provides kernel API + for battery control part of MC13892. + If you want MC13892 battery support, you should say Y here +config MXC_MC13892_CONNECTIVITY + tristate "MC13892 Connectivity API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 connectivity module driver. This module provides kernel API + for USB/RS232 connectivity control part of MC13892. + If you want MC13892 connectivity support, you should say Y here +config MXC_MC13892_POWER + tristate "MC13892 Power API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 power and supplies module driver. This module provides kernel API + for power and regulator control part of MC13892. + If you want MC13892 power support, you should say Y here diff --git a/drivers/mxc/pmic/mc13892/Makefile b/drivers/mxc/pmic/mc13892/Makefile new file mode 100644 index 000000000000..0ed2b7eb4c11 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the mc13783 pmic drivers. +# + +obj-$(CONFIG_MXC_MC13892_ADC) += pmic_adc.o +#obj-$(CONFIG_MXC_MC13892_RTC) += pmic_rtc.o +obj-$(CONFIG_MXC_MC13892_LIGHT) += pmic_light.o +obj-$(CONFIG_MXC_MC13892_BATTERY) += pmic_battery.o +#obj-$(CONFIG_MXC_MC13892_CONNECTIVITY) += pmic_convity.o +#obj-$(CONFIG_MXC_MC13892_POWER) += pmic_power.o diff --git a/drivers/mxc/pmic/mc13892/pmic_adc.c b/drivers/mxc/pmic/mc13892/pmic_adc.c new file mode 100644 index 000000000000..cc74078da002 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_adc.c @@ -0,0 +1,1073 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../core/pmic.h" + +#define DEF_ADC_0 0x008000 +#define DEF_ADC_3 0x0001c0 + +#define ADC_NB_AVAILABLE 2 + +#define MAX_CHANNEL 7 + +#define MC13892_ADC0_TS_M_LSH 14 +#define MC13892_ADC0_TS_M_WID 3 + +/* + * Maximun allowed variation in the three X/Y co-ordinates acquired from + * touch-screen + */ +#define DELTA_Y_MAX 50 +#define DELTA_X_MAX 50 + +/* + * ADC 0 + */ +#define ADC_CHRGICON 0x000002 +#define ADC_WAIT_TSI_0 0x001400 + +#define ADC_INC 0x030000 +#define ADC_BIS 0x800000 +#define ADC_CHRGRAW_D5 0x008000 + +/* + * ADC 1 + */ + +#define ADC_EN 0x000001 +#define ADC_SGL_CH 0x000002 +#define ADC_ADCCAL 0x000004 +#define ADC_ADSEL 0x000008 +#define ADC_TRIGMASK 0x000010 +#define ADC_CH_0_POS 5 +#define ADC_CH_0_MASK 0x0000E0 +#define ADC_CH_1_POS 8 +#define ADC_CH_1_MASK 0x000700 +#define ADC_DELAY_POS 11 +#define ADC_DELAY_MASK 0x07F800 +#define ADC_ATO 0x080000 +#define ASC_ADC 0x100000 +#define ADC_WAIT_TSI_1 0x200001 +#define ADC_NO_ADTRIG 0x200000 + +/* + * ADC 2 - 4 + */ +#define ADD1_RESULT_MASK 0x00000FFC +#define ADD2_RESULT_MASK 0x00FFC000 +#define ADC_TS_MASK 0x00FFCFFC + +#define ADC_WCOMP 0x040000 +#define ADC_WCOMP_H_POS 0 +#define ADC_WCOMP_L_POS 9 +#define ADC_WCOMP_H_MASK 0x00003F +#define ADC_WCOMP_L_MASK 0x007E00 + +#define ADC_MODE_MASK 0x00003F + +#define ADC_INT_BISDONEI 0x02 +#define ADC_TSMODE_MASK 0x007000 + +typedef enum adc_state { + ADC_FREE, + ADC_USED, + ADC_MONITORING, +} t_adc_state; + +typedef enum reading_mode { + /*! + * Enables lithium cell reading + */ + M_LITHIUM_CELL = 0x000001, + /*! + * Enables charge current reading + */ + M_CHARGE_CURRENT = 0x000002, + /*! + * Enables battery current reading + */ + M_BATTERY_CURRENT = 0x000004, +} t_reading_mode; + +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Sets the single channel mode + */ + bool single_channel; + /*! + * Channel selection 1 + */ + t_channel channel_0; + /*! + * Channel selection 2 + */ + t_channel channel_1; + /*! + * Used to configure ADC mode with t_reading_mode + */ + t_reading_mode read_mode; + /*! + * Sets the Touch screen mode + */ + bool read_ts; + /*! + * Wait TSI event before touch screen reading + */ + bool wait_tsi; + /*! + * Sets CHRGRAW scaling to divide by 5 + * Only supported on 2.0 and higher + */ + bool chrgraw_devide_5; + /*! + * Return ADC values + */ + unsigned int value[8]; + /*! + * Return touch screen values + */ + t_touch_screen ts_value; +} t_adc_param; + +static int pmic_adc_filter(t_touch_screen *ts_curr); +int mc13892_adc_request(bool read_ts); +int mc13892_adc_release(int adc_index); +t_reading_mode mc13892_set_read_mode(t_channel channel); +PMIC_STATUS mc13892_adc_read_ts(t_touch_screen *touch_sample, int wait_tsi); + +/* internal function */ +static void callback_tsi(void *); +static void callback_adcdone(void *); +static void callback_adcbisdone(void *); + +static int swait; + +static int suspend_flag; + +static wait_queue_head_t suspendq; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_adc_init); +EXPORT_SYMBOL(pmic_adc_deinit); +EXPORT_SYMBOL(pmic_adc_convert); +EXPORT_SYMBOL(pmic_adc_convert_8x); +EXPORT_SYMBOL(pmic_adc_set_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_sample); + +static DECLARE_COMPLETION(adcdone_it); +static DECLARE_COMPLETION(adcbisdone_it); +static DECLARE_COMPLETION(adc_tsi); +static pmic_event_callback_t tsi_event; +static pmic_event_callback_t event_adc; +static pmic_event_callback_t event_adc_bis; +static bool data_ready_adc_1; +static bool data_ready_adc_2; +static bool adc_ts; +static bool wait_ts; +static bool monitor_en; +static bool monitor_adc; +static DECLARE_MUTEX(convert_mutex); + +static DECLARE_WAIT_QUEUE_HEAD(queue_adc_busy); +static t_adc_state adc_dev[2]; + +static unsigned channel_num[] = { + 0, + 1, + 3, + 4, + 2, + 0, + 1, + 3, + 4, + -1, + 5, + 6, + 7, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 +}; + +static bool pmic_adc_ready; + +int is_pmic_adc_ready() +{ + return pmic_adc_ready; +} +EXPORT_SYMBOL(is_pmic_adc_ready); + + +static int pmic_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_ADC0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC4, 0, PMIC_ALL_BITS)); + + return 0; +}; + +static int pmic_adc_resume(struct platform_device *pdev) +{ + /* nothing for mc13892 adc */ + unsigned int adc_0_reg, adc_1_reg, reg_mask; + suspend_flag = 0; + + /* let interrupt of TSI again */ + adc_0_reg = ADC_WAIT_TSI_0; + reg_mask = ADC_WAIT_TSI_0; + CHECK_ERROR(pmic_write_reg(REG_ADC0, adc_0_reg, reg_mask)); + adc_1_reg = ADC_WAIT_TSI_1 | (ADC_BIS * adc_ts); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, PMIC_ALL_BITS)); + + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +static void callback_tsi(void *unused) +{ + pr_debug("*** TSI IT mc13892 PMIC_ADC_GET_TOUCH_SAMPLE ***\n"); + if (wait_ts) { + complete(&adc_tsi); + pmic_event_mask(EVENT_TSI); + } +} + +static void callback_adcdone(void *unused) +{ + if (data_ready_adc_1) + complete(&adcdone_it); +} + +static void callback_adcbisdone(void *unused) +{ + pr_debug("* adcdone bis it callback *\n"); + if (data_ready_adc_2) + complete(&adcbisdone_it); +} + +static int pmic_adc_filter(t_touch_screen *ts_curr) +{ + unsigned int ydiff, xdiff; + unsigned int sample_sumx, sample_sumy; + + if (ts_curr->contact_resistance == 0) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + return 0; + } + + ydiff = abs(ts_curr->y_position1 - ts_curr->y_position2); + if (ydiff > DELTA_Y_MAX) { + pr_debug("pmic_adc_filter: Ret pos y\n"); + return -1; + } + + xdiff = abs(ts_curr->x_position1 - ts_curr->x_position2); + if (xdiff > DELTA_X_MAX) { + pr_debug("mc13892_adc_filter: Ret pos x\n"); + return -1; + } + + sample_sumx = ts_curr->x_position1 + ts_curr->x_position2; + sample_sumy = ts_curr->y_position1 + ts_curr->y_position2; + + ts_curr->y_position = sample_sumy / 2; + ts_curr->x_position = sample_sumx / 2; + + return 0; +} + +int pmic_adc_init(void) +{ + unsigned int reg_value = 0, i = 0; + + if (suspend_flag == 1) + return -EBUSY; + + for (i = 0; i < ADC_NB_AVAILABLE; i++) + adc_dev[i] = ADC_FREE; + + CHECK_ERROR(pmic_write_reg(REG_ADC0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC4, 0, PMIC_ALL_BITS)); + reg_value = 0x001000; + + data_ready_adc_1 = false; + data_ready_adc_2 = false; + adc_ts = false; + wait_ts = false; + monitor_en = false; + monitor_adc = false; + + /* sub to ADCDone IT */ + event_adc.param = NULL; + event_adc.func = callback_adcdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCDONEI, event_adc)); + + /* sub to ADCDoneBis IT */ + event_adc_bis.param = NULL; + event_adc_bis.func = callback_adcbisdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCBISDONEI, event_adc_bis)); + + /* sub to Touch Screen IT */ + tsi_event.param = NULL; + tsi_event.func = callback_tsi; + CHECK_ERROR(pmic_event_subscribe(EVENT_TSI, tsi_event)); + + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_deinit(void) +{ + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCDONEI, event_adc)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCBISDONEI, event_adc_bis)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TSI, tsi_event)); + + return PMIC_SUCCESS; +} + +int mc13892_adc_init_param(t_adc_param *adc_param) +{ + int i = 0; + + if (suspend_flag == 1) + return -EBUSY; + + adc_param->delay = 0; + adc_param->conv_delay = false; + adc_param->single_channel = false; + adc_param->channel_0 = BATTERY_VOLTAGE; + adc_param->channel_1 = BATTERY_VOLTAGE; + adc_param->read_mode = 0; + adc_param->wait_tsi = 0; + adc_param->chrgraw_devide_5 = true; + adc_param->read_ts = false; + adc_param->ts_value.x_position = 0; + adc_param->ts_value.y_position = 0; + adc_param->ts_value.contact_resistance = 0; + for (i = 0; i <= MAX_CHANNEL; i++) + adc_param->value[i] = 0; + + return 0; +} + +PMIC_STATUS mc13892_adc_convert(t_adc_param *adc_param) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, result_reg = 0, i = 0; + unsigned int result = 0, temp = 0; + pmic_version_t mc13892_ver; + int ret; + + pr_debug("mc13892 ADC - mc13892_adc_convert ....\n"); + if (suspend_flag == 1) + return -EBUSY; + + if (adc_param->wait_tsi) { + /* configure adc to wait tsi interrupt */ + INIT_COMPLETION(adc_tsi); + + /*for ts don't use bis */ + /*put ts in interrupt mode */ + /* still kep reference? */ + adc_0_reg = 0x001400 | (ADC_BIS * 0); + pmic_event_unmask(EVENT_TSI); + CHECK_ERROR(pmic_write_reg(REG_ADC0, adc_0_reg, PMIC_ALL_BITS)); + /*for ts don't use bis */ + adc_1_reg = 0x200001 | (ADC_BIS * 0); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, PMIC_ALL_BITS)); + pr_debug("wait tsi ....\n"); + wait_ts = true; + wait_for_completion_interruptible(&adc_tsi); + wait_ts = false; + } + down(&convert_mutex); + use_bis = mc13892_adc_request(adc_param->read_ts); + if (use_bis < 0) { + pr_debug("process has received a signal and got interrupted\n"); + ret = -EINTR; + goto out_up_convert_mutex; + } + + /* CONFIGURE ADC REG 0 */ + adc_0_reg = 0; + adc_1_reg = 0; + if (adc_param->read_ts == false) { + adc_0_reg = adc_param->read_mode & 0x00003F; + /* add auto inc */ + adc_0_reg |= ADC_INC; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + mc13892_ver = pmic_get_version(); + if (mc13892_ver.revision >= 20) + if (adc_param->chrgraw_devide_5) + adc_0_reg |= ADC_CHRGRAW_D5; + + if (adc_param->single_channel) + adc_1_reg |= ADC_SGL_CH; + + if (adc_param->conv_delay) + adc_1_reg |= ADC_ATO; + + if (adc_param->single_channel) + adc_1_reg |= ADC_SGL_CH; + + adc_1_reg |= (adc_param->channel_0 << ADC_CH_0_POS) & + ADC_CH_0_MASK; + adc_1_reg |= (adc_param->channel_1 << ADC_CH_1_POS) & + ADC_CH_1_MASK; + } else { + adc_0_reg = 0x002400 | (ADC_BIS * use_bis) | ADC_INC; + } + + if (adc_param->channel_0 == channel_num[CHARGE_CURRENT]) + adc_0_reg |= ADC_CHRGICON; + + /*Change has been made here */ + ret = pmic_write_reg(REG_ADC0, adc_0_reg, + ADC_INC | ADC_BIS | ADC_CHRGRAW_D5 | 0xfff00ff); + if (ret != PMIC_SUCCESS) { + pr_debug("pmic_write_reg"); + goto out_mc13892_adc_release; + } + + /* CONFIGURE ADC REG 1 */ + if (adc_param->read_ts == false) { + adc_1_reg |= ADC_NO_ADTRIG; + adc_1_reg |= ADC_EN; + adc_1_reg |= (adc_param->delay << ADC_DELAY_POS) & + ADC_DELAY_MASK; + if (use_bis) + adc_1_reg |= ADC_BIS; + } else { + /* configure and start convert to read x and y position */ + /* configure to read 2 value in channel selection 1 & 2 */ + adc_1_reg = 0x100409 | (ADC_BIS * use_bis) | ADC_NO_ADTRIG; + /* set ATOx = 5, it could be better for ts ADC */ + adc_1_reg |= 0x002800; + } + + if (adc_param->channel_0 == channel_num[CHARGE_CURRENT]) { + adc_param->channel_1 = channel_num[CHARGE_CURRENT]; + adc_1_reg &= ~(ADC_CH_0_MASK | ADC_CH_1_MASK | ADC_NO_ADTRIG | + ADC_TRIGMASK | ADC_EN | ADC_SGL_CH | ADC_ADCCAL); + adc_1_reg |= ((adc_param->channel_0 << ADC_CH_0_POS) | + (adc_param->channel_1 << ADC_CH_1_POS)); + adc_1_reg |= (ADC_EN | ADC_SGL_CH | ADC_ADCCAL); + + if (use_bis == 0) { + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, + 0xFFFFFF)); + } else { + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, + 0xFFFFFF)); + temp = 0x800000; + CHECK_ERROR(pmic_write_reg(REG_ADC3, temp, 0xFFFFFF)); + } + + adc_1_reg &= ~(ADC_NO_ADTRIG | ASC_ADC | ADC_ADCCAL); + adc_1_reg |= (ADC_NO_ADTRIG | ASC_ADC); + if (use_bis == 0) { + data_ready_adc_1 = true; + INIT_COMPLETION(adcdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, + 0xFFFFFF)); + pr_debug("wait adc done\n"); + wait_for_completion_interruptible(&adcdone_it); + data_ready_adc_1 = false; + } else { + data_ready_adc_2 = true; + INIT_COMPLETION(adcbisdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, + 0xFFFFFF)); + temp = 0x800000; + CHECK_ERROR(pmic_write_reg(REG_ADC3, temp, 0xFFFFFF)); + pr_debug("wait adc done bis\n"); + wait_for_completion_interruptible(&adcbisdone_it); + data_ready_adc_2 = false; + } + } else { + if (use_bis == 0) { + data_ready_adc_1 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_1 = true; + pr_debug("Write Reg %i = %x\n", REG_ADC1, adc_1_reg); + INIT_COMPLETION(adcdone_it); + ret = pmic_write_reg(REG_ADC1, adc_1_reg, + ADC_SGL_CH | ADC_ATO | + ADC_ADSEL | ADC_CH_0_MASK | + ADC_CH_1_MASK | + ADC_NO_ADTRIG | ADC_EN | + ADC_DELAY_MASK | ASC_ADC | + ADC_BIS); + if (ret != PMIC_SUCCESS) { + pr_debug("pmic_write_reg"); + goto out_mc13892_adc_release; + } + + pr_debug("wait adc done\n"); + wait_for_completion_interruptible(&adcdone_it); + data_ready_adc_1 = false; + } else { + data_ready_adc_2 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_2 = true; + INIT_COMPLETION(adcbisdone_it); + ret = pmic_write_reg(REG_ADC1, adc_1_reg, 0xFFFFFF); + if (ret != PMIC_SUCCESS) { + pr_debug("pmic_write_reg"); + goto out_mc13892_adc_release; + } + + temp = 0x800000; + ret = pmic_write_reg(REG_ADC3, temp, 0xFFFFFF); + if (ret != PMIC_SUCCESS) { + pr_info("pmic_write_reg"); + goto out_mc13892_adc_release; + } + + pr_debug("wait adc done bis\n"); + wait_for_completion_interruptible(&adcbisdone_it); + data_ready_adc_2 = false; + } + } + + /* read result and store in adc_param */ + result = 0; + if (use_bis == 0) + result_reg = REG_ADC2; + else + result_reg = REG_ADC4; + + ret = pmic_write_reg(REG_ADC1, 4 << ADC_CH_1_POS, + ADC_CH_0_MASK | ADC_CH_1_MASK); + if (ret != PMIC_SUCCESS) { + pr_debug("pmic_write_reg"); + goto out_mc13892_adc_release; + } + + for (i = 0; i <= 3; i++) { + ret = pmic_read_reg(result_reg, &result, PMIC_ALL_BITS); + if (ret != PMIC_SUCCESS) { + pr_debug("pmic_write_reg"); + goto out_mc13892_adc_release; + } + + adc_param->value[i] = ((result & ADD1_RESULT_MASK) >> 2); + adc_param->value[i + 4] = ((result & ADD2_RESULT_MASK) >> 14); + pr_debug("value[%d] = %d, value[%d] = %d\n", + i, adc_param->value[i], + i + 4, adc_param->value[i + 4]); + } + if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[0]; + adc_param->ts_value.x_position1 = adc_param->value[0]; + adc_param->ts_value.x_position2 = adc_param->value[1]; + adc_param->ts_value.y_position = adc_param->value[3]; + adc_param->ts_value.y_position1 = adc_param->value[3]; + adc_param->ts_value.y_position2 = adc_param->value[4]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + ret = pmic_write_reg(REG_ADC0, 0x0, ADC_TSMODE_MASK); + if (ret != PMIC_SUCCESS) { + pr_debug("pmic_write_reg"); + goto out_mc13892_adc_release; + } + } + + /*if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + } */ + ret = PMIC_SUCCESS; +out_mc13892_adc_release: + mc13892_adc_release(use_bis); +out_up_convert_mutex: + up(&convert_mutex); + + return ret; +} + +t_reading_mode mc13892_set_read_mode(t_channel channel) +{ + t_reading_mode read_mode = 0; + + switch (channel) { + case CHARGE_CURRENT: + read_mode = M_CHARGE_CURRENT; + break; + case BATTERY_CURRENT: + read_mode = M_BATTERY_CURRENT; + break; + default: + read_mode = 0; + } + + return read_mode; +} + +PMIC_STATUS pmic_adc_convert(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + PMIC_STATUS ret; + unsigned int i; + + if (suspend_flag == 1) + return -EBUSY; + + channel = channel_num[channel]; + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13892_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13892_set_read_mode(channel); + + /* Find the group */ + if (channel <= 7) + adc_param.channel_0 = channel; + else + return PMIC_PARAMETER_ERROR; + + ret = mc13892_adc_convert(&adc_param); + for (i = 0; i <= 7; i++) + result[i] = adc_param.value[i]; + + return ret; +} + +PMIC_STATUS pmic_adc_convert_8x(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) + return -EBUSY; + + channel = channel_num[channel]; + + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13892_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_8x\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13892_set_read_mode(channel); + + if (channel <= 7) { + adc_param.channel_0 = channel; + adc_param.channel_1 = channel; + } else + return PMIC_PARAMETER_ERROR; + + ret = mc13892_adc_convert(&adc_param); + for (i = 0; i <= 7; i++) + result[i] = adc_param.value[i]; + + return ret; +} + +PMIC_STATUS pmic_adc_set_touch_mode(t_touch_mode touch_mode) +{ + if (suspend_flag == 1) + return -EBUSY; + + CHECK_ERROR(pmic_write_reg(REG_ADC0, + BITFVAL(MC13892_ADC0_TS_M, touch_mode), + BITFMASK(MC13892_ADC0_TS_M))); + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_get_touch_mode(t_touch_mode *touch_mode) +{ + unsigned int value; + if (suspend_flag == 1) + return -EBUSY; + + CHECK_ERROR(pmic_read_reg(REG_ADC0, &value, PMIC_ALL_BITS)); + + *touch_mode = BITFEXT(value, MC13892_ADC0_TS_M); + + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_get_touch_sample(t_touch_screen *touch_sample, int wait) +{ + if (mc13892_adc_read_ts(touch_sample, wait) != 0) + return PMIC_ERROR; + if (0 == pmic_adc_filter(touch_sample)) + return PMIC_SUCCESS; + else + return PMIC_ERROR; +} + +PMIC_STATUS mc13892_adc_read_ts(t_touch_screen *ts_value, int wait_tsi) +{ + t_adc_param param; + pr_debug("mc13892_adc : mc13892_adc_read_ts\n"); + if (suspend_flag == 1) + return -EBUSY; + + if (wait_ts) { + pr_debug("mc13892_adc : error TS busy \n"); + return PMIC_ERROR; + } + mc13892_adc_init_param(¶m); + param.wait_tsi = wait_tsi; + param.read_ts = true; + if (mc13892_adc_convert(¶m) != 0) + return PMIC_ERROR; + /* check if x-y is ok */ + if (param.ts_value.contact_resistance < 1000) { + ts_value->x_position = param.ts_value.x_position; + ts_value->x_position1 = param.ts_value.x_position1; + ts_value->x_position2 = param.ts_value.x_position2; + + ts_value->y_position = param.ts_value.y_position; + ts_value->y_position1 = param.ts_value.y_position1; + ts_value->y_position2 = param.ts_value.y_position2; + + ts_value->contact_resistance = + param.ts_value.contact_resistance + 1; + + } else { + ts_value->x_position = 0; + ts_value->y_position = 0; + ts_value->contact_resistance = 0; + + } + return PMIC_SUCCESS; +} + +int mc13892_adc_request(bool read_ts) +{ + int adc_index = -1; + if (read_ts != 0) { + /*for ts we use bis=0 */ + if (adc_dev[0] == ADC_USED) + return -1; + /*no wait here */ + adc_dev[0] = ADC_USED; + adc_index = 0; + } else { + /*for other adc use bis = 1 */ + if (adc_dev[1] == ADC_USED) { + return -1; + /*no wait here */ + } + adc_dev[1] = ADC_USED; + adc_index = 1; + } + pr_debug("mc13892_adc : request ADC %d\n", adc_index); + return adc_index; +} + +int mc13892_adc_release(int adc_index) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) + return -ERESTARTSYS; + } + + pr_debug("mc13892_adc : release ADC %d\n", adc_index); + if ((adc_dev[adc_index] == ADC_MONITORING) || + (adc_dev[adc_index] == ADC_USED)) { + adc_dev[adc_index] = ADC_FREE; + wake_up(&queue_adc_busy); + return 0; + } + return -1; +} + +#ifdef DEBUG +static t_adc_param adc_param_db; + +static ssize_t adc_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int *value = adc_param_db.value; + + pr_debug("adc_info\n"); + + pr_debug("ch0\t\t%d\n", adc_param_db.channel_0); + pr_debug("ch1\t\t%d\n", adc_param_db.channel_1); + pr_debug("d5\t\t%d\n", adc_param_db.chrgraw_devide_5); + pr_debug("conv delay\t%d\n", adc_param_db.conv_delay); + pr_debug("delay\t\t%d\n", adc_param_db.delay); + pr_debug("read mode\t%d\n", adc_param_db.read_mode); + pr_debug("read ts\t\t%d\n", adc_param_db.read_ts); + pr_debug("single ch\t%d\n", adc_param_db.single_channel); + pr_debug("wait ts int\t%d\n", adc_param_db.wait_tsi); + pr_debug("value0-3:\t%d\t%d\t%d\t%d\n", value[0], value[1], + value[2], value[3]); + pr_debug("value4-7:\t%d\t%d\t%d\t%d\n", value[4], value[5], + value[6], value[7]); + + return 0; +} + +enum { + ADC_SET_CH0 = 0, + ADC_SET_CH1, + ADC_SET_DV5, + ADC_SET_CON_DELAY, + ADC_SET_DELAY, + ADC_SET_RM, + ADC_SET_RT, + ADC_SET_S_CH, + ADC_SET_WAIT_TS, + ADC_INIT_P, + ADC_START, + ADC_TS, + ADC_TS_READ, + ADC_TS_CAL, + ADC_CMD_MAX +}; + +static const char *const adc_cmd[ADC_CMD_MAX] = { + [ADC_SET_CH0] = "ch0", + [ADC_SET_CH1] = "ch1", + [ADC_SET_DV5] = "dv5", + [ADC_SET_CON_DELAY] = "cd", + [ADC_SET_DELAY] = "dl", + [ADC_SET_RM] = "rm", + [ADC_SET_RT] = "rt", + [ADC_SET_S_CH] = "sch", + [ADC_SET_WAIT_TS] = "wt", + [ADC_INIT_P] = "init", + [ADC_START] = "start", + [ADC_TS] = "touch", + [ADC_TS_READ] = "touchr", + [ADC_TS_CAL] = "cal" +}; + +static int cmd(unsigned int index, int value) +{ + t_touch_screen ts; + + switch (index) { + case ADC_SET_CH0: + adc_param_db.channel_0 = value; + break; + case ADC_SET_CH1: + adc_param_db.channel_1 = value; + break; + case ADC_SET_DV5: + adc_param_db.chrgraw_devide_5 = value; + break; + case ADC_SET_CON_DELAY: + adc_param_db.conv_delay = value; + break; + case ADC_SET_RM: + adc_param_db.read_mode = value; + break; + case ADC_SET_RT: + adc_param_db.read_ts = value; + break; + case ADC_SET_S_CH: + adc_param_db.single_channel = value; + break; + case ADC_SET_WAIT_TS: + adc_param_db.wait_tsi = value; + break; + case ADC_INIT_P: + mc13892_adc_init_param(&adc_param_db); + break; + case ADC_START: + mc13892_adc_convert(&adc_param_db); + break; + case ADC_TS: + pmic_adc_get_touch_sample(&ts, 1); + pr_debug("x = %d\n", ts.x_position); + pr_debug("y = %d\n", ts.y_position); + pr_debug("p = %d\n", ts.contact_resistance); + break; + case ADC_TS_READ: + pmic_adc_get_touch_sample(&ts, 0); + pr_debug("x = %d\n", ts.x_position); + pr_debug("y = %d\n", ts.y_position); + pr_debug("p = %d\n", ts.contact_resistance); + break; + case ADC_TS_CAL: + break; + default: + pr_debug("error command\n"); + break; + } + return 0; +} + +static ssize_t adc_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int state = 0; + const char *const *s; + char *p, *q; + int error; + int len, value = 0; + + pr_debug("adc_ctl\n"); + + q = NULL; + q = memchr(buf, ' ', count); + + if (q != NULL) { + len = q - buf; + q += 1; + value = simple_strtoul(q, NULL, 10); + } else { + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + } + + for (s = &adc_cmd[state]; state < ADC_CMD_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) + break; + } + if (state < ADC_CMD_MAX && *s) + error = cmd(state, value); + else + error = -EINVAL; + + return count; +} + +#else +static ssize_t adc_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static ssize_t adc_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +#endif + +static DEVICE_ATTR(adc, 0644, adc_info, adc_ctl); + +static int pmic_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + + pr_debug("PMIC ADC start probe\n"); + ret = device_create_file(&(pdev->dev), &dev_attr_adc); + if (ret) { + pr_debug("Can't create device file!\n"); + return -ENODEV; + } + + init_waitqueue_head(&suspendq); + + ret = pmic_adc_init(); + if (ret != PMIC_SUCCESS) { + pr_debug("Error in pmic_adc_init.\n"); + goto rm_dev_file; + } + + pmic_adc_ready = 1; + pr_debug("PMIC ADC successfully probed\n"); + return 0; + +rm_dev_file: + device_remove_file(&(pdev->dev), &dev_attr_adc); + return ret; +} + +static int pmic_adc_module_remove(struct platform_device *pdev) +{ + pmic_adc_deinit(); + pmic_adc_ready = 0; + pr_debug("PMIC ADC successfully removed\n"); + return 0; +} + +static struct platform_driver pmic_adc_driver_ldm = { + .driver = { + .name = "pmic_adc", + }, + .suspend = pmic_adc_suspend, + .resume = pmic_adc_resume, + .probe = pmic_adc_module_probe, + .remove = pmic_adc_module_remove, +}; + +static int __init pmic_adc_module_init(void) +{ + pr_debug("PMIC ADC driver loading...\n"); + return platform_driver_register(&pmic_adc_driver_ldm); +} + +static void __exit pmic_adc_module_exit(void) +{ + platform_driver_unregister(&pmic_adc_driver_ldm); + pr_debug("PMIC ADC driver successfully unloaded\n"); +} + +module_init(pmic_adc_module_init); +module_exit(pmic_adc_module_exit); + +MODULE_DESCRIPTION("PMIC ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13892/pmic_battery.c b/drivers/mxc/pmic/mc13892/pmic_battery.c new file mode 100644 index 000000000000..6061e6e78d87 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_battery.c @@ -0,0 +1,797 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BIT_CHG_VOL_LSH 0 +#define BIT_CHG_VOL_WID 3 + +#define BIT_CHG_CURR_LSH 3 +#define BIT_CHG_CURR_WID 4 + +#define BIT_CHG_PLIM_LSH 15 +#define BIT_CHG_PLIM_WID 2 + +#define BIT_CHG_DETS_LSH 6 +#define BIT_CHG_DETS_WID 1 +#define BIT_CHG_CURRS_LSH 11 +#define BIT_CHG_CURRS_WID 1 + +#define TRICKLE_CHG_EN_LSH 7 +#define LOW_POWER_BOOT_ACK_LSH 8 +#define BAT_TH_CHECK_DIS_LSH 9 +#define BATTFET_CTL_EN_LSH 10 +#define BATTFET_CTL_LSH 11 +#define REV_MOD_EN_LSH 13 +#define PLIM_DIS_LSH 17 +#define CHG_LED_EN_LSH 18 +#define RESTART_CHG_STAT_LSH 20 +#define AUTO_CHG_DIS_LSH 21 +#define CYCLING_DIS_LSH 22 +#define VI_PROGRAM_EN_LSH 23 + +#define TRICKLE_CHG_EN_WID 1 +#define LOW_POWER_BOOT_ACK_WID 1 +#define BAT_TH_CHECK_DIS_WID 1 +#define BATTFET_CTL_EN_WID 1 +#define BATTFET_CTL_WID 1 +#define REV_MOD_EN_WID 1 +#define PLIM_DIS_WID 1 +#define CHG_LED_EN_WID 1 +#define RESTART_CHG_STAT_WID 1 +#define AUTO_CHG_DIS_WID 1 +#define CYCLING_DIS_WID 1 +#define VI_PROGRAM_EN_WID 1 + +#define ACC_STARTCC_LSH 0 +#define ACC_STARTCC_WID 1 +#define ACC_RSTCC_LSH 1 +#define ACC_RSTCC_WID 1 +#define ACC_CCFAULT_LSH 7 +#define ACC_CCFAULT_WID 7 +#define ACC_CCOUT_LSH 8 +#define ACC_CCOUT_WID 16 +#define ACC1_ONEC_LSH 0 +#define ACC1_ONEC_WID 15 + +#define ACC_CALIBRATION 0x17 +#define ACC_START_COUNTER 0x07 +#define ACC_STOP_COUNTER 0x2 +#define ACC_CONTROL_BIT_MASK 0x1f +#define ACC_ONEC_VALUE 2621 +#define ACC_COULOMB_PER_LSB 1 +#define ACC_CALIBRATION_DURATION_MSECS 20 + +#define BAT_VOLTAGE_UNIT_UV 4692 +#define BAT_CURRENT_UNIT_UA 5870 +#define CHG_VOLTAGE_UINT_UV 23474 +#define CHG_MIN_CURRENT_UA 3500 + +#define COULOMB_TO_UAH(c) (10000 * c / 36) + +enum chg_setting { + TRICKLE_CHG_EN, + LOW_POWER_BOOT_ACK, + BAT_TH_CHECK_DIS, + BATTFET_CTL_EN, + BATTFET_CTL, + REV_MOD_EN, + PLIM_DIS, + CHG_LED_EN, + RESTART_CHG_STAT, + AUTO_CHG_DIS, + CYCLING_DIS, + VI_PROGRAM_EN +}; + +/* Flag used to indicate if Charger workaround is active. */ +int chg_wa_is_active; +/* Flag used to indicate if Charger workaround timer is on. */ +int chg_wa_timer; +int disable_chg_timer; +struct workqueue_struct *chg_wq; +struct delayed_work chg_work; + +static int pmic_set_chg_current(unsigned short curr) +{ + unsigned int mask; + unsigned int value; + + value = BITFVAL(BIT_CHG_CURR, curr); + mask = BITFMASK(BIT_CHG_CURR); + CHECK_ERROR(pmic_write_reg(REG_CHARGE, value, mask)); + + return 0; +} + +static int pmic_set_chg_misc(enum chg_setting type, unsigned short flag) +{ + + unsigned int reg_value = 0; + unsigned int mask = 0; + + switch (type) { + case TRICKLE_CHG_EN: + reg_value = BITFVAL(TRICKLE_CHG_EN, flag); + mask = BITFMASK(TRICKLE_CHG_EN); + break; + case LOW_POWER_BOOT_ACK: + reg_value = BITFVAL(LOW_POWER_BOOT_ACK, flag); + mask = BITFMASK(LOW_POWER_BOOT_ACK); + break; + case BAT_TH_CHECK_DIS: + reg_value = BITFVAL(BAT_TH_CHECK_DIS, flag); + mask = BITFMASK(BAT_TH_CHECK_DIS); + break; + case BATTFET_CTL_EN: + reg_value = BITFVAL(BATTFET_CTL_EN, flag); + mask = BITFMASK(BATTFET_CTL_EN); + break; + case BATTFET_CTL: + reg_value = BITFVAL(BATTFET_CTL, flag); + mask = BITFMASK(BATTFET_CTL); + break; + case REV_MOD_EN: + reg_value = BITFVAL(REV_MOD_EN, flag); + mask = BITFMASK(REV_MOD_EN); + break; + case PLIM_DIS: + reg_value = BITFVAL(PLIM_DIS, flag); + mask = BITFMASK(PLIM_DIS); + break; + case CHG_LED_EN: + reg_value = BITFVAL(CHG_LED_EN, flag); + mask = BITFMASK(CHG_LED_EN); + break; + case RESTART_CHG_STAT: + reg_value = BITFVAL(RESTART_CHG_STAT, flag); + mask = BITFMASK(RESTART_CHG_STAT); + break; + case AUTO_CHG_DIS: + reg_value = BITFVAL(AUTO_CHG_DIS, flag); + mask = BITFMASK(AUTO_CHG_DIS); + break; + case CYCLING_DIS: + reg_value = BITFVAL(CYCLING_DIS, flag); + mask = BITFMASK(CYCLING_DIS); + break; + case VI_PROGRAM_EN: + reg_value = BITFVAL(VI_PROGRAM_EN, flag); + mask = BITFMASK(VI_PROGRAM_EN); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_CHARGE, reg_value, mask)); + + return 0; +} + +static int pmic_get_batt_voltage(unsigned short *voltage) +{ + t_channel channel; + unsigned short result[8]; + + channel = BATTERY_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *voltage = result[0]; + + return 0; +} + +static int pmic_get_batt_current(unsigned short *curr) +{ + t_channel channel; + unsigned short result[8]; + + channel = BATTERY_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *curr = result[0]; + + return 0; +} + +static int coulomb_counter_calibration; +static unsigned int coulomb_counter_start_time_msecs; + +static int pmic_start_coulomb_counter(void) +{ + /* set scaler */ + CHECK_ERROR(pmic_write_reg(REG_ACC1, + ACC_COULOMB_PER_LSB * ACC_ONEC_VALUE, BITFMASK(ACC1_ONEC))); + + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_START_COUNTER, ACC_CONTROL_BIT_MASK)); + coulomb_counter_start_time_msecs = jiffies_to_msecs(jiffies); + pr_debug("coulomb counter start time %u\n", + coulomb_counter_start_time_msecs); + return 0; +} + +static int pmic_stop_coulomb_counter(void) +{ + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_STOP_COUNTER, ACC_CONTROL_BIT_MASK)); + return 0; +} + +static int pmic_calibrate_coulomb_counter(void) +{ + int ret; + unsigned int value; + + /* set scaler */ + CHECK_ERROR(pmic_write_reg(REG_ACC1, + 0x1, BITFMASK(ACC1_ONEC))); + + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_CALIBRATION, ACC_CONTROL_BIT_MASK)); + msleep(ACC_CALIBRATION_DURATION_MSECS); + + ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); + if (ret != 0) + return -1; + value = BITFEXT(value, ACC_CCOUT); + pr_debug("calibrate value = %x\n", value); + coulomb_counter_calibration = (int)((s16)((u16) value)); + pr_debug("coulomb_counter_calibration = %d\n", + coulomb_counter_calibration); + + return 0; + +} + +static int pmic_get_charger_coulomb(int *coulomb) +{ + int ret; + unsigned int value; + int calibration; + unsigned int time_diff_msec; + + ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); + if (ret != 0) + return -1; + value = BITFEXT(value, ACC_CCOUT); + pr_debug("counter value = %x\n", value); + *coulomb = ((s16)((u16)value)) * ACC_COULOMB_PER_LSB; + + if (abs(*coulomb) >= ACC_COULOMB_PER_LSB) { + /* calibrate */ + time_diff_msec = jiffies_to_msecs(jiffies); + time_diff_msec = + (time_diff_msec > coulomb_counter_start_time_msecs) ? + (time_diff_msec - coulomb_counter_start_time_msecs) : + (0xffffffff - coulomb_counter_start_time_msecs + + time_diff_msec); + calibration = coulomb_counter_calibration * (int)time_diff_msec + / (ACC_ONEC_VALUE * ACC_CALIBRATION_DURATION_MSECS); + *coulomb -= calibration; + } + + return 0; +} + +static int pmic_restart_charging(void) +{ + pmic_set_chg_misc(BAT_TH_CHECK_DIS, 1); + pmic_set_chg_misc(AUTO_CHG_DIS, 0); + pmic_set_chg_misc(VI_PROGRAM_EN, 1); + pmic_set_chg_current(0x8); + pmic_set_chg_misc(RESTART_CHG_STAT, 1); + pmic_set_chg_misc(PLIM_DIS, 3); + return 0; +} + +struct mc13892_dev_info { + struct device *dev; + + unsigned short voltage_raw; + int voltage_uV; + unsigned short current_raw; + int current_uA; + int battery_status; + int full_counter; + int charger_online; + int charger_voltage_uV; + int accum_current_uAh; + + struct power_supply bat; + struct power_supply charger; + + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; +}; + +#define mc13892_SENSER 25 +#define to_mc13892_dev_info(x) container_of((x), struct mc13892_dev_info, \ + bat); + +static enum power_supply_property mc13892_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_STATUS, +}; + +static enum power_supply_property mc13892_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int pmic_get_chg_value(unsigned int *value) +{ + t_channel channel; + unsigned short result[8], max1 = 0, min1 = 0, max2 = 0, min2 = 0, i; + unsigned int average = 0, average1 = 0, average2 = 0; + + channel = CHARGE_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + + + for (i = 0; i < 8; i++) { + if ((result[i] & 0x200) != 0) { + result[i] = 0x400 - result[i]; + average2 += result[i]; + if ((max2 == 0) || (max2 < result[i])) + max2 = result[i]; + if ((min2 == 0) || (min2 > result[i])) + min2 = result[i]; + } else { + average1 += result[i]; + if ((max1 == 0) || (max1 < result[i])) + max1 = result[i]; + if ((min1 == 0) || (min1 > result[i])) + min1 = result[i]; + } + } + + if (max1 != 0) { + average1 -= max1; + if (max2 != 0) + average2 -= max2; + else + average1 -= min1; + } else + average2 -= max2 + min2; + + if (average1 >= average2) { + average = (average1 - average2) / 6; + *value = average; + } else { + average = (average2 - average1) / 6; + *value = ((~average) + 1) & 0x3FF; + } + + return 0; +} + +static void chg_thread(struct work_struct *work) +{ + int ret; + unsigned int value = 0; + int dets; + + if (disable_chg_timer) { + disable_chg_timer = 0; + pmic_set_chg_current(0x8); + queue_delayed_work(chg_wq, &chg_work, 100); + chg_wa_timer = 1; + return; + } + + ret = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_DETS)); + + if (ret == 0) { + dets = BITFEXT(value, BIT_CHG_DETS); + pr_debug("dets=%d\n", dets); + + if (dets == 1) { + pmic_get_chg_value(&value); + pr_debug("average value=%d\n", value); + if ((value <= 3) | ((value & 0x200) != 0)) { + pr_debug("%s: Disable the charger\n", __func__); + pmic_set_chg_current(0); + disable_chg_timer = 1; + } + + queue_delayed_work(chg_wq, &chg_work, 100); + chg_wa_timer = 1; + } + } +} + +static int mc13892_charger_update_status(struct mc13892_dev_info *di) +{ + int ret; + unsigned int value; + int online; + + ret = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_DETS)); + + if (ret == 0) { + online = BITFEXT(value, BIT_CHG_DETS); + if (online != di->charger_online) { + di->charger_online = online; + dev_info(di->charger.dev, "charger status: %s\n", + online ? "online" : "offline"); + power_supply_changed(&di->charger); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, + &di->monitor_work, HZ / 10); + if (online) { + pmic_start_coulomb_counter(); + pmic_restart_charging(); + queue_delayed_work(chg_wq, &chg_work, 100); + chg_wa_timer = 1; + } else { + cancel_delayed_work(&chg_work); + chg_wa_timer = 0; + pmic_stop_coulomb_counter(); + } + } + } + + return ret; +} + +static int mc13892_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mc13892_dev_info *di = + container_of((psy), struct mc13892_dev_info, charger); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->charger_online; + return 0; + default: + break; + } + return -EINVAL; +} + +static int mc13892_battery_read_status(struct mc13892_dev_info *di) +{ + int retval; + int coulomb; + retval = pmic_get_batt_voltage(&(di->voltage_raw)); + if (retval == 0) + di->voltage_uV = di->voltage_raw * BAT_VOLTAGE_UNIT_UV; + + retval = pmic_get_batt_current(&(di->current_raw)); + if (retval == 0) { + if (di->current_raw & 0x200) + di->current_uA = + (0x1FF - (di->current_raw & 0x1FF)) * + BAT_CURRENT_UNIT_UA * (-1); + else + di->current_uA = + (di->current_raw & 0x1FF) * BAT_CURRENT_UNIT_UA; + } + retval = pmic_get_charger_coulomb(&coulomb); + if (retval == 0) + di->accum_current_uAh = COULOMB_TO_UAH(coulomb); + + return retval; +} + +static void mc13892_battery_update_status(struct mc13892_dev_info *di) +{ + unsigned int value; + int retval; + int old_battery_status = di->battery_status; + + if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) + di->full_counter = 0; + + if (di->charger_online) { + retval = pmic_read_reg(REG_INT_SENSE0, + &value, BITFMASK(BIT_CHG_CURRS)); + + if (retval == 0) { + value = BITFEXT(value, BIT_CHG_CURRS); + if (value) + di->battery_status = + POWER_SUPPLY_STATUS_CHARGING; + else + di->battery_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + if (di->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING) + di->full_counter++; + else + di->full_counter = 0; + } else { + di->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + di->full_counter = 0; + } + + dev_dbg(di->bat.dev, "bat status: %d\n", + di->battery_status); + + if (old_battery_status != POWER_SUPPLY_STATUS_UNKNOWN && + di->battery_status != old_battery_status) + power_supply_changed(&di->bat); +} + +static void mc13892_battery_work(struct work_struct *work) +{ + struct mc13892_dev_info *di = container_of(work, + struct mc13892_dev_info, + monitor_work.work); + const int interval = HZ * 60; + + dev_dbg(di->dev, "%s\n", __func__); + + mc13892_battery_update_status(di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); +} + +static void charger_online_event_callback(void *para) +{ + struct mc13892_dev_info *di = (struct mc13892_dev_info *) para; + pr_info("\n\n DETECTED charger plug/unplug event\n"); + mc13892_charger_update_status(di); +} + + +static int mc13892_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mc13892_dev_info *di = to_mc13892_dev_info(psy); + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) { + mc13892_charger_update_status(di); + mc13892_battery_update_status(di); + } + val->intval = di->battery_status; + return 0; + default: + break; + } + + mc13892_battery_read_status(di); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = di->accum_current_uAh; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = 3800000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = 3300000; + break; + default: + return -EINVAL; + } + + return 0; +} + +static ssize_t chg_wa_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (chg_wa_is_active & chg_wa_timer) + return sprintf(buf, "Charger LED workaround timer is on\n"); + else + return sprintf(buf, "Charger LED workaround timer is off\n"); +} + +static ssize_t chg_wa_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + if (strstr(buf, "1") != NULL) { + if (chg_wa_is_active) { + if (chg_wa_timer) + printk(KERN_INFO "Charger timer is already on\n"); + else { + queue_delayed_work(chg_wq, &chg_work, 100); + chg_wa_timer = 1; + printk(KERN_INFO "Turned on the timer\n"); + } + } + } else if (strstr(buf, "0") != NULL) { + if (chg_wa_is_active) { + if (chg_wa_timer) { + cancel_delayed_work(&chg_work); + chg_wa_timer = 0; + printk(KERN_INFO "Turned off charger timer\n"); + } else { + printk(KERN_INFO "The Charger workaround timer is off\n"); + } + } + } + + return size; +} + +static DEVICE_ATTR(enable, 0644, chg_wa_enable_show, chg_wa_enable_store); + +static int pmic_battery_remove(struct platform_device *pdev) +{ + pmic_event_callback_t bat_event_callback; + struct mc13892_dev_info *di = platform_get_drvdata(pdev); + + bat_event_callback.func = charger_online_event_callback; + bat_event_callback.param = (void *) di; + pmic_event_unsubscribe(EVENT_CHGDETI, bat_event_callback); + + cancel_rearming_delayed_workqueue(di->monitor_wqueue, + &di->monitor_work); + cancel_rearming_delayed_workqueue(chg_wq, + &chg_work); + destroy_workqueue(di->monitor_wqueue); + destroy_workqueue(chg_wq); + chg_wa_timer = 0; + chg_wa_is_active = 0; + disable_chg_timer = 0; + power_supply_unregister(&di->bat); + power_supply_unregister(&di->charger); + + kfree(di); + + return 0; +} + +static int pmic_battery_probe(struct platform_device *pdev) +{ + int retval = 0; + struct mc13892_dev_info *di; + pmic_event_callback_t bat_event_callback; + pmic_version_t pmic_version; + + /* Only apply battery driver for MC13892 V2.0 due to ENGR108085 */ + pmic_version = pmic_get_version(); + if (pmic_version.revision < 20) { + pr_debug("Battery driver is only applied for MC13892 V2.0\n"); + return -1; + } + if (machine_is_mx50_arm2()) { + pr_debug("mc13892 charger is not used for this platform\n"); + return -1; + } + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + platform_set_drvdata(pdev, di); + + di->charger.name = "mc13892_charger"; + di->charger.type = POWER_SUPPLY_TYPE_MAINS; + di->charger.properties = mc13892_charger_props; + di->charger.num_properties = ARRAY_SIZE(mc13892_charger_props); + di->charger.get_property = mc13892_charger_get_property; + retval = power_supply_register(&pdev->dev, &di->charger); + if (retval) { + dev_err(di->dev, "failed to register charger\n"); + goto charger_failed; + } + + INIT_DELAYED_WORK(&chg_work, chg_thread); + chg_wq = create_singlethread_workqueue("mxc_chg"); + if (!chg_wq) { + retval = -ESRCH; + goto workqueue_failed; + } + + INIT_DELAYED_WORK(&di->monitor_work, mc13892_battery_work); + di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!di->monitor_wqueue) { + retval = -ESRCH; + goto workqueue_failed; + } + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 10); + + di->dev = &pdev->dev; + di->bat.name = "mc13892_bat"; + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = mc13892_battery_props; + di->bat.num_properties = ARRAY_SIZE(mc13892_battery_props); + di->bat.get_property = mc13892_battery_get_property; + di->bat.use_for_apm = 1; + + di->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; + + retval = power_supply_register(&pdev->dev, &di->bat); + if (retval) { + dev_err(di->dev, "failed to register battery\n"); + goto batt_failed; + } + + bat_event_callback.func = charger_online_event_callback; + bat_event_callback.param = (void *) di; + pmic_event_subscribe(EVENT_CHGDETI, bat_event_callback); + retval = sysfs_create_file(&pdev->dev.kobj, &dev_attr_enable.attr); + + if (retval) { + printk(KERN_ERR + "Battery: Unable to register sysdev entry for Battery"); + goto workqueue_failed; + } + chg_wa_is_active = 1; + chg_wa_timer = 0; + disable_chg_timer = 0; + + pmic_stop_coulomb_counter(); + pmic_calibrate_coulomb_counter(); + goto success; + +workqueue_failed: + power_supply_unregister(&di->charger); +charger_failed: + power_supply_unregister(&di->bat); +batt_failed: + kfree(di); +di_alloc_failed: +success: + dev_dbg(di->dev, "%s battery probed!\n", __func__); + return retval; + + + return 0; +} + +static struct platform_driver pmic_battery_driver_ldm = { + .driver = { + .name = "pmic_battery", + .bus = &platform_bus_type, + }, + .probe = pmic_battery_probe, + .remove = pmic_battery_remove, +}; + +static int __init pmic_battery_init(void) +{ + pr_debug("PMIC Battery driver loading...\n"); + return platform_driver_register(&pmic_battery_driver_ldm); +} + +static void __exit pmic_battery_exit(void) +{ + platform_driver_unregister(&pmic_battery_driver_ldm); + pr_debug("PMIC Battery driver successfully unloaded\n"); +} + +module_init(pmic_battery_init); +module_exit(pmic_battery_exit); + +MODULE_DESCRIPTION("pmic_battery driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13892/pmic_light.c b/drivers/mxc/pmic/mc13892/pmic_light.c new file mode 100644 index 000000000000..17459335dd8f --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_light.c @@ -0,0 +1,685 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13892/pmic_light.c + * @brief This is the main file of PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +/* + * Includes + */ +#define DEBUG +#include +#include +#include +#include +#include + +#define BIT_CL_MAIN_LSH 9 +#define BIT_CL_AUX_LSH 21 +#define BIT_CL_KEY_LSH 9 +#define BIT_CL_RED_LSH 9 +#define BIT_CL_GREEN_LSH 21 +#define BIT_CL_BLUE_LSH 9 + +#define BIT_CL_MAIN_WID 3 +#define BIT_CL_AUX_WID 3 +#define BIT_CL_KEY_WID 3 +#define BIT_CL_RED_WID 3 +#define BIT_CL_GREEN_WID 3 +#define BIT_CL_BLUE_WID 3 + +#define BIT_DC_MAIN_LSH 3 +#define BIT_DC_AUX_LSH 15 +#define BIT_DC_KEY_LSH 3 +#define BIT_DC_RED_LSH 3 +#define BIT_DC_GREEN_LSH 15 +#define BIT_DC_BLUE_LSH 3 + +#define BIT_DC_MAIN_WID 6 +#define BIT_DC_AUX_WID 6 +#define BIT_DC_KEY_WID 6 +#define BIT_DC_RED_WID 6 +#define BIT_DC_GREEN_WID 6 +#define BIT_DC_BLUE_WID 6 + +#define BIT_RP_MAIN_LSH 2 +#define BIT_RP_AUX_LSH 14 +#define BIT_RP_KEY_LSH 2 +#define BIT_RP_RED_LSH 2 +#define BIT_RP_GREEN_LSH 14 +#define BIT_RP_BLUE_LSH 2 + +#define BIT_RP_MAIN_WID 1 +#define BIT_RP_AUX_WID 1 +#define BIT_RP_KEY_WID 1 +#define BIT_RP_RED_WID 1 +#define BIT_RP_GREEN_WID 1 +#define BIT_RP_BLUE_WID 1 + +#define BIT_HC_MAIN_LSH 1 +#define BIT_HC_AUX_LSH 13 +#define BIT_HC_KEY_LSH 1 + +#define BIT_HC_MAIN_WID 1 +#define BIT_HC_AUX_WID 1 +#define BIT_HC_KEY_WID 1 + +#define BIT_BP_RED_LSH 0 +#define BIT_BP_GREEN_LSH 12 +#define BIT_BP_BLUE_LSH 0 + +#define BIT_BP_RED_WID 2 +#define BIT_BP_GREEN_WID 2 +#define BIT_BP_BLUE_WID 2 + +int pmic_light_init_reg(void) +{ + CHECK_ERROR(pmic_write_reg(REG_LED_CTL0, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL3, 0, PMIC_ALL_BITS)); + + return 0; +} + +static int pmic_light_suspend(struct platform_device *dev, pm_message_t state) +{ + return 0; +}; + +static int pmic_light_resume(struct platform_device *pdev) +{ + return 0; +}; + +PMIC_STATUS mc13892_bklit_set_hi_current(enum lit_channel channel, int mode) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_HC_MAIN, mode); + mask = BITFMASK(BIT_HC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_HC_AUX, mode); + mask = BITFMASK(BIT_HC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_HC_KEY, mode); + mask = BITFMASK(BIT_HC_KEY); + reg = REG_LED_CTL1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_hi_current(enum lit_channel channel, int *mode) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_HC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_HC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_HC_KEY); + reg = REG_LED_CTL1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, mode, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_current(enum lit_channel channel, + unsigned char level) +{ + unsigned int mask; + unsigned int value; + int reg; + + if (level > LIT_CURR_HI_42) + return PMIC_PARAMETER_ERROR; + else if (level >= LIT_CURR_HI_0) { + CHECK_ERROR(mc13892_bklit_set_hi_current(channel, 1)); + level -= LIT_CURR_HI_0; + } + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_CL_MAIN, level); + mask = BITFMASK(BIT_CL_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_CL_AUX, level); + mask = BITFMASK(BIT_CL_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_CL_KEY, level); + mask = BITFMASK(BIT_CL_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_CL_RED, level); + mask = BITFMASK(BIT_CL_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_CL_GREEN, level); + mask = BITFMASK(BIT_CL_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_CL_BLUE, level); + mask = BITFMASK(BIT_CL_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_current(enum lit_channel channel, + unsigned char *level) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + int reg, mode; + + CHECK_ERROR(mc13892_bklit_get_hi_current(channel, &mode)); + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_CL_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_CL_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_CL_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_CL_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_CL_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_CL_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_value, mask)); + + switch (channel) { + case LIT_MAIN: + *level = BITFEXT(reg_value, BIT_CL_MAIN); + break; + case LIT_AUX: + *level = BITFEXT(reg_value, BIT_CL_AUX); + break; + case LIT_KEY: + *level = BITFEXT(reg_value, BIT_CL_KEY); + break; + case LIT_RED: + *level = BITFEXT(reg_value, BIT_CL_RED); + break; + case LIT_GREEN: + *level = BITFEXT(reg_value, BIT_CL_GREEN); + break; + case LIT_BLUE: + *level = BITFEXT(reg_value, BIT_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (mode == 1) + *level += LIT_CURR_HI_0; + + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_dutycycle(enum lit_channel channel, + unsigned char dc) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_DC_MAIN, dc); + mask = BITFMASK(BIT_DC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_DC_AUX, dc); + mask = BITFMASK(BIT_DC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_DC_KEY, dc); + mask = BITFMASK(BIT_DC_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_DC_RED, dc); + mask = BITFMASK(BIT_DC_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_DC_GREEN, dc); + mask = BITFMASK(BIT_DC_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_DC_BLUE, dc); + mask = BITFMASK(BIT_DC_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_dutycycle(enum lit_channel channel, + unsigned char *dc) +{ + unsigned int mask; + int reg; + unsigned int reg_value = 0; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_DC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_DC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_DC_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_DC_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_DC_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_DC_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_ramp(enum lit_channel channel, int flag) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_RP_MAIN, flag); + mask = BITFMASK(BIT_RP_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_RP_AUX, flag); + mask = BITFMASK(BIT_RP_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_RP_KEY, flag); + mask = BITFMASK(BIT_RP_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_RP_RED, flag); + mask = BITFMASK(BIT_RP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_RP_GREEN, flag); + mask = BITFMASK(BIT_RP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_RP_BLUE, flag); + mask = BITFMASK(BIT_RP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_ramp(enum lit_channel channel, int *flag) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_RP_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_RP_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_RP_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_RP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_RP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_RP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, flag, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_blink_p(enum lit_channel channel, int period) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_RED: + value = BITFVAL(BIT_BP_RED, period); + mask = BITFMASK(BIT_BP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_BP_GREEN, period); + mask = BITFMASK(BIT_BP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_BP_BLUE, period); + mask = BITFMASK(BIT_BP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_blink_p(enum lit_channel channel, int *period) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_RED: + mask = BITFMASK(BIT_BP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_BP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_BP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, period, mask)); + return PMIC_SUCCESS; +} + +EXPORT_SYMBOL(mc13892_bklit_set_current); +EXPORT_SYMBOL(mc13892_bklit_get_current); +EXPORT_SYMBOL(mc13892_bklit_set_dutycycle); +EXPORT_SYMBOL(mc13892_bklit_get_dutycycle); +EXPORT_SYMBOL(mc13892_bklit_set_ramp); +EXPORT_SYMBOL(mc13892_bklit_get_ramp); +EXPORT_SYMBOL(mc13892_bklit_set_blink_p); +EXPORT_SYMBOL(mc13892_bklit_get_blink_p); + +static int pmic_light_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef DEBUG +static ssize_t lit_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +enum { + SET_CURR = 0, + SET_DC, + SET_RAMP, + SET_BP, + SET_CH, + LIT_CMD_MAX +}; + +static const char *const lit_cmd[LIT_CMD_MAX] = { + [SET_CURR] = "cur", + [SET_DC] = "dc", + [SET_RAMP] = "ra", + [SET_BP] = "bp", + [SET_CH] = "ch" +}; + +static int cmd(unsigned int index, int value) +{ + static int ch = LIT_MAIN; + int ret = 0; + + switch (index) { + case SET_CH: + ch = value; + break; + case SET_CURR: + pr_debug("set %d cur %d\n", ch, value); + ret = mc13892_bklit_set_current(ch, value); + break; + case SET_DC: + pr_debug("set %d dc %d\n", ch, value); + ret = mc13892_bklit_set_dutycycle(ch, value); + break; + case SET_RAMP: + pr_debug("set %d ramp %d\n", ch, value); + ret = mc13892_bklit_set_ramp(ch, value); + break; + case SET_BP: + pr_debug("set %d bp %d\n", ch, value); + ret = mc13892_bklit_set_blink_p(ch, value); + break; + default: + pr_debug("error command\n"); + break; + } + + if (ret == PMIC_SUCCESS) + pr_debug("command exec successfully!\n"); + + return 0; +} + +static ssize_t lit_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int state = 0; + const char *const *s; + char *p, *q; + int error; + int len, value = 0; + + pr_debug("lit_ctl\n"); + + q = NULL; + q = memchr(buf, ' ', count); + + if (q != NULL) { + len = q - buf; + q += 1; + value = simple_strtoul(q, NULL, 10); + } else { + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + } + + for (s = &lit_cmd[state]; state < LIT_CMD_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) + break; + } + if (state < LIT_CMD_MAX && *s) + error = cmd(state, value); + else + error = -EINVAL; + + return count; +} + +#else +static ssize_t lit_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static ssize_t lit_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +#endif + +static DEVICE_ATTR(lit, 0644, lit_info, lit_ctl); + +static int pmic_light_probe(struct platform_device *pdev) +{ + int ret = 0; + + pr_debug("PMIC ADC start probe\n"); + ret = device_create_file(&(pdev->dev), &dev_attr_lit); + if (ret) { + pr_debug("Can't create device file!\n"); + return -ENODEV; + } + + pmic_light_init_reg(); + + pr_debug("PMIC Light successfully loaded\n"); + return 0; +} + +static struct platform_driver pmic_light_driver_ldm = { + .driver = { + .name = "pmic_light", + }, + .suspend = pmic_light_suspend, + .resume = pmic_light_resume, + .probe = pmic_light_probe, + .remove = pmic_light_remove, +}; + +/* + * Initialization and Exit + */ + +static int __init pmic_light_init(void) +{ + pr_debug("PMIC Light driver loading...\n"); + return platform_driver_register(&pmic_light_driver_ldm); +} +static void __exit pmic_light_exit(void) +{ + platform_driver_unregister(&pmic_light_driver_ldm); + pr_debug("PMIC Light driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_light_init); +module_exit(pmic_light_exit); + +MODULE_DESCRIPTION("PMIC_LIGHT"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ssi/Kconfig b/drivers/mxc/ssi/Kconfig new file mode 100644 index 000000000000..4cb581c2f945 --- /dev/null +++ b/drivers/mxc/ssi/Kconfig @@ -0,0 +1,12 @@ +# +# SPI device configuration +# + +menu "MXC SSI support" + +config MXC_SSI + tristate "SSI support" + ---help--- + Say Y to get the SSI services API available on MXC platform. + +endmenu diff --git a/drivers/mxc/ssi/Makefile b/drivers/mxc/ssi/Makefile new file mode 100644 index 000000000000..f9bb4419fc19 --- /dev/null +++ b/drivers/mxc/ssi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the kernel SSI device drivers. +# + +obj-$(CONFIG_MXC_SSI) += ssimod.o + +ssimod-objs := ssi.o diff --git a/drivers/mxc/ssi/registers.h b/drivers/mxc/ssi/registers.h new file mode 100644 index 000000000000..dd62365f3d75 --- /dev/null +++ b/drivers/mxc/ssi/registers.h @@ -0,0 +1,208 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 + +#define SPBA_CPU_SSI 0x07 + +#define MXC_SSISTX0 0x00 +#define MXC_SSISTX1 0x04 +#define MXC_SSISRX0 0x08 +#define MXC_SSISRX1 0x0C +#define MXC_SSISCR 0x10 +#define MXC_SSISISR 0x14 +#define MXC_SSISIER 0x18 +#define MXC_SSISTCR 0x1C +#define MXC_SSISRCR 0x20 +#define MXC_SSISTCCR 0x24 +#define MXC_SSISRCCR 0x28 +#define MXC_SSISFCSR 0x2C +#define MXC_SSISTR 0x30 +#define MXC_SSISOR 0x34 +#define MXC_SSISACNT 0x38 +#define MXC_SSISACADD 0x3C +#define MXC_SSISACDAT 0x40 +#define MXC_SSISATAG 0x44 +#define MXC_SSISTMSK 0x48 +#define MXC_SSISRMSK 0x4C + +/* MXC 91221 only */ +#define MXC_SSISACCST 0x50 +#define MXC_SSISACCEN 0x54 +#define MXC_SSISACCDIS 0x58 + +/*! SSI1 registers offset*/ +#define MXC_SSI1STX0 0x00 +#define MXC_SSI1STX1 0x04 +#define MXC_SSI1SRX0 0x08 +#define MXC_SSI1SRX1 0x0C +#define MXC_SSI1SCR 0x10 +#define MXC_SSI1SISR 0x14 +#define MXC_SSI1SIER 0x18 +#define MXC_SSI1STCR 0x1C +#define MXC_SSI1SRCR 0x20 +#define MXC_SSI1STCCR 0x24 +#define MXC_SSI1SRCCR 0x28 +#define MXC_SSI1SFCSR 0x2C +#define MXC_SSI1STR 0x30 +#define MXC_SSI1SOR 0x34 +#define MXC_SSI1SACNT 0x38 +#define MXC_SSI1SACADD 0x3C +#define MXC_SSI1SACDAT 0x40 +#define MXC_SSI1SATAG 0x44 +#define MXC_SSI1STMSK 0x48 +#define MXC_SSI1SRMSK 0x4C + +/* MXC91221 only */ + +#define MXC_SSISACCST 0x50 +#define MXC_SSISACCEN 0x54 +#define MXC_SSISACCDIS 0x58 + +/* Not on MXC91221 */ +/*! SSI2 registers offset*/ +#define MXC_SSI2STX0 0x00 +#define MXC_SSI2STX1 0x04 +#define MXC_SSI2SRX0 0x08 +#define MXC_SSI2SRX1 0x0C +#define MXC_SSI2SCR 0x10 +#define MXC_SSI2SISR 0x14 +#define MXC_SSI2SIER 0x18 +#define MXC_SSI2STCR 0x1C +#define MXC_SSI2SRCR 0x20 +#define MXC_SSI2STCCR 0x24 +#define MXC_SSI2SRCCR 0x28 +#define MXC_SSI2SFCSR 0x2C +#define MXC_SSI2STR 0x30 +#define MXC_SSI2SOR 0x34 +#define MXC_SSI2SACNT 0x38 +#define MXC_SSI2SACADD 0x3C +#define MXC_SSI2SACDAT 0x40 +#define MXC_SSI2SATAG 0x44 +#define MXC_SSI2STMSK 0x48 +#define MXC_SSI2SRMSK 0x4C + +/*! + * SCR Register bit shift definitions + */ +#define SSI_ENABLE_SHIFT 0 +#define SSI_TRANSMIT_ENABLE_SHIFT 1 +#define SSI_RECEIVE_ENABLE_SHIFT 2 +#define SSI_NETWORK_MODE_SHIFT 3 +#define SSI_SYNCHRONOUS_MODE_SHIFT 4 +#define SSI_I2S_MODE_SHIFT 5 +#define SSI_SYSTEM_CLOCK_SHIFT 7 +#define SSI_TWO_CHANNEL_SHIFT 8 +#define SSI_CLOCK_IDLE_SHIFT 9 + +/* MXC91221 only*/ +#define SSI_TX_FRAME_CLOCK_DISABLE_SHIFT 10 +#define SSI_RX_FRAME_CLOCK_DISABLE_SHIFT 11 + +/*! + * STCR & SRCR Registers bit shift definitions + */ +#define SSI_EARLY_FRAME_SYNC_SHIFT 0 +#define SSI_FRAME_SYNC_LENGTH_SHIFT 1 +#define SSI_FRAME_SYNC_INVERT_SHIFT 2 +#define SSI_CLOCK_POLARITY_SHIFT 3 +#define SSI_SHIFT_DIRECTION_SHIFT 4 +#define SSI_CLOCK_DIRECTION_SHIFT 5 +#define SSI_FRAME_DIRECTION_SHIFT 6 +#define SSI_FIFO_ENABLE_0_SHIFT 7 +#define SSI_FIFO_ENABLE_1_SHIFT 8 +#define SSI_BIT_0_SHIFT 9 + +/* MXC91221 only*/ +#define SSI_TX_FRAME_CLOCK_DISABLE_SHIFT 10 +#define SSI_RX_DATA_EXTENSION_SHIFT 10 /*SRCR only */ +/*! + * STCCR & SRCCR Registers bit shift definitions + */ +#define SSI_PRESCALER_MODULUS_SHIFT 0 +#define SSI_FRAME_RATE_DIVIDER_SHIFT 8 +#define SSI_WORD_LENGTH_SHIFT 13 +#define SSI_PRESCALER_RANGE_SHIFT 17 +#define SSI_DIVIDE_BY_TWO_SHIFT 18 +#define SSI_FRAME_DIVIDER_MASK 31 +#define SSI_MIN_FRAME_DIVIDER_RATIO 1 +#define SSI_MAX_FRAME_DIVIDER_RATIO 32 +#define SSI_PRESCALER_MODULUS_MASK 255 +#define SSI_MIN_PRESCALER_MODULUS_RATIO 1 +#define SSI_MAX_PRESCALER_MODULUS_RATIO 256 +#define SSI_WORD_LENGTH_MASK 15 + +#define SSI_IRQ_STATUS_NUMBER 25 + +/*! + * SFCSR Register bit shift definitions + */ +#define SSI_RX_FIFO_1_COUNT_SHIFT 28 +#define SSI_TX_FIFO_1_COUNT_SHIFT 24 +#define SSI_RX_FIFO_1_WATERMARK_SHIFT 20 +#define SSI_TX_FIFO_1_WATERMARK_SHIFT 16 +#define SSI_RX_FIFO_0_COUNT_SHIFT 12 +#define SSI_TX_FIFO_0_COUNT_SHIFT 8 +#define SSI_RX_FIFO_0_WATERMARK_SHIFT 4 +#define SSI_TX_FIFO_0_WATERMARK_SHIFT 0 +#define SSI_MIN_FIFO_WATERMARK 0 +#define SSI_MAX_FIFO_WATERMARK 8 + +/*! + * SSI Option Register (SOR) bit shift definitions + */ +#define SSI_FRAME_SYN_RESET_SHIFT 0 +#define SSI_WAIT_SHIFT 1 +#define SSI_INIT_SHIFT 3 +#define SSI_TRANSMITTER_CLEAR_SHIFT 4 +#define SSI_RECEIVER_CLEAR_SHIFT 5 +#define SSI_CLOCK_OFF_SHIFT 6 +#define SSI_WAIT_STATE_MASK 0x3 + +/*! + * SSI AC97 Control Register (SACNT) bit shift definitions + */ +#define AC97_MODE_ENABLE_SHIFT 0 +#define AC97_VARIABLE_OPERATION_SHIFT 1 +#define AC97_TAG_IN_FIFO_SHIFT 2 +#define AC97_READ_COMMAND_SHIFT 3 +#define AC97_WRITE_COMMAND_SHIFT 4 +#define AC97_FRAME_RATE_DIVIDER_SHIFT 5 +#define AC97_FRAME_RATE_MASK 0x3F + +/*! + * SSI Test Register (STR) bit shift definitions + */ +#define SSI_TEST_MODE_SHIFT 15 +#define SSI_RCK2TCK_SHIFT 14 +#define SSI_RFS2TFS_SHIFT 13 +#define SSI_RXSTATE_SHIFT 8 +#define SSI_TXD2RXD_SHIFT 7 +#define SSI_TCK2RCK_SHIFT 6 +#define SSI_TFS2RFS_SHIFT 5 +#define SSI_TXSTATE_SHIFT 0 + +#endif /* __MXC_SSI_REGISTERS_H__ */ diff --git a/drivers/mxc/ssi/ssi.c b/drivers/mxc/ssi/ssi.c new file mode 100644 index 000000000000..da09631283e0 --- /dev/null +++ b/drivers/mxc/ssi/ssi.c @@ -0,0 +1,1221 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include +#include + +#include "registers.h" +#include "ssi.h" + +static spinlock_t ssi_lock; +struct mxc_audio_platform_data *ssi_platform_data; + +EXPORT_SYMBOL(ssi_ac97_frame_rate_divider); +EXPORT_SYMBOL(ssi_ac97_get_command_address_register); +EXPORT_SYMBOL(ssi_ac97_get_command_data_register); +EXPORT_SYMBOL(ssi_ac97_get_tag_register); +EXPORT_SYMBOL(ssi_ac97_mode_enable); +EXPORT_SYMBOL(ssi_ac97_tag_in_fifo); +EXPORT_SYMBOL(ssi_ac97_read_command); +EXPORT_SYMBOL(ssi_ac97_set_command_address_register); +EXPORT_SYMBOL(ssi_ac97_set_command_data_register); +EXPORT_SYMBOL(ssi_ac97_set_tag_register); +EXPORT_SYMBOL(ssi_ac97_variable_mode); +EXPORT_SYMBOL(ssi_ac97_write_command); +EXPORT_SYMBOL(ssi_clock_idle_state); +EXPORT_SYMBOL(ssi_clock_off); +EXPORT_SYMBOL(ssi_enable); +EXPORT_SYMBOL(ssi_get_data); +EXPORT_SYMBOL(ssi_get_status); +EXPORT_SYMBOL(ssi_i2s_mode); +EXPORT_SYMBOL(ssi_interrupt_disable); +EXPORT_SYMBOL(ssi_interrupt_enable); +EXPORT_SYMBOL(ssi_network_mode); +EXPORT_SYMBOL(ssi_receive_enable); +EXPORT_SYMBOL(ssi_rx_bit0); +EXPORT_SYMBOL(ssi_rx_clock_direction); +EXPORT_SYMBOL(ssi_rx_clock_divide_by_two); +EXPORT_SYMBOL(ssi_rx_clock_polarity); +EXPORT_SYMBOL(ssi_rx_clock_prescaler); +EXPORT_SYMBOL(ssi_rx_early_frame_sync); +EXPORT_SYMBOL(ssi_rx_fifo_counter); +EXPORT_SYMBOL(ssi_rx_fifo_enable); +EXPORT_SYMBOL(ssi_rx_fifo_full_watermark); +EXPORT_SYMBOL(ssi_rx_flush_fifo); +EXPORT_SYMBOL(ssi_rx_frame_direction); +EXPORT_SYMBOL(ssi_rx_frame_rate); +EXPORT_SYMBOL(ssi_rx_frame_sync_active); +EXPORT_SYMBOL(ssi_rx_frame_sync_length); +EXPORT_SYMBOL(ssi_rx_mask_time_slot); +EXPORT_SYMBOL(ssi_rx_prescaler_modulus); +EXPORT_SYMBOL(ssi_rx_shift_direction); +EXPORT_SYMBOL(ssi_rx_word_length); +EXPORT_SYMBOL(ssi_set_data); +EXPORT_SYMBOL(ssi_set_wait_states); +EXPORT_SYMBOL(ssi_synchronous_mode); +EXPORT_SYMBOL(ssi_system_clock); +EXPORT_SYMBOL(ssi_transmit_enable); +EXPORT_SYMBOL(ssi_two_channel_mode); +EXPORT_SYMBOL(ssi_tx_bit0); +EXPORT_SYMBOL(ssi_tx_clock_direction); +EXPORT_SYMBOL(ssi_tx_clock_divide_by_two); +EXPORT_SYMBOL(ssi_tx_clock_polarity); +EXPORT_SYMBOL(ssi_tx_clock_prescaler); +EXPORT_SYMBOL(ssi_tx_early_frame_sync); +EXPORT_SYMBOL(ssi_tx_fifo_counter); +EXPORT_SYMBOL(ssi_tx_fifo_empty_watermark); +EXPORT_SYMBOL(ssi_tx_fifo_enable); +EXPORT_SYMBOL(ssi_tx_flush_fifo); +EXPORT_SYMBOL(ssi_tx_frame_direction); +EXPORT_SYMBOL(ssi_tx_frame_rate); +EXPORT_SYMBOL(ssi_tx_frame_sync_active); +EXPORT_SYMBOL(ssi_tx_frame_sync_length); +EXPORT_SYMBOL(ssi_tx_mask_time_slot); +EXPORT_SYMBOL(ssi_tx_prescaler_modulus); +EXPORT_SYMBOL(ssi_tx_shift_direction); +EXPORT_SYMBOL(ssi_tx_word_length); +EXPORT_SYMBOL(get_ssi_fifo_addr); + +struct resource *res; +unsigned long base_addr_1; +unsigned long base_addr_2; + +unsigned int get_ssi_fifo_addr(unsigned int ssi, int direction) +{ + unsigned int fifo_addr; + if (direction == 1) { + if (ssi_platform_data->ssi_num == 2) { + fifo_addr = + (ssi == + SSI1) ? (int)(base_addr_1 + + MXC_SSI1STX0) : (int)(base_addr_2 + + MXC_SSI2STX0); + } else { + fifo_addr = (int)(base_addr_1 + MXC_SSI1STX0); + } + } else { + fifo_addr = (int)(base_addr_1 + MXC_SSI1SRX0); + } + return fifo_addr; +} + +void *get_ssi_base_addr(unsigned int ssi) +{ + if (ssi_platform_data->ssi_num == 2) { + if (ssi == SSI1) + return IO_ADDRESS(base_addr_1); + else + return IO_ADDRESS(base_addr_2); + } + return IO_ADDRESS(base_addr_1); +} + +void set_register_bits(unsigned int mask, unsigned int data, + unsigned int offset, unsigned int ssi) +{ + volatile unsigned long reg = 0; + void *base_addr = get_ssi_base_addr(ssi); + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + reg = __raw_readl(base_addr + offset); + reg = (reg & (~mask)) | data; + __raw_writel(reg, base_addr + offset); + spin_unlock_irqrestore(&ssi_lock, flags); +} + +unsigned long getreg_value(unsigned int offset, unsigned int ssi) +{ + void *base_addr = get_ssi_base_addr(ssi); + return __raw_readl(base_addr + offset); +} + +void set_register(unsigned int data, unsigned int offset, unsigned int ssi) +{ + void *base_addr = get_ssi_base_addr(ssi); + __raw_writel(data, base_addr + offset); +} + +/*! + * This function controls the AC97 frame rate divider. + * + * @param module the module number + * @param frame_rate_divider the AC97 frame rate divider + */ +void ssi_ac97_frame_rate_divider(ssi_mod module, + unsigned char frame_rate_divider) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + reg |= ((frame_rate_divider & AC97_FRAME_RATE_MASK) + << AC97_FRAME_RATE_DIVIDER_SHIFT); + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function gets the AC97 command address register. + * + * @param module the module number + * @return This function returns the command address slot information. + */ +unsigned int ssi_ac97_get_command_address_register(ssi_mod module) +{ + return getreg_value(MXC_SSISACADD, module); +} + +/*! + * This function gets the AC97 command data register. + * + * @param module the module number + * @return This function returns the command data slot information. + */ +unsigned int ssi_ac97_get_command_data_register(ssi_mod module) +{ + return getreg_value(MXC_SSISACDAT, module); +} + +/*! + * This function gets the AC97 tag register. + * + * @param module the module number + * @return This function returns the tag information. + */ +unsigned int ssi_ac97_get_tag_register(ssi_mod module) +{ + return getreg_value(MXC_SSISATAG, module); +} + +/*! + * This function controls the AC97 mode. + * + * @param module the module number + * @param state the AC97 mode state (enabled or disabled) + */ +void ssi_ac97_mode_enable(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_MODE_ENABLE_SHIFT); + } else { + reg &= ~(1 << AC97_MODE_ENABLE_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 tag in FIFO behavior. + * + * @param module the module number + * @param state the tag in fifo behavior (Tag info stored in Rx FIFO 0 if true, + * Tag info stored in SATAG register otherwise) + */ +void ssi_ac97_tag_in_fifo(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_TAG_IN_FIFO_SHIFT); + } else { + reg &= ~(1 << AC97_TAG_IN_FIFO_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 read command. + * + * @param module the module number + * @param state the next AC97 command is a read command or not + */ +void ssi_ac97_read_command(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_READ_COMMAND_SHIFT); + } else { + reg &= ~(1 << AC97_READ_COMMAND_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function sets the AC97 command address register. + * + * @param module the module number + * @param address the command address slot information + */ +void ssi_ac97_set_command_address_register(ssi_mod module, unsigned int address) +{ + set_register(address, MXC_SSISACADD, module); +} + +/*! + * This function sets the AC97 command data register. + * + * @param module the module number + * @param data the command data slot information + */ +void ssi_ac97_set_command_data_register(ssi_mod module, unsigned int data) +{ + set_register(data, MXC_SSISACDAT, module); +} + +/*! + * This function sets the AC97 tag register. + * + * @param module the module number + * @param tag the tag information + */ +void ssi_ac97_set_tag_register(ssi_mod module, unsigned int tag) +{ + set_register(tag, MXC_SSISATAG, module); +} + +/*! + * This function controls the AC97 variable mode. + * + * @param module the module number + * @param state the AC97 variable mode state (enabled or disabled) + */ void ssi_ac97_variable_mode(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_VARIABLE_OPERATION_SHIFT); + } else { + reg &= ~(1 << AC97_VARIABLE_OPERATION_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 write command. + * + * @param module the module number + * @param state the next AC97 command is a write command or not + */ +void ssi_ac97_write_command(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_WRITE_COMMAND_SHIFT); + } else { + reg &= ~(1 << AC97_WRITE_COMMAND_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the idle state of the transmit clock port during SSI internal gated mode. + * + * @param module the module number + * @param state the clock idle state + */ +void ssi_clock_idle_state(ssi_mod module, idle_state state) +{ + set_register_bits(1 << SSI_CLOCK_IDLE_SHIFT, + state << SSI_CLOCK_IDLE_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function turns off/on the ccm_ssi_clk to reduce power consumption. + * + * @param module the module number + * @param state the state for ccm_ssi_clk (true: turn off, else:turn on) + */ +void ssi_clock_off(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_CLOCK_OFF_SHIFT, + state << SSI_CLOCK_OFF_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function enables/disables the SSI module. + * + * @param module the module number + * @param state the state for SSI module + */ +void ssi_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_ENABLE_SHIFT, state << SSI_ENABLE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function gets the data word in the Receive FIFO of the SSI module. + * + * @param module the module number + * @param fifo the Receive FIFO to read + * @return This function returns the read data. + */ +unsigned int ssi_get_data(ssi_mod module, fifo_nb fifo) +{ + unsigned int result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISRX0, module); + } else { + result = getreg_value(MXC_SSISRX1, module); + } + + return result; +} + +/*! + * This function returns the status of the SSI module (SISR register) as a combination of status. + * + * @param module the module number + * @return This function returns the status of the SSI module + */ +ssi_status_enable_mask ssi_get_status(ssi_mod module) +{ + unsigned int result; + + result = getreg_value(MXC_SSISISR, module); + result &= ((1 << SSI_IRQ_STATUS_NUMBER) - 1); + + return (ssi_status_enable_mask) result; +} + +/*! + * This function selects the I2S mode of the SSI module. + * + * @param module the module number + * @param mode the I2S mode + */ +void ssi_i2s_mode(ssi_mod module, mode_i2s mode) +{ + set_register_bits(3 << SSI_I2S_MODE_SHIFT, mode << SSI_I2S_MODE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function disables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to disable + */ +void ssi_interrupt_disable(ssi_mod module, ssi_status_enable_mask mask) +{ + set_register_bits(mask, 0, MXC_SSISIER, module); +} + +/*! + * This function enables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to enable + */ +void ssi_interrupt_enable(ssi_mod module, ssi_status_enable_mask mask) +{ + set_register_bits(0, mask, MXC_SSISIER, module); +} + +/*! + * This function enables/disables the network mode of the SSI module. + * + * @param module the module number + * @param state the network mode state + */ +void ssi_network_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_NETWORK_MODE_SHIFT, + state << SSI_NETWORK_MODE_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function enables/disables the receive section of the SSI module. + * + * @param module the module number + * @param state the receive section state + */ +void ssi_receive_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_RECEIVE_ENABLE_SHIFT, + state << SSI_RECEIVE_ENABLE_SHIFT, MXC_SSISCR, + module); +} + +/*! + * This function configures the SSI module to receive data word at bit position 0 or 23 in the Receive shift register. + * + * @param module the module number + * @param state the state to receive at bit 0 + */ +void ssi_rx_bit0(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_BIT_0_SHIFT, state << SSI_BIT_0_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function controls the source of the clock signal used to clock the Receive shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_rx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_CLOCK_DIRECTION_SHIFT, + direction << SSI_CLOCK_DIRECTION_SHIFT, MXC_SSISRCR, + module); +} + +/*! + * This function configures the divide-by-two divider of the SSI module for the receive section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_rx_clock_divide_by_two(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_DIVIDE_BY_TWO_SHIFT, + state << SSI_DIVIDE_BY_TWO_SHIFT, MXC_SSISRCCR, + module); +} + +/*! + * This function controls which bit clock edge is used to clock in data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_rx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity) +{ + set_register_bits(1 << SSI_CLOCK_POLARITY_SHIFT, + polarity << SSI_CLOCK_POLARITY_SHIFT, MXC_SSISRCR, + module); +} + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the receive section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_rx_clock_prescaler(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_PRESCALER_RANGE_SHIFT, + state << SSI_PRESCALER_RANGE_SHIFT, + MXC_SSISRCCR, module); +} + +/*! + * This function controls the early frame sync configuration. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_rx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early) +{ + set_register_bits(1 << SSI_EARLY_FRAME_SYNC_SHIFT, + early << SSI_EARLY_FRAME_SYNC_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function gets the number of data words in the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Rx FIFO. + */ +unsigned char ssi_rx_fifo_counter(ssi_mod module, fifo_nb fifo) +{ + unsigned char result; + result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_RX_FIFO_0_COUNT_SHIFT); + result = result >> SSI_RX_FIFO_0_COUNT_SHIFT; + } else { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_RX_FIFO_1_COUNT_SHIFT); + result = result >> SSI_RX_FIFO_1_COUNT_SHIFT; + } + + return result; +} + +/*! + * This function enables the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the fifo, enabled or disabled + */ + +void ssi_rx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable) +{ + volatile unsigned int reg; + + reg = getreg_value(MXC_SSISRCR, module); + if (enable == true) { + reg |= ((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } else { + reg &= ~((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } + + set_register(reg, MXC_SSISRCR, module); +} + +/*! + * This function controls the threshold at which the RFFx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_fifo_full_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark) +{ + int result = -1; + result = -1; + + if ((watermark > SSI_MIN_FIFO_WATERMARK) && + (watermark <= SSI_MAX_FIFO_WATERMARK)) { + if (ssi_fifo_0 == fifo) { + set_register_bits(0xf << SSI_RX_FIFO_0_WATERMARK_SHIFT, + watermark << + SSI_RX_FIFO_0_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } else { + set_register_bits(0xf << SSI_RX_FIFO_1_WATERMARK_SHIFT, + watermark << + SSI_RX_FIFO_1_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } + + result = 0; + } + + return result; +} + +/*! + * This function flushes the Receive FIFOs. + * + * @param module the module number + */ +void ssi_rx_flush_fifo(ssi_mod module) +{ + set_register_bits(0, 1 << SSI_RECEIVER_CLEAR_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function controls the direction of the Frame Sync signal for the receive section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_rx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_FRAME_DIRECTION_SHIFT, + direction << SSI_FRAME_DIRECTION_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the Receive frame rate divider for the receive section. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_frame_rate(ssi_mod module, unsigned char ratio) +{ + int result = -1; + + if ((ratio >= SSI_MIN_FRAME_DIVIDER_RATIO) && + (ratio <= SSI_MAX_FRAME_DIVIDER_RATIO)) { + set_register_bits(SSI_FRAME_DIVIDER_MASK << + SSI_FRAME_RATE_DIVIDER_SHIFT, + (ratio - 1) << SSI_FRAME_RATE_DIVIDER_SHIFT, + MXC_SSISRCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls the Frame Sync active polarity for the receive section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_rx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active) +{ + set_register_bits(1 << SSI_FRAME_SYNC_INVERT_SHIFT, + active << SSI_FRAME_SYNC_INVERT_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the receive section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_rx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length) +{ + set_register_bits(1 << SSI_FRAME_SYNC_LENGTH_SHIFT, + length << SSI_FRAME_SYNC_LENGTH_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the time slot(s) to mask for the receive section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_rx_mask_time_slot(ssi_mod module, unsigned int mask) +{ + set_register_bits(0xFFFFFFFF, mask, MXC_SSISRMSK, module); +} + +/*! + * This function configures the Prescale divider for the receive section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_prescaler_modulus(ssi_mod module, unsigned int divider) +{ + int result = -1; + + if ((divider >= SSI_MIN_PRESCALER_MODULUS_RATIO) && + (divider <= SSI_MAX_PRESCALER_MODULUS_RATIO)) { + + set_register_bits(SSI_PRESCALER_MODULUS_MASK << + SSI_PRESCALER_MODULUS_SHIFT, + (divider - 1) << SSI_PRESCALER_MODULUS_SHIFT, + MXC_SSISRCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls whether the MSB or LSB will be received first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_rx_shift_direction(ssi_mod module, ssi_tx_rx_shift_direction direction) +{ + set_register_bits(1 << SSI_SHIFT_DIRECTION_SHIFT, + direction << SSI_SHIFT_DIRECTION_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the Receive word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_rx_word_length(ssi_mod module, ssi_word_length length) +{ + set_register_bits(SSI_WORD_LENGTH_MASK << SSI_WORD_LENGTH_SHIFT, + length << SSI_WORD_LENGTH_SHIFT, + MXC_SSISRCCR, module); +} + +/*! + * This function sets the data word in the Transmit FIFO of the SSI module. + * + * @param module the module number + * @param fifo the FIFO number + * @param data the data to load in the FIFO + */ + +void ssi_set_data(ssi_mod module, fifo_nb fifo, unsigned int data) +{ + if (ssi_fifo_0 == fifo) { + set_register(data, MXC_SSISTX0, module); + } else { + set_register(data, MXC_SSISTX1, module); + } +} + +/*! + * This function controls the number of wait states between the core and SSI. + * + * @param module the module number + * @param wait the number of wait state(s) + */ +void ssi_set_wait_states(ssi_mod module, ssi_wait_states wait) +{ + set_register_bits(SSI_WAIT_STATE_MASK << SSI_WAIT_SHIFT, + wait << SSI_WAIT_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function enables/disables the synchronous mode of the SSI module. + * + * @param module the module number + * @param state the synchronous mode state + */ +void ssi_synchronous_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_SYNCHRONOUS_MODE_SHIFT, + state << SSI_SYNCHRONOUS_MODE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function allows the SSI module to output the SYS_CLK at the SRCK port. + * + * @param module the module number + * @param state the system clock state + */ +void ssi_system_clock(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_SYSTEM_CLOCK_SHIFT, + state << SSI_SYSTEM_CLOCK_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function enables/disables the transmit section of the SSI module. + * + * @param module the module number + * @param state the transmit section state + */ +void ssi_transmit_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_TRANSMIT_ENABLE_SHIFT, + state << SSI_TRANSMIT_ENABLE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function allows the SSI module to operate in the two channel mode. + * + * @param module the module number + * @param state the two channel mode state + */ +void ssi_two_channel_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_TWO_CHANNEL_SHIFT, + state << SSI_TWO_CHANNEL_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function configures the SSI module to transmit data word from bit position 0 or 23 in the Transmit shift register. + * + * @param module the module number + * @param state the transmit from bit 0 state + */ +void ssi_tx_bit0(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_BIT_0_SHIFT, + state << SSI_BIT_0_SHIFT, MXC_SSISTCR, module); +} + +/*! + * This function controls the direction of the clock signal used to clock the Transmit shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_tx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_CLOCK_DIRECTION_SHIFT, + direction << SSI_CLOCK_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the divide-by-two divider of the SSI module for the transmit section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_tx_clock_divide_by_two(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_DIVIDE_BY_TWO_SHIFT, + state << SSI_DIVIDE_BY_TWO_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function controls which bit clock edge is used to clock out data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_tx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity) +{ + set_register_bits(1 << SSI_CLOCK_POLARITY_SHIFT, + polarity << SSI_CLOCK_POLARITY_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the transmit section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_tx_clock_prescaler(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_PRESCALER_RANGE_SHIFT, + state << SSI_PRESCALER_RANGE_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function controls the early frame sync configuration for the transmit section. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_tx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early) +{ + set_register_bits(1 << SSI_EARLY_FRAME_SYNC_SHIFT, + early << SSI_EARLY_FRAME_SYNC_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function gets the number of data words in the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Tx FIFO. + */ +unsigned char ssi_tx_fifo_counter(ssi_mod module, fifo_nb fifo) +{ + unsigned char result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_TX_FIFO_0_COUNT_SHIFT); + result >>= SSI_TX_FIFO_0_COUNT_SHIFT; + } else { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_TX_FIFO_1_COUNT_SHIFT); + result >>= SSI_TX_FIFO_1_COUNT_SHIFT; + } + + return result; +} + +/*! + * This function controls the threshold at which the TFEx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_fifo_empty_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark) +{ + int result = -1; + + if ((watermark > SSI_MIN_FIFO_WATERMARK) && + (watermark <= SSI_MAX_FIFO_WATERMARK)) { + if (ssi_fifo_0 == fifo) { + set_register_bits(0xf << SSI_TX_FIFO_0_WATERMARK_SHIFT, + watermark << + SSI_TX_FIFO_0_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } else { + set_register_bits(0xf << SSI_TX_FIFO_1_WATERMARK_SHIFT, + watermark << + SSI_TX_FIFO_1_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } + + result = 0; + } + + return result; +} + +/*! + * This function enables the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the fifo, enabled or disabled + */ + +void ssi_tx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable) +{ + unsigned int reg; + + reg = getreg_value(MXC_SSISTCR, module); + if (enable == true) { + reg |= ((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } else { + reg &= ~((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } + + set_register(reg, MXC_SSISTCR, module); +} + +/*! + * This function flushes the Transmit FIFOs. + * + * @param module the module number + */ +void ssi_tx_flush_fifo(ssi_mod module) +{ + set_register_bits(0, 1 << SSI_TRANSMITTER_CLEAR_SHIFT, + MXC_SSISOR, module); +} + +/*! + * This function controls the direction of the Frame Sync signal for the transmit section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_tx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_FRAME_DIRECTION_SHIFT, + direction << SSI_FRAME_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the Transmit frame rate divider. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_frame_rate(ssi_mod module, unsigned char ratio) +{ + int result = -1; + + if ((ratio >= SSI_MIN_FRAME_DIVIDER_RATIO) && + (ratio <= SSI_MAX_FRAME_DIVIDER_RATIO)) { + + set_register_bits(SSI_FRAME_DIVIDER_MASK << + SSI_FRAME_RATE_DIVIDER_SHIFT, + (ratio - 1) << SSI_FRAME_RATE_DIVIDER_SHIFT, + MXC_SSISTCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls the Frame Sync active polarity for the transmit section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_tx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active) +{ + set_register_bits(1 << SSI_FRAME_SYNC_INVERT_SHIFT, + active << SSI_FRAME_SYNC_INVERT_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the transmit section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_tx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length) +{ + set_register_bits(1 << SSI_FRAME_SYNC_LENGTH_SHIFT, + length << SSI_FRAME_SYNC_LENGTH_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the time slot(s) to mask for the transmit section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_tx_mask_time_slot(ssi_mod module, unsigned int mask) +{ + set_register_bits(0xFFFFFFFF, mask, MXC_SSISTMSK, module); +} + +/*! + * This function configures the Prescale divider for the transmit section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_prescaler_modulus(ssi_mod module, unsigned int divider) +{ + int result = -1; + + if ((divider >= SSI_MIN_PRESCALER_MODULUS_RATIO) && + (divider <= SSI_MAX_PRESCALER_MODULUS_RATIO)) { + + set_register_bits(SSI_PRESCALER_MODULUS_MASK << + SSI_PRESCALER_MODULUS_SHIFT, + (divider - 1) << SSI_PRESCALER_MODULUS_SHIFT, + MXC_SSISTCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls whether the MSB or LSB will be transmitted first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_tx_shift_direction(ssi_mod module, ssi_tx_rx_shift_direction direction) +{ + set_register_bits(1 << SSI_SHIFT_DIRECTION_SHIFT, + direction << SSI_SHIFT_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the Transmit word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_tx_word_length(ssi_mod module, ssi_word_length length) +{ + set_register_bits(SSI_WORD_LENGTH_MASK << SSI_WORD_LENGTH_SHIFT, + length << SSI_WORD_LENGTH_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __init ssi_probe(struct platform_device *pdev) +{ + int ret = -1; + ssi_platform_data = + (struct mxc_audio_platform_data *)pdev->dev.platform_data; + if (!ssi_platform_data) { + dev_err(&pdev->dev, "can't get the platform data for SSI\n"); + return -EINVAL; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) { + dev_err(&pdev->dev, "can't get platform resource - SSI\n"); + ret = -ENOMEM; + goto err; + } + + if (pdev->id == 0) { + base_addr_1 = res->start; + } else if (pdev->id == 1) { + base_addr_2 = res->start; + } + + printk(KERN_INFO "SSI %d module loaded successfully \n", pdev->id + 1); + + return 0; + err: + return -1; + +} + +static int ssi_remove(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver mxc_ssi_driver = { + .probe = ssi_probe, + .remove = ssi_remove, + .driver = { + .name = "mxc_ssi", + }, +}; + +/*! + * This function implements the init function of the SSI device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init ssi_init(void) +{ + spin_lock_init(&ssi_lock); + return platform_driver_register(&mxc_ssi_driver); + +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit ssi_exit(void) +{ + platform_driver_unregister(&mxc_ssi_driver); + printk(KERN_INFO "SSI module unloaded successfully\n"); +} + +module_init(ssi_init); +module_exit(ssi_exit); + +MODULE_DESCRIPTION("SSI char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ssi/ssi.h b/drivers/mxc/ssi/ssi.h new file mode 100644 index 000000000000..f31edbeef782 --- /dev/null +++ b/drivers/mxc/ssi/ssi.h @@ -0,0 +1,574 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @defgroup SSI Synchronous Serial Interface (SSI) Driver + */ + + /*! + * @file ssi.h + * @brief This header file contains SSI driver functions prototypes. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_H__ +#define __MXC_SSI_H__ + +#include "ssi_types.h" + +/*! + * This function gets the SSI fifo address. + * + * @param ssi ssi number + * @param direction To indicate playback / recording + * @return This function returns the SSI fifo address. + */ +unsigned int get_ssi_fifo_addr(unsigned int ssi, int direction); + +/*! + * This function controls the AC97 frame rate divider. + * + * @param module the module number + * @param frame_rate_divider the AC97 frame rate divider + */ +void ssi_ac97_frame_rate_divider(ssi_mod module, + unsigned char frame_rate_divider); + +/*! + * This function gets the AC97 command address register. + * + * @param module the module number + * @return This function returns the command address slot information. + */ +unsigned int ssi_ac97_get_command_address_register(ssi_mod module); + +/*! + * This function gets the AC97 command data register. + * + * @param module the module number + * @return This function returns the command data slot information. + */ +unsigned int ssi_ac97_get_command_data_register(ssi_mod module); + +/*! + * This function gets the AC97 tag register. + * + * @param module the module number + * @return This function returns the tag information. + */ +unsigned int ssi_ac97_get_tag_register(ssi_mod module); + +/*! + * This function controls the AC97 mode. + * + * @param module the module number + * @param state the AC97 mode state (enabled or disabled) + */ +void ssi_ac97_mode_enable(ssi_mod module, bool state); + +/*! + * This function controls the AC97 tag in FIFO behavior. + * + * @param module the module number + * @param state the tag in fifo behavior (Tag info stored in Rx FIFO 0 if TRUE, + * Tag info stored in SATAG register otherwise) + */ +void ssi_ac97_tag_in_fifo(ssi_mod module, bool state); + +/*! + * This function controls the AC97 read command. + * + * @param module the module number + * @param state the next AC97 command is a read command or not + */ +void ssi_ac97_read_command(ssi_mod module, bool state); + +/*! + * This function sets the AC97 command address register. + * + * @param module the module number + * @param address the command address slot information + */ +void ssi_ac97_set_command_address_register(ssi_mod module, + unsigned int address); + +/*! + * This function sets the AC97 command data register. + * + * @param module the module number + * @param data the command data slot information + */ +void ssi_ac97_set_command_data_register(ssi_mod module, unsigned int data); + +/*! + * This function sets the AC97 tag register. + * + * @param module the module number + * @param tag the tag information + */ +void ssi_ac97_set_tag_register(ssi_mod module, unsigned int tag); + +/*! + * This function controls the AC97 variable mode. + * + * @param module the module number + * @param state the AC97 variable mode state (enabled or disabled) + */ +void ssi_ac97_variable_mode(ssi_mod module, bool state); + +/*! + * This function controls the AC97 write command. + * + * @param module the module number + * @param state the next AC97 command is a write command or not + */ +void ssi_ac97_write_command(ssi_mod module, bool state); + +/*! + * This function controls the idle state of the transmit clock port during SSI internal gated mode. + * + * @param module the module number + * @param state the clock idle state + */ +void ssi_clock_idle_state(ssi_mod module, idle_state state); + +/*! + * This function turns off/on the ccm_ssi_clk to reduce power consumption. + * + * @param module the module number + * @param state the state for ccm_ssi_clk (true: turn off, else:turn on) + */ +void ssi_clock_off(ssi_mod module, bool state); + +/*! + * This function enables/disables the SSI module. + * + * @param module the module number + * @param state the state for SSI module + */ +void ssi_enable(ssi_mod module, bool state); + +/*! + * This function gets the data word in the Receive FIFO of the SSI module. + * + * @param module the module number + * @param fifo the Receive FIFO to read + * @return This function returns the read data. + */ +unsigned int ssi_get_data(ssi_mod module, fifo_nb fifo); + +/*! + * This function returns the status of the SSI module (SISR register) as a combination of status. + * + * @param module the module number + * @return This function returns the status of the SSI module. + */ +ssi_status_enable_mask ssi_get_status(ssi_mod module); + +/*! + * This function selects the I2S mode of the SSI module. + * + * @param module the module number + * @param mode the I2S mode + */ +void ssi_i2s_mode(ssi_mod module, mode_i2s mode); + +/*! + * This function disables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to disable + */ +void ssi_interrupt_disable(ssi_mod module, ssi_status_enable_mask mask); + +/*! + * This function enables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to enable + */ +void ssi_interrupt_enable(ssi_mod module, ssi_status_enable_mask mask); + +/*! + * This function enables/disables the network mode of the SSI module. + * + * @param module the module number + * @param state the network mode state + */ +void ssi_network_mode(ssi_mod module, bool state); + +/*! + * This function enables/disables the receive section of the SSI module. + * + * @param module the module number + * @param state the receive section state + */ +void ssi_receive_enable(ssi_mod module, bool state); + +/*! + * This function configures the SSI module to receive data word at bit position 0 or 23 in the Receive shift register. + * + * @param module the module number + * @param state the state to receive at bit 0 + */ +void ssi_rx_bit0(ssi_mod module, bool state); + +/*! + * This function controls the source of the clock signal used to clock the Receive shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_rx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the divide-by-two divider of the SSI module for the receive section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_rx_clock_divide_by_two(ssi_mod module, bool state); + +/*! + * This function controls which bit clock edge is used to clock in data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_rx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity); + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the receive section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_rx_clock_prescaler(ssi_mod module, bool state); + +/*! + * This function controls the early frame sync configuration. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_rx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early); + +/*! + * This function gets the number of data words in the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Rx FIFO. + */ +unsigned char ssi_rx_fifo_counter(ssi_mod module, fifo_nb fifo); + +/*! + * This function enables the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enabled the state of the fifo, enabled or disabled + */ +void ssi_rx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enabled); + +/*! + * This function controls the threshold at which the RFFx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_fifo_full_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark); + +/*! + * This function flushes the Receive FIFOs. + * + * @param module the module number + */ +void ssi_rx_flush_fifo(ssi_mod module); + +/*! + * This function controls the direction of the Frame Sync signal for the receive section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_rx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the Receive frame rate divider for the receive section. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_frame_rate(ssi_mod module, unsigned char ratio); + +/*! + * This function controls the Frame Sync active polarity for the receive section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_rx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active); + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the receive section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_rx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length); + +/*! + * This function configures the time slot(s) to mask for the receive section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_rx_mask_time_slot(ssi_mod module, unsigned int mask); + +/*! + * This function configures the Prescale divider for the receive section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_prescaler_modulus(ssi_mod module, unsigned int divider); + +/*! + * This function controls whether the MSB or LSB will be received first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_rx_shift_direction(ssi_mod module, + ssi_tx_rx_shift_direction direction); + +/*! + * This function configures the Receive word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_rx_word_length(ssi_mod module, ssi_word_length length); + +/*! + * This function sets the data word in the Transmit FIFO of the SSI module. + * + * @param module the module number + * @param fifo the FIFO number + * @param data the data to load in the FIFO + */ +void ssi_set_data(ssi_mod module, fifo_nb fifo, unsigned int data); + +/*! + * This function controls the number of wait states between the core and SSI. + * + * @param module the module number + * @param wait the number of wait state(s) + */ +void ssi_set_wait_states(ssi_mod module, ssi_wait_states wait); + +/*! + * This function enables/disables the synchronous mode of the SSI module. + * + * @param module the module number + * @param state the synchronous mode state + */ +void ssi_synchronous_mode(ssi_mod module, bool state); + +/*! + * This function allows the SSI module to output the SYS_CLK at the SRCK port. + * + * @param module the module number + * @param state the system clock state + */ +void ssi_system_clock(ssi_mod module, bool state); + +/*! + * This function enables/disables the transmit section of the SSI module. + * + * @param module the module number + * @param state the transmit section state + */ +void ssi_transmit_enable(ssi_mod module, bool state); + +/*! + * This function allows the SSI module to operate in the two channel mode. + * + * @param module the module number + * @param state the two channel mode state + */ +void ssi_two_channel_mode(ssi_mod module, bool state); + +/*! + * This function configures the SSI module to transmit data word from bit position 0 or 23 in the Transmit shift register. + * + * @param module the module number + * @param state the transmit from bit 0 state + */ +void ssi_tx_bit0(ssi_mod module, bool state); + +/*! + * This function controls the direction of the clock signal used to clock the Transmit shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_tx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the divide-by-two divider of the SSI module for the transmit section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_tx_clock_divide_by_two(ssi_mod module, bool state); + +/*! + * This function controls which bit clock edge is used to clock out data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_tx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity); + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the transmit section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_tx_clock_prescaler(ssi_mod module, bool state); + +/*! + * This function controls the early frame sync configuration for the transmit section. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_tx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early); + +/*! + * This function gets the number of data words in the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Tx FIFO. + */ +unsigned char ssi_tx_fifo_counter(ssi_mod module, fifo_nb fifo); + +/*! + * This function controls the threshold at which the TFEx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_fifo_empty_watermark(ssi_mod module, fifo_nb fifo, + unsigned char watermark); + +/*! + * This function enables the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the FIFO, enabled or disabled + */ +void ssi_tx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable); + +/*! + * This function flushes the Transmit FIFOs. + * + * @param module the module number + */ +void ssi_tx_flush_fifo(ssi_mod module); + +/*! + * This function controls the direction of the Frame Sync signal for the transmit section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_tx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the Transmit frame rate divider. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_frame_rate(ssi_mod module, unsigned char ratio); + +/*! + * This function controls the Frame Sync active polarity for the transmit section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_tx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active); + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the transmit section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_tx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length); + +/*! + * This function configures the time slot(s) to mask for the transmit section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_tx_mask_time_slot(ssi_mod module, unsigned int mask); + +/*! + * This function configures the Prescale divider for the transmit section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_prescaler_modulus(ssi_mod module, unsigned int divider); + +/*! + * This function controls whether the MSB or LSB will be transmited first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_tx_shift_direction(ssi_mod module, + ssi_tx_rx_shift_direction direction); + +/*! + * This function configures the Transmit word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_tx_word_length(ssi_mod module, ssi_word_length length); + +#endif /* __MXC_SSI_H__ */ diff --git a/drivers/mxc/ssi/ssi_types.h b/drivers/mxc/ssi/ssi_types.h new file mode 100644 index 000000000000..eb8024d45331 --- /dev/null +++ b/drivers/mxc/ssi/ssi_types.h @@ -0,0 +1,367 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file ssi_types.h + * @brief This header file contains SSI types. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_TYPES_H__ +#define __MXC_SSI_TYPES_H__ + +/*! + * This enumeration describes the FIFO number. + */ +typedef enum { + /*! + * FIFO 0 + */ + ssi_fifo_0 = 0, + /*! + * FIFO 1 + */ + ssi_fifo_1 = 1 +} fifo_nb; + +/*! + * This enumeration describes the clock idle state. + */ +typedef enum { + /*! + * Clock idle state is 1 + */ + clock_idle_state_1 = 0, + /*! + * Clock idle state is 0 + */ + clock_idle_state_0 = 1 +} idle_state; + +/*! + * This enumeration describes I2S mode. + */ +typedef enum { + /*! + * Normal mode + */ + i2s_normal = 0, + /*! + * Master mode + */ + i2s_master = 1, + /*! + * Slave mode + */ + i2s_slave = 2 +} mode_i2s; + +/*! + * This enumeration describes index for both SSI1 and SSI2 modules. + */ +typedef enum { + /*! + * SSI1 index + */ + SSI1 = 0, + /*! + * SSI2 index not present on MXC 91221 and MXC91311 + */ + SSI2 = 1 +} ssi_mod; + +/*! + * This enumeration describes the status/enable bits for interrupt source of the SSI module. + */ +typedef enum { + /*! + * SSI Transmit FIFO 0 empty bit + */ + ssi_tx_fifo_0_empty = 0x00000001, + /*! + * SSI Transmit FIFO 1 empty bit + */ + ssi_tx_fifo_1_empty = 0x00000002, + /*! + * SSI Receive FIFO 0 full bit + */ + ssi_rx_fifo_0_full = 0x00000004, + /*! + * SSI Receive FIFO 1 full bit + */ + ssi_rx_fifo_1_full = 0x00000008, + /*! + * SSI Receive Last Time Slot bit + */ + ssi_rls = 0x00000010, + /*! + * SSI Transmit Last Time Slot bit + */ + ssi_tls = 0x00000020, + /*! + * SSI Receive Frame Sync bit + */ + ssi_rfs = 0x00000040, + /*! + * SSI Transmit Frame Sync bit + */ + ssi_tfs = 0x00000080, + /*! + * SSI Transmitter underrun 0 bit + */ + ssi_transmitter_underrun_0 = 0x00000100, + /*! + * SSI Transmitter underrun 1 bit + */ + ssi_transmitter_underrun_1 = 0x00000200, + /*! + * SSI Receiver overrun 0 bit + */ + ssi_receiver_overrun_0 = 0x00000400, + /*! + * SSI Receiver overrun 1 bit + */ + ssi_receiver_overrun_1 = 0x00000800, + /*! + * SSI Transmit Data register empty 0 bit + */ + ssi_tx_data_reg_empty_0 = 0x00001000, + /*! + * SSI Transmit Data register empty 1 bit + */ + ssi_tx_data_reg_empty_1 = 0x00002000, + + /*! + * SSI Receive Data Ready 0 bit + */ + ssi_rx_data_ready_0 = 0x00004000, + /*! + * SSI Receive Data Ready 1 bit + */ + ssi_rx_data_ready_1 = 0x00008000, + /*! + * SSI Receive tag updated bit + */ + ssi_rx_tag_updated = 0x00010000, + /*! + * SSI Command data register updated bit + */ + ssi_cmd_data_reg_updated = 0x00020000, + /*! + * SSI Command address register updated bit + */ + ssi_cmd_address_reg_updated = 0x00040000, + /*! + * SSI Transmit interrupt enable bit + */ + ssi_tx_interrupt_enable = 0x00080000, + /*! + * SSI Transmit DMA enable bit + */ + ssi_tx_dma_interrupt_enable = 0x00100000, + /*! + * SSI Receive interrupt enable bit + */ + ssi_rx_interrupt_enable = 0x00200000, + /*! + * SSI Receive DMA enable bit + */ + ssi_rx_dma_interrupt_enable = 0x00400000, + /*! + * SSI Tx frame complete enable bit on MXC91221 & MXC91311 only + */ + ssi_tx_frame_complete = 0x00800000, + /*! + * SSI Rx frame complete on MXC91221 & MXC91311 only + */ + ssi_rx_frame_complete = 0x001000000 +} ssi_status_enable_mask; + +/*! + * This enumeration describes the clock edge to clock in or clock out data. + */ +typedef enum { + /*! + * Clock on rising edge + */ + ssi_clock_on_rising_edge = 0, + /*! + * Clock on falling edge + */ + ssi_clock_on_falling_edge = 1 +} ssi_tx_rx_clock_polarity; + +/*! + * This enumeration describes the clock direction. + */ +typedef enum { + /*! + * Clock is external + */ + ssi_tx_rx_externally = 0, + /*! + * Clock is generated internally + */ + ssi_tx_rx_internally = 1 +} ssi_tx_rx_direction; + +/*! + * This enumeration describes the early frame sync behavior. + */ +typedef enum { + /*! + * Frame Sync starts on the first data bit + */ + ssi_frame_sync_first_bit = 0, + /*! + * Frame Sync starts one bit before the first data bit + */ + ssi_frame_sync_one_bit_before = 1 +} ssi_tx_rx_early_frame_sync; + +/*! + * This enumeration describes the Frame Sync active value. + */ +typedef enum { + /*! + * Frame Sync is active when high + */ + ssi_frame_sync_active_high = 0, + /*! + * Frame Sync is active when low + */ + ssi_frame_sync_active_low = 1 +} ssi_tx_rx_frame_sync_active; + +/*! + * This enumeration describes the Frame Sync active length. + */ +typedef enum { + /*! + * Frame Sync is active when high + */ + ssi_frame_sync_one_word = 0, + /*! + * Frame Sync is active when low + */ + ssi_frame_sync_one_bit = 1 +} ssi_tx_rx_frame_sync_length; + +/*! + * This enumeration describes the Tx/Rx frame shift direction. + */ +typedef enum { + /*! + * MSB first + */ + ssi_msb_first = 0, + /*! + * LSB first + */ + ssi_lsb_first = 1 +} ssi_tx_rx_shift_direction; + +/*! + * This enumeration describes the wait state number. + */ +typedef enum { + /*! + * 0 wait state + */ + ssi_waitstates0 = 0x0, + /*! + * 1 wait state + */ + ssi_waitstates1 = 0x1, + /*! + * 2 wait states + */ + ssi_waitstates2 = 0x2, + /*! + * 3 wait states + */ + ssi_waitstates3 = 0x3 +} ssi_wait_states; + +/*! + * This enumeration describes the word length. + */ +typedef enum { + /*! + * 2 bits long + */ + ssi_2_bits = 0x0, + /*! + * 4 bits long + */ + ssi_4_bits = 0x1, + /*! + * 6 bits long + */ + ssi_6_bits = 0x2, + /*! + * 8 bits long + */ + ssi_8_bits = 0x3, + /*! + * 10 bits long + */ + ssi_10_bits = 0x4, + /*! + * 12 bits long + */ + ssi_12_bits = 0x5, + /*! + * 14 bits long + */ + ssi_14_bits = 0x6, + /*! + * 16 bits long + */ + ssi_16_bits = 0x7, + /*! + * 18 bits long + */ + ssi_18_bits = 0x8, + /*! + * 20 bits long + */ + ssi_20_bits = 0x9, + /*! + * 22 bits long + */ + ssi_22_bits = 0xA, + /*! + * 24 bits long + */ + ssi_24_bits = 0xB, + /*! + * 26 bits long + */ + ssi_26_bits = 0xC, + /*! + * 28 bits long + */ + ssi_28_bits = 0xD, + /*! + * 30 bits long + */ + ssi_30_bits = 0xE, + /*! + * 32 bits long + */ + ssi_32_bits = 0xF +} ssi_word_length; + +#endif /* __MXC_SSI_TYPES_H__ */ diff --git a/drivers/mxc/vpu/Kconfig b/drivers/mxc/vpu/Kconfig new file mode 100644 index 000000000000..37e55e03d2f2 --- /dev/null +++ b/drivers/mxc/vpu/Kconfig @@ -0,0 +1,30 @@ +# +# Codec configuration +# + +menu "MXC VPU(Video Processing Unit) support" + +config MXC_VPU + tristate "Support for MXC VPU(Video Processing Unit)" + depends on (ARCH_MX3 || ARCH_MX27 || ARCH_MX37 || ARCH_MX5) + default y + ---help--- + The VPU codec device provides codec function for H.264/MPEG4/H.263, + as well as MPEG2/VC-1/DivX on some platforms. + +config MXC_VPU_IRAM + tristate "Use IRAM as temporary buffer for VPU to enhance performace" + depends on (ARCH_MX37 || ARCH_MX5) + default y + ---help--- + The VPU can use internal RAM as temporary buffer to save external + memroy bandwith, thus to enhance video performance. + +config MXC_VPU_DEBUG + bool "MXC VPU debugging" + depends on MXC_VPU != n + help + This is an option for the developers; most people should + say N here. This enables MXC VPU driver debugging. + +endmenu diff --git a/drivers/mxc/vpu/Makefile b/drivers/mxc/vpu/Makefile new file mode 100644 index 000000000000..88e8f2c084a0 --- /dev/null +++ b/drivers/mxc/vpu/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the VPU drivers. +# + +obj-$(CONFIG_MXC_VPU) += vpu.o +vpu-objs := mxc_vpu.o mxc_vl2cc.o + +ifeq ($(CONFIG_MXC_VPU_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/mxc/vpu/mxc_vl2cc.c b/drivers/mxc/vpu/mxc_vl2cc.c new file mode 100644 index 000000000000..f30efea25dac --- /dev/null +++ b/drivers/mxc/vpu/mxc_vl2cc.c @@ -0,0 +1,124 @@ +/* + * Copyright 2007-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include + +#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..afac6d95e8c3 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vpu.c @@ -0,0 +1,884 @@ +/* + * Copyright 2006-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +struct vpu_priv { + struct fasync_struct *async_queue; + struct work_struct work; + struct workqueue_struct *workqueue; +}; + +/* To track the allocated memory buffer */ +typedef struct memalloc_record { + struct list_head list; + struct vpu_mem_desc mem; +} memalloc_record; + +struct iram_setting { + u32 start; + u32 end; +}; + +static DEFINE_SPINLOCK(vpu_lock); +static LIST_HEAD(head); + +static int vpu_major; +static int vpu_clk_usercount; +static struct class *vpu_class; +static struct vpu_priv vpu_data; +static u8 open_count; +static struct clk *vpu_clk; +static struct vpu_mem_desc bitwork_mem = { 0 }; +static struct vpu_mem_desc pic_para_mem = { 0 }; +static struct vpu_mem_desc user_data_mem = { 0 }; +static struct vpu_mem_desc share_mem = { 0 }; + +static void __iomem *vpu_base; +static int vpu_irq; +static u32 phy_vpu_base_addr; +static struct mxc_vpu_platform_data *vpu_plat; + +/* IRAM setting */ +static struct iram_setting iram; + +/* implement the blocking ioctl */ +static int codec_done; +static wait_queue_head_t vpu_queue; + +static u32 workctrl_regsave[6]; +static u32 rd_ptr_regsave[4]; +static u32 wr_ptr_regsave[4]; +static u32 dis_flag_regsave[4]; + +#define READ_REG(x) __raw_readl(vpu_base + x) +#define WRITE_REG(val, x) __raw_writel(val, vpu_base + x) +#define SAVE_WORK_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(workctrl_regsave)/2; i++) \ + workctrl_regsave[i] = READ_REG(BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define RESTORE_WORK_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(workctrl_regsave)/2; i++) \ + WRITE_REG(workctrl_regsave[i], BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define SAVE_CTRL_REGS do { \ + int i; \ + for (i = ARRAY_SIZE(workctrl_regsave)/2; \ + i < ARRAY_SIZE(workctrl_regsave); i++) \ + workctrl_regsave[i] = READ_REG(BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define RESTORE_CTRL_REGS do { \ + int i; \ + for (i = ARRAY_SIZE(workctrl_regsave)/2; \ + i < ARRAY_SIZE(workctrl_regsave); i++) \ + WRITE_REG(workctrl_regsave[i], BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define SAVE_RDWR_PTR_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(rd_ptr_regsave); i++) \ + rd_ptr_regsave[i] = READ_REG(BIT_RD_PTR_REG(i)); \ + for (i = 0; i < ARRAY_SIZE(wr_ptr_regsave); i++) \ + wr_ptr_regsave[i] = READ_REG(BIT_WR_PTR_REG(i)); \ +} while (0) +#define RESTORE_RDWR_PTR_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(rd_ptr_regsave); i++) \ + WRITE_REG(rd_ptr_regsave[i], BIT_RD_PTR_REG(i)); \ + for (i = 0; i < ARRAY_SIZE(wr_ptr_regsave); i++) \ + WRITE_REG(wr_ptr_regsave[i], BIT_WR_PTR_REG(i)); \ +} while (0) +#define SAVE_DIS_FLAG_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(dis_flag_regsave); i++) \ + dis_flag_regsave[i] = READ_REG(BIT_FRM_DIS_FLG_REG(i)); \ +} while (0) +#define RESTORE_DIS_FLAG_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(dis_flag_regsave); i++) \ + WRITE_REG(dis_flag_regsave[i], BIT_FRM_DIS_FLG_REG(i)); \ +} while (0) + +/*! + * Private function to alloc dma buffer + * @return status 0 success. + */ +static int vpu_alloc_dma_buffer(struct vpu_mem_desc *mem) +{ + mem->cpu_addr = (unsigned long) + dma_alloc_coherent(NULL, PAGE_ALIGN(mem->size), + (dma_addr_t *) (&mem->phy_addr), + GFP_DMA | GFP_KERNEL); + pr_debug("[ALLOC] mem alloc cpu_addr = 0x%x\n", mem->cpu_addr); + if ((void *)(mem->cpu_addr) == NULL) { + printk(KERN_ERR "Physical memory allocation error!\n"); + return -1; + } + return 0; +} + +/*! + * Private function to free dma buffer + */ +static void vpu_free_dma_buffer(struct vpu_mem_desc *mem) +{ + if (mem->cpu_addr != 0) { + dma_free_coherent(0, PAGE_ALIGN(mem->size), + (void *)mem->cpu_addr, mem->phy_addr); + } +} + +/*! + * Private function to free buffers + * @return status 0 success. + */ +static int vpu_free_buffers(void) +{ + struct memalloc_record *rec, *n; + struct vpu_mem_desc mem; + + list_for_each_entry_safe(rec, n, &head, list) { + mem = rec->mem; + if (mem.cpu_addr != 0) { + vpu_free_dma_buffer(&mem); + pr_debug("[FREE] freed paddr=0x%08X\n", mem.phy_addr); + /* delete from list */ + list_del(&rec->list); + kfree(rec); + } + } + + return 0; +} + +static inline void vpu_worker_callback(struct work_struct *w) +{ + struct vpu_priv *dev = container_of(w, struct vpu_priv, + work); + + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + codec_done = 1; + wake_up_interruptible(&vpu_queue); + + /* + * Clock is gated on when dec/enc started, gate it off when + * interrupt is received. + */ + clk_disable(vpu_clk); +} + +/*! + * @brief vpu interrupt handler + */ +static irqreturn_t vpu_irq_handler(int irq, void *dev_id) +{ + struct vpu_priv *dev = dev_id; + + READ_REG(BIT_INT_STATUS); + WRITE_REG(0x1, BIT_INT_CLEAR); + + queue_work(dev->workqueue, &dev->work); + + return IRQ_HANDLED; +} + +/*! + * @brief open function for vpu file operation + * + * @return 0 on success or negative error code on error + */ +static int vpu_open(struct inode *inode, struct file *filp) +{ + spin_lock(&vpu_lock); + if ((open_count++ == 0) && cpu_is_mx32()) + vl2cc_enable(); + filp->private_data = (void *)(&vpu_data); + spin_unlock(&vpu_lock); + return 0; +} + +/*! + * @brief IO ctrl function for vpu file operation + * @param cmd IO ctrl command + * @return 0 on success or negative error code on error + */ +static long vpu_ioctl(struct file *filp, u_int cmd, + u_long arg) +{ + int ret = 0; + + switch (cmd) { + case VPU_IOC_PHYMEM_ALLOC: + { + struct memalloc_record *rec; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (!rec) + return -ENOMEM; + + ret = copy_from_user(&(rec->mem), + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc)); + if (ret) { + kfree(rec); + return -EFAULT; + } + + pr_debug("[ALLOC] mem alloc size = 0x%x\n", + rec->mem.size); + + ret = vpu_alloc_dma_buffer(&(rec->mem)); + if (ret == -1) { + kfree(rec); + printk(KERN_ERR + "Physical memory allocation error!\n"); + break; + } + ret = copy_to_user((void __user *)arg, &(rec->mem), + sizeof(struct vpu_mem_desc)); + if (ret) { + kfree(rec); + ret = -EFAULT; + break; + } + + spin_lock(&vpu_lock); + list_add(&rec->list, &head); + spin_unlock(&vpu_lock); + + break; + } + case VPU_IOC_PHYMEM_FREE: + { + struct memalloc_record *rec, *n; + struct vpu_mem_desc vpu_mem; + + ret = copy_from_user(&vpu_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc)); + if (ret) + return -EACCES; + + pr_debug("[FREE] mem freed cpu_addr = 0x%x\n", + vpu_mem.cpu_addr); + if ((void *)vpu_mem.cpu_addr != NULL) { + vpu_free_dma_buffer(&vpu_mem); + } + + spin_lock(&vpu_lock); + list_for_each_entry_safe(rec, n, &head, list) { + if (rec->mem.cpu_addr == vpu_mem.cpu_addr) { + /* delete from list */ + list_del(&rec->list); + kfree(rec); + break; + } + } + spin_unlock(&vpu_lock); + + break; + } + case VPU_IOC_WAIT4INT: + { + u_long timeout = (u_long) arg; + if (!wait_event_interruptible_timeout + (vpu_queue, codec_done != 0, + msecs_to_jiffies(timeout))) { + printk(KERN_WARNING "VPU blocking: timeout.\n"); + ret = -ETIME; + } else if (signal_pending(current)) { + printk(KERN_WARNING + "VPU interrupt received.\n"); + ret = -ERESTARTSYS; + } else + codec_done = 0; + break; + } + case VPU_IOC_VL2CC_FLUSH: + if (cpu_is_mx32()) { + vl2cc_flush(); + } + break; + case VPU_IOC_IRAM_SETTING: + { + ret = copy_to_user((void __user *)arg, &iram, + sizeof(struct iram_setting)); + if (ret) + ret = -EFAULT; + + break; + } + case VPU_IOC_CLKGATE_SETTING: + { + u32 clkgate_en; + + if (get_user(clkgate_en, (u32 __user *) arg)) + return -EFAULT; + + if (clkgate_en) { + clk_enable(vpu_clk); + } else { + clk_disable(vpu_clk); + } + + break; + } + case VPU_IOC_GET_SHARE_MEM: + { + spin_lock(&vpu_lock); + if (share_mem.cpu_addr != 0) { + ret = copy_to_user((void __user *)arg, + &share_mem, + sizeof(struct vpu_mem_desc)); + spin_unlock(&vpu_lock); + break; + } else { + if (copy_from_user(&share_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) { + spin_unlock(&vpu_lock); + return -EFAULT; + } + if (vpu_alloc_dma_buffer(&share_mem) == -1) + ret = -EFAULT; + else { + if (copy_to_user((void __user *)arg, + &share_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + } + spin_unlock(&vpu_lock); + break; + } + case VPU_IOC_GET_WORK_ADDR: + { + if (bitwork_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &bitwork_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&bitwork_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&bitwork_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &bitwork_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_GET_PIC_PARA_ADDR: + { + if (pic_para_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &pic_para_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&pic_para_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&pic_para_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &pic_para_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_GET_USER_DATA_ADDR: + { + if (user_data_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &user_data_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&user_data_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&user_data_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &user_data_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_SYS_SW_RESET: + { + if (vpu_plat->reset) + vpu_plat->reset(); + + break; + } + case VPU_IOC_REG_DUMP: + break; + case VPU_IOC_PHYMEM_DUMP: + break; + default: + { + printk(KERN_ERR "No such IOCTL, cmd is %d\n", cmd); + break; + } + } + return ret; +} + +/*! + * @brief Release function for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_release(struct inode *inode, struct file *filp) +{ + spin_lock(&vpu_lock); + if (open_count > 0 && !(--open_count)) { + vpu_free_buffers(); + + if (cpu_is_mx32()) + vl2cc_disable(); + + /* Free shared memory when vpu device is idle */ + vpu_free_dma_buffer(&share_mem); + share_mem.cpu_addr = 0; + } + spin_unlock(&vpu_lock); + + return 0; +} + +/*! + * @brief fasync function for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_fasync(int fd, struct file *filp, int mode) +{ + struct vpu_priv *dev = (struct vpu_priv *)filp->private_data; + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + +/*! + * @brief memory map function of harware registers for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_map_hwregs(struct file *fp, struct vm_area_struct *vm) +{ + unsigned long pfn; + + vm->vm_flags |= VM_IO | VM_RESERVED; + vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot); + pfn = phy_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_writecombine(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, + .unlocked_ioctl = vpu_ioctl, + .release = vpu_release, + .fasync = vpu_fasync, + .mmap = vpu_mmap, +}; + +/*! + * This function is called by the driver framework to initialize the vpu device. + * @param dev The device structure for the vpu passed in by the framework. + * @return 0 on success or negative error code on error + */ +static int vpu_dev_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *temp_class; + struct resource *res; + unsigned long addr = 0; + + vpu_plat = pdev->dev.platform_data; + + if (VPU_IRAM_SIZE) + iram_alloc(VPU_IRAM_SIZE, &addr); + if (addr == 0) + iram.start = iram.end = 0; + else { + iram.start = addr; + iram.end = addr + VPU_IRAM_SIZE - 1; + } + + if (cpu_is_mx32()) { + err = vl2cc_init(iram.start); + if (err != 0) + return err; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_ERR "vpu: unable to get vpu base addr\n"); + return -ENODEV; + } + phy_vpu_base_addr = res->start; + vpu_base = ioremap(res->start, res->end - res->start); + + vpu_major = register_chrdev(vpu_major, "mxc_vpu", &vpu_fops); + if (vpu_major < 0) { + printk(KERN_ERR "vpu: unable to get a major for VPU\n"); + err = -EBUSY; + goto error; + } + + vpu_class = class_create(THIS_MODULE, "mxc_vpu"); + if (IS_ERR(vpu_class)) { + err = PTR_ERR(vpu_class); + goto err_out_chrdev; + } + + temp_class = device_create(vpu_class, NULL, MKDEV(vpu_major, 0), + NULL, "mxc_vpu"); + if (IS_ERR(temp_class)) { + err = PTR_ERR(temp_class); + goto err_out_class; + } + + vpu_clk = clk_get(&pdev->dev, "vpu_clk"); + if (IS_ERR(vpu_clk)) { + err = -ENOENT; + goto err_out_class; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + printk(KERN_ERR "vpu: unable to get vpu interrupt\n"); + err = -ENXIO; + goto err_out_class; + } + vpu_irq = res->start; + + err = request_irq(vpu_irq, vpu_irq_handler, 0, "VPU_CODEC_IRQ", + (void *)(&vpu_data)); + if (err) + goto err_out_class; + + vpu_data.workqueue = create_workqueue("vpu_wq"); + INIT_WORK(&vpu_data.work, vpu_worker_callback); + printk(KERN_INFO "VPU initialized\n"); + goto out; + + err_out_class: + device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + err_out_chrdev: + unregister_chrdev(vpu_major, "mxc_vpu"); + error: + iounmap(vpu_base); + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + out: + return err; +} + +static int vpu_dev_remove(struct platform_device *pdev) +{ + free_irq(vpu_irq, &vpu_data); + cancel_work_sync(&vpu_data.work); + flush_workqueue(vpu_data.workqueue); + destroy_workqueue(vpu_data.workqueue); + + iounmap(vpu_base); + if (VPU_IRAM_SIZE) + iram_free(iram.start, VPU_IRAM_SIZE); + + return 0; +} + +#ifdef CONFIG_PM +static int vpu_suspend(struct platform_device *pdev, pm_message_t state) +{ + int i; + unsigned long timeout; + + /* Wait for vpu go to idle state, suspect vpu cannot be changed + to idle state after about 1 sec */ + if (open_count > 0) { + timeout = jiffies + HZ; + clk_enable(vpu_clk); + while (READ_REG(BIT_BUSY_FLAG)) { + msleep(1); + if (time_after(jiffies, timeout)) + goto out; + } + clk_disable(vpu_clk); + } + + /* Make sure clock is disabled before suspend */ + vpu_clk_usercount = clk_get_usecount(vpu_clk); + for (i = 0; i < vpu_clk_usercount; i++) + clk_disable(vpu_clk); + + if (!cpu_is_mx53()) { + clk_enable(vpu_clk); + if (bitwork_mem.cpu_addr != 0) { + SAVE_WORK_REGS; + SAVE_CTRL_REGS; + SAVE_RDWR_PTR_REGS; + SAVE_DIS_FLAG_REGS; + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(VPU_SLEEP_REG_VALUE, BIT_RUN_COMMAND); + while (READ_REG(BIT_BUSY_FLAG)) + ; + } + clk_disable(vpu_clk); + } + + if (cpu_is_mx37() || cpu_is_mx51() || cpu_is_mx53()) + if (vpu_plat->pg) + vpu_plat->pg(1); + + return 0; + +out: + clk_disable(vpu_clk); + return -EAGAIN; + +} + +static int vpu_resume(struct platform_device *pdev) +{ + int i; + + if (cpu_is_mx37() || cpu_is_mx51() || cpu_is_mx53()) + if (vpu_plat->pg) + vpu_plat->pg(0); + + if (cpu_is_mx53()) + goto recover_clk; + + clk_enable(vpu_clk); + if (bitwork_mem.cpu_addr != 0) { + u32 *p = (u32 *) bitwork_mem.cpu_addr; + u32 data; + u16 data_hi; + u16 data_lo; + + RESTORE_WORK_REGS; + + WRITE_REG(0x0, BIT_RESET_CTRL); + WRITE_REG(0x0, BIT_CODE_RUN); + + /* + * Re-load boot code, from the codebuffer in external RAM. + * Thankfully, we only need 4096 bytes, same for all platforms. + */ + if (cpu_is_mx51()) { + for (i = 0; i < 2048; i += 4) { + data = p[(i / 2) + 1]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + WRITE_REG((i << 16) | data_hi, BIT_CODE_DOWN); + WRITE_REG(((i + 1) << 16) | data_lo, + BIT_CODE_DOWN); + + data = p[i / 2]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + WRITE_REG(((i + 2) << 16) | data_hi, + BIT_CODE_DOWN); + WRITE_REG(((i + 3) << 16) | data_lo, + BIT_CODE_DOWN); + } + } else { + for (i = 0; i < 2048; i += 2) { + if (cpu_is_mx37()) + data = swab32(p[i / 2]); + else + data = p[i / 2]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + + WRITE_REG((i << 16) | data_hi, BIT_CODE_DOWN); + WRITE_REG(((i + 1) << 16) | data_lo, + BIT_CODE_DOWN); + } + } + + RESTORE_CTRL_REGS; + + WRITE_REG(BITVAL_PIC_RUN, BIT_INT_ENABLE); + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(0x1, BIT_CODE_RUN); + while (READ_REG(BIT_BUSY_FLAG)) + ; + + RESTORE_RDWR_PTR_REGS; + RESTORE_DIS_FLAG_REGS; + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(VPU_WAKE_REG_VALUE, BIT_RUN_COMMAND); + while (READ_REG(BIT_BUSY_FLAG)) + ; + } + clk_disable(vpu_clk); + +recover_clk: + /* Recover vpu clock */ + for (i = 0; i < vpu_clk_usercount; i++) + clk_enable(vpu_clk); + + return 0; +} +#else +#define vpu_suspend NULL +#define vpu_resume NULL +#endif /* !CONFIG_PM */ + +/*! Driver definition + * + */ +static struct platform_driver mxcvpu_driver = { + .driver = { + .name = "mxc_vpu", + }, + .probe = vpu_dev_probe, + .remove = vpu_dev_remove, + .suspend = vpu_suspend, + .resume = vpu_resume, +}; + +static int __init vpu_init(void) +{ + int ret = platform_driver_register(&mxcvpu_driver); + + init_waitqueue_head(&vpu_queue); + + return ret; +} + +static void __exit vpu_exit(void) +{ + if (vpu_major > 0) { + device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + unregister_chrdev(vpu_major, "mxc_vpu"); + vpu_major = 0; + } + + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + + vpu_free_dma_buffer(&bitwork_mem); + vpu_free_dma_buffer(&pic_para_mem); + vpu_free_dma_buffer(&user_data_mem); + + clk_put(vpu_clk); + + platform_driver_unregister(&mxcvpu_driver); + return; +} + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX/MXC"); +MODULE_LICENSE("GPL"); + +module_init(vpu_init); +module_exit(vpu_exit); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 549b960667c8..9afd6e8b4f21 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2389,6 +2389,10 @@ source "drivers/video/omap2/Kconfig" source "drivers/video/backlight/Kconfig" source "drivers/video/display/Kconfig" +if ARCH_MXC +source "drivers/video/mxc/Kconfig" +endif + if VT source "drivers/video/console/Kconfig" endif diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 8b83129e209c..a3ae7aafe5b6 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_FB_KYRO) += kyro/ obj-$(CONFIG_FB_SAVAGE) += savage/ obj-$(CONFIG_FB_GEODE) += geode/ obj-$(CONFIG_FB_MBX) += mbx/ +obj-$(CONFIG_FB_MXC) += mxc/ obj-$(CONFIG_FB_NEOMAGIC) += neofb.o obj-$(CONFIG_FB_3DFX) += tdfxfb.o obj-$(CONFIG_FB_CONTROL) += controlfb.o diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig new file mode 100644 index 000000000000..e2b79faeaf35 --- /dev/null +++ b/drivers/video/mxc/Kconfig @@ -0,0 +1,111 @@ +config FB_MXC + tristate "MXC Framebuffer support" + depends on FB && (MXC_IPU || ARCH_MX21 || ARCH_MX27 || ARCH_MX25) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + default y + help + This is a framebuffer device for the MXC LCD Controller. + See for information on framebuffer + devices. + + If you plan to use the LCD display with your MXC system, say + Y here. + +config FB_MXC_SYNC_PANEL + depends on FB_MXC + tristate "Synchronous Panel Framebuffer" + default y + +config FB_MXC_EPSON_VGA_SYNC_PANEL + depends on FB_MXC_SYNC_PANEL + tristate "Epson VGA Panel" + default n + +config FB_MXC_TVOUT_TVE + tristate "MXC TVE TV Out Encoder" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 + +config FB_MXC_LDB + tristate "MXC LDB" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 + +config FB_MXC_CLAA_WVGA_SYNC_PANEL + depends on FB_MXC_SYNC_PANEL + tristate "CLAA WVGA Panel" + +config FB_MXC_SEIKO_WVGA_SYNC_PANEL + depends on FB_MXC_SYNC_PANEL + tristate "SEIKO WVGA Panel" + +config FB_MXC_SII902X + depends on FB_MXC_SYNC_PANEL + tristate "Si Image SII9022 DVI/HDMI Interface Chip" + +config FB_MXC_CH7026 + depends on FB_MXC_SYNC_PANEL + tristate "Chrontel CH7026 VGA Interface Chip" + +config FB_MXC_TVOUT_CH7024 + tristate "CH7024 TV Out Encoder" + depends on FB_MXC_SYNC_PANEL + +config FB_MXC_LOW_PWR_DISPLAY + bool "Low Power Display Refresh Mode" + depends on FB_MXC_SYNC_PANEL && MXC_FB_IRAM + default y + +config FB_MXC_INTERNAL_MEM + bool "Framebuffer in Internal RAM" + depends on FB_MXC_SYNC_PANEL && MXC_FB_IRAM + default y + +config FB_MXC_ASYNC_PANEL + depends on FB_MXC + bool "Asynchronous Panels" + default n + +menu "Asynchronous Panel Type" + depends on FB_MXC_ASYNC_PANEL && FB_MXC + +config FB_MXC_EPSON_PANEL + depends on FB_MXC_ASYNC_PANEL + default n + bool "Epson 176x220 Panel" + +endmenu + +config FB_MXC_EINK_PANEL + depends on FB_MXC + depends on DMA_ENGINE + select FB_DEFERRED_IO + tristate "E-Ink Panel Framebuffer" + +config FB_MXC_EINK_AUTO_UPDATE_MODE + bool "E-Ink Auto-update Mode Support" + default n + depends on FB_MXC_EINK_PANEL + +config FB_MXC_ELCDIF_FB + depends on FB && ARCH_MXC + tristate "Support MXC ELCDIF framebuffer" + +choice + prompt "Async Panel Interface Type" + depends on FB_MXC_ASYNC_PANEL && FB_MXC + default FB_MXC_ASYNC_PANEL_IFC_16_BIT + +config FB_MXC_ASYNC_PANEL_IFC_8_BIT + bool "8-bit Parallel Bus Interface" + +config FB_MXC_ASYNC_PANEL_IFC_16_BIT + bool "16-bit Parallel Bus Interface" + +config FB_MXC_ASYNC_PANEL_IFC_SERIAL + bool "Serial Bus Interface" + +endchoice diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile new file mode 100644 index 000000000000..723001a166a1 --- /dev/null +++ b/drivers/video/mxc/Makefile @@ -0,0 +1,26 @@ +obj-$(CONFIG_FB_MXC_TVOUT_TVE) += tve.o +obj-$(CONFIG_FB_MXC_SII902X) += mxcfb_sii902x.o +obj-$(CONFIG_FB_MXC_LDB) += ldb.o +obj-$(CONFIG_FB_MODE_HELPERS) += mxc_edid.o +ifeq ($(CONFIG_ARCH_MX21)$(CONFIG_ARCH_MX27)$(CONFIG_ARCH_MX25),y) + obj-$(CONFIG_FB_MXC_TVOUT) += fs453.o + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mx2fb.o mxcfb_modedb.o + obj-$(CONFIG_FB_MXC_EPSON_PANEL) += mx2fb_epson.o +else +ifeq ($(CONFIG_MXC_IPU_V1),y) + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxcfb.o mxcfb_modedb.o +else + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_ipuv3_fb.o +endif + obj-$(CONFIG_FB_MXC_EPSON_PANEL) += mxcfb_epson.o + obj-$(CONFIG_FB_MXC_EPSON_QVGA_PANEL) += mxcfb_epson_qvga.o + obj-$(CONFIG_FB_MXC_TOSHIBA_QVGA_PANEL) += mxcfb_toshiba_qvga.o + obj-$(CONFIG_FB_MXC_SHARP_128_PANEL) += mxcfb_sharp_128x128.o +endif +obj-$(CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL) += mxcfb_epson_vga.o +obj-$(CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL) += mxcfb_claa_wvga.o +obj-$(CONFIG_FB_MXC_SEIKO_WVGA_SYNC_PANEL) += mxcfb_seiko_wvga.o +obj-$(CONFIG_FB_MXC_TVOUT_CH7024) += ch7024.o +obj-$(CONFIG_FB_MXC_CH7026) += mxcfb_ch7026.o +obj-$(CONFIG_FB_MXC_EINK_PANEL) += mxc_epdc_fb.o +obj-$(CONFIG_FB_MXC_ELCDIF_FB) += mxc_elcdif_fb.o diff --git a/drivers/video/mxc/ch7024.c b/drivers/video/mxc/ch7024.c new file mode 100644 index 000000000000..32ca332dcc5d --- /dev/null +++ b/drivers/video/mxc/ch7024.c @@ -0,0 +1,866 @@ +/* + * Copyright 2007-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ch7024.c + * @brief Driver for CH7024 TV encoder + * + * @ingroup Framebuffer + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + * CH7024 registers + */ +#define CH7024_DEVID 0x00 +#define CH7024_REVID 0x01 +#define CH7024_PG 0x02 + +#define CH7024_RESET 0x03 +#define CH7024_POWER 0x04 +#define CH7024_TVHUE 0x05 +#define CH7024_TVSAT 0x06 +#define CH7024_TVCTA 0x07 +#define CH7024_TVBRI 0x08 +#define CH7024_TVSHARP 0x09 +#define CH7024_OUT_FMT 0x0A +#define CH7024_XTAL 0x0B +#define CH7024_IDF1 0x0C +#define CH7024_IDF2 0x0D +#define CH7024_SYNC 0x0E +#define CH7024_TVFILTER1 0x0F +#define CH7024_TVFILTER2 0x10 +#define CH7024_IN_TIMING1 0x11 +#define CH7024_IN_TIMING2 0x12 +#define CH7024_IN_TIMING3 0x13 +#define CH7024_IN_TIMING4 0x14 +#define CH7024_IN_TIMING5 0x15 +#define CH7024_IN_TIMING6 0x16 +#define CH7024_IN_TIMING7 0x17 +#define CH7024_IN_TIMING8 0x18 +#define CH7024_IN_TIMING9 0x19 +#define CH7024_IN_TIMING10 0x1A +#define CH7024_IN_TIMING11 0x1B +#define CH7024_ACIV 0x1C +#define CH7024_CLK_TREE 0x1D +#define CH7024_OUT_TIMING1 0x1E +#define CH7024_OUT_TIMING2 0x1F +#define CH7024_V_POS1 0x20 +#define CH7024_V_POS2 0x21 +#define CH7024_H_POS1 0x22 +#define CH7024_H_POS2 0x23 +#define CH7024_PCLK_A1 0x24 +#define CH7024_PCLK_A2 0x25 +#define CH7024_PCLK_A3 0x26 +#define CH7024_PCLK_A4 0x27 +#define CH7024_CLK_P1 0x28 +#define CH7024_CLK_P2 0x29 +#define CH7024_CLK_P3 0x2A +#define CH7024_CLK_N1 0x2B +#define CH7024_CLK_N2 0x2C +#define CH7024_CLK_N3 0x2D +#define CH7024_CLK_T 0x2E +#define CH7024_PLL1 0x2F +#define CH7024_PLL2 0x30 +#define CH7024_PLL3 0x31 +#define CH7024_SC_FREQ1 0x34 +#define CH7024_SC_FREQ2 0x35 +#define CH7024_SC_FREQ3 0x36 +#define CH7024_SC_FREQ4 0x37 +#define CH7024_DAC_TRIM 0x62 +#define CH7024_DATA_IO 0x63 +#define CH7024_ATT_DISP 0x7E + +/*! + * CH7024 register values + */ +/* video output formats */ +#define CH7024_VOS_NTSC_M 0x0 +#define CH7024_VOS_NTSC_J 0x1 +#define CH7024_VOS_NTSC_443 0x2 +#define CH7024_VOS_PAL_BDGHKI 0x3 +#define CH7024_VOS_PAL_M 0x4 +#define CH7024_VOS_PAL_N 0x5 +#define CH7024_VOS_PAL_NC 0x6 +#define CH7024_VOS_PAL_60 0x7 +/* crystal predefined */ +#define CH7024_XTAL_13MHZ 0x4 +#define CH7024_XTAL_26MHZ 0xB + +/* chip ID */ +#define CH7024_DEVICE_ID 0x45 + +/* clock source define */ +#define CLK_HIGH 0 +#define CLK_LOW 1 + +/* CH7024 presets structs */ +struct ch7024_clock { + u32 A; + u32 P; + u32 N; + u32 T; + u8 PLLN1; + u8 PLLN2; + u8 PLLN3; +}; + +struct ch7024_input_timing { + u32 HTI; + u32 VTI; + u32 HAI; + u32 VAI; + u32 HW; + u32 HO; + u32 VW; + u32 VO; + u32 VOS; +}; + +#define TVOUT_FMT_OFF 0 +#define TVOUT_FMT_NTSC 1 +#define TVOUT_FMT_PAL 2 + +static int enabled; /* enable power on or not */ +static int pm_status; /* status before suspend */ + +static struct i2c_client *ch7024_client; +static struct fb_info *ch7024_fbi; +static int ch7024_cur_mode; +static u32 detect_gpio; +static struct regulator *io_reg; +static struct regulator *core_reg; +static struct regulator *analog_reg; + +static void hp_detect_wq_handler(struct work_struct *); +DECLARE_DELAYED_WORK(ch7024_wq, hp_detect_wq_handler); + +static inline int ch7024_read_reg(u8 reg) +{ + return i2c_smbus_read_byte_data(ch7024_client, reg); +} + +static inline int ch7024_write_reg(u8 reg, u8 word) +{ + return i2c_smbus_write_byte_data(ch7024_client, reg, word); +} + +/** + * PAL B/D/G/H/K/I clock and timting structures + */ +static struct ch7024_clock ch7024_clk_pal = { + .A = 0x0, + .P = 0x36b00, + .N = 0x41eb00, + .T = 0x3f, + .PLLN1 = 0x0, + .PLLN2 = 0x1b, + .PLLN3 = 0x12, +}; + +static struct ch7024_input_timing ch7024_timing_pal = { + .HTI = 950, + .VTI = 560, + .HAI = 640, + .VAI = 480, + .HW = 60, + .HO = 250, + .VW = 40, + .VO = 40, + .VOS = CH7024_VOS_PAL_BDGHKI, +}; + +/** + * NTSC_M clock and timting structures + * TODO: change values to work well. + */ +static struct ch7024_clock ch7024_clk_ntsc = { + .A = 0x0, + .P = 0x2ac90, + .N = 0x36fc90, + .T = 0x3f, + .PLLN1 = 0x0, + .PLLN2 = 0x1b, + .PLLN3 = 0x12, +}; + +static struct ch7024_input_timing ch7024_timing_ntsc = { + .HTI = 801, + .VTI = 554, + .HAI = 640, + .VAI = 480, + .HW = 60, + .HO = 101, + .VW = 20, + .VO = 54, + .VOS = CH7024_VOS_NTSC_M, +}; + +static struct fb_videomode video_modes[] = { + { + /* NTSC TV output */ + "TV-NTSC", 60, 640, 480, 37594, + 0, 101, + 0, 54, + 60, 20, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* PAL TV output */ + "TV-PAL", 50, 640, 480, 37594, + 0, 250, + 0, 40, + 60, 40, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +/** + * ch7024_setup + * initial the CH7024 chipset by setting register + * @param: + * vos: output video format + * @return: + * 0 successful + * otherwise failed + */ +static int ch7024_setup(int vos) +{ + struct ch7024_input_timing *ch_timing; + struct ch7024_clock *ch_clk; +#ifdef DEBUG_CH7024 + int i, val; +#endif + + /* select output video format */ + if (vos == TVOUT_FMT_PAL) { + ch_timing = &ch7024_timing_pal; + ch_clk = &ch7024_clk_pal; + pr_debug("CH7024: change to PAL video\n"); + } else if (vos == TVOUT_FMT_NTSC) { + ch_timing = &ch7024_timing_ntsc; + ch_clk = &ch7024_clk_ntsc; + pr_debug("CH7024: change to NTSC video\n"); + } else { + + pr_debug("CH7024: no such video format.\n"); + return -EINVAL; + } + ch7024_write_reg(CH7024_RESET, 0x0); + ch7024_write_reg(CH7024_RESET, 0x3); + + ch7024_write_reg(CH7024_POWER, 0x0C); /* power on, disable DAC */ + ch7024_write_reg(CH7024_XTAL, CH7024_XTAL_26MHZ); + ch7024_write_reg(CH7024_SYNC, 0x0D); /* SLAVE mode, and TTL */ + ch7024_write_reg(CH7024_IDF1, 0x00); + ch7024_write_reg(CH7024_TVFILTER1, 0x00); /* set XCH=0 */ + ch7024_write_reg(CH7024_CLK_TREE, 0x9E); /* Invert input clk */ + + /* set input clock and divider */ + /* set PLL */ + ch7024_write_reg(CH7024_PLL1, ch_clk->PLLN1); + ch7024_write_reg(CH7024_PLL2, ch_clk->PLLN2); + ch7024_write_reg(CH7024_PLL3, ch_clk->PLLN3); + /* set A register */ + ch7024_write_reg(CH7024_PCLK_A1, (ch_clk->A >> 24) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A2, (ch_clk->A >> 16) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A3, (ch_clk->A >> 8) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A4, ch_clk->A & 0xFF); + /* set P register */ + ch7024_write_reg(CH7024_CLK_P1, (ch_clk->P >> 16) & 0xFF); + ch7024_write_reg(CH7024_CLK_P2, (ch_clk->P >> 8) & 0xFF); + ch7024_write_reg(CH7024_CLK_P3, ch_clk->P & 0xFF); + /* set N register */ + ch7024_write_reg(CH7024_CLK_N1, (ch_clk->N >> 16) & 0xFF); + ch7024_write_reg(CH7024_CLK_N2, (ch_clk->N >> 8) & 0xFF); + ch7024_write_reg(CH7024_CLK_N3, ch_clk->N & 0xFF); + /* set T register */ + ch7024_write_reg(CH7024_CLK_T, ch_clk->T & 0xFF); + + /* set sub-carrier frequency generation method */ + ch7024_write_reg(CH7024_ACIV, 0x00); /* ACIV = 0, automatical SCF */ + /* TV out pattern and DAC switch */ + ch7024_write_reg(CH7024_OUT_FMT, (0x10 | ch_timing->VOS) & 0xFF); + + /* input settings */ + /* input format, RGB666 */ + ch7024_write_reg(CH7024_IDF2, 0x02); + /* HAI/HTI VAI */ + ch7024_write_reg(CH7024_IN_TIMING1, ((ch_timing->HTI >> 5) & 0x38) | + ((ch_timing->HAI >> 8) & 0x07)); + ch7024_write_reg(CH7024_IN_TIMING2, ch_timing->HAI & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING8, ch_timing->VAI & 0xFF); + /* HTI VTI */ + ch7024_write_reg(CH7024_IN_TIMING3, ch_timing->HTI & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING9, ch_timing->VTI & 0xFF); + /* HW/HO(h) VW */ + ch7024_write_reg(CH7024_IN_TIMING4, ((ch_timing->HW >> 5) & 0x18) | + ((ch_timing->HO >> 8) & 0x7)); + ch7024_write_reg(CH7024_IN_TIMING6, ch_timing->HW & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING11, ch_timing->VW & 0x3F); + /* HO(l) VO/VAI/VTI */ + ch7024_write_reg(CH7024_IN_TIMING5, ch_timing->HO & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING7, ((ch_timing->VO >> 4) & 0x30) | + ((ch_timing->VTI >> 6) & 0x0C) | + ((ch_timing->VAI >> 8) & 0x03)); + ch7024_write_reg(CH7024_IN_TIMING10, ch_timing->VO & 0xFF); + + /* adjust the brightness */ + ch7024_write_reg(CH7024_TVBRI, 0x90); + + ch7024_write_reg(CH7024_OUT_TIMING1, 0x4); + ch7024_write_reg(CH7024_OUT_TIMING2, 0xe0); + + if (vos == TVOUT_FMT_PAL) { + ch7024_write_reg(CH7024_V_POS1, 0x03); + ch7024_write_reg(CH7024_V_POS2, 0x7d); + } else { + ch7024_write_reg(CH7024_V_POS1, 0x02); + ch7024_write_reg(CH7024_V_POS2, 0x7b); + } + + ch7024_write_reg(CH7024_POWER, 0x00); + +#ifdef DEBUG_CH7024 + for (i = 0; i < CH7024_SC_FREQ4; i++) { + + val = ch7024_read_reg(i); + pr_debug("CH7024, reg[0x%x] = %x\n", i, val); + } +#endif + return 0; +} + +/** + * ch7024_enable + * Enable the ch7024 Power to begin TV encoder + */ +static int ch7024_enable(void) +{ + int en = enabled; + + if (!enabled) { + regulator_enable(core_reg); + regulator_enable(io_reg); + regulator_enable(analog_reg); + msleep(200); + enabled = 1; + ch7024_write_reg(CH7024_POWER, 0x00); + pr_debug("CH7024 power on.\n"); + } + return en; +} + +/** + * ch7024_disable + * Disable the ch7024 Power to stop TV encoder + */ +static void ch7024_disable(void) +{ + if (enabled) { + enabled = 0; + ch7024_write_reg(CH7024_POWER, 0x0D); + regulator_disable(analog_reg); + regulator_disable(io_reg); + regulator_disable(core_reg); + pr_debug("CH7024 power off.\n"); + } +} + +static int ch7024_detect(void) +{ + int en; + int detect = 0; + + if (gpio_get_value(detect_gpio) == 1) { + set_irq_type(ch7024_client->irq, IRQF_TRIGGER_FALLING); + + en = ch7024_enable(); + + ch7024_write_reg(CH7024_DAC_TRIM, 0xB4); + msleep(50); + detect = ch7024_read_reg(CH7024_ATT_DISP) & 0x3; + ch7024_write_reg(CH7024_DAC_TRIM, 0x34); + + if (!en) + ch7024_disable(); + } else { + set_irq_type(ch7024_client->irq, IRQF_TRIGGER_RISING); + } + dev_dbg(&ch7024_client->dev, "detect = %d\n", detect); + return detect; +} + +static irqreturn_t hp_detect_handler(int irq, void *data) +{ + disable_irq(irq); + schedule_delayed_work(&ch7024_wq, 50); + + return IRQ_HANDLED; +} + +static void hp_detect_wq_handler(struct work_struct *work) +{ + int detect; + struct mxc_hw_event event = { HWE_PHONEJACK_PLUG, 0 }; + + detect = ch7024_detect(); + + enable_irq(ch7024_client->irq); + + sysfs_notify(&ch7024_client->dev.kobj, NULL, "headphone"); + + /* send hw event by netlink */ + event.args = detect; + hw_event_send(1, &event); +} + +int ch7024_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + if ((ch7024_fbi != NULL) || strcmp(fbi->fix.id, "DISP3 BG")) + break; + + ch7024_fbi = fbi; + fb_add_videomode(&video_modes[0], &ch7024_fbi->modelist); + fb_add_videomode(&video_modes[1], &ch7024_fbi->modelist); + break; + case FB_EVENT_MODE_CHANGE: + if (ch7024_fbi != fbi) + break; + + if (!fbi->mode) { + ch7024_disable(); + ch7024_cur_mode = TVOUT_FMT_OFF; + return 0; + } + + if (fb_mode_is_equal(fbi->mode, &video_modes[0])) { + ch7024_cur_mode = TVOUT_FMT_NTSC; + ch7024_enable(); + ch7024_setup(TVOUT_FMT_NTSC); + } else if (fb_mode_is_equal(fbi->mode, &video_modes[1])) { + ch7024_cur_mode = TVOUT_FMT_PAL; + ch7024_enable(); + ch7024_setup(TVOUT_FMT_PAL); + } else { + ch7024_disable(); + ch7024_cur_mode = TVOUT_FMT_OFF; + return 0; + } + break; + case FB_EVENT_BLANK: + if ((ch7024_fbi != fbi) || (ch7024_cur_mode == TVOUT_FMT_OFF)) + return 0; + + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + ch7024_enable(); + ch7024_setup(ch7024_cur_mode); + } else { + ch7024_disable(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = ch7024_fb_event, +}; + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + int detect; + + detect = ch7024_detect(); + + if (detect == 0) { + strcpy(buf, "none\n"); + } else if (detect == 1) { + strcpy(buf, "cvbs\n"); + } else { + strcpy(buf, "headset\n"); + } + + return strlen(buf); +} + +DRIVER_ATTR(headphone, 0644, show_headphone, NULL); + +static ssize_t show_brightness(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVBRI); + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_brightness(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int brightness = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + if (brightness > 255) + brightness = 255; + + ch7024_write_reg(CH7024_TVBRI, brightness); + + return count; +} + +DRIVER_ATTR(brightness, 0644, show_brightness, store_brightness); + +static ssize_t show_contrast(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVCTA); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_contrast(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int contrast = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + contrast /= 2; + if (contrast > 127) + contrast = 127; + + ch7024_write_reg(CH7024_TVCTA, contrast); + + return count; +} + +DRIVER_ATTR(contrast, 0644, show_contrast, store_contrast); + +static ssize_t show_hue(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVHUE); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_hue(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int hue = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + hue /= 2; + if (hue > 127) + hue = 127; + + ch7024_write_reg(CH7024_TVHUE, hue); + + return count; +} + +DRIVER_ATTR(hue, 0644, show_hue, store_hue); + +static ssize_t show_saturation(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVSAT); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_saturation(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int saturation = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + saturation /= 2; + if (saturation > 127) + saturation = 127; + + ch7024_write_reg(CH7024_TVSAT, saturation); + + return count; +} + +DRIVER_ATTR(saturation, 0644, show_saturation, store_saturation); + +static ssize_t show_sharpness(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVSHARP); + + reg *= 32; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_sharpness(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int sharpness = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + sharpness /= 32; /* Scale to 0 - 7 */ + if (sharpness > 7) + sharpness = 7; + + ch7024_write_reg(CH7024_TVSHARP, sharpness); + + return count; +} + +DRIVER_ATTR(sharpness, 0644, show_sharpness, store_sharpness); + +static int ch7024_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) +{ + int ret, i; + u32 id; + u32 irqtype; + struct mxc_tvout_platform_data *plat_data = client->dev.platform_data; + + ch7024_client = client; + + io_reg = regulator_get(&client->dev, plat_data->io_reg); + core_reg = regulator_get(&client->dev, plat_data->core_reg); + analog_reg = regulator_get(&client->dev, plat_data->analog_reg); + + regulator_enable(io_reg); + regulator_enable(core_reg); + regulator_enable(analog_reg); + msleep(200); + + id = ch7024_read_reg(CH7024_DEVID); + + regulator_disable(core_reg); + regulator_disable(io_reg); + regulator_disable(analog_reg); + + if (id < 0 || id != CH7024_DEVICE_ID) { + printk(KERN_ERR + "ch7024: TV encoder not present: id = %x\n", id); + return -ENODEV; + } + printk(KERN_ERR "ch7024: TV encoder present: id = %x\n", id); + + detect_gpio = plat_data->detect_line; + + if (client->irq > 0) { + if (ch7024_detect() == 0) + irqtype = IRQF_TRIGGER_RISING; + else + irqtype = IRQF_TRIGGER_FALLING; + + ret = request_irq(client->irq, hp_detect_handler, irqtype, + client->name, client); + if (ret < 0) + goto err0; + + ret = driver_create_file(&client->driver->driver, + &driver_attr_headphone); + if (ret < 0) + goto err1; + } + + ret = driver_create_file(&client->driver->driver, + &driver_attr_brightness); + if (ret) + goto err2; + + ret = driver_create_file(&client->driver->driver, + &driver_attr_contrast); + if (ret) + goto err3; + ret = driver_create_file(&client->driver->driver, &driver_attr_hue); + if (ret) + goto err4; + ret = driver_create_file(&client->driver->driver, + &driver_attr_saturation); + if (ret) + goto err5; + ret = driver_create_file(&client->driver->driver, + &driver_attr_sharpness); + if (ret) + goto err6; + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0) { + ch7024_fbi = registered_fb[i]; + break; + } + } + if (ch7024_fbi != NULL) { + fb_add_videomode(&video_modes[0], &ch7024_fbi->modelist); + fb_add_videomode(&video_modes[1], &ch7024_fbi->modelist); + } + fb_register_client(&nb); + + return 0; + err6: + driver_remove_file(&client->driver->driver, &driver_attr_saturation); + err5: + driver_remove_file(&client->driver->driver, &driver_attr_hue); + err4: + driver_remove_file(&client->driver->driver, &driver_attr_contrast); + err3: + driver_remove_file(&client->driver->driver, &driver_attr_brightness); + err2: + driver_remove_file(&client->driver->driver, &driver_attr_headphone); + err1: + free_irq(client->irq, client); + err0: + return ret; +} + +static int ch7024_remove(struct i2c_client *client) +{ + free_irq(client->irq, client); + + regulator_put(io_reg); + regulator_put(core_reg); + regulator_put(analog_reg); + + driver_remove_file(&client->driver->driver, &driver_attr_headphone); + driver_remove_file(&client->driver->driver, &driver_attr_brightness); + driver_remove_file(&client->driver->driver, &driver_attr_contrast); + driver_remove_file(&client->driver->driver, &driver_attr_hue); + driver_remove_file(&client->driver->driver, &driver_attr_saturation); + driver_remove_file(&client->driver->driver, &driver_attr_sharpness); + + fb_unregister_client(&nb); + + ch7024_client = 0; + + return 0; +} + +#ifdef CONFIG_PM +/*! + * PM suspend/resume routing + */ +static int ch7024_suspend(struct i2c_client *client, pm_message_t state) +{ + pr_debug("Ch7024 suspend routing..\n"); + if (enabled) { + ch7024_disable(); + pm_status = 1; + } else { + pm_status = 0; + } + return 0; +} + +static int ch7024_resume(struct i2c_client *client) +{ + pr_debug("Ch7024 resume routing..\n"); + if (pm_status) { + ch7024_enable(); + ch7024_setup(ch7024_cur_mode); + } + return 0; +} +#else +#define ch7024_suspend NULL +#define ch7024_resume NULL +#endif + +static const struct i2c_device_id ch7024_id[] = { + { "ch7024", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ch7024_id); + +static struct i2c_driver ch7024_driver = { + .driver = { + .name = "ch7024", + }, + .probe = ch7024_probe, + .remove = ch7024_remove, + .suspend = ch7024_suspend, + .resume = ch7024_resume, + .id_table = ch7024_id, +}; + +static int __init ch7024_init(void) +{ + return i2c_add_driver(&ch7024_driver); +} + +static void __exit ch7024_exit(void) +{ + i2c_del_driver(&ch7024_driver); +} + +module_init(ch7024_init); +module_exit(ch7024_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CH7024 TV encoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/elcdif_regs.h b/drivers/video/mxc/elcdif_regs.h new file mode 100644 index 000000000000..42e377de6751 --- /dev/null +++ b/drivers/video/mxc/elcdif_regs.h @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2010-2011 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +/* + * Based on arch/arm/mach-mx28/include/mach/regs-lcdif.h. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#ifndef __ELCDIF_REGS_INCLUDED_ +#define __ELCDIF_REGS_INCLUDED_ + +#define HW_ELCDIF_CTRL (0x00000000) +#define HW_ELCDIF_CTRL_SET (0x00000004) +#define HW_ELCDIF_CTRL_CLR (0x00000008) +#define HW_ELCDIF_CTRL_TOG (0x0000000c) + +#define BM_ELCDIF_CTRL_SFTRST 0x80000000 +#define BM_ELCDIF_CTRL_CLKGATE 0x40000000 +#define BM_ELCDIF_CTRL_YCBCR422_INPUT 0x20000000 +#define BM_ELCDIF_CTRL_READ_WRITEB 0x10000000 +#define BM_ELCDIF_CTRL_WAIT_FOR_VSYNC_EDGE 0x08000000 +#define BM_ELCDIF_CTRL_DATA_SHIFT_DIR 0x04000000 +#define BV_ELCDIF_CTRL_DATA_SHIFT_DIR__TXDATA_SHIFT_LEFT 0x0 +#define BV_ELCDIF_CTRL_DATA_SHIFT_DIR__TXDATA_SHIFT_RIGHT 0x1 +#define BP_ELCDIF_CTRL_SHIFT_NUM_BITS 21 +#define BM_ELCDIF_CTRL_SHIFT_NUM_BITS 0x03E00000 +#define BF_ELCDIF_CTRL_SHIFT_NUM_BITS(v) \ + (((v) << 21) & BM_ELCDIF_CTRL_SHIFT_NUM_BITS) +#define BM_ELCDIF_CTRL_DVI_MODE 0x00100000 +#define BM_ELCDIF_CTRL_BYPASS_COUNT 0x00080000 +#define BM_ELCDIF_CTRL_VSYNC_MODE 0x00040000 +#define BM_ELCDIF_CTRL_DOTCLK_MODE 0x00020000 +#define BM_ELCDIF_CTRL_DATA_SELECT 0x00010000 +#define BV_ELCDIF_CTRL_DATA_SELECT__CMD_MODE 0x0 +#define BV_ELCDIF_CTRL_DATA_SELECT__DATA_MODE 0x1 +#define BP_ELCDIF_CTRL_INPUT_DATA_SWIZZLE 14 +#define BM_ELCDIF_CTRL_INPUT_DATA_SWIZZLE 0x0000C000 +#define BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(v) \ + (((v) << 14) & BM_ELCDIF_CTRL_INPUT_DATA_SWIZZLE) +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__NO_SWAP 0x0 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__LITTLE_ENDIAN 0x0 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__BIG_ENDIAN_SWAP 0x1 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__SWAP_ALL_BYTES 0x1 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__HWD_SWAP 0x2 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__HWD_BYTE_SWAP 0x3 +#define BP_ELCDIF_CTRL_CSC_DATA_SWIZZLE 12 +#define BM_ELCDIF_CTRL_CSC_DATA_SWIZZLE 0x00003000 +#define BF_ELCDIF_CTRL_CSC_DATA_SWIZZLE(v) \ + (((v) << 12) & BM_ELCDIF_CTRL_CSC_DATA_SWIZZLE) +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__NO_SWAP 0x0 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__LITTLE_ENDIAN 0x0 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__BIG_ENDIAN_SWAP 0x1 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__SWAP_ALL_BYTES 0x1 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__HWD_SWAP 0x2 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__HWD_BYTE_SWAP 0x3 +#define BP_ELCDIF_CTRL_LCD_DATABUS_WIDTH 10 +#define BM_ELCDIF_CTRL_LCD_DATABUS_WIDTH 0x00000C00 +#define BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(v) \ + (((v) << 10) & BM_ELCDIF_CTRL_LCD_DATABUS_WIDTH) +#define BV_ELCDIF_CTRL_LCD_DATABUS_WIDTH__16_BIT 0x0 +#define BV_ELCDIF_CTRL_LCD_DATABUS_WIDTH__8_BIT 0x1 +#define BV_ELCDIF_CTRL_LCD_DATABUS_WIDTH__18_BIT 0x2 +#define BV_ELCDIF_CTRL_LCD_DATABUS_WIDTH__24_BIT 0x3 +#define BP_ELCDIF_CTRL_WORD_LENGTH 8 +#define BM_ELCDIF_CTRL_WORD_LENGTH 0x00000300 +#define BF_ELCDIF_CTRL_WORD_LENGTH(v) \ + (((v) << 8) & BM_ELCDIF_CTRL_WORD_LENGTH) +#define BV_ELCDIF_CTRL_WORD_LENGTH__16_BIT 0x0 +#define BV_ELCDIF_CTRL_WORD_LENGTH__8_BIT 0x1 +#define BV_ELCDIF_CTRL_WORD_LENGTH__18_BIT 0x2 +#define BV_ELCDIF_CTRL_WORD_LENGTH__24_BIT 0x3 +#define BM_ELCDIF_CTRL_RGB_TO_YCBCR422_CSC 0x00000080 +#define BM_ELCDIF_CTRL_ENABLE_PXP_HANDSHAKE 0x00000040 +#define BM_ELCDIF_CTRL_ELCDIF_MASTER 0x00000020 +#define BM_ELCDIF_CTRL_RSRVD0 0x00000010 +#define BM_ELCDIF_CTRL_DATA_FORMAT_16_BIT 0x00000008 +#define BM_ELCDIF_CTRL_DATA_FORMAT_18_BIT 0x00000004 +#define BV_ELCDIF_CTRL_DATA_FORMAT_18_BIT__LOWER_18_BITS_VALID 0x0 +#define BV_ELCDIF_CTRL_DATA_FORMAT_18_BIT__UPPER_18_BITS_VALID 0x1 +#define BM_ELCDIF_CTRL_DATA_FORMAT_24_BIT 0x00000002 +#define BV_ELCDIF_CTRL_DATA_FORMAT_24_BIT__ALL_24_BITS_VALID 0x0 +#define BV_ELCDIF_CTRL_DATA_FORMAT_24_BIT__DROP_UPPER_2_BITS_PER_BYTE 0x1 +#define BM_ELCDIF_CTRL_RUN 0x00000001 + +#define HW_ELCDIF_CTRL1 (0x00000010) +#define HW_ELCDIF_CTRL1_SET (0x00000014) +#define HW_ELCDIF_CTRL1_CLR (0x00000018) +#define HW_ELCDIF_CTRL1_TOG (0x0000001c) + +#define BP_ELCDIF_CTRL1_RSRVD1 28 +#define BM_ELCDIF_CTRL1_RSRVD1 0xF0000000 +#define BF_ELCDIF_CTRL1_RSRVD1(v) \ + (((v) << 28) & BM_ELCDIF_CTRL1_RSRVD1) +#define BM_ELCDIF_CTRL1_COMBINE_MPU_WR_STRB 0x08000000 +#define BM_ELCDIF_CTRL1_BM_ERROR_IRQ_EN 0x04000000 +#define BM_ELCDIF_CTRL1_BM_ERROR_IRQ 0x02000000 +#define BV_ELCDIF_CTRL1_BM_ERROR_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_BM_ERROR_IRQ__REQUEST 0x1 +#define BM_ELCDIF_CTRL1_RECOVER_ON_UNDERFLOW 0x01000000 +#define BM_ELCDIF_CTRL1_INTERLACE_FIELDS 0x00800000 +#define BM_ELCDIF_CTRL1_START_INTERLACE_FROM_SECOND_FIELD 0x00400000 +#define BM_ELCDIF_CTRL1_FIFO_CLEAR 0x00200000 +#define BM_ELCDIF_CTRL1_IRQ_ON_ALTERNATE_FIELDS 0x00100000 +#define BP_ELCDIF_CTRL1_BYTE_PACKING_FORMAT 16 +#define BM_ELCDIF_CTRL1_BYTE_PACKING_FORMAT 0x000F0000 +#define BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(v) \ + (((v) << 16) & BM_ELCDIF_CTRL1_BYTE_PACKING_FORMAT) +#define BM_ELCDIF_CTRL1_OVERFLOW_IRQ_EN 0x00008000 +#define BM_ELCDIF_CTRL1_UNDERFLOW_IRQ_EN 0x00004000 +#define BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ_EN 0x00002000 +#define BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ_EN 0x00001000 +#define BM_ELCDIF_CTRL1_OVERFLOW_IRQ 0x00000800 +#define BV_ELCDIF_CTRL1_OVERFLOW_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_OVERFLOW_IRQ__REQUEST 0x1 +#define BM_ELCDIF_CTRL1_UNDERFLOW_IRQ 0x00000400 +#define BV_ELCDIF_CTRL1_UNDERFLOW_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_UNDERFLOW_IRQ__REQUEST 0x1 +#define BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ 0x00000200 +#define BV_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ__REQUEST 0x1 +#define BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ 0x00000100 +#define BV_ELCDIF_CTRL1_VSYNC_EDGE_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_VSYNC_EDGE_IRQ__REQUEST 0x1 +#define BP_ELCDIF_CTRL1_RSRVD0 3 +#define BM_ELCDIF_CTRL1_RSRVD0 0x000000F8 +#define BF_ELCDIF_CTRL1_RSRVD0(v) \ + (((v) << 3) & BM_ELCDIF_CTRL1_RSRVD0) +#define BM_ELCDIF_CTRL1_BUSY_ENABLE 0x00000004 +#define BV_ELCDIF_CTRL1_BUSY_ENABLE__BUSY_DISABLED 0x0 +#define BV_ELCDIF_CTRL1_BUSY_ENABLE__BUSY_ENABLED 0x1 +#define BM_ELCDIF_CTRL1_MODE86 0x00000002 +#define BV_ELCDIF_CTRL1_MODE86__8080_MODE 0x0 +#define BV_ELCDIF_CTRL1_MODE86__6800_MODE 0x1 +#define BM_ELCDIF_CTRL1_RESET 0x00000001 +#define BV_ELCDIF_CTRL1_RESET__LCDRESET_LOW 0x0 +#define BV_ELCDIF_CTRL1_RESET__LCDRESET_HIGH 0x1 + +#define HW_ELCDIF_CTRL2 (0x00000020) +#define HW_ELCDIF_CTRL2_SET (0x00000024) +#define HW_ELCDIF_CTRL2_CLR (0x00000028) +#define HW_ELCDIF_CTRL2_TOG (0x0000002c) + +#define BP_ELCDIF_CTRL2_RSRVD5 24 +#define BM_ELCDIF_CTRL2_RSRVD5 0xFF000000 +#define BF_ELCDIF_CTRL2_RSRVD5(v) \ + (((v) << 24) & BM_ELCDIF_CTRL2_RSRVD5) +#define BP_ELCDIF_CTRL2_OUTSTANDING_REQS 21 +#define BM_ELCDIF_CTRL2_OUTSTANDING_REQS 0x00E00000 +#define BF_ELCDIF_CTRL2_OUTSTANDING_REQS(v) \ + (((v) << 21) & BM_ELCDIF_CTRL2_OUTSTANDING_REQS) +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_1 0x0 +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_2 0x1 +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_4 0x2 +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_8 0x3 +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_16 0x4 +#define BM_ELCDIF_CTRL2_BURST_LEN_8 0x00100000 +#define BM_ELCDIF_CTRL2_RSRVD4 0x00080000 +#define BP_ELCDIF_CTRL2_ODD_LINE_PATTERN 16 +#define BM_ELCDIF_CTRL2_ODD_LINE_PATTERN 0x00070000 +#define BF_ELCDIF_CTRL2_ODD_LINE_PATTERN(v) \ + (((v) << 16) & BM_ELCDIF_CTRL2_ODD_LINE_PATTERN) +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__RGB 0x0 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__RBG 0x1 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__GBR 0x2 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__GRB 0x3 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__BRG 0x4 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__BGR 0x5 +#define BM_ELCDIF_CTRL2_RSRVD3 0x00008000 +#define BP_ELCDIF_CTRL2_EVEN_LINE_PATTERN 12 +#define BM_ELCDIF_CTRL2_EVEN_LINE_PATTERN 0x00007000 +#define BF_ELCDIF_CTRL2_EVEN_LINE_PATTERN(v) \ + (((v) << 12) & BM_ELCDIF_CTRL2_EVEN_LINE_PATTERN) +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__RGB 0x0 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__RBG 0x1 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__GBR 0x2 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__GRB 0x3 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__BRG 0x4 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__BGR 0x5 +#define BM_ELCDIF_CTRL2_RSRVD2 0x00000800 +#define BM_ELCDIF_CTRL2_READ_PACK_DIR 0x00000400 +#define BM_ELCDIF_CTRL2_READ_MODE_OUTPUT_IN_RGB_FORMAT 0x00000200 +#define BM_ELCDIF_CTRL2_READ_MODE_6_BIT_INPUT 0x00000100 +#define BM_ELCDIF_CTRL2_RSRVD1 0x00000080 +#define BP_ELCDIF_CTRL2_READ_MODE_NUM_PACKED_SUBWORDS 4 +#define BM_ELCDIF_CTRL2_READ_MODE_NUM_PACKED_SUBWORDS 0x00000070 +#define BF_ELCDIF_CTRL2_READ_MODE_NUM_PACKED_SUBWORDS(v) \ + (((v) << 4) & BM_ELCDIF_CTRL2_READ_MODE_NUM_PACKED_SUBWORDS) +#define BP_ELCDIF_CTRL2_INITIAL_DUMMY_READ 1 +#define BM_ELCDIF_CTRL2_INITIAL_DUMMY_READ 0x0000000E +#define BF_ELCDIF_CTRL2_INITIAL_DUMMY_READ(v) \ + (((v) << 1) & BM_ELCDIF_CTRL2_INITIAL_DUMMY_READ) +#define BM_ELCDIF_CTRL2_RSRVD0 0x00000001 + +#define HW_ELCDIF_TRANSFER_COUNT (0x00000030) + +#define BP_ELCDIF_TRANSFER_COUNT_V_COUNT 16 +#define BM_ELCDIF_TRANSFER_COUNT_V_COUNT 0xFFFF0000 +#define BF_ELCDIF_TRANSFER_COUNT_V_COUNT(v) \ + (((v) << 16) & BM_ELCDIF_TRANSFER_COUNT_V_COUNT) +#define BP_ELCDIF_TRANSFER_COUNT_H_COUNT 0 +#define BM_ELCDIF_TRANSFER_COUNT_H_COUNT 0x0000FFFF +#define BF_ELCDIF_TRANSFER_COUNT_H_COUNT(v) \ + (((v) << 0) & BM_ELCDIF_TRANSFER_COUNT_H_COUNT) + +#define HW_ELCDIF_CUR_BUF (0x00000040) + +#define BP_ELCDIF_CUR_BUF_ADDR 0 +#define BM_ELCDIF_CUR_BUF_ADDR 0xFFFFFFFF +#define BF_ELCDIF_CUR_BUF_ADDR(v) (v) + +#define HW_ELCDIF_NEXT_BUF (0x00000050) + +#define BP_ELCDIF_NEXT_BUF_ADDR 0 +#define BM_ELCDIF_NEXT_BUF_ADDR 0xFFFFFFFF +#define BF_ELCDIF_NEXT_BUF_ADDR(v) (v) + +#define HW_ELCDIF_TIMING (0x00000060) + +#define BP_ELCDIF_TIMING_CMD_HOLD 24 +#define BM_ELCDIF_TIMING_CMD_HOLD 0xFF000000 +#define BF_ELCDIF_TIMING_CMD_HOLD(v) \ + (((v) << 24) & BM_ELCDIF_TIMING_CMD_HOLD) +#define BP_ELCDIF_TIMING_CMD_SETUP 16 +#define BM_ELCDIF_TIMING_CMD_SETUP 0x00FF0000 +#define BF_ELCDIF_TIMING_CMD_SETUP(v) \ + (((v) << 16) & BM_ELCDIF_TIMING_CMD_SETUP) +#define BP_ELCDIF_TIMING_DATA_HOLD 8 +#define BM_ELCDIF_TIMING_DATA_HOLD 0x0000FF00 +#define BF_ELCDIF_TIMING_DATA_HOLD(v) \ + (((v) << 8) & BM_ELCDIF_TIMING_DATA_HOLD) +#define BP_ELCDIF_TIMING_DATA_SETUP 0 +#define BM_ELCDIF_TIMING_DATA_SETUP 0x000000FF +#define BF_ELCDIF_TIMING_DATA_SETUP(v) \ + (((v) << 0) & BM_ELCDIF_TIMING_DATA_SETUP) + +#define HW_ELCDIF_VDCTRL0 (0x00000070) +#define HW_ELCDIF_VDCTRL0_SET (0x00000074) +#define HW_ELCDIF_VDCTRL0_CLR (0x00000078) +#define HW_ELCDIF_VDCTRL0_TOG (0x0000007c) + +#define BP_ELCDIF_VDCTRL0_RSRVD2 30 +#define BM_ELCDIF_VDCTRL0_RSRVD2 0xC0000000 +#define BF_ELCDIF_VDCTRL0_RSRVD2(v) \ + (((v) << 30) & BM_ELCDIF_VDCTRL0_RSRVD2) +#define BM_ELCDIF_VDCTRL0_VSYNC_OEB 0x20000000 +#define BV_ELCDIF_VDCTRL0_VSYNC_OEB__VSYNC_OUTPUT 0x0 +#define BV_ELCDIF_VDCTRL0_VSYNC_OEB__VSYNC_INPUT 0x1 +#define BM_ELCDIF_VDCTRL0_ENABLE_PRESENT 0x10000000 +#define BM_ELCDIF_VDCTRL0_VSYNC_POL 0x08000000 +#define BM_ELCDIF_VDCTRL0_HSYNC_POL 0x04000000 +#define BM_ELCDIF_VDCTRL0_DOTCLK_POL 0x02000000 +#define BM_ELCDIF_VDCTRL0_ENABLE_POL 0x01000000 +#define BP_ELCDIF_VDCTRL0_RSRVD1 22 +#define BM_ELCDIF_VDCTRL0_RSRVD1 0x00C00000 +#define BF_ELCDIF_VDCTRL0_RSRVD1(v) \ + (((v) << 22) & BM_ELCDIF_VDCTRL0_RSRVD1) +#define BM_ELCDIF_VDCTRL0_VSYNC_PERIOD_UNIT 0x00200000 +#define BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT 0x00100000 +#define BM_ELCDIF_VDCTRL0_HALF_LINE 0x00080000 +#define BM_ELCDIF_VDCTRL0_HALF_LINE_MODE 0x00040000 +#define BP_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH 0 +#define BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH 0x0003FFFF +#define BF_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH(v) \ + (((v) << 0) & BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH) + +#define HW_ELCDIF_VDCTRL1 (0x00000080) + +#define BP_ELCDIF_VDCTRL1_VSYNC_PERIOD 0 +#define BM_ELCDIF_VDCTRL1_VSYNC_PERIOD 0xFFFFFFFF +#define BF_ELCDIF_VDCTRL1_VSYNC_PERIOD(v) (v) + +#define HW_ELCDIF_VDCTRL2 (0x00000090) + +#define BP_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH 18 +#define BM_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH 0xFFFC0000 +#define BF_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH(v) \ + (((v) << 18) & BM_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH) +#define BP_ELCDIF_VDCTRL2_HSYNC_PERIOD 0 +#define BM_ELCDIF_VDCTRL2_HSYNC_PERIOD 0x0003FFFF +#define BF_ELCDIF_VDCTRL2_HSYNC_PERIOD(v) \ + (((v) << 0) & BM_ELCDIF_VDCTRL2_HSYNC_PERIOD) + +#define HW_ELCDIF_VDCTRL3 (0x000000a0) + +#define BP_ELCDIF_VDCTRL3_RSRVD0 30 +#define BM_ELCDIF_VDCTRL3_RSRVD0 0xC0000000 +#define BF_ELCDIF_VDCTRL3_RSRVD0(v) \ + (((v) << 30) & BM_ELCDIF_VDCTRL3_RSRVD0) +#define BM_ELCDIF_VDCTRL3_MUX_SYNC_SIGNALS 0x20000000 +#define BM_ELCDIF_VDCTRL3_VSYNC_ONLY 0x10000000 +#define BP_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT 16 +#define BM_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT 0x0FFF0000 +#define BF_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT(v) \ + (((v) << 16) & BM_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT) +#define BP_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT 0 +#define BM_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT 0x0000FFFF +#define BF_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT(v) \ + (((v) << 0) & BM_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT) + +#define HW_ELCDIF_VDCTRL4 (0x000000b0) + +#define BP_ELCDIF_VDCTRL4_DOTCLK_DLY_SEL 29 +#define BM_ELCDIF_VDCTRL4_DOTCLK_DLY_SEL 0xE0000000 +#define BF_ELCDIF_VDCTRL4_DOTCLK_DLY_SEL(v) \ + (((v) << 29) & BM_ELCDIF_VDCTRL4_DOTCLK_DLY_SEL) +#define BP_ELCDIF_VDCTRL4_RSRVD0 19 +#define BM_ELCDIF_VDCTRL4_RSRVD0 0x1FF80000 +#define BF_ELCDIF_VDCTRL4_RSRVD0(v) \ + (((v) << 19) & BM_ELCDIF_VDCTRL4_RSRVD0) +#define BM_ELCDIF_VDCTRL4_SYNC_SIGNALS_ON 0x00040000 +#define BP_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT 0 +#define BM_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT 0x0003FFFF +#define BF_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT(v) \ + (((v) << 0) & BM_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT) + +#define HW_ELCDIF_DVICTRL0 (0x000000c0) + +#define BP_ELCDIF_DVICTRL0_RSRVD1 28 +#define BM_ELCDIF_DVICTRL0_RSRVD1 0xF0000000 +#define BF_ELCDIF_DVICTRL0_RSRVD1(v) \ + (((v) << 28) & BM_ELCDIF_DVICTRL0_RSRVD1) +#define BP_ELCDIF_DVICTRL0_H_ACTIVE_CNT 16 +#define BM_ELCDIF_DVICTRL0_H_ACTIVE_CNT 0x0FFF0000 +#define BF_ELCDIF_DVICTRL0_H_ACTIVE_CNT(v) \ + (((v) << 16) & BM_ELCDIF_DVICTRL0_H_ACTIVE_CNT) +#define BP_ELCDIF_DVICTRL0_RSRVD0 12 +#define BM_ELCDIF_DVICTRL0_RSRVD0 0x0000F000 +#define BF_ELCDIF_DVICTRL0_RSRVD0(v) \ + (((v) << 12) & BM_ELCDIF_DVICTRL0_RSRVD0) +#define BP_ELCDIF_DVICTRL0_H_BLANKING_CNT 0 +#define BM_ELCDIF_DVICTRL0_H_BLANKING_CNT 0x00000FFF +#define BF_ELCDIF_DVICTRL0_H_BLANKING_CNT(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL0_H_BLANKING_CNT) + +#define HW_ELCDIF_DVICTRL1 (0x000000d0) + +#define BP_ELCDIF_DVICTRL1_RSRVD0 30 +#define BM_ELCDIF_DVICTRL1_RSRVD0 0xC0000000 +#define BF_ELCDIF_DVICTRL1_RSRVD0(v) \ + (((v) << 30) & BM_ELCDIF_DVICTRL1_RSRVD0) +#define BP_ELCDIF_DVICTRL1_F1_START_LINE 20 +#define BM_ELCDIF_DVICTRL1_F1_START_LINE 0x3FF00000 +#define BF_ELCDIF_DVICTRL1_F1_START_LINE(v) \ + (((v) << 20) & BM_ELCDIF_DVICTRL1_F1_START_LINE) +#define BP_ELCDIF_DVICTRL1_F1_END_LINE 10 +#define BM_ELCDIF_DVICTRL1_F1_END_LINE 0x000FFC00 +#define BF_ELCDIF_DVICTRL1_F1_END_LINE(v) \ + (((v) << 10) & BM_ELCDIF_DVICTRL1_F1_END_LINE) +#define BP_ELCDIF_DVICTRL1_F2_START_LINE 0 +#define BM_ELCDIF_DVICTRL1_F2_START_LINE 0x000003FF +#define BF_ELCDIF_DVICTRL1_F2_START_LINE(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL1_F2_START_LINE) + +#define HW_ELCDIF_DVICTRL2 (0x000000e0) + +#define BP_ELCDIF_DVICTRL2_RSRVD0 30 +#define BM_ELCDIF_DVICTRL2_RSRVD0 0xC0000000 +#define BF_ELCDIF_DVICTRL2_RSRVD0(v) \ + (((v) << 30) & BM_ELCDIF_DVICTRL2_RSRVD0) +#define BP_ELCDIF_DVICTRL2_F2_END_LINE 20 +#define BM_ELCDIF_DVICTRL2_F2_END_LINE 0x3FF00000 +#define BF_ELCDIF_DVICTRL2_F2_END_LINE(v) \ + (((v) << 20) & BM_ELCDIF_DVICTRL2_F2_END_LINE) +#define BP_ELCDIF_DVICTRL2_V1_BLANK_START_LINE 10 +#define BM_ELCDIF_DVICTRL2_V1_BLANK_START_LINE 0x000FFC00 +#define BF_ELCDIF_DVICTRL2_V1_BLANK_START_LINE(v) \ + (((v) << 10) & BM_ELCDIF_DVICTRL2_V1_BLANK_START_LINE) +#define BP_ELCDIF_DVICTRL2_V1_BLANK_END_LINE 0 +#define BM_ELCDIF_DVICTRL2_V1_BLANK_END_LINE 0x000003FF +#define BF_ELCDIF_DVICTRL2_V1_BLANK_END_LINE(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL2_V1_BLANK_END_LINE) + +#define HW_ELCDIF_DVICTRL3 (0x000000f0) + +#define BP_ELCDIF_DVICTRL3_RSRVD0 30 +#define BM_ELCDIF_DVICTRL3_RSRVD0 0xC0000000 +#define BF_ELCDIF_DVICTRL3_RSRVD0(v) \ + (((v) << 30) & BM_ELCDIF_DVICTRL3_RSRVD0) +#define BP_ELCDIF_DVICTRL3_V2_BLANK_START_LINE 20 +#define BM_ELCDIF_DVICTRL3_V2_BLANK_START_LINE 0x3FF00000 +#define BF_ELCDIF_DVICTRL3_V2_BLANK_START_LINE(v) \ + (((v) << 20) & BM_ELCDIF_DVICTRL3_V2_BLANK_START_LINE) +#define BP_ELCDIF_DVICTRL3_V2_BLANK_END_LINE 10 +#define BM_ELCDIF_DVICTRL3_V2_BLANK_END_LINE 0x000FFC00 +#define BF_ELCDIF_DVICTRL3_V2_BLANK_END_LINE(v) \ + (((v) << 10) & BM_ELCDIF_DVICTRL3_V2_BLANK_END_LINE) +#define BP_ELCDIF_DVICTRL3_V_LINES_CNT 0 +#define BM_ELCDIF_DVICTRL3_V_LINES_CNT 0x000003FF +#define BF_ELCDIF_DVICTRL3_V_LINES_CNT(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL3_V_LINES_CNT) + +#define HW_ELCDIF_DVICTRL4 (0x00000100) + +#define BP_ELCDIF_DVICTRL4_Y_FILL_VALUE 24 +#define BM_ELCDIF_DVICTRL4_Y_FILL_VALUE 0xFF000000 +#define BF_ELCDIF_DVICTRL4_Y_FILL_VALUE(v) \ + (((v) << 24) & BM_ELCDIF_DVICTRL4_Y_FILL_VALUE) +#define BP_ELCDIF_DVICTRL4_CB_FILL_VALUE 16 +#define BM_ELCDIF_DVICTRL4_CB_FILL_VALUE 0x00FF0000 +#define BF_ELCDIF_DVICTRL4_CB_FILL_VALUE(v) \ + (((v) << 16) & BM_ELCDIF_DVICTRL4_CB_FILL_VALUE) +#define BP_ELCDIF_DVICTRL4_CR_FILL_VALUE 8 +#define BM_ELCDIF_DVICTRL4_CR_FILL_VALUE 0x0000FF00 +#define BF_ELCDIF_DVICTRL4_CR_FILL_VALUE(v) \ + (((v) << 8) & BM_ELCDIF_DVICTRL4_CR_FILL_VALUE) +#define BP_ELCDIF_DVICTRL4_H_FILL_CNT 0 +#define BM_ELCDIF_DVICTRL4_H_FILL_CNT 0x000000FF +#define BF_ELCDIF_DVICTRL4_H_FILL_CNT(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL4_H_FILL_CNT) + +#define HW_ELCDIF_CSC_COEFF0 (0x00000110) + +#define BP_ELCDIF_CSC_COEFF0_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF0_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF0_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF0_RSRVD1) +#define BP_ELCDIF_CSC_COEFF0_C0 16 +#define BM_ELCDIF_CSC_COEFF0_C0 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF0_C0(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF0_C0) +#define BP_ELCDIF_CSC_COEFF0_RSRVD0 2 +#define BM_ELCDIF_CSC_COEFF0_RSRVD0 0x0000FFFC +#define BF_ELCDIF_CSC_COEFF0_RSRVD0(v) \ + (((v) << 2) & BM_ELCDIF_CSC_COEFF0_RSRVD0) +#define BP_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER 0 +#define BM_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER 0x00000003 +#define BF_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER) +#define BV_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER__SAMPLE_AND_HOLD 0x0 +#define BV_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER__RSRVD 0x1 +#define BV_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER__INTERSTITIAL 0x2 +#define BV_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER__COSITED 0x3 + +#define HW_ELCDIF_CSC_COEFF1 (0x00000120) + +#define BP_ELCDIF_CSC_COEFF1_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF1_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF1_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF1_RSRVD1) +#define BP_ELCDIF_CSC_COEFF1_C2 16 +#define BM_ELCDIF_CSC_COEFF1_C2 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF1_C2(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF1_C2) +#define BP_ELCDIF_CSC_COEFF1_RSRVD0 10 +#define BM_ELCDIF_CSC_COEFF1_RSRVD0 0x0000FC00 +#define BF_ELCDIF_CSC_COEFF1_RSRVD0(v) \ + (((v) << 10) & BM_ELCDIF_CSC_COEFF1_RSRVD0) +#define BP_ELCDIF_CSC_COEFF1_C1 0 +#define BM_ELCDIF_CSC_COEFF1_C1 0x000003FF +#define BF_ELCDIF_CSC_COEFF1_C1(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF1_C1) + +#define HW_ELCDIF_CSC_COEFF2 (0x00000130) + +#define BP_ELCDIF_CSC_COEFF2_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF2_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF2_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF2_RSRVD1) +#define BP_ELCDIF_CSC_COEFF2_C4 16 +#define BM_ELCDIF_CSC_COEFF2_C4 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF2_C4(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF2_C4) +#define BP_ELCDIF_CSC_COEFF2_RSRVD0 10 +#define BM_ELCDIF_CSC_COEFF2_RSRVD0 0x0000FC00 +#define BF_ELCDIF_CSC_COEFF2_RSRVD0(v) \ + (((v) << 10) & BM_ELCDIF_CSC_COEFF2_RSRVD0) +#define BP_ELCDIF_CSC_COEFF2_C3 0 +#define BM_ELCDIF_CSC_COEFF2_C3 0x000003FF +#define BF_ELCDIF_CSC_COEFF2_C3(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF2_C3) + +#define HW_ELCDIF_CSC_COEFF3 (0x00000140) + +#define BP_ELCDIF_CSC_COEFF3_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF3_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF3_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF3_RSRVD1) +#define BP_ELCDIF_CSC_COEFF3_C6 16 +#define BM_ELCDIF_CSC_COEFF3_C6 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF3_C6(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF3_C6) +#define BP_ELCDIF_CSC_COEFF3_RSRVD0 10 +#define BM_ELCDIF_CSC_COEFF3_RSRVD0 0x0000FC00 +#define BF_ELCDIF_CSC_COEFF3_RSRVD0(v) \ + (((v) << 10) & BM_ELCDIF_CSC_COEFF3_RSRVD0) +#define BP_ELCDIF_CSC_COEFF3_C5 0 +#define BM_ELCDIF_CSC_COEFF3_C5 0x000003FF +#define BF_ELCDIF_CSC_COEFF3_C5(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF3_C5) + +#define HW_ELCDIF_CSC_COEFF4 (0x00000150) + +#define BP_ELCDIF_CSC_COEFF4_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF4_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF4_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF4_RSRVD1) +#define BP_ELCDIF_CSC_COEFF4_C8 16 +#define BM_ELCDIF_CSC_COEFF4_C8 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF4_C8(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF4_C8) +#define BP_ELCDIF_CSC_COEFF4_RSRVD0 10 +#define BM_ELCDIF_CSC_COEFF4_RSRVD0 0x0000FC00 +#define BF_ELCDIF_CSC_COEFF4_RSRVD0(v) \ + (((v) << 10) & BM_ELCDIF_CSC_COEFF4_RSRVD0) +#define BP_ELCDIF_CSC_COEFF4_C7 0 +#define BM_ELCDIF_CSC_COEFF4_C7 0x000003FF +#define BF_ELCDIF_CSC_COEFF4_C7(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF4_C7) + +#define HW_ELCDIF_CSC_OFFSET (0x00000160) + +#define BP_ELCDIF_CSC_OFFSET_RSRVD1 25 +#define BM_ELCDIF_CSC_OFFSET_RSRVD1 0xFE000000 +#define BF_ELCDIF_CSC_OFFSET_RSRVD1(v) \ + (((v) << 25) & BM_ELCDIF_CSC_OFFSET_RSRVD1) +#define BP_ELCDIF_CSC_OFFSET_CBCR_OFFSET 16 +#define BM_ELCDIF_CSC_OFFSET_CBCR_OFFSET 0x01FF0000 +#define BF_ELCDIF_CSC_OFFSET_CBCR_OFFSET(v) \ + (((v) << 16) & BM_ELCDIF_CSC_OFFSET_CBCR_OFFSET) +#define BP_ELCDIF_CSC_OFFSET_RSRVD0 9 +#define BM_ELCDIF_CSC_OFFSET_RSRVD0 0x0000FE00 +#define BF_ELCDIF_CSC_OFFSET_RSRVD0(v) \ + (((v) << 9) & BM_ELCDIF_CSC_OFFSET_RSRVD0) +#define BP_ELCDIF_CSC_OFFSET_Y_OFFSET 0 +#define BM_ELCDIF_CSC_OFFSET_Y_OFFSET 0x000001FF +#define BF_ELCDIF_CSC_OFFSET_Y_OFFSET(v) \ + (((v) << 0) & BM_ELCDIF_CSC_OFFSET_Y_OFFSET) + +#define HW_ELCDIF_CSC_LIMIT (0x00000170) + +#define BP_ELCDIF_CSC_LIMIT_CBCR_MIN 24 +#define BM_ELCDIF_CSC_LIMIT_CBCR_MIN 0xFF000000 +#define BF_ELCDIF_CSC_LIMIT_CBCR_MIN(v) \ + (((v) << 24) & BM_ELCDIF_CSC_LIMIT_CBCR_MIN) +#define BP_ELCDIF_CSC_LIMIT_CBCR_MAX 16 +#define BM_ELCDIF_CSC_LIMIT_CBCR_MAX 0x00FF0000 +#define BF_ELCDIF_CSC_LIMIT_CBCR_MAX(v) \ + (((v) << 16) & BM_ELCDIF_CSC_LIMIT_CBCR_MAX) +#define BP_ELCDIF_CSC_LIMIT_Y_MIN 8 +#define BM_ELCDIF_CSC_LIMIT_Y_MIN 0x0000FF00 +#define BF_ELCDIF_CSC_LIMIT_Y_MIN(v) \ + (((v) << 8) & BM_ELCDIF_CSC_LIMIT_Y_MIN) +#define BP_ELCDIF_CSC_LIMIT_Y_MAX 0 +#define BM_ELCDIF_CSC_LIMIT_Y_MAX 0x000000FF +#define BF_ELCDIF_CSC_LIMIT_Y_MAX(v) \ + (((v) << 0) & BM_ELCDIF_CSC_LIMIT_Y_MAX) + +#define HW_ELCDIF_DATA (0x00000180) + +#define BP_ELCDIF_DATA_DATA_THREE 24 +#define BM_ELCDIF_DATA_DATA_THREE 0xFF000000 +#define BF_ELCDIF_DATA_DATA_THREE(v) \ + (((v) << 24) & BM_ELCDIF_DATA_DATA_THREE) +#define BP_ELCDIF_DATA_DATA_TWO 16 +#define BM_ELCDIF_DATA_DATA_TWO 0x00FF0000 +#define BF_ELCDIF_DATA_DATA_TWO(v) \ + (((v) << 16) & BM_ELCDIF_DATA_DATA_TWO) +#define BP_ELCDIF_DATA_DATA_ONE 8 +#define BM_ELCDIF_DATA_DATA_ONE 0x0000FF00 +#define BF_ELCDIF_DATA_DATA_ONE(v) \ + (((v) << 8) & BM_ELCDIF_DATA_DATA_ONE) +#define BP_ELCDIF_DATA_DATA_ZERO 0 +#define BM_ELCDIF_DATA_DATA_ZERO 0x000000FF +#define BF_ELCDIF_DATA_DATA_ZERO(v) \ + (((v) << 0) & BM_ELCDIF_DATA_DATA_ZERO) + +#define HW_ELCDIF_BM_ERROR_STAT (0x00000190) + +#define BP_ELCDIF_BM_ERROR_STAT_ADDR 0 +#define BM_ELCDIF_BM_ERROR_STAT_ADDR 0xFFFFFFFF +#define BF_ELCDIF_BM_ERROR_STAT_ADDR(v) (v) + +#define HW_ELCDIF_CRC_STAT (0x000001a0) + +#define BP_ELCDIF_CRC_STAT_CRC_VALUE 0 +#define BM_ELCDIF_CRC_STAT_CRC_VALUE 0xFFFFFFFF +#define BF_ELCDIF_CRC_STAT_CRC_VALUE(v) (v) + +#define HW_ELCDIF_STAT (0x000001b0) + +#define BM_ELCDIF_STAT_PRESENT 0x80000000 +#define BM_ELCDIF_STAT_DMA_REQ 0x40000000 +#define BM_ELCDIF_STAT_LFIFO_FULL 0x20000000 +#define BM_ELCDIF_STAT_LFIFO_EMPTY 0x10000000 +#define BM_ELCDIF_STAT_TXFIFO_FULL 0x08000000 +#define BM_ELCDIF_STAT_TXFIFO_EMPTY 0x04000000 +#define BM_ELCDIF_STAT_BUSY 0x02000000 +#define BM_ELCDIF_STAT_DVI_CURRENT_FIELD 0x01000000 +#define BP_ELCDIF_STAT_RSRVD0 9 +#define BM_ELCDIF_STAT_RSRVD0 0x00FFFE00 +#define BF_ELCDIF_STAT_RSRVD0(v) \ + (((v) << 9) & BM_ELCDIF_STAT_RSRVD0) +#define BP_ELCDIF_STAT_LFIFO_COUNT 0 +#define BM_ELCDIF_STAT_LFIFO_COUNT 0x000001FF +#define BF_ELCDIF_STAT_LFIFO_COUNT(v) \ + (((v) << 0) & BM_ELCDIF_STAT_LFIFO_COUNT) + +#define HW_ELCDIF_VERSION (0x000001c0) + +#define BP_ELCDIF_VERSION_MAJOR 24 +#define BM_ELCDIF_VERSION_MAJOR 0xFF000000 +#define BF_ELCDIF_VERSION_MAJOR(v) \ + (((v) << 24) & BM_ELCDIF_VERSION_MAJOR) +#define BP_ELCDIF_VERSION_MINOR 16 +#define BM_ELCDIF_VERSION_MINOR 0x00FF0000 +#define BF_ELCDIF_VERSION_MINOR(v) \ + (((v) << 16) & BM_ELCDIF_VERSION_MINOR) +#define BP_ELCDIF_VERSION_STEP 0 +#define BM_ELCDIF_VERSION_STEP 0x0000FFFF +#define BF_ELCDIF_VERSION_STEP(v) \ + (((v) << 0) & BM_ELCDIF_VERSION_STEP) + +#define HW_ELCDIF_DEBUG0 (0x000001d0) + +#define BM_ELCDIF_DEBUG0_STREAMING_END_DETECTED 0x80000000 +#define BM_ELCDIF_DEBUG0_WAIT_FOR_VSYNC_EDGE_OUT 0x40000000 +#define BM_ELCDIF_DEBUG0_SYNC_SIGNALS_ON_REG 0x20000000 +#define BM_ELCDIF_DEBUG0_DMACMDKICK 0x10000000 +#define BM_ELCDIF_DEBUG0_ENABLE 0x08000000 +#define BM_ELCDIF_DEBUG0_HSYNC 0x04000000 +#define BM_ELCDIF_DEBUG0_VSYNC 0x02000000 +#define BM_ELCDIF_DEBUG0_CUR_FRAME_TX 0x01000000 +#define BM_ELCDIF_DEBUG0_EMPTY_WORD 0x00800000 +#define BP_ELCDIF_DEBUG0_CUR_STATE 16 +#define BM_ELCDIF_DEBUG0_CUR_STATE 0x007F0000 +#define BF_ELCDIF_DEBUG0_CUR_STATE(v) \ + (((v) << 16) & BM_ELCDIF_DEBUG0_CUR_STATE) +#define BM_ELCDIF_DEBUG0_PXP_ELCDIF_B0_READY 0x00008000 +#define BM_ELCDIF_DEBUG0_ELCDIF_PXP_B0_DONE 0x00004000 +#define BM_ELCDIF_DEBUG0_PXP_ELCDIF_B1_READY 0x00002000 +#define BM_ELCDIF_DEBUG0_ELCDIF_PXP_B1_DONE 0x00001000 +#define BP_ELCDIF_DEBUG0_CUR_REQ_STATE 10 +#define BM_ELCDIF_DEBUG0_CUR_REQ_STATE 0x00000C00 +#define BF_ELCDIF_DEBUG0_CUR_REQ_STATE(v) \ + (((v) << 10) & BM_ELCDIF_DEBUG0_CUR_REQ_STATE) +#define BM_ELCDIF_DEBUG0_MST_AVALID 0x00000200 +#define BP_ELCDIF_DEBUG0_MST_OUTSTANDING_REQS 4 +#define BM_ELCDIF_DEBUG0_MST_OUTSTANDING_REQS 0x000001F0 +#define BF_ELCDIF_DEBUG0_MST_OUTSTANDING_REQS(v) \ + (((v) << 4) & BM_ELCDIF_DEBUG0_MST_OUTSTANDING_REQS) +#define BP_ELCDIF_DEBUG0_MST_WORDS 0 +#define BM_ELCDIF_DEBUG0_MST_WORDS 0x0000000F +#define BF_ELCDIF_DEBUG0_MST_WORDS(v) \ + (((v) << 0) & BM_ELCDIF_DEBUG0_MST_WORDS) + +#define HW_ELCDIF_DEBUG1 (0x000001e0) + +#define BP_ELCDIF_DEBUG1_H_DATA_COUNT 16 +#define BM_ELCDIF_DEBUG1_H_DATA_COUNT 0xFFFF0000 +#define BF_ELCDIF_DEBUG1_H_DATA_COUNT(v) \ + (((v) << 16) & BM_ELCDIF_DEBUG1_H_DATA_COUNT) +#define BP_ELCDIF_DEBUG1_V_DATA_COUNT 0 +#define BM_ELCDIF_DEBUG1_V_DATA_COUNT 0x0000FFFF +#define BF_ELCDIF_DEBUG1_V_DATA_COUNT(v) \ + (((v) << 0) & BM_ELCDIF_DEBUG1_V_DATA_COUNT) + +#define HW_ELCDIF_DEBUG2 (0x000001f0) + +#define BP_ELCDIF_DEBUG2_MST_ADDRESS 0 +#define BM_ELCDIF_DEBUG2_MST_ADDRESS 0xFFFFFFFF +#define BF_ELCDIF_DEBUG2_MST_ADDRESS(v) (v) +#endif /* __ELCDIF_REGS_INCLUDED_ */ diff --git a/drivers/video/mxc/epdc_regs.h b/drivers/video/mxc/epdc_regs.h new file mode 100644 index 000000000000..98307174fdce --- /dev/null +++ b/drivers/video/mxc/epdc_regs.h @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2010-2011 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 + * + */ +#ifndef __EPDC_REGS_INCLUDED__ +#define __EPDC_REGS_INCLUDED__ + +extern void __iomem *epdc_base; + +/************************************* + * Register addresses + **************************************/ + +#define EPDC_CTRL (epdc_base + 0x000) +#define EPDC_CTRL_SET (epdc_base + 0x004) +#define EPDC_CTRL_CLEAR (epdc_base + 0x008) +#define EPDC_CTRL_TOGGLE (epdc_base + 0x00C) +#define EPDC_WVADDR (epdc_base + 0x020) +#define EPDC_WB_ADDR (epdc_base + 0x030) +#define EPDC_RES (epdc_base + 0x040) +#define EPDC_FORMAT (epdc_base + 0x050) +#define EPDC_FORMAT_SET (epdc_base + 0x054) +#define EPDC_FORMAT_CLEAR (epdc_base + 0x058) +#define EPDC_FORMAT_TOGGLE (epdc_base + 0x05C) +#define EPDC_FIFOCTRL (epdc_base + 0x0A0) +#define EPDC_FIFOCTRL_SET (epdc_base + 0x0A4) +#define EPDC_FIFOCTRL_CLEAR (epdc_base + 0x0A8) +#define EPDC_FIFOCTRL_TOGGLE (epdc_base + 0x0AC) +#define EPDC_UPD_ADDR (epdc_base + 0x100) +#define EPDC_UPD_CORD (epdc_base + 0x120) +#define EPDC_UPD_SIZE (epdc_base + 0x140) +#define EPDC_UPD_CTRL (epdc_base + 0x160) +#define EPDC_UPD_FIXED (epdc_base + 0x180) +#define EPDC_TEMP (epdc_base + 0x1A0) +#define EPDC_TCE_CTRL (epdc_base + 0x200) +#define EPDC_TCE_SDCFG (epdc_base + 0x220) +#define EPDC_TCE_GDCFG (epdc_base + 0x240) +#define EPDC_TCE_HSCAN1 (epdc_base + 0x260) +#define EPDC_TCE_HSCAN2 (epdc_base + 0x280) +#define EPDC_TCE_VSCAN (epdc_base + 0x2A0) +#define EPDC_TCE_OE (epdc_base + 0x2C0) +#define EPDC_TCE_POLARITY (epdc_base + 0x2E0) +#define EPDC_TCE_TIMING1 (epdc_base + 0x300) +#define EPDC_TCE_TIMING2 (epdc_base + 0x310) +#define EPDC_TCE_TIMING3 (epdc_base + 0x320) +#define EPDC_IRQ_MASK (epdc_base + 0x400) +#define EPDC_IRQ_MASK_SET (epdc_base + 0x404) +#define EPDC_IRQ_MASK_CLEAR (epdc_base + 0x408) +#define EPDC_IRQ_MASK_TOGGLE (epdc_base + 0x40C) +#define EPDC_IRQ (epdc_base + 0x420) +#define EPDC_IRQ_SET (epdc_base + 0x424) +#define EPDC_IRQ_CLEAR (epdc_base + 0x428) +#define EPDC_IRQ_TOGGLE (epdc_base + 0x42C) +#define EPDC_STATUS_LUTS (epdc_base + 0x440) +#define EPDC_STATUS_LUTS_SET (epdc_base + 0x444) +#define EPDC_STATUS_LUTS_CLEAR (epdc_base + 0x448) +#define EPDC_STATUS_LUTS_TOGGLE (epdc_base + 0x44C) +#define EPDC_STATUS_NEXTLUT (epdc_base + 0x460) +#define EPDC_STATUS_COL (epdc_base + 0x480) +#define EPDC_STATUS (epdc_base + 0x4A0) +#define EPDC_STATUS_SET (epdc_base + 0x4A4) +#define EPDC_STATUS_CLEAR (epdc_base + 0x4A8) +#define EPDC_STATUS_TOGGLE (epdc_base + 0x4AC) +#define EPDC_DEBUG (epdc_base + 0x500) +#define EPDC_DEBUG_LUT0 (epdc_base + 0x540) +#define EPDC_DEBUG_LUT1 (epdc_base + 0x550) +#define EPDC_DEBUG_LUT2 (epdc_base + 0x560) +#define EPDC_DEBUG_LUT3 (epdc_base + 0x570) +#define EPDC_DEBUG_LUT4 (epdc_base + 0x580) +#define EPDC_DEBUG_LUT5 (epdc_base + 0x590) +#define EPDC_DEBUG_LUT6 (epdc_base + 0x5A0) +#define EPDC_DEBUG_LUT7 (epdc_base + 0x5B0) +#define EPDC_DEBUG_LUT8 (epdc_base + 0x5C0) +#define EPDC_DEBUG_LUT9 (epdc_base + 0x5D0) +#define EPDC_DEBUG_LUT10 (epdc_base + 0x5E0) +#define EPDC_DEBUG_LUT11 (epdc_base + 0x5F0) +#define EPDC_DEBUG_LUT12 (epdc_base + 0x600) +#define EPDC_DEBUG_LUT13 (epdc_base + 0x610) +#define EPDC_DEBUG_LUT14 (epdc_base + 0x620) +#define EPDC_DEBUG_LUT15 (epdc_base + 0x630) +#define EPDC_GPIO (epdc_base + 0x700) +#define EPDC_VERSION (epdc_base + 0x7F0) + +/* + * Register field definitions + */ + +enum { +/* EPDC_CTRL field values */ + EPDC_CTRL_SFTRST = 0x80000000, + EPDC_CTRL_CLKGATE = 0x40000000, + EPDC_CTRL_SRAM_POWERDOWN = 0x100, + EPDC_CTRL_UPD_DATA_SWIZZLE_MASK = 0xC0, + EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP = 0, + EPDC_CTRL_UPD_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x40, + EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_SWAP = 0x80, + EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_BYTE_SWAP = 0xC0, + EPDC_CTRL_LUT_DATA_SWIZZLE_MASK = 0x30, + EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP = 0, + EPDC_CTRL_LUT_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x10, + EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_SWAP = 0x20, + EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_BYTE_SWAP = 0x30, + EPDC_CTRL_BURST_LEN_8_8 = 0x1, + EPDC_CTRL_BURST_LEN_8_16 = 0, + +/* EPDC_RES field values */ + EPDC_RES_VERTICAL_MASK = 0x1FFF0000, + EPDC_RES_VERTICAL_OFFSET = 16, + EPDC_RES_HORIZONTAL_MASK = 0x1FFF, + EPDC_RES_HORIZONTAL_OFFSET = 0, + +/* EPDC_FORMAT field values */ + EPDC_FORMAT_BUF_PIXEL_SCALE_ROUND = 0x1000000, + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK = 0xFF0000, + EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET = 16, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P2N = 0x200, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P3N = 0x300, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N = 0x400, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N = 0x500, + EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT = 0x0, + EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT_VCOM = 0x1, + EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT = 0x2, + EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT_VCOM = 0x3, + +/* EPDC_FIFOCTRL field values */ + EPDC_FIFOCTRL_ENABLE_PRIORITY = 0x80000000, + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK = 0xFF0000, + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET = 16, + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK = 0xFF00, + EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET = 8, + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK = 0xFF, + EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET = 0, + +/* EPDC_UPD_CORD field values */ + EPDC_UPD_CORD_YCORD_MASK = 0x1FFF0000, + EPDC_UPD_CORD_YCORD_OFFSET = 16, + EPDC_UPD_CORD_XCORD_MASK = 0x1FFF, + EPDC_UPD_CORD_XCORD_OFFSET = 0, + +/* EPDC_UPD_SIZE field values */ + EPDC_UPD_SIZE_HEIGHT_MASK = 0x1FFF0000, + EPDC_UPD_SIZE_HEIGHT_OFFSET = 16, + EPDC_UPD_SIZE_WIDTH_MASK = 0x1FFF, + EPDC_UPD_SIZE_WIDTH_OFFSET = 0, + +/* EPDC_UPD_CTRL field values */ + EPDC_UPD_CTRL_USE_FIXED = 0x80000000, + EPDC_UPD_CTRL_LUT_SEL_MASK = 0xF0000, + EPDC_UPD_CTRL_LUT_SEL_OFFSET = 16, + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK = 0xFF00, + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET = 8, + EPDC_UPD_CTRL_UPDATE_MODE_FULL = 0x1, + +/* EPDC_UPD_FIXED field values */ + EPDC_UPD_FIXED_FIXNP_EN = 0x80000000, + EPDC_UPD_FIXED_FIXCP_EN = 0x40000000, + EPDC_UPD_FIXED_FIXNP_MASK = 0xFF00, + EPDC_UPD_FIXED_FIXNP_OFFSET = 8, + EPDC_UPD_FIXED_FIXCP_MASK = 0xFF, + EPDC_UPD_FIXED_FIXCP_OFFSET = 0, + +/* EPDC_TCE_CTRL field values */ + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK = 0x1FF0000, + EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET = 16, + EPDC_TCE_CTRL_VCOM_VAL_MASK = 0xC00, + EPDC_TCE_CTRL_VCOM_VAL_OFFSET = 10, + EPDC_TCE_CTRL_VCOM_MODE_AUTO = 0x200, + EPDC_TCE_CTRL_VCOM_MODE_MANUAL = 0x000, + EPDC_TCE_CTRL_DDR_MODE_ENABLE = 0x100, + EPDC_TCE_CTRL_LVDS_MODE_CE_ENABLE = 0x80, + EPDC_TCE_CTRL_LVDS_MODE_ENABLE = 0x40, + EPDC_TCE_CTRL_SCAN_DIR_1_UP = 0x20, + EPDC_TCE_CTRL_SCAN_DIR_0_UP = 0x10, + EPDC_TCE_CTRL_DUAL_SCAN_ENABLE = 0x8, + EPDC_TCE_CTRL_SDDO_WIDTH_16BIT = 0x4, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_2 = 1, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4 = 2, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_8 = 3, + +/* EPDC_TCE_SDCFG field values */ + EPDC_TCE_SDCFG_SDCLK_HOLD = 0x200000, + EPDC_TCE_SDCFG_SDSHR = 0x100000, + EPDC_TCE_SDCFG_NUM_CE_MASK = 0xF0000, + EPDC_TCE_SDCFG_NUM_CE_OFFSET = 16, + EPDC_TCE_SDCFG_SDDO_REFORMAT_STANDARD = 0, + EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS = 0x4000, + EPDC_TCE_SDCFG_SDDO_INVERT_ENABLE = 0x2000, + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK = 0x1FFF, + EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET = 0, + +/* EPDC_TCE_GDCFG field values */ + EPDC_TCE_SDCFG_GDRL = 0x10, + EPDC_TCE_SDCFG_GDOE_MODE_DELAYED_GDCLK = 0x2, + EPDC_TCE_SDCFG_GDSP_MODE_FRAME_SYNC = 0x1, + EPDC_TCE_SDCFG_GDSP_MODE_ONE_LINE = 0x0, + +/* EPDC_TCE_HSCAN1 field values */ + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK = 0xFFF0000, + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET = 16, + EPDC_TCE_HSCAN1_LINE_SYNC_MASK = 0xFFF, + EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET = 0, + +/* EPDC_TCE_HSCAN2 field values */ + EPDC_TCE_HSCAN2_LINE_END_MASK = 0xFFF0000, + EPDC_TCE_HSCAN2_LINE_END_OFFSET = 16, + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK = 0xFFF, + EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET = 0, + +/* EPDC_TCE_VSCAN field values */ + EPDC_TCE_VSCAN_FRAME_END_MASK = 0xFF0000, + EPDC_TCE_VSCAN_FRAME_END_OFFSET = 16, + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK = 0xFF00, + EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET = 8, + EPDC_TCE_VSCAN_FRAME_SYNC_MASK = 0xFF, + EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET = 0, + +/* EPDC_TCE_OE field values */ + EPDC_TCE_OE_SDOED_WIDTH_MASK = 0xFF000000, + EPDC_TCE_OE_SDOED_WIDTH_OFFSET = 24, + EPDC_TCE_OE_SDOED_DLY_MASK = 0xFF0000, + EPDC_TCE_OE_SDOED_DLY_OFFSET = 16, + EPDC_TCE_OE_SDOEZ_WIDTH_MASK = 0xFF00, + EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET = 8, + EPDC_TCE_OE_SDOEZ_DLY_MASK = 0xFF, + EPDC_TCE_OE_SDOEZ_DLY_OFFSET = 0, + +/* EPDC_TCE_POLARITY field values */ + EPDC_TCE_POLARITY_GDSP_POL_ACTIVE_HIGH = 0x10, + EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH = 0x8, + EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH = 0x4, + EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH = 0x2, + EPDC_TCE_POLARITY_SDCE_POL_ACTIVE_HIGH = 0x1, + +/* EPDC_TCE_TIMING1 field values */ + EPDC_TCE_TIMING1_SDLE_SHIFT_NONE = 0x00, + EPDC_TCE_TIMING1_SDLE_SHIFT_1 = 0x10, + EPDC_TCE_TIMING1_SDLE_SHIFT_2 = 0x20, + EPDC_TCE_TIMING1_SDLE_SHIFT_3 = 0x30, + EPDC_TCE_TIMING1_SDCLK_INVERT = 0x8, + EPDC_TCE_TIMING1_SDCLK_SHIFT_NONE = 0, + EPDC_TCE_TIMING1_SDCLK_SHIFT_1CYCLE = 1, + EPDC_TCE_TIMING1_SDCLK_SHIFT_2CYCLES = 2, + EPDC_TCE_TIMING1_SDCLK_SHIFT_3CYCLES = 3, + +/* EPDC_TCE_TIMING2 field values */ + EPDC_TCE_TIMING2_GDCLK_HP_MASK = 0xFFFF0000, + EPDC_TCE_TIMING2_GDCLK_HP_OFFSET = 16, + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK = 0xFFFF, + EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET = 0, + +/* EPDC_TCE_TIMING3 field values */ + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK = 0xFFFF0000, + EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET = 16, + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK = 0xFFFF, + EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET = 0, + +/* EPDC_IRQ_MASK/EPDC_IRQ field values */ + EPDC_IRQ_WB_CMPLT_IRQ = 0x10000, + EPDC_IRQ_LUT_COL_IRQ = 0x20000, + EPDC_IRQ_TCE_UNDERRUN_IRQ = 0x40000, + EPDC_IRQ_FRAME_END_IRQ = 0x80000, + EPDC_IRQ_BUS_ERROR_IRQ = 0x100000, + EPDC_IRQ_TCE_IDLE_IRQ = 0x200000, + +/* EPDC_STATUS_NEXTLUT field values */ + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID = 0x100, + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK = 0xF, + EPDC_STATUS_NEXTLUT_NEXT_LUT_OFFSET = 0, + +/* EPDC_STATUS field values */ + EPDC_STATUS_LUTS_UNDERRUN = 0x4, + EPDC_STATUS_LUTS_BUSY = 0x2, + EPDC_STATUS_WB_BUSY = 0x1, + +/* EPDC_DEBUG field values */ + EPDC_DEBUG_UNDERRUN_RECOVER = 0x2, + EPDC_DEBUG_COLLISION_OFF = 0x1, + +/* EPDC_GPIO field values */ + EPDC_GPIO_PWRCOM = 0x40, + EPDC_GPIO_PWRCTRL_MASK = 0x3C, + EPDC_GPIO_PWRCTRL_OFFSET = 2, + EPDC_GPIO_BDR_MASK = 0x3, + EPDC_GPIO_BDR_OFFSET = 0, +}; + +#endif /* __EPDC_REGS_INCLUDED__ */ diff --git a/drivers/video/mxc/ldb.c b/drivers/video/mxc/ldb.c new file mode 100644 index 000000000000..0fbdc5fafe10 --- /dev/null +++ b/drivers/video/mxc/ldb.c @@ -0,0 +1,1525 @@ +/* + * Copyright (C) 2011 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @file mxc_ldb.c + * + * @brief This file contains the LDB driver device interface and fops + * functions. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LDB_BGREF_RMODE_MASK 0x00008000 +#define LDB_BGREF_RMODE_INT 0x00008000 +#define LDB_BGREF_RMODE_EXT 0x0 + +#define LDB_DI1_VS_POL_MASK 0x00000400 +#define LDB_DI1_VS_POL_ACT_LOW 0x00000400 +#define LDB_DI1_VS_POL_ACT_HIGH 0x0 +#define LDB_DI0_VS_POL_MASK 0x00000200 +#define LDB_DI0_VS_POL_ACT_LOW 0x00000200 +#define LDB_DI0_VS_POL_ACT_HIGH 0x0 + +#define LDB_BIT_MAP_CH1_MASK 0x00000100 +#define LDB_BIT_MAP_CH1_JEIDA 0x00000100 +#define LDB_BIT_MAP_CH1_SPWG 0x0 +#define LDB_BIT_MAP_CH0_MASK 0x00000040 +#define LDB_BIT_MAP_CH0_JEIDA 0x00000040 +#define LDB_BIT_MAP_CH0_SPWG 0x0 + +#define LDB_DATA_WIDTH_CH1_MASK 0x00000080 +#define LDB_DATA_WIDTH_CH1_24 0x00000080 +#define LDB_DATA_WIDTH_CH1_18 0x0 +#define LDB_DATA_WIDTH_CH0_MASK 0x00000020 +#define LDB_DATA_WIDTH_CH0_24 0x00000020 +#define LDB_DATA_WIDTH_CH0_18 0x0 + +#define LDB_CH1_MODE_MASK 0x0000000C +#define LDB_CH1_MODE_EN_TO_DI1 0x0000000C +#define LDB_CH1_MODE_EN_TO_DI0 0x00000004 +#define LDB_CH1_MODE_DISABLE 0x0 +#define LDB_CH0_MODE_MASK 0x00000003 +#define LDB_CH0_MODE_EN_TO_DI1 0x00000003 +#define LDB_CH0_MODE_EN_TO_DI0 0x00000001 +#define LDB_CH0_MODE_DISABLE 0x0 + +#define LDB_SPLIT_MODE_EN 0x00000010 + +enum ldb_chan_mode_opt { + LDB_SIN_DI0 = 0, + LDB_SIN_DI1 = 1, + LDB_SEP = 2, + LDB_DUL_DI0 = 3, + LDB_DUL_DI1 = 4, + LDB_SPL_DI0 = 5, + LDB_SPL_DI1 = 6, + LDB_NO_MODE = 7, +}; + +static struct ldb_data { + struct fb_info *fbi[2]; + bool ch_working[2]; + int blank[2]; + uint32_t chan_mode_opt; + uint32_t chan_bit_map[2]; + uint32_t bgref_rmode; + uint32_t base_addr; + uint32_t *control_reg; + struct clk *ldb_di_clk[2]; + struct regulator *lvds_bg_reg; +} ldb; + +static struct device *g_ldb_dev; +static u32 *ldb_reg; +static int g_chan_mode_opt = LDB_NO_MODE; +static int g_chan_bit_map[2]; +static bool g_enable_ldb; +static bool g_di0_used; +static bool g_di1_used; + +DEFINE_SPINLOCK(ldb_lock); + +/*fake clock api*/ +int clk_get_usecount(struct clk *clk) +{ + return 1; +} + +struct fb_videomode mxcfb_ldb_modedb[] = { + { + "1080P60", 60, 1920, 1080, 7692, + 100, 40, + 30, 3, + 10, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + "XGA", 60, 1024, 768, 15385, + 220, 40, + 21, 7, + 60, 10, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; +int mxcfb_ldb_modedb_sz = ARRAY_SIZE(mxcfb_ldb_modedb); + +static int bits_per_pixel(int pixel_fmt) +{ + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 24; + break; + case IPU_PIX_FMT_BGR666: + case IPU_PIX_FMT_RGB666: + case IPU_PIX_FMT_LVDS666: + return 18; + break; + default: + break; + } + return 0; +} + +static int valid_mode(int pixel_fmt) +{ + return ((pixel_fmt == IPU_PIX_FMT_RGB24) || + (pixel_fmt == IPU_PIX_FMT_BGR24) || + (pixel_fmt == IPU_PIX_FMT_LVDS666) || + (pixel_fmt == IPU_PIX_FMT_RGB666) || + (pixel_fmt == IPU_PIX_FMT_BGR666)); +} + +static void ldb_disable(int ipu_di) +{ + uint32_t reg; + int i = 0; + + spin_lock(&ldb_lock); + + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + if (ipu_di != 0 || !ldb.ch_working[0]) { + spin_unlock(&ldb_lock); + return; + } + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_DISABLE, + ldb.control_reg); + + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[0]) != 0) + clk_disable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + + ldb.ch_working[0] = false; + break; + case LDB_SIN_DI1: + if (ipu_di != 1 || !ldb.ch_working[1]) { + spin_unlock(&ldb_lock); + return; + } + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_DISABLE, + ldb.control_reg); + + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[1]) != 0) + clk_disable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + + ldb.ch_working[1] = false; + break; + case LDB_SPL_DI0: + case LDB_DUL_DI0: + if (ipu_di != 0) { + spin_unlock(&ldb_lock); + return; + } + + for (i = 0; i < 2; i++) { + if (ldb.ch_working[i]) { + reg = __raw_readl(ldb.control_reg); + if (i == 0) + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_DISABLE, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH1_MODE_DISABLE, + ldb.control_reg); + + if (ldb.chan_mode_opt == LDB_SPL_DI0) { + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg & ~LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + + ldb.ldb_di_clk[i] = clk_get(g_ldb_dev, i ? + "ldb_di1_clk" : + "ldb_di0_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[i]) != 0) + clk_disable(ldb.ldb_di_clk[i]); + clk_put(ldb.ldb_di_clk[i]); + + ldb.ch_working[i] = false; + } + } + break; + case LDB_SPL_DI1: + case LDB_DUL_DI1: + if (ipu_di != 1) { + spin_unlock(&ldb_lock); + return; + } + + for (i = 0; i < 2; i++) { + if (ldb.ch_working[i]) { + reg = __raw_readl(ldb.control_reg); + if (i == 0) + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_DISABLE, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH1_MODE_DISABLE, + ldb.control_reg); + + if (ldb.chan_mode_opt == LDB_SPL_DI1) { + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg & ~LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + + ldb.ldb_di_clk[i] = clk_get(g_ldb_dev, i ? + "ldb_di1_clk" : + "ldb_di0_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[i]) != 0) + clk_disable(ldb.ldb_di_clk[i]); + clk_put(ldb.ldb_di_clk[i]); + + ldb.ch_working[i] = false; + } + } + break; + case LDB_SEP: + if (ldb.ch_working[ipu_di]) { + reg = __raw_readl(ldb.control_reg); + if (ipu_di == 0) + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_DISABLE, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_DISABLE, + ldb.control_reg); + + ldb.ldb_di_clk[ipu_di] = clk_get(g_ldb_dev, ipu_di ? + "ldb_di1_clk" : + "ldb_di0_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[ipu_di]) != 0) + clk_disable(ldb.ldb_di_clk[ipu_di]); + clk_put(ldb.ldb_di_clk[ipu_di]); + + ldb.ch_working[ipu_di] = false; + } + break; + default: + break; + } + + spin_unlock(&ldb_lock); + return; +} + +static void ldb_enable(int ipu_di) +{ + uint32_t reg; + + spin_lock(&ldb_lock); + + reg = __raw_readl(ldb.control_reg); + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + if (ldb.ch_working[0] || ipu_di != 0) { + spin_unlock(&ldb_lock); + return; + } + + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[0]) == 0) + clk_enable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, ldb.control_reg); + ldb.ch_working[0] = true; + break; + case LDB_SIN_DI1: + if (ldb.ch_working[1] || ipu_di != 1) { + spin_unlock(&ldb_lock); + return; + } + + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[1]) == 0) + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, ldb.control_reg); + ldb.ch_working[1] = true; + break; + case LDB_SEP: + if (ldb.ch_working[ipu_di]) { + spin_unlock(&ldb_lock); + return; + } + + if (ipu_di == 0) { + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[0]) == 0) + clk_enable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, + ldb.control_reg); + ldb.ch_working[0] = true; + } else { + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[1]) == 0) + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + ldb.ch_working[1] = true; + } + break; + case LDB_DUL_DI0: + case LDB_SPL_DI0: + if (ipu_di != 0) + return; + else + goto proc; + case LDB_DUL_DI1: + case LDB_SPL_DI1: + if (ipu_di != 1) + return; +proc: + if (ldb.ch_working[0] || ldb.ch_working[1]) { + spin_unlock(&ldb_lock); + return; + } + + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + if (clk_get_usecount(ldb.ldb_di_clk[0]) == 0) + clk_enable(ldb.ldb_di_clk[0]); + if (clk_get_usecount(ldb.ldb_di_clk[1]) == 0) + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[1]); + + if (ldb.chan_mode_opt == LDB_DUL_DI0 || + ldb.chan_mode_opt == LDB_SPL_DI0) { + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI0, + ldb.control_reg); + } else if (ldb.chan_mode_opt == LDB_DUL_DI1 || + ldb.chan_mode_opt == LDB_SPL_DI1) { + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI1, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + } + if (ldb.chan_mode_opt == LDB_SPL_DI0 || + ldb.chan_mode_opt == LDB_SPL_DI1) { + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + ldb.ch_working[0] = true; + ldb.ch_working[1] = true; + break; + default: + break; + } + spin_unlock(&ldb_lock); + return; +} + +static int ldb_fb_pre_setup(struct fb_info *fbi) +{ + int ipu_di = 0; + struct clk *di_clk, *ldb_clk_parent; + unsigned long ldb_clk_prate = 455000000; + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + if (!fbi->mode) { + dev_warn(g_ldb_dev, "can not find mode for xres=%d, yres=%d\n", + fbi->var.xres, fbi->var.yres); + return 0; + } + + if (fbi->fbops->fb_ioctl) { + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_IPU_DI, + (unsigned long)&ipu_di); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_BLANK, + (unsigned int)(&ldb.blank[ipu_di])); + set_fs(old_fs); + + /* + * Default ldb mode: + * 1080p: DI0 split, SPWG or DI1 split, SPWG + * others: single, SPWG + */ + if (ldb.chan_mode_opt == LDB_NO_MODE) { + if (fb_mode_is_equal(fbi->mode, &mxcfb_ldb_modedb[0])) { + if (ipu_di == 0) { + ldb.chan_mode_opt = LDB_SPL_DI0; + dev_warn(g_ldb_dev, + "default di0 split mode\n"); + } else { + ldb.chan_mode_opt = LDB_SPL_DI1; + dev_warn(g_ldb_dev, + "default di1 split mode\n"); + } + ldb.chan_bit_map[0] = LDB_BIT_MAP_SPWG; + ldb.chan_bit_map[1] = LDB_BIT_MAP_SPWG; + } else if (fb_mode_is_equal(fbi->mode, &mxcfb_ldb_modedb[1])) { + if (ipu_di == 0) { + ldb.chan_mode_opt = LDB_SIN_DI0; + ldb.chan_bit_map[0] = LDB_BIT_MAP_SPWG; + dev_warn(g_ldb_dev, + "default di0 single mode\n"); + } else { + ldb.chan_mode_opt = LDB_SIN_DI1; + ldb.chan_bit_map[1] = LDB_BIT_MAP_SPWG; + dev_warn(g_ldb_dev, + "default di1 single mode\n"); + } + } + } + + /* TODO:Set the correct pll4 rate for all situations */ + if (ipu_di == 1) { + ldb.ldb_di_clk[1] = + clk_get(g_ldb_dev, "ldb_di1_clk"); + di_clk = clk_get(g_ldb_dev, "ipu_di1_clk"); + ldb_clk_parent = + clk_get_parent(ldb.ldb_di_clk[1]); + clk_set_rate(ldb_clk_parent, ldb_clk_prate); + clk_set_parent(di_clk, ldb.ldb_di_clk[1]); + clk_put(di_clk); + clk_put(ldb.ldb_di_clk[1]); + } else { + ldb.ldb_di_clk[0] = + clk_get(g_ldb_dev, "ldb_di0_clk"); + di_clk = clk_get(g_ldb_dev, "ipu_di0_clk"); + ldb_clk_parent = + clk_get_parent(ldb.ldb_di_clk[0]); + clk_set_rate(ldb_clk_parent, ldb_clk_prate); + clk_set_parent(di_clk, ldb.ldb_di_clk[0]); + clk_put(di_clk); + clk_put(ldb.ldb_di_clk[0]); + } + + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + clk_set_rate(ldb.ldb_di_clk[0], ldb_clk_prate/7); + if (ldb.blank[0] == FB_BLANK_UNBLANK && + clk_get_usecount(ldb.ldb_di_clk[0]) == 0) + clk_enable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + break; + case LDB_SIN_DI1: + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + clk_set_rate(ldb.ldb_di_clk[1], ldb_clk_prate/7); + if (ldb.blank[1] == FB_BLANK_UNBLANK && + clk_get_usecount(ldb.ldb_di_clk[1]) == 0) + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + break; + case LDB_SEP: + if (ipu_di == 0) { + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + clk_set_rate(ldb.ldb_di_clk[0], ldb_clk_prate/7); + if (ldb.blank[0] == FB_BLANK_UNBLANK && + clk_get_usecount(ldb.ldb_di_clk[0]) == 0) + clk_enable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + } else { + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + clk_set_rate(ldb.ldb_di_clk[1], ldb_clk_prate/7); + if (ldb.blank[1] == FB_BLANK_UNBLANK && + clk_get_usecount(ldb.ldb_di_clk[1]) == 0) + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + } + break; + case LDB_DUL_DI0: + case LDB_SPL_DI0: + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + if (ldb.chan_mode_opt == LDB_DUL_DI0) { + clk_set_rate(ldb.ldb_di_clk[0], ldb_clk_prate/7); + } else { + clk_set_rate(ldb.ldb_di_clk[0], 2*ldb_clk_prate/7); + clk_set_rate(ldb.ldb_di_clk[1], 2*ldb_clk_prate/7); + } + if (ldb.blank[0] == FB_BLANK_UNBLANK) { + if (clk_get_usecount(ldb.ldb_di_clk[0]) == 0) + clk_enable(ldb.ldb_di_clk[0]); + if (clk_get_usecount(ldb.ldb_di_clk[1]) == 0) + clk_enable(ldb.ldb_di_clk[1]); + } + clk_put(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[1]); + break; + case LDB_DUL_DI1: + case LDB_SPL_DI1: + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + if (ldb.chan_mode_opt == LDB_DUL_DI1) { + clk_set_rate(ldb.ldb_di_clk[1], ldb_clk_prate/7); + } else { + clk_set_rate(ldb.ldb_di_clk[0], 2*ldb_clk_prate/7); + clk_set_rate(ldb.ldb_di_clk[1], 2*ldb_clk_prate/7); + } + if (ldb.blank[1] == FB_BLANK_UNBLANK) { + if (clk_get_usecount(ldb.ldb_di_clk[0]) == 0) + clk_enable(ldb.ldb_di_clk[0]); + if (clk_get_usecount(ldb.ldb_di_clk[1]) == 0) + clk_enable(ldb.ldb_di_clk[1]); + } + clk_put(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[1]); + break; + default: + break; + } + + if (ldb.blank[ipu_di] == FB_BLANK_UNBLANK) + ldb_enable(ipu_di); + } + + return 0; +} + +int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + mm_segment_t old_fs; + int ipu_di = 0; + + /* Get rid of impact from FG fb */ + if (strcmp(fbi->fix.id, "DISP3 FG") == 0) + return 0; + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_IPU_DI, + (unsigned long)&ipu_di); + set_fs(old_fs); + } else + return 0; + + if ((ipu_di == 0 && !g_di0_used) || + (ipu_di == 1 && !g_di1_used) || + ipu_di > 1) + return 0; + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + if (!fbi->mode) { + dev_warn(g_ldb_dev, "can not find mode for xres=%d, yres=%d\n", + fbi->var.xres, fbi->var.yres); + return 0; + } + + switch (val) { + case FB_EVENT_MODE_CHANGE: { + int ipu_di_pix_fmt; + uint32_t reg; + + if ((ldb.fbi[0] != NULL && ldb.chan_mode_opt != LDB_SEP) || + ldb.fbi[1] != NULL) + return 0; + + /* + * We cannot support two LVDS panels with different + * pixel clock rates except that one's pixel clock rate + * is two times of the others'. + */ + if (ldb.fbi[0]) { + if (ldb.fbi[0]->var.pixclock == fbi->var.pixclock || + ldb.fbi[0]->var.pixclock == + 2 * fbi->var.pixclock || + fbi->var.pixclock == 2 * ldb.fbi[0]->var.pixclock) + ldb.fbi[1] = fbi; + else + return 0; + } else + ldb.fbi[0] = fbi; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_GET_DIFMT, + (unsigned long)&ipu_di_pix_fmt); + set_fs(old_fs); + + if (!valid_mode(ipu_di_pix_fmt)) { + dev_err(g_ldb_dev, "Unsupport pixel format " + "for ldb input\n"); + return 0; + } + + reg = __raw_readl(ldb.control_reg); + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) { + if (ipu_di == 0) + __raw_writel((reg & + ~LDB_DI0_VS_POL_MASK) | + LDB_DI0_VS_POL_ACT_HIGH, + ldb.control_reg); + else + __raw_writel((reg & + ~LDB_DI1_VS_POL_MASK) | + LDB_DI1_VS_POL_ACT_HIGH, + ldb.control_reg); + } else { + if (ipu_di == 0) + __raw_writel((reg & + ~LDB_DI0_VS_POL_MASK) | + LDB_DI0_VS_POL_ACT_LOW, + ldb.control_reg); + else + __raw_writel((reg & + ~LDB_DI1_VS_POL_MASK) | + LDB_DI1_VS_POL_ACT_LOW, + ldb.control_reg); + } + + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt) == 24) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt) == 18) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[0] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, ldb.control_reg); + if (ldb.blank[0] == FB_BLANK_UNBLANK) + ldb.ch_working[0] = true; + break; + case LDB_SIN_DI1: + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt) == 24) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH1_MASK) | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt) == 18) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH1_MASK) | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[1] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, ldb.control_reg); + if (ldb.blank[1] == FB_BLANK_UNBLANK) + ldb.ch_working[1] = true; + break; + case LDB_SEP: + reg = __raw_readl(ldb.control_reg); + if (ipu_di == 0) { + if (bits_per_pixel(ipu_di_pix_fmt) == 24) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt) == 18) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_18, + ldb.control_reg); + } else { + if (bits_per_pixel(ipu_di_pix_fmt) == 24) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH1_MASK) | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt) == 18) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH1_MASK) | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + } + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[0] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[1] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI1, ldb.control_reg); + if (ldb.blank[0] == FB_BLANK_UNBLANK) + ldb.ch_working[0] = true; + if (ldb.blank[1] == FB_BLANK_UNBLANK) + ldb.ch_working[1] = true; + break; + case LDB_DUL_DI0: + case LDB_SPL_DI0: + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt) == 24) + __raw_writel((reg & ~(LDB_DATA_WIDTH_CH0_MASK | + LDB_DATA_WIDTH_CH1_MASK)) | + LDB_DATA_WIDTH_CH0_24 | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt) == 18) + __raw_writel((reg & ~(LDB_DATA_WIDTH_CH0_MASK | + LDB_DATA_WIDTH_CH1_MASK)) | + LDB_DATA_WIDTH_CH0_18 | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[0] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[1] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_mode_opt == LDB_SPL_DI0) + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI0, ldb.control_reg); + if (ldb.blank[0] == FB_BLANK_UNBLANK) { + ldb.ch_working[0] = true; + ldb.ch_working[1] = true; + } + break; + case LDB_DUL_DI1: + case LDB_SPL_DI1: + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt) == 24) + __raw_writel((reg & ~(LDB_DATA_WIDTH_CH0_MASK | + LDB_DATA_WIDTH_CH1_MASK)) | + LDB_DATA_WIDTH_CH0_24 | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt) == 18) + __raw_writel((reg & ~(LDB_DATA_WIDTH_CH0_MASK | + LDB_DATA_WIDTH_CH1_MASK)) | + LDB_DATA_WIDTH_CH0_18 | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[0] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[1] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_mode_opt == LDB_SPL_DI1) + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI1 | + LDB_CH1_MODE_EN_TO_DI1, ldb.control_reg); + if (ldb.blank[1] == FB_BLANK_UNBLANK) { + ldb.ch_working[0] = true; + ldb.ch_working[1] = true; + } + break; + default: + break; + } + break; + } + case FB_EVENT_BLANK: { + if (ldb.fbi[0] != fbi && ldb.fbi[1] != fbi) + return 0; + + if (*((int *)event->data) == ldb.blank[ipu_di]) + return 0; + + if (*((int *)event->data) == FB_BLANK_UNBLANK) + ldb_enable(ipu_di); + else + ldb_disable(ipu_di); + + ldb.blank[ipu_di] = *((int *)event->data); + break; + } + default: + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = ldb_fb_event, +}; + +static long mxc_ldb_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + uint32_t reg; + + switch (cmd) { + case LDB_BGREF_RMODE: + { + ldb_bgref_parm parm; + + if (copy_from_user(&parm, (ldb_bgref_parm *) arg, + sizeof(ldb_bgref_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + reg = __raw_readl(ldb.control_reg); + if (parm.bgref_mode == LDB_EXT_REF) + __raw_writel((reg & ~LDB_BGREF_RMODE_MASK) | + LDB_BGREF_RMODE_EXT, ldb.control_reg); + else if (parm.bgref_mode == LDB_INT_REF) + __raw_writel((reg & ~LDB_BGREF_RMODE_MASK) | + LDB_BGREF_RMODE_INT, ldb.control_reg); + spin_unlock(&ldb_lock); + break; + } + case LDB_VSYNC_POL: + { + ldb_vsync_parm parm; + + if (copy_from_user(&parm, (ldb_vsync_parm *) arg, + sizeof(ldb_vsync_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + reg = __raw_readl(ldb.control_reg); + if (parm.vsync_mode == LDB_VS_ACT_H) { + if (parm.di == 0) + __raw_writel((reg & + ~LDB_DI0_VS_POL_MASK) | + LDB_DI0_VS_POL_ACT_HIGH, + ldb.control_reg); + else + __raw_writel((reg & + ~LDB_DI1_VS_POL_MASK) | + LDB_DI1_VS_POL_ACT_HIGH, + ldb.control_reg); + } else if (parm.vsync_mode == LDB_VS_ACT_L) { + if (parm.di == 0) + __raw_writel((reg & + ~LDB_DI0_VS_POL_MASK) | + LDB_DI0_VS_POL_ACT_LOW, + ldb.control_reg); + else + __raw_writel((reg & + ~LDB_DI1_VS_POL_MASK) | + LDB_DI1_VS_POL_ACT_LOW, + ldb.control_reg); + + } + spin_unlock(&ldb_lock); + break; + } + case LDB_BIT_MAP: + { + ldb_bitmap_parm parm; + + if (copy_from_user(&parm, (ldb_bitmap_parm *) arg, + sizeof(ldb_bitmap_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + reg = __raw_readl(ldb.control_reg); + if (parm.bitmap_mode == LDB_BIT_MAP_SPWG) { + if (parm.channel == 0) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + } else if (parm.bitmap_mode == LDB_BIT_MAP_JEIDA) { + if (parm.channel == 0) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + } + spin_unlock(&ldb_lock); + break; + } + case LDB_DATA_WIDTH: + { + ldb_data_width_parm parm; + + if (copy_from_user(&parm, (ldb_data_width_parm *) arg, + sizeof(ldb_data_width_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + reg = __raw_readl(ldb.control_reg); + if (parm.data_width == 24) { + if (parm.channel == 0) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_24, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + } else if (parm.data_width == 18) { + if (parm.channel == 0) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_18, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + } + spin_unlock(&ldb_lock); + break; + } + case LDB_CHAN_MODE: + { + ldb_chan_mode_parm parm; + struct clk *pll4_clk; + unsigned long pll4_rate = 0; + + if (copy_from_user(&parm, (ldb_chan_mode_parm *) arg, + sizeof(ldb_chan_mode_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + + /* TODO:Set the correct pll4 rate for all situations */ + pll4_clk = clk_get(g_ldb_dev, "pll4"); + pll4_rate = clk_get_rate(pll4_clk); + pll4_rate = 455000000; + clk_set_rate(pll4_clk, pll4_rate); + clk_put(pll4_clk); + + reg = __raw_readl(ldb.control_reg); + switch (parm.channel_mode) { + case LDB_CHAN_MODE_SIN: + if (parm.di == 0) { + ldb.chan_mode_opt = LDB_SIN_DI0; + + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, + "ldb_di0_clk"); + clk_set_rate(ldb.ldb_di_clk[0], pll4_rate/7); + clk_put(ldb.ldb_di_clk[0]); + + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, + ldb.control_reg); + } else { + ldb.chan_mode_opt = LDB_SIN_DI1; + + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, + "ldb_di1_clk"); + clk_set_rate(ldb.ldb_di_clk[1], pll4_rate/7); + clk_put(ldb.ldb_di_clk[1]); + + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + } + break; + case LDB_CHAN_MODE_SEP: + ldb.chan_mode_opt = LDB_SEP; + + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + clk_set_rate(ldb.ldb_di_clk[0], pll4_rate/7); + clk_put(ldb.ldb_di_clk[0]); + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + clk_set_rate(ldb.ldb_di_clk[1], pll4_rate/7); + clk_put(ldb.ldb_di_clk[1]); + + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + break; + case LDB_CHAN_MODE_DUL: + case LDB_CHAN_MODE_SPL: + ldb.ldb_di_clk[0] = clk_get(g_ldb_dev, "ldb_di0_clk"); + ldb.ldb_di_clk[1] = clk_get(g_ldb_dev, "ldb_di1_clk"); + if (parm.di == 0) { + if (parm.channel_mode == LDB_CHAN_MODE_DUL) { + ldb.chan_mode_opt = LDB_DUL_DI0; + clk_set_rate(ldb.ldb_di_clk[0], + pll4_rate/7); + } else { + ldb.chan_mode_opt = LDB_SPL_DI0; + clk_set_rate(ldb.ldb_di_clk[0], + 2*pll4_rate/7); + clk_set_rate(ldb.ldb_di_clk[1], + 2*pll4_rate/7); + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI0, + ldb.control_reg); + } else { + if (parm.channel_mode == LDB_CHAN_MODE_DUL) { + ldb.chan_mode_opt = LDB_DUL_DI1; + clk_set_rate(ldb.ldb_di_clk[1], + pll4_rate/7); + } else { + ldb.chan_mode_opt = LDB_SPL_DI1; + clk_set_rate(ldb.ldb_di_clk[0], + 2*pll4_rate/7); + clk_set_rate(ldb.ldb_di_clk[1], + 2*pll4_rate/7); + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI1 | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + } + clk_put(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[1]); + break; + default: + ret = -EINVAL; + break; + } + spin_unlock(&ldb_lock); + break; + } + case LDB_ENABLE: + { + int ipu_di; + + if (copy_from_user(&ipu_di, (int *) arg, sizeof(int))) + return -EFAULT; + + ldb_enable(ipu_di); + break; + } + case LDB_DISABLE: + { + int ipu_di; + + if (copy_from_user(&ipu_di, (int *) arg, sizeof(int))) + return -EFAULT; + + ldb_disable(ipu_di); + break; + } + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int mxc_ldb_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int mxc_ldb_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int mxc_ldb_mmap(struct file *file, struct vm_area_struct *vma) +{ + return 0; +} + +static const struct file_operations mxc_ldb_fops = { + .owner = THIS_MODULE, + .open = mxc_ldb_open, + .mmap = mxc_ldb_mmap, + .release = mxc_ldb_release, + .unlocked_ioctl = mxc_ldb_ioctl +}; + +/*! + * This function is called by the driver framework to initialize the LDB + * device. + * + * @param dev The device structure for the LDB passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int ldb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res; + struct ldb_platform_data *plat_data = pdev->dev.platform_data; + uint32_t reg; + struct device *temp; + int mxc_ldb_major; + struct class *mxc_ldb_class; + + if (g_enable_ldb == false) + return -ENODEV; + + spin_lock_init(&ldb_lock); + + g_ldb_dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) + return -ENODEV; + + memset(&ldb, 0, sizeof(struct ldb_data)); + ldb.chan_mode_opt = g_chan_mode_opt; + ldb.chan_bit_map[0] = g_chan_bit_map[0]; + ldb.chan_bit_map[1] = g_chan_bit_map[1]; + + ldb.base_addr = res->start; + ldb_reg = ioremap(ldb.base_addr, res->end - res->start + 1); + ldb.control_reg = ldb_reg + 2; + + ldb.bgref_rmode = plat_data->ext_ref; + ldb.lvds_bg_reg = regulator_get(&pdev->dev, plat_data->lvds_bg_reg); + if (!IS_ERR(ldb.lvds_bg_reg)) { + regulator_set_voltage(ldb.lvds_bg_reg, 2500000, 2500000); + regulator_enable(ldb.lvds_bg_reg); + } + + reg = __raw_readl(ldb.control_reg); + if (ldb.bgref_rmode == LDB_EXT_REF) + __raw_writel((reg & ~LDB_BGREF_RMODE_MASK) | + LDB_BGREF_RMODE_EXT, ldb.control_reg); + else + __raw_writel((reg & ~LDB_BGREF_RMODE_MASK) | + LDB_BGREF_RMODE_INT, ldb.control_reg); + + mxc_ldb_major = register_chrdev(0, "mxc_ldb", &mxc_ldb_fops); + if (mxc_ldb_major < 0) { + dev_err(g_ldb_dev, "Unable to register MXC LDB as a char " + "device\n"); + ret = mxc_ldb_major; + goto err0; + } + + mxc_ldb_class = class_create(THIS_MODULE, "mxc_ldb"); + if (IS_ERR(mxc_ldb_class)) { + dev_err(g_ldb_dev, "Unable to create class for MXC LDB\n"); + ret = PTR_ERR(mxc_ldb_class); + goto err1; + } + + temp = device_create(mxc_ldb_class, NULL, MKDEV(mxc_ldb_major, 0), + NULL, "mxc_ldb"); + if (IS_ERR(temp)) { + dev_err(g_ldb_dev, "Unable to create class device for " + "MXC LDB\n"); + ret = PTR_ERR(temp); + goto err2; + } + + if (g_di0_used) { + mxcfb_register_mode(0, mxcfb_ldb_modedb, + mxcfb_ldb_modedb_sz, + MXC_DISP_SPEC_DEV); + mxcfb_register_presetup(0, ldb_fb_pre_setup); + } + if (g_di1_used) { + mxcfb_register_mode(1, mxcfb_ldb_modedb, + mxcfb_ldb_modedb_sz, + MXC_DISP_SPEC_DEV); + mxcfb_register_presetup(1, ldb_fb_pre_setup); + } + + ret = fb_register_client(&nb); + if (ret < 0) + goto err2; + + ldb.blank[0] = ldb.blank[1] = -1; + + return ret; +err2: + class_destroy(mxc_ldb_class); +err1: + unregister_chrdev(mxc_ldb_major, "mxc_ldb"); +err0: + iounmap(ldb_reg); + return ret; +} + +static int ldb_remove(struct platform_device *pdev) +{ + int i; + + __raw_writel(0, ldb.control_reg); + + for (i = 0; i < 2; i++) { + if (ldb.ch_working[i]) { + ldb.ldb_di_clk[i] = clk_get(g_ldb_dev, + i ? "ldb_di1_clk" : "ldb_di0_clk"); + clk_disable(ldb.ldb_di_clk[i]); + clk_put(ldb.ldb_di_clk[i]); + ldb.ch_working[i] = false; + } + } + + fb_unregister_client(&nb); + return 0; +} + +static int ldb_suspend(struct platform_device *pdev, pm_message_t state) +{ + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + case LDB_DUL_DI0: + case LDB_SPL_DI0: + if (ldb.blank[0] != FB_BLANK_UNBLANK) + ldb_disable(0); + break; + case LDB_SIN_DI1: + case LDB_DUL_DI1: + case LDB_SPL_DI1: + if (ldb.blank[1] != FB_BLANK_UNBLANK) + ldb_disable(1); + break; + case LDB_SEP: + if (ldb.blank[0] != FB_BLANK_UNBLANK) + ldb_disable(0); + if (ldb.blank[1] != FB_BLANK_UNBLANK) + ldb_disable(1); + break; + default: + break; + } + return 0; +} + +static int ldb_resume(struct platform_device *pdev) +{ + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + case LDB_DUL_DI0: + case LDB_SPL_DI0: + if (ldb.blank[0] == FB_BLANK_UNBLANK) + ldb_enable(0); + break; + case LDB_SIN_DI1: + case LDB_DUL_DI1: + case LDB_SPL_DI1: + if (ldb.blank[1] == FB_BLANK_UNBLANK) + ldb_enable(1); + break; + case LDB_SEP: + if (ldb.blank[0] == FB_BLANK_UNBLANK) + ldb_enable(0); + if (ldb.blank[1] == FB_BLANK_UNBLANK) + ldb_enable(1); + break; + default: + break; + } + return 0; +} + +static struct platform_driver mxcldb_driver = { + .driver = { + .name = "mxc_ldb", + }, + .probe = ldb_probe, + .remove = ldb_remove, + .suspend = ldb_suspend, + .resume = ldb_resume, +}; + +/* + * Parse user specified options (`ldb=') + * example: + * ldb=single(separate, dual or split),(di=0 or di=1), + * ch0_map=SPWG or JEIDA,ch1_map=SPWG or JEIDA + * + */ +static int __init ldb_setup(char *options) +{ + g_enable_ldb = true; + + if (!strlen(options)) + return 1; + else if (!strsep(&options, "=")) + return 1; + + if (!strncmp(options, "di0", 3)) + g_di0_used = true; + + if (!strncmp(options, "di1", 3)) + g_di1_used = true; + + if (!strncmp(options, "single", 6)) { + strsep(&options, ","); + if (!strncmp(options, "di=0", 4)) { + g_chan_mode_opt = LDB_SIN_DI0; + g_di0_used = true; + } else { + g_chan_mode_opt = LDB_SIN_DI1; + g_di1_used = true; + } + } else if (!strncmp(options, "separate", 8)) { + g_chan_mode_opt = LDB_SEP; + g_di0_used = true; + g_di1_used = true; + } else if (!strncmp(options, "dual", 4)) { + strsep(&options, ","); + if (!strncmp(options, "di=", 3)) { + if (simple_strtoul(options + 3, NULL, 0) == 0) { + g_chan_mode_opt = LDB_DUL_DI0; + g_di0_used = true; + } else { + g_chan_mode_opt = LDB_DUL_DI1; + g_di1_used = true; + } + } + } else if (!strncmp(options, "split", 5)) { + strsep(&options, ","); + if (!strncmp(options, "di=", 3)) { + if (simple_strtoul(options + 3, NULL, 0) == 0) { + g_chan_mode_opt = LDB_SPL_DI0; + g_di0_used = true; + } else { + g_chan_mode_opt = LDB_SPL_DI1; + g_di1_used = true; + } + } + } else + return 1; + + if ((strsep(&options, ",") != NULL) && + !strncmp(options, "ch0_map=", 8)) { + if (!strncmp(options + 8, "SPWG", 4)) + g_chan_bit_map[0] = LDB_BIT_MAP_SPWG; + else + g_chan_bit_map[0] = LDB_BIT_MAP_JEIDA; + } + + if (!(g_chan_mode_opt == LDB_SIN_DI0 || + g_chan_mode_opt == LDB_SIN_DI1) && + (strsep(&options, ",") != NULL) && + !strncmp(options, "ch1_map=", 8)) { + if (!strncmp(options + 8, "SPWG", 4)) + g_chan_bit_map[1] = LDB_BIT_MAP_SPWG; + else + g_chan_bit_map[1] = LDB_BIT_MAP_JEIDA; + } + + return 1; +} +__setup("ldb", ldb_setup); + +static int __init ldb_init(void) +{ + int ret; + + ret = platform_driver_register(&mxcldb_driver); + return 0; +} + +static void __exit ldb_uninit(void) +{ + platform_driver_unregister(&mxcldb_driver); +} + +module_init(ldb_init); +module_exit(ldb_uninit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC LDB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mx2fb.c b/drivers/video/mxc/mx2fb.c new file mode 100644 index 000000000000..0b967712e05d --- /dev/null +++ b/drivers/video/mxc/mx2fb.c @@ -0,0 +1,1349 @@ +/* + * Copyright (C) 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer_MX27 Framebuffer Driver for MX27. + */ + +/*! + * @file mx2fb.c + * + * @brief Frame buffer driver for MX27 ADS. + * + * @ingroup Framebuffer_MX27 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mx2fb.h" + +#define MX2FB_TYPE_BG 0 +#define MX2FB_TYPE_GW 1 + +extern void gpio_lcdc_active(void); +extern void gpio_lcdc_inactive(void); +extern void board_power_lcd(int on); + +static char *fb_mode; +static int fb_enabled; +static unsigned long default_bpp = 16; +static ATOMIC_NOTIFIER_HEAD(mx2fb_notifier_list); +static struct clk *lcdc_clk; +/*! + * @brief Structure containing the MX2 specific framebuffer information. + */ +struct mx2fb_info { + int type; + char *id; + int registered; + int blank; + unsigned long pseudo_palette[16]; +}; + +/* Framebuffer APIs */ +static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +static int mx2fb_set_par(struct fb_info *info); +static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info); +static int mx2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int mx2fb_blank(int blank_mode, struct fb_info *info); +static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg); + +/* Driver entries */ +int __init mx2fb_init(void); +void __exit mx2fb_exit(void); +#ifndef MODULE +static int __init mx2fb_setup(char *); +#endif + +/* Internal functions */ +static int __init _init_fbinfo(struct fb_info *info, + struct platform_device *pdev); +static int __init _install_fb(struct fb_info *info, + struct platform_device *pdev); +static void __exit _uninstall_fb(struct fb_info *info); +static int _map_video_memory(struct fb_info *info); +static void _unmap_video_memory(struct fb_info *info); +static void _set_fix(struct fb_info *info); +static void _enable_lcdc(struct fb_info *info); +static void _disable_lcdc(struct fb_info *info); +static void _enable_graphic_window(struct fb_info *info); +static void _disable_graphic_window(struct fb_info *info); +static void _update_lcdc(struct fb_info *info); +static void _request_irq(void); +static void _free_irq(void); + +#ifdef CONFIG_PM +static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state); +static int mx2fb_resume(struct platform_device *pdev); +#else +#define mx2fb_suspend 0 +#define mx2fb_resume 0 +#endif + +static int mx2fb_probe(struct platform_device *pdev); + +#ifdef CONFIG_FB_MXC_TVOUT +#include +/* + * FIXME: VGA mode is not defined by video_encoder.h + * while FS453 supports VGA output. + */ +#ifndef VIDEO_ENCODER_VGA +#define VIDEO_ENCODER_VGA 32 +#endif + +#define MODE_PAL "TV-PAL" +#define MODE_NTSC "TV-NTSC" +#define MODE_VGA "TV-VGA" + +extern int fs453_ioctl(unsigned int cmd, void *arg); +#endif + +struct mx2fb_info mx2fbi_bg = { + .type = MX2FB_TYPE_BG, + .id = "DISP0 BG", + .registered = 0, +}; + +static struct mx2fb_info mx2fbi_gw = { + .type = MX2FB_TYPE_GW, + .id = "DISP0 FG", + .registered = 0, +}; + +/*! Current graphic window information */ +static struct fb_gwinfo g_gwinfo = { + .enabled = 0, + .alpha_value = 255, + .ck_enabled = 0, + .ck_red = 0, + .ck_green = 0, + .ck_blue = 0, + .xpos = 0, + .ypos = 0, +}; + +/*! + * @brief Framebuffer information structures. + * There are up to 3 framebuffers: background, TVout, and graphic window. + * If graphic window is configured, it must be the last framebuffer. + */ +static struct fb_info mx2fb_info[] = { + {.par = &mx2fbi_bg}, + {.par = &mx2fbi_gw}, +}; + +/*! + * @brief This structure contains pointers to the power management + * callback functions. + */ +static struct platform_driver mx2fb_driver = { + .driver = { + .name = "mxc_sdc_fb", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = mx2fb_probe, + .suspend = mx2fb_suspend, + .resume = mx2fb_resume, +}; + +/*! + * @brief Framebuffer file operations + */ +static struct fb_ops mx2fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mx2fb_check_var, + .fb_set_par = mx2fb_set_par, + .fb_setcolreg = mx2fb_setcolreg, + .fb_blank = mx2fb_blank, + .fb_pan_display = mx2fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_ioctl = mx2fb_ioctl, +}; + +/*! + * @brief Validates a var passed in. + * + * @param var Frame buffer variable screen structure + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Checks to see if the hardware supports the state requested by var passed + * in. This function does not alter the hardware state! If the var passed in + * is slightly off by what the hardware can support then we alter the var + * PASSED in to what we can do. If the hardware doesn't support mode change + * a -EINVAL will be returned by the upper layers. + * + */ +static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + unsigned long htotal, vtotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xoffset < 0) + var->xoffset = 0; + + if (var->yoffset < 0) + var->yoffset = 0; + + if (var->xoffset + info->var.xres > info->var.xres_virtual) + var->xoffset = info->var.xres_virtual - info->var.xres; + + if (var->yoffset + info->var.yres > info->var.yres_virtual) + var->yoffset = info->var.yres_virtual - info->var.yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* Copy nonstd field to/from sync for fbset usage */ + var->sync |= var->nonstd; + var->nonstd |= var->sync; + + return 0; +} + +/*! + * @brief Alters the hardware state. + * + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Zero on success others on failure + * + * Using the fb_var_screeninfo in fb_info we set the resolution of this + * particular framebuffer. This function alters the fb_fix_screeninfo stored + * in fb_info. It doesn't not alter var in fb_info since we are using that + * data. This means we depend on the data in var inside fb_info to be + * supported by the hardware. mx2fb_check_var is always called before + * mx2fb_set_par to ensure this. + */ +static int mx2fb_set_par(struct fb_info *info) +{ + unsigned long len; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + _set_fix(info); + + len = info->var.yres_virtual * info->fix.line_length; + if (len > info->fix.smem_len) { + if (info->fix.smem_start) + _unmap_video_memory(info); + + /* Memory allocation for framebuffer */ + if (_map_video_memory(info)) { + dev_err(info->device, "Unable to allocate fb memory\n"); + return -ENOMEM; + } + } + + _update_lcdc(info); + if (info->fbops->fb_blank) + info->fbops->fb_blank(mx2fbi->blank, info); + + return 0; +} + +/*! + * @brief Sets a color register. + * + * @param regno Which register in the CLUT we are programming + * @param red The red value which can be up to 16 bits wide + * @param green The green value which can be up to 16 bits wide + * @param blue The blue value which can be up to 16 bits wide. + * @param transp If supported the alpha value which can be up to + * 16 bits wide. + * @param info Frame buffer info structure + * + * @return Negative errno on error, or zero on success. + * + * Set a single color register. The values supplied have a 16 bit magnitude + * which needs to be scaled in this function for the hardware. Things to take + * into consideration are how many color registers, if any, are supported with + * the current color visual. With truecolor mode no color palettes are + * supported. Here a psuedo palette is created which we store the value in + * pseudo_palette in struct fb_info. For pseudocolor mode we have a limited + * color palette. + */ +static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + u32 v; + +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); +#undef CNVT_TOHW + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + pal[regno] = v; + ret = 0; + } + break; + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/*! + * @brief Pans the display. + * + * @param var Frame buffer variable screen structure + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Pan (or wrap, depending on the `vmode' field) the display using the + * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values + * don't fit, return -EINVAL. + */ +static int mx2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) { + return 0; /* No change, do nothing */ + } + + if (var->xoffset < 0 || var->yoffset < 0 + || var->xoffset + info->var.xres > info->var.xres_virtual + || var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + _update_lcdc(info); + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + return 0; +} + +/*! + * @brief Blanks the display. + * + * @param blank_mode The blank mode we want. + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Blank the screen if blank_mode != 0, else unblank. Return 0 if blanking + * succeeded, != 0 if un-/blanking failed. + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + */ +static int mx2fb_blank(int blank_mode, struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + dev_dbg(info->device, "blank mode = %d\n", blank_mode); + + mx2fbi->blank = blank_mode; + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + _disable_lcdc(info); + break; + case FB_BLANK_UNBLANK: + _enable_lcdc(info); + break; + } + + return 0; +} + +/*! + * @brief Ioctl function to support customized ioctl operations. + * + * @param info Framebuffer structure that represents a single frame buffer + * @param cmd The command number + * @param arg Argument which depends on cmd + * + * @return Negative errno on error, or zero on success. + */ +static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + struct mx2fb_gbl_alpha ga; + struct mx2fb_color_key ck; + + switch (cmd) { + case MX2FB_SET_GBL_ALPHA: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&ga, (void *)arg, sizeof(ga))) + return -EFAULT; + + g_gwinfo.alpha_value = ga.alpha; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; + case MX2FB_SET_CLR_KEY: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&ck, (void *)arg, sizeof(ck))) + return -EFAULT; + + g_gwinfo.ck_enabled = ck.enable; + g_gwinfo.ck_red = (ck.color_key & 0x003F0000) >> 16; + g_gwinfo.ck_green = (ck.color_key & 0x00003F00) >> 8; + g_gwinfo.ck_blue = ck.color_key & 0x0000003F; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; + case FBIOGET_GWINFO: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* get graphic window information */ + if (copy_to_user((void *)arg, (void *)&g_gwinfo, + sizeof(g_gwinfo))) + return -EFAULT; + break; + case FBIOPUT_GWINFO: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&g_gwinfo, (void *)arg, + sizeof(g_gwinfo))) + return -EFAULT; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; +#ifdef CONFIG_FB_MXC_TVOUT + case ENCODER_GET_CAPABILITIES:{ + int ret; + struct video_encoder_capability cap; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + ret = fs453_ioctl(cmd, &cap); + if (ret) + return ret; + + if (copy_to_user((void *)arg, &cap, sizeof(cap))) + return -EFAULT; + break; + } + case ENCODER_SET_NORM:{ + int ret; + unsigned long mode; + char *smode; + struct fb_var_screeninfo var; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + if (copy_from_user(&mode, (void *)arg, sizeof(mode))) + return -EFAULT; + ret = fs453_ioctl(cmd, &mode); + if (ret) + return ret; + + if (mode == VIDEO_ENCODER_PAL) + smode = MODE_PAL; + else if (mode == VIDEO_ENCODER_NTSC) + smode = MODE_NTSC; + else + smode = MODE_VGA; + + var = info->var; + var.nonstd = 0; + ret = fb_find_mode(&var, info, smode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp); + /* check for specified mode not found */ + if ((ret != 1) && (ret != 2)) + return -ENODEV; + + info->var = var; + fb_mode = smode; + return mx2fb_set_par(info); + } + case ENCODER_SET_INPUT: + case ENCODER_SET_OUTPUT: + case ENCODER_ENABLE_OUTPUT:{ + unsigned long varg; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + if (copy_from_user(&varg, (void *)arg, sizeof(varg))) + return -EFAULT; + return fs453_ioctl(cmd, &varg); + } +#endif + default: + dev_dbg(info->device, "Unknown ioctl command (0x%08X)\n", cmd); + return -EINVAL; + } + + return 0; +} + +/*! + * @brief Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + * @return Negative errno on error, or zero on success. + */ +static void _set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + strncpy(fix->id, mx2fbi->id, strlen(mx2fbi->id)); + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; +} + +/*! + * @brief Initialize framebuffer information structure. + * + * @param info framebuffer information pointer + * @param pdev pointer to struct device + * @return Negative errno on error, or zero on success. + */ +static int __init _init_fbinfo(struct fb_info *info, + struct platform_device *pdev) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + info->device = &pdev->dev; + info->var.activate = FB_ACTIVATE_NOW; + info->fbops = &mx2fb_ops; + info->flags = FBINFO_FLAG_DEFAULT; + info->pseudo_palette = &mx2fbi->pseudo_palette; + + /* Allocate colormap */ + fb_alloc_cmap(&info->cmap, 16, 0); + + return 0; +} + +/*! + * @brief Install framebuffer into the system. + * + * @param info framebuffer information pointer + * @param pdev pointer to struct device + * @return Negative errno on error, or zero on success. + */ +static int __init _install_fb(struct fb_info *info, + struct platform_device *pdev) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (_init_fbinfo(info, pdev)) + return -EINVAL; + + if (fb_mode == 0) + fb_mode = pdev->dev.platform_data; + + if (!fb_find_mode(&info->var, info, fb_mode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp)) { + fb_dealloc_cmap(&info->cmap); + return -EBUSY; + } + + /* Default Y virtual size is 2x panel size */ + /* info->var.yres_virtual = info->var.yres << 1; */ + + if (mx2fbi->type == MX2FB_TYPE_GW) + mx2fbi->blank = FB_BLANK_NORMAL; + else + mx2fbi->blank = FB_BLANK_UNBLANK; + + if (mx2fb_set_par(info)) { + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + if (register_framebuffer(info) < 0) { + _unmap_video_memory(info); + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + mx2fbi->registered = 1; + dev_info(info->device, "fb%d: %s fb device registered successfully.\n", + info->node, info->fix.id); + + return 0; +} + +/*! + * @brief Uninstall framebuffer from the system. + * + * @param info framebuffer information pointer + */ +static void __exit _uninstall_fb(struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (!mx2fbi->registered) + return; + + unregister_framebuffer(info); + _unmap_video_memory(info); + if (&info->cmap) + fb_dealloc_cmap(&info->cmap); + + mx2fbi->registered = 0; +} + +/*! + * @brief Allocate memory for framebuffer. + * + * @param info framebuffer information pointer + * @return Negative errno on error, or zero on success. + */ +static int _map_video_memory(struct fb_info *info) +{ + info->fix.smem_len = info->fix.line_length * info->var.yres_virtual; + info->screen_base = dma_alloc_coherent(0, + info->fix.smem_len, + (dma_addr_t *) &info->fix.smem_start, + GFP_DMA | GFP_KERNEL); + + if (info->screen_base == 0) { + dev_err(info->device, "Unable to allocate fb memory\n"); + return -EBUSY; + } + dev_dbg(info->device, "Allocated fb @ paddr=0x%08lX, size=%d.\n", + info->fix.smem_start, info->fix.smem_len); + + info->screen_size = info->fix.smem_len; + + /* Clear the screen */ + memset((char *)info->screen_base, 0, info->fix.smem_len); + + return 0; +} + +/*! + * @brief Release memory for framebuffer. + * @param info framebuffer information pointer + */ +static void _unmap_video_memory(struct fb_info *info) +{ + dma_free_coherent(0, info->fix.smem_len, info->screen_base, + (dma_addr_t) info->fix.smem_start); + + info->screen_base = 0; + info->fix.smem_start = 0; + info->fix.smem_len = 0; +} + +/*! + * @brief Enable LCD controller. + * @param info framebuffer information pointer + */ +static void _enable_lcdc(struct fb_info *info) +{ + static int first_enable = 1; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + /* + * Graphic window can only be enabled while the HCLK to the LCDC + * is disabled. Once enabled it can subsequently be disabled and + * enabled without turning off the HCLK. + * The graphic window is enabled and then disabled here. So next + * time to enable graphic window the HCLK to LCDC does not need + * to be disabled, and the flicker (due to disabling of HCLK to + * LCDC) is avoided. + */ + if (first_enable) { + _enable_graphic_window(info); + _disable_graphic_window(info); + first_enable = 0; + } + + if (mx2fbi->type == MX2FB_TYPE_GW) + _enable_graphic_window(info); + else if (!fb_enabled) { + clk_enable(lcdc_clk); + gpio_lcdc_active(); + board_power_lcd(1); + fb_enabled++; +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + unsigned long mode = 0; + + if (strcmp(fb_mode, MODE_VGA) == 0) + mode = VIDEO_ENCODER_VGA; + else if (strcmp(fb_mode, MODE_NTSC) == 0) + mode = VIDEO_ENCODER_NTSC; + else if (strcmp(fb_mode, MODE_PAL) == 0) + mode = VIDEO_ENCODER_PAL; + if (mode) + fs453_ioctl(ENCODER_SET_NORM, &mode); + } +#endif + } +} + +/*! + * @brief Disable LCD controller. + * @param info framebuffer information pointer + */ +static void _disable_lcdc(struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (mx2fbi->type == MX2FB_TYPE_GW) + _disable_graphic_window(info); + else { + if (fb_enabled) { + gpio_lcdc_inactive(); + board_power_lcd(0); + clk_disable(lcdc_clk); + fb_enabled = 0; + } +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + int enable = 0; + + if ((strcmp(fb_mode, MODE_VGA) == 0) + || (strcmp(fb_mode, MODE_NTSC) == 0) + || (strcmp(fb_mode, MODE_PAL) == 0)) + fs453_ioctl(ENCODER_ENABLE_OUTPUT, &enable); + } +#endif + } +} + +/*! + * @brief Enable graphic window. + * @param info framebuffer information pointer + */ +static void _enable_graphic_window(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + + g_gwinfo.enabled = 1; + + g_gwinfo.base = (var->yoffset * var->xres_virtual + var->xoffset); + g_gwinfo.base *= (var->bits_per_pixel) / 8; + g_gwinfo.base += info->fix.smem_start; + + g_gwinfo.xres = var->xres; + g_gwinfo.yres = var->yres; + g_gwinfo.xres_virtual = var->xres_virtual; + + mx2_gw_set(&g_gwinfo); +} + +/*! + * @brief Disable graphic window. + * @param info framebuffer information pointer + */ +static void _disable_graphic_window(struct fb_info *info) +{ + unsigned long i = 0; + + g_gwinfo.enabled = 0; + + /* + * Set alpha value to zero and reduce gw size, otherwise the graphic + * window will not be able to be enabled again. + */ + __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & 0x00FFFFFF, + LCDC_REG(LCDC_LGWCR)); + __raw_writel(((16 >> 4) << 20) + 16, LCDC_REG(LCDC_LGWSR)); + while (i < 1000) + i++; + + /* Now disable graphic window */ + __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & ~0x00400000, + LCDC_REG(LCDC_LGWCR)); + + dev_dbg(info->device, "Graphic window disabled.\n"); +} + +/*! + * @brief Setup graphic window properties. + * @param gwinfo graphic window information pointer + */ +void mx2_gw_set(struct fb_gwinfo *gwinfo) +{ + int width, height, xpos, ypos; + int width_bg, height_bg; + /* Graphic window control register */ + unsigned long lgwcr = 0x00400000; + + if (!gwinfo->enabled) { + _disable_graphic_window(0); + return; + } + + /* Graphic window start address register */ + __raw_writel(gwinfo->base, LCDC_REG(LCDC_LGWSAR)); + + /* + * The graphic window width, height, x position and y position + * must be synced up width the background window, otherwise there + * may be flickering. + */ + width_bg = (__raw_readl(LCDC_REG(LCDC_LSR)) & 0x03F00000) >> 16; + height_bg = __raw_readl(LCDC_REG(LCDC_LSR)) & 0x000003FF; + + width = (gwinfo->xres > width_bg) ? width_bg : gwinfo->xres; + height = (gwinfo->yres > height_bg) ? height_bg : gwinfo->yres; + + xpos = gwinfo->xpos; + ypos = gwinfo->ypos; + + if (xpos + width > width_bg) + xpos = width_bg - width; + if (ypos + height > height_bg) + ypos = height_bg - height; + + /* Graphic window size register */ + __raw_writel(((width >> 4) << 20) + height, LCDC_REG(LCDC_LGWSR)); + + /* Graphic window virtual page width register */ + __raw_writel(gwinfo->xres_virtual >> 1, LCDC_REG(LCDC_LGWVPWR)); + + /* Graphic window position register */ + __raw_writel(((xpos & 0x000003FF) << 16) | (ypos & 0x000003FF), + LCDC_REG(LCDC_LGWPR)); + + /* Graphic window panning offset register */ + __raw_writel(0, LCDC_REG(LCDC_LGWPOR)); + + /* Graphic window DMA control register */ + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + __raw_writel(0x00040060, LCDC_REG(LCDC_LGWDCR)); + else + __raw_writel(0x00020010, LCDC_REG(LCDC_LGWDCR)); + + /* Graphic window control register */ + lgwcr |= (gwinfo->alpha_value & 0x000000FF) << 24; + lgwcr |= gwinfo->ck_enabled ? 0x00800000 : 0; + lgwcr |= gwinfo->vs_reversed ? 0x00200000 : 0; + + /* + * Color keying value + * Todo: assume always use RGB565 + */ + lgwcr |= (gwinfo->ck_red & 0x0000003F) << 12; + lgwcr |= (gwinfo->ck_green & 0x0000003F) << 6; + lgwcr |= gwinfo->ck_blue & 0x0000003F; + + __raw_writel(lgwcr, LCDC_REG(LCDC_LGWCR)); + + pr_debug("Graphic window enabled.\n"); +} +EXPORT_SYMBOL(mx2_gw_set); + +/*! + * @brief Update LCDC registers + * @param info framebuffer information pointer + */ +static void _update_lcdc(struct fb_info *info) +{ + unsigned long base; + unsigned long perclk, pcd, pcr; + struct fb_var_screeninfo *var = &info->var; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (mx2fbi->type == MX2FB_TYPE_GW) { + _enable_graphic_window(info); + return; + } + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + /* Screen start address register */ + __raw_writel(base, LCDC_REG(LCDC_LSSAR)); + + /* Size register */ + dev_dbg(info->device, "xres = %d, yres = %d\n", + info->var.xres, info->var.yres); + __raw_writel(((info->var.xres >> 4) << 20) + info->var.yres, + LCDC_REG(LCDC_LSR)); + + /* Virtual page width register */ + __raw_writel(info->var.xres_virtual >> 1, LCDC_REG(LCDC_LVPWR)); + + /* To setup LCDC pixel clock */ + perclk = clk_round_rate(lcdc_clk, 134000000); + if (clk_set_rate(lcdc_clk, perclk)) { + printk(KERN_INFO "mx2fb: Unable to set clock to %lu\n", perclk); + perclk = clk_get_rate(lcdc_clk); + } + + /* Calculate pixel clock divider, and round to the nearest integer */ + pcd = (perclk * 8 / (PICOS2KHZ(var->pixclock) * 1000UL) + 4) / 8; + if (--pcd > 0x3F) + pcd = 0x3F; + + /* Panel configuration register */ + pcr = 0xFA008B80 | pcd; + pcr |= (var->sync & FB_SYNC_CLK_LAT_FALL) ? 0x00200000 : 0; + pcr |= (var->sync & FB_SYNC_DATA_INVERT) ? 0x01000000 : 0; + pcr |= (var->sync & FB_SYNC_SHARP_MODE) ? 0x00000040 : 0; + pcr |= (var->sync & FB_SYNC_OE_LOW_ACT) ? 0x00100000 : 0; + __raw_writel(pcr, LCDC_REG(LCDC_LPCR)); + + /* Horizontal and vertical configuration register */ + __raw_writel(((var->hsync_len - 1) << 26) + + ((var->right_margin - 1) << 8) + + (var->left_margin - 3), LCDC_REG(LCDC_LHCR)); + __raw_writel((var->vsync_len << 26) + + (var->lower_margin << 8) + + var->upper_margin, LCDC_REG(LCDC_LVCR)); + + /* Sharp configuration register */ + __raw_writel(0x00120300, LCDC_REG(LCDC_LSCR)); + + /* Refresh mode control reigster */ + __raw_writel(0x00000000, LCDC_REG(LCDC_LRMCR)); + + /* DMA control register */ + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + __raw_writel(0x00040060, LCDC_REG(LCDC_LDCR)); + else + __raw_writel(0x00020010, LCDC_REG(LCDC_LDCR)); +} + +/*! + * @brief Set LCD brightness + * @param level brightness level + */ +void mx2fb_set_brightness(uint8_t level) +{ + /* Set LCDC PWM contract control register */ + __raw_writel(0x00A90300 | level, LCDC_REG(LCDC_LPCCR)); +} +EXPORT_SYMBOL(mx2fb_set_brightness); + +/* + * @brief LCDC interrupt handler + */ +static irqreturn_t mx2fb_isr(int irq, void *dev_id) +{ + struct fb_event event; + unsigned long status = __raw_readl(LCDC_REG(LCDC_LISR)); + + if (status & MX2FB_INT_EOF) { + event.info = &mx2fb_info[0]; + atomic_notifier_call_chain(&mx2fb_notifier_list, + FB_EVENT_MXC_EOF, &event); + } + + if (status & MX2FB_INT_GW_EOF) { + event.info = &mx2fb_info[1]; + atomic_notifier_call_chain(&mx2fb_notifier_list, + FB_EVENT_MXC_EOF, &event); + } + + return IRQ_HANDLED; +} + +/*! + * @brief Config and request LCDC interrupt + */ +static void _request_irq(void) +{ + unsigned long status; + unsigned long flags; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + if (request_irq(MXC_INT_LCDC, mx2fb_isr, 0, "LCDC", 0)) + pr_info("Request LCDC IRQ failed.\n"); + else { + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Enable interrupt in case client has registered */ + if (mx2fb_notifier_list.head != NULL) { + unsigned long status; + unsigned long ints = MX2FB_INT_EOF; + + ints |= MX2FB_INT_GW_EOF; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + /* Configure interrupt condition for EOF */ + __raw_writel(0x0, LCDC_REG(LCDC_LICR)); + + /* Enable EOF and graphic window EOF interrupt */ + __raw_writel(ints, LCDC_REG(LCDC_LIER)); + } + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + } +} + +/*! + * @brief Free LCDC interrupt handler + */ +static void _free_irq(void) +{ + /* Disable all LCDC interrupt */ + __raw_writel(0x0, LCDC_REG(LCDC_LIER)); + + free_irq(MXC_INT_LCDC, 0); +} + +/*! + * @brief Register a client notifier + * @param nb notifier block to callback on events + */ +int mx2fb_register_client(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + ret = atomic_notifier_chain_register(&mx2fb_notifier_list, nb); + + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Enable interrupt in case client has registered */ + if (mx2fb_notifier_list.head != NULL) { + unsigned long status; + unsigned long ints = MX2FB_INT_EOF; + + ints |= MX2FB_INT_GW_EOF; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + /* Configure interrupt condition for EOF */ + __raw_writel(0x0, LCDC_REG(LCDC_LICR)); + + /* Enable EOF and graphic window EOF interrupt */ + __raw_writel(ints, LCDC_REG(LCDC_LIER)); + } + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + + return ret; +} +EXPORT_SYMBOL(mx2fb_register_client); + +/*! + * @brief Unregister a client notifier + * @param nb notifier block to callback on events + */ +int mx2fb_unregister_client(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + ret = atomic_notifier_chain_unregister(&mx2fb_notifier_list, nb); + + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Mask interrupt in case no client registered */ + if (mx2fb_notifier_list.head == NULL) + __raw_writel(0x0, LCDC_REG(LCDC_LIER)); + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + + return ret; +} +EXPORT_SYMBOL(mx2fb_unregister_client); + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * @brief Suspends the framebuffer and blanks the screen. + * Power management support + */ +static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + _disable_lcdc(&mx2fb_info[0]); + + return 0; +} + +/*! + * @brief Resumes the framebuffer and unblanks the screen. + * Power management support + */ +static int mx2fb_resume(struct platform_device *pdev) +{ + _enable_lcdc(&mx2fb_info[0]); + + return 0; +} + +#endif /* CONFIG_PM */ + +/*! + * @brief Probe routine for the framebuffer driver. It is called during the + * driver binding process. + * + * @return Appropriate error code to the kernel common code + */ +static int mx2fb_probe(struct platform_device *pdev) +{ + int ret, i; + + lcdc_clk = clk_get(&pdev->dev, "lcdc_clk"); + + for (i = 0; i < sizeof(mx2fb_info) / sizeof(struct fb_info); i++) { + ret = _install_fb(&mx2fb_info[i], pdev); + if (ret) { + dev_err(&pdev->dev, + "Failed to register framebuffer %d\n", i); + return ret; + } + } + _request_irq(); + + return 0; +} + +/*! + * @brief Initialization + */ +int __init mx2fb_init(void) +{ + /* + * For kernel boot options (in 'video=xxxfb:' format) + */ +#ifndef MODULE + { + char *option; + + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mx2fb_setup(option); + } +#endif + return platform_driver_register(&mx2fb_driver); +} + +/*! + * @brief Cleanup + */ +void __exit mx2fb_exit(void) +{ + int i; + + _free_irq(); + for (i = sizeof(mx2fb_info) / sizeof(struct fb_info); i > 0; i--) + _uninstall_fb(&mx2fb_info[i - 1]); + + platform_driver_unregister(&mx2fb_driver); +} + +#ifndef MODULE +/*! + * @brief Setup + * Parse user specified options + * Example: video=mxcfb:240x320,bpp=16,Sharp-QVGA + */ +static int __init mx2fb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + fb_mode = 0; + fb_enabled = 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + + return 0; +} +#endif + +/* Modularization */ +module_init(mx2fb_init); +module_exit(mx2fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX2 framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mx2fb.h b/drivers/video/mxc/mx2fb.h new file mode 100644 index 000000000000..86c7abfd0c0f --- /dev/null +++ b/drivers/video/mxc/mx2fb.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2004-2007, 2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx2fb.h + * + * @brief Header file for the MX27 Frame buffer + * + * @ingroup Framebuffer + */ +#ifndef __MX2FB_H__ +#define __MX2FB_H__ + +/*! @brief MX27 LCDC graphic window information */ +struct fb_gwinfo { + /*! Non-zero if graphic window is enabled */ + __u32 enabled; + + /* The fields below are valid only when graphic window is enabled */ + + /*! Graphic window alpha value from 0 to 255 */ + __u32 alpha_value; + + /*! Non-zero if graphic window color keying is enabled. */ + __u32 ck_enabled; + + /* + * The fields ck_red, ck_green and ck_blue are valid only when + * graphic window and the color keying are enabled. They are the + * color component of graphic window color keying. + */ + + /*! Color keying red component */ + __u32 ck_red; + + /*! Color keying green component */ + __u32 ck_green; + + /*! Color keying blue component */ + __u32 ck_blue; + + /*! Graphic window x position */ + __u32 xpos; + + /*! Graphic window y position */ + __u32 ypos; + + /*! Non-zero if graphic window vertical scan in reverse direction. */ + __u32 vs_reversed; + + /* + * The following fields are valid for FBIOGET_GWINFO and + * mx2_gw_set(). FBIOPUT_GWINFO ignores these fields. + */ + __u32 base; /* Graphic window start address */ + __u32 xres; /* Visible x resolution */ + __u32 yres; /* Visible y resolution */ + __u32 xres_virtual; /* Virtual x resolution */ +}; + +/* 0x46E0-0x46FF are reserved for MX27 */ +#define FBIOGET_GWINFO 0x46E0 /*!< Get graphic window information */ +#define FBIOPUT_GWINFO 0x46E1 /*!< Set graphic window information */ + +struct mx2fb_gbl_alpha { + int enable; + int alpha; +}; + +struct mx2fb_color_key { + int enable; + __u32 color_key; +}; + +#define MX2FB_SET_GBL_ALPHA _IOW('M', 0, struct mx2fb_gbl_alpha) +#define MX2FB_SET_CLR_KEY _IOW('M', 1, struct mx2fb_color_key) +#define MX2FB_WAIT_FOR_VSYNC _IOW('F', 0x20, u_int32_t) + +#ifdef __KERNEL__ + +/* + * LCDC register definitions + */ +#define LCDC_LSSAR 0x00 +#define LCDC_LSR 0x04 +#define LCDC_LVPWR 0x08 +#define LCDC_LCPR 0x0C +#define LCDC_LCWHBR 0x10 +#define LCDC_LCCMR 0x14 +#define LCDC_LPCR 0x18 +#define LCDC_LHCR 0x1C +#define LCDC_LVCR 0x20 +#define LCDC_LPOR 0x24 +#define LCDC_LSCR 0x28 +#define LCDC_LPCCR 0x2C +#define LCDC_LDCR 0x30 +#define LCDC_LRMCR 0x34 +#define LCDC_LICR 0x38 +#define LCDC_LIER 0x3C +#define LCDC_LISR 0x40 +#define LCDC_LGWSAR 0x50 +#define LCDC_LGWSR 0x54 +#define LCDC_LGWVPWR 0x58 +#define LCDC_LGWPOR 0x5C +#define LCDC_LGWPR 0x60 +#define LCDC_LGWCR 0x64 +#define LCDC_LGWDCR 0x68 +#define LCDC_LAUSCR 0x80 +#define LCDC_LAUSCCR 0x84 + +#define LCDC_REG(reg) (IO_ADDRESS(LCDC_BASE_ADDR) + reg) + +#define MX2FB_INT_BOF 0x0001 /* Beginning of Frame */ +#define MX2FB_INT_EOF 0x0002 /* End of Frame */ +#define MX2FB_INT_ERR_RES 0x0004 /* Error Response */ +#define MX2FB_INT_UDR_ERR 0x0008 /* Under Run Error */ +#define MX2FB_INT_GW_BOF 0x0010 /* Graphic Window BOF */ +#define MX2FB_INT_GW_EOF 0x0020 /* Graphic Window EOF */ +#define MX2FB_INT_GW_ERR_RES 0x0040 /* Graphic Window ERR_RES */ +#define MX2FB_INT_GW_UDR_ERR 0x0080 /* Graphic Window UDR_ERR */ + +#define FB_EVENT_MXC_EOF 0x8001 /* End of Frame event */ + +int mx2fb_register_client(struct notifier_block *nb); +int mx2fb_unregister_client(struct notifier_block *nb); + +void mx2_gw_set(struct fb_gwinfo *gwinfo); + +#endif /* __KERNEL__ */ + +#endif /* __MX2FB_H__ */ diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c new file mode 100644 index 000000000000..1102a640ed71 --- /dev/null +++ b/drivers/video/mxc/mxc_edid.c @@ -0,0 +1,395 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.c + * + * @brief MXC EDID driver + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../edid.h" + +#define MXC_EDID_LENGTH (EDID_LENGTH*2) + +#undef DEBUG /* define this for verbose EDID parsing output */ + +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +struct mxc_ddc_data { + struct platform_device *pdev; + struct i2c_client *client; + struct delayed_work det_work; + struct fb_info *fbi; + struct mxc_edid_cfg edid_cfg; + u8 cable_plugin; + u8 edid[MXC_EDID_LENGTH]; + + u32 di; + void (*init)(void); + int (*update)(void); +} mxc_ddc; + +static bool g_enable_ddc; + +void mxc_edid_parse_ext_blk(unsigned char *edid, + struct mxc_edid_cfg *cfg, + struct fb_monspecs *specs) +{ + unsigned char index = 0x0; + + if (edid[index++] != 0x2) /* only support cea ext block now */ + return; + if (edid[index++] != 0x3) /* only support version 3*/ + return; + + cfg->cea_underscan = (edid[index] >> 7) & 0x1; + cfg->cea_basicaudio = (edid[index] >> 6) & 0x1; + cfg->cea_ycbcr444 = (edid[index] >> 5) & 0x1; + cfg->cea_ycbcr422 = (edid[index] >> 4) & 0x1; + + return fb_edid_add_monspecs(edid, specs); +} + +/* make sure edid has 256 bytes*/ +int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi) +{ + u8 buf0[2] = {0, 0}; + int dat = 0; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .len = 1, + .buf = buf0, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + if (adp == NULL) + return -EINVAL; + + memset(edid, 0, 256); + memset(cfg, 0, sizeof(struct mxc_edid_cfg)); + + buf0[0] = 0x00; + dat = i2c_transfer(adp, msg, 2); + + /* If 0x50 fails, try 0x37. */ + if (edid[1] == 0x00) { + msg[0].addr = msg[1].addr = 0x37; + dat = i2c_transfer(adp, msg, 2); + if (dat < 0) + return dat; + } + + if (edid[1] == 0x00) + return -ENOENT; + + /* edid first block parsing */ + memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); + fb_edid_to_monspecs(edid, &fbi->monspecs); + + /* need read ext block? Only support one more blk now*/ + if (edid[0x7E]) { + if (edid[0x7E] > 1) + DPRINTK("Edid has %d ext block, \ + but now only support 1 ext blk\n", edid[0x7E]); + buf0[0] = 0x80; + msg[1].buf = edid + EDID_LENGTH; + dat = i2c_transfer(adp, msg, 2); + if (dat < 0) + return dat; + + /* edid ext block parsing */ + mxc_edid_parse_ext_blk(edid + 128, cfg, &fbi->monspecs); + } + + return 0; +} +EXPORT_SYMBOL(mxc_edid_read); + +static ssize_t mxc_ddc_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (mxc_ddc.cable_plugin == 0) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, mxc_ddc_show_state, NULL); + +static ssize_t mxc_ddc_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, j, len = 0; + + for (j = 0; j < MXC_EDID_LENGTH/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + mxc_ddc.edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, mxc_ddc_show_edid, NULL); + +static void det_worker(struct work_struct *work) +{ + char event_string[16]; + char *envp[] = { event_string, NULL }; + + /* cable connection changes */ + if (mxc_ddc.update()) { + mxc_ddc.cable_plugin = 1; + sprintf(event_string, "EVENT=plugin"); + + /* make sure fb is powerdown */ + console_lock(); + fb_blank(mxc_ddc.fbi, FB_BLANK_POWERDOWN); + console_unlock(); + + if (mxc_edid_read(mxc_ddc.client->adapter, mxc_ddc.client->addr, + mxc_ddc.edid, &mxc_ddc.edid_cfg, mxc_ddc.fbi) < 0) + dev_err(&mxc_ddc.client->dev, + "MXC ddc: read edid fail\n"); + else { + if (mxc_ddc.fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + fb_destroy_modelist(&mxc_ddc.fbi->modelist); + + for (i = 0; i < mxc_ddc.fbi->monspecs.modedb_len; i++) + fb_add_videomode(&mxc_ddc.fbi->monspecs.modedb[i], + &mxc_ddc.fbi->modelist); + + fb_var_to_videomode(&m, &mxc_ddc.fbi->var); + mode = fb_find_nearest_mode(&m, + &mxc_ddc.fbi->modelist); + + fb_videomode_to_var(&mxc_ddc.fbi->var, mode); + + mxc_ddc.fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + mxc_ddc.fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(mxc_ddc.fbi, &mxc_ddc.fbi->var); + mxc_ddc.fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + } + + console_lock(); + fb_blank(mxc_ddc.fbi, FB_BLANK_UNBLANK); + console_unlock(); + } + } else { + mxc_ddc.cable_plugin = 0; + sprintf(event_string, "EVENT=plugout"); + console_lock(); + fb_blank(mxc_ddc.fbi, FB_BLANK_POWERDOWN); + console_unlock(); + } + + kobject_uevent_env(&mxc_ddc.pdev->dev.kobj, KOBJ_CHANGE, envp); +} + +static irqreturn_t mxc_ddc_detect_handler(int irq, void *data) +{ + if (mxc_ddc.fbi) + schedule_delayed_work(&(mxc_ddc.det_work), msecs_to_jiffies(300)); + return IRQ_HANDLED; +} + +static int mxc_ddc_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + if ((mxc_ddc.di)) { + if (strcmp(event->info->fix.id, "DISP3 BG - DI1")) + return 0; + } else { + if (strcmp(event->info->fix.id, "DISP3 BG")) + return 0; + } + + switch (val) { + case FB_EVENT_FB_REGISTERED: + if (mxc_ddc.fbi != NULL) + break; + mxc_ddc.fbi = fbi; + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = mxc_ddc_fb_event, +}; + +static int __devinit mxc_ddc_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct fb_info edid_fbi; + struct fsl_mxc_ddc_platform_data *plat = client->dev.platform_data; + + if (g_enable_ddc == false) + return -EPERM; + + mxc_ddc.client = client; + mxc_ddc.di = plat->di; + mxc_ddc.init = plat->init; + mxc_ddc.update = plat->update; + + if (!mxc_ddc.update) + return -EINVAL; + + if (mxc_ddc.init) + mxc_ddc.init(); + + if (mxc_ddc.update()) { + mxc_ddc.cable_plugin = 1; + /* try to read edid */ + if (mxc_edid_read(client->adapter, client->addr, + mxc_ddc.edid, &mxc_ddc.edid_cfg, &edid_fbi) < 0) + dev_warn(&client->dev, "Can not read edid\n"); +#if defined(CONFIG_MXC_IPU_V3) && defined(CONFIG_FB_MXC_SYNC_PANEL) + else + mxcfb_register_mode(mxc_ddc.di, edid_fbi.monspecs.modedb, + edid_fbi.monspecs.modedb_len, MXC_DISP_DDC_DEV); +#endif + } else + mxc_ddc.cable_plugin = 0; + + if (client->irq) { + ret = request_irq(client->irq, mxc_ddc_detect_handler, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "ddc_det", &mxc_ddc); + if (ret < 0) { + dev_warn(&client->dev, + "MXC ddc: cound not request det irq %d\n", + client->irq); + goto err; + } else { + INIT_DELAYED_WORK(&(mxc_ddc.det_work), det_worker); + ret = device_create_file(&mxc_ddc.pdev->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&client->dev, + "MXC ddc: cound not create sys node for cable state\n"); + ret = device_create_file(&mxc_ddc.pdev->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&client->dev, + "MXC ddc: cound not create sys node for edid\n"); + } + } + + fb_register_client(&nb); + +err: + return ret; +} + +static int __devexit mxc_ddc_remove(struct i2c_client *client) +{ + fb_unregister_client(&nb); + return 0; +} + +static int __init enable_ddc_setup(char *options) +{ + g_enable_ddc = true; + + return 1; +} +__setup("ddc", enable_ddc_setup); + +static const struct i2c_device_id mxc_ddc_id[] = { + { "mxc_ddc", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mxc_ddc_id); + +static struct i2c_driver mxc_ddc_i2c_driver = { + .driver = { + .name = "mxc_ddc", + }, + .probe = mxc_ddc_probe, + .remove = mxc_ddc_remove, + .id_table = mxc_ddc_id, +}; + +static int __init mxc_ddc_init(void) +{ + int ret; + + memset(&mxc_ddc, 0, sizeof(mxc_ddc)); + + mxc_ddc.pdev = platform_device_register_simple("mxc_ddc", 0, NULL, 0); + if (IS_ERR(mxc_ddc.pdev)) { + printk(KERN_ERR + "Unable to register MXC DDC as a platform device\n"); + ret = PTR_ERR(mxc_ddc.pdev); + goto err; + } + + return i2c_add_driver(&mxc_ddc_i2c_driver); +err: + return ret; +} + +static void __exit mxc_ddc_exit(void) +{ + i2c_del_driver(&mxc_ddc_i2c_driver); + platform_device_unregister(mxc_ddc.pdev); +} + +module_init(mxc_ddc_init); +module_exit(mxc_ddc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC DDC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxc_elcdif_fb.c b/drivers/video/mxc/mxc_elcdif_fb.c new file mode 100644 index 000000000000..7a5fa6d1900c --- /dev/null +++ b/drivers/video/mxc/mxc_elcdif_fb.c @@ -0,0 +1,1466 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +/* + * Based on drivers/video/mxc/mxc_ipuv3_fb.c, drivers/video/mxs/lcdif.c + * and arch/arm/mach-mx28/include/mach/lcdif.h. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "elcdif_regs.h" + +/* ELCDIF Pixel format definitions */ +/* Four-character-code (FOURCC) */ +#define fourcc(a, b, c, d) \ + (((__u32)(a)<<0)|((__u32)(b)<<8)|((__u32)(c)<<16)|((__u32)(d)<<24)) + +/* + * ELCDIF RGB Formats + */ +#define ELCDIF_PIX_FMT_RGB332 fourcc('R', 'G', 'B', '1') +#define ELCDIF_PIX_FMT_RGB555 fourcc('R', 'G', 'B', 'O') +#define ELCDIF_PIX_FMT_RGB565 fourcc('R', 'G', 'B', 'P') +#define ELCDIF_PIX_FMT_RGB666 fourcc('R', 'G', 'B', '6') +#define ELCDIF_PIX_FMT_BGR666 fourcc('B', 'G', 'R', '6') +#define ELCDIF_PIX_FMT_BGR24 fourcc('B', 'G', 'R', '3') +#define ELCDIF_PIX_FMT_RGB24 fourcc('R', 'G', 'B', '3') +#define ELCDIF_PIX_FMT_BGR32 fourcc('B', 'G', 'R', '4') +#define ELCDIF_PIX_FMT_BGRA32 fourcc('B', 'G', 'R', 'A') +#define ELCDIF_PIX_FMT_RGB32 fourcc('R', 'G', 'B', '4') +#define ELCDIF_PIX_FMT_RGBA32 fourcc('R', 'G', 'B', 'A') +#define ELCDIF_PIX_FMT_ABGR32 fourcc('A', 'B', 'G', 'R') + +struct mxc_elcdif_fb_data { + int cur_blank; + int next_blank; + int output_pix_fmt; + int dma_irq; + bool wait4vsync; + bool wait4framedone; + bool panning; + struct completion vsync_complete; + struct completion frame_done_complete; + struct semaphore flip_sem; + u32 pseudo_palette[16]; +}; + +struct elcdif_signal_cfg { + unsigned clk_pol:1; /* true = falling edge */ + unsigned enable_pol:1; /* true = active high */ + unsigned Hsync_pol:1; /* true = active high */ + unsigned Vsync_pol:1; /* true = active high */ +}; + +static int mxc_elcdif_fb_blank(int blank, struct fb_info *info); +static int mxc_elcdif_fb_map_video_memory(struct fb_info *info); +static int mxc_elcdif_fb_unmap_video_memory(struct fb_info *info); +static char *fb_mode; +static unsigned long default_bpp = 16; +static void __iomem *elcdif_base; +static struct device *g_elcdif_dev; +static bool g_elcdif_axi_clk_enable; +static bool g_elcdif_pix_clk_enable; +static struct clk *g_elcdif_axi_clk; +static struct clk *g_elcdif_pix_clk; + +static inline void setup_dotclk_panel(u32 pixel_clk, + u16 v_pulse_width, + u16 v_period, + u16 v_wait_cnt, + u16 v_active, + u16 h_pulse_width, + u16 h_period, + u16 h_wait_cnt, + u16 h_active, + int in_pixel_format, + int out_pixel_format, + struct elcdif_signal_cfg sig_cfg, + int enable_present) +{ + u32 val, rounded_pixel_clk; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + dev_dbg(g_elcdif_dev, "pixel clk = %d\n", pixel_clk); + rounded_pixel_clk = clk_round_rate(g_elcdif_pix_clk, pixel_clk); + clk_set_rate(g_elcdif_pix_clk, rounded_pixel_clk); + + __raw_writel(BM_ELCDIF_CTRL_DATA_SHIFT_DIR, + elcdif_base + HW_ELCDIF_CTRL_CLR); + + __raw_writel(BM_ELCDIF_CTRL_SHIFT_NUM_BITS, + elcdif_base + HW_ELCDIF_CTRL_CLR); + + __raw_writel(BF_ELCDIF_CTRL2_OUTSTANDING_REQS + (BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_8), + elcdif_base + HW_ELCDIF_CTRL2_SET); + + /* Recover on underflow */ + __raw_writel(BM_ELCDIF_CTRL1_RECOVER_ON_UNDERFLOW, + elcdif_base + HW_ELCDIF_CTRL1_SET); + + /* Configure the input pixel format */ + __raw_writel(BM_ELCDIF_CTRL_WORD_LENGTH | + BM_ELCDIF_CTRL_INPUT_DATA_SWIZZLE | + BM_ELCDIF_CTRL_DATA_FORMAT_16_BIT | + BM_ELCDIF_CTRL_DATA_FORMAT_18_BIT | + BM_ELCDIF_CTRL_DATA_FORMAT_24_BIT, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL1_BYTE_PACKING_FORMAT, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + switch (in_pixel_format) { + case ELCDIF_PIX_FMT_RGB565: + __raw_writel(BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(0xF), + elcdif_base + HW_ELCDIF_CTRL1_SET); + __raw_writel(BF_ELCDIF_CTRL_WORD_LENGTH(0) | + BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(0), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + case ELCDIF_PIX_FMT_RGB24: + __raw_writel(BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(0xF), + elcdif_base + HW_ELCDIF_CTRL1_SET); + __raw_writel(BF_ELCDIF_CTRL_WORD_LENGTH(3) | + BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(0), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + case ELCDIF_PIX_FMT_RGB32: + __raw_writel(BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(0x7), + elcdif_base + HW_ELCDIF_CTRL1_SET); + __raw_writel(BF_ELCDIF_CTRL_WORD_LENGTH(3) | + BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(0), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + default: + dev_err(g_elcdif_dev, "ELCDIF unsupported input pixel format " + "%d\n", in_pixel_format); + break; + } + + /* Configure the output pixel format */ + __raw_writel(BM_ELCDIF_CTRL_LCD_DATABUS_WIDTH, + elcdif_base + HW_ELCDIF_CTRL_CLR); + switch (out_pixel_format) { + case ELCDIF_PIX_FMT_RGB565: + __raw_writel(BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(0), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + case ELCDIF_PIX_FMT_RGB666: + __raw_writel(BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(2), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + case ELCDIF_PIX_FMT_RGB24: + __raw_writel(BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(3), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + default: + dev_err(g_elcdif_dev, "ELCDIF unsupported output pixel format " + "%d\n", out_pixel_format); + break; + } + + val = __raw_readl(elcdif_base + HW_ELCDIF_TRANSFER_COUNT); + val &= ~(BM_ELCDIF_TRANSFER_COUNT_V_COUNT | + BM_ELCDIF_TRANSFER_COUNT_H_COUNT); + val |= BF_ELCDIF_TRANSFER_COUNT_H_COUNT(h_active) | + BF_ELCDIF_TRANSFER_COUNT_V_COUNT(v_active); + __raw_writel(val, elcdif_base + HW_ELCDIF_TRANSFER_COUNT); + + __raw_writel(BM_ELCDIF_CTRL_VSYNC_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_WAIT_FOR_VSYNC_EDGE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_DVI_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_DOTCLK_MODE, + elcdif_base + HW_ELCDIF_CTRL_SET); + __raw_writel(BM_ELCDIF_CTRL_BYPASS_COUNT, + elcdif_base + HW_ELCDIF_CTRL_SET); + + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL0); + val &= ~(BM_ELCDIF_VDCTRL0_VSYNC_POL | + BM_ELCDIF_VDCTRL0_HSYNC_POL | + BM_ELCDIF_VDCTRL0_ENABLE_POL | + BM_ELCDIF_VDCTRL0_DOTCLK_POL); + if (sig_cfg.Vsync_pol) + val |= BM_ELCDIF_VDCTRL0_VSYNC_POL; + if (sig_cfg.Hsync_pol) + val |= BM_ELCDIF_VDCTRL0_HSYNC_POL; + if (sig_cfg.clk_pol) + val |= BM_ELCDIF_VDCTRL0_DOTCLK_POL; + if (sig_cfg.enable_pol) + val |= BM_ELCDIF_VDCTRL0_ENABLE_POL; + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL0); + + /* vsync is output */ + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL0); + val &= ~(BM_ELCDIF_VDCTRL0_VSYNC_OEB); + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL0); + + /* + * need enable sig for true RGB i/f. Or, if not true RGB, leave it + * zero. + */ + if (enable_present) { + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL0); + val |= BM_ELCDIF_VDCTRL0_ENABLE_PRESENT; + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL0); + } + + /* + * For DOTCLK mode, count VSYNC_PERIOD in terms of complete hz lines + */ + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL0); + val &= ~(BM_ELCDIF_VDCTRL0_VSYNC_PERIOD_UNIT | + BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT); + val |= BM_ELCDIF_VDCTRL0_VSYNC_PERIOD_UNIT | + BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT; + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL0); + + __raw_writel(BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH, + elcdif_base + HW_ELCDIF_VDCTRL0_CLR); + __raw_writel(v_pulse_width, elcdif_base + HW_ELCDIF_VDCTRL0_SET); + + __raw_writel(BF_ELCDIF_VDCTRL1_VSYNC_PERIOD(v_period), + elcdif_base + HW_ELCDIF_VDCTRL1); + + __raw_writel(BF_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH(h_pulse_width) | + BF_ELCDIF_VDCTRL2_HSYNC_PERIOD(h_period), + elcdif_base + HW_ELCDIF_VDCTRL2); + + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL4); + val &= ~BM_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT; + val |= BF_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT(h_active); + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL4); + + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL3); + val &= ~(BM_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT | + BM_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT); + val |= BF_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT(h_wait_cnt) | + BF_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT(v_wait_cnt); + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL3); + + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL4); + val |= BM_ELCDIF_VDCTRL4_SYNC_SIGNALS_ON; + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL4); + + return; +} + +static inline void release_dotclk_panel(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_DOTCLK_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(0, elcdif_base + HW_ELCDIF_VDCTRL0); + __raw_writel(0, elcdif_base + HW_ELCDIF_VDCTRL1); + __raw_writel(0, elcdif_base + HW_ELCDIF_VDCTRL2); + __raw_writel(0, elcdif_base + HW_ELCDIF_VDCTRL3); + + return; +} + +static inline void setup_dvi_panel(u16 h_active, u16 v_active, + u16 h_blanking, u16 v_lines, + u16 v1_blank_start, u16 v1_blank_end, + u16 v2_blank_start, u16 v2_blank_end, + u16 f1_start, u16 f1_end, + u16 f2_start, u16 f2_end) +{ + u32 val; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + /* 32bit packed format (RGB) */ + __raw_writel(BM_ELCDIF_CTRL1_BYTE_PACKING_FORMAT, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + __raw_writel(BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(0x7) | + BM_ELCDIF_CTRL1_RECOVER_ON_UNDERFLOW, + elcdif_base + HW_ELCDIF_CTRL1_SET); + + val = __raw_readl(elcdif_base + HW_ELCDIF_TRANSFER_COUNT); + val &= ~(BM_ELCDIF_TRANSFER_COUNT_V_COUNT | + BM_ELCDIF_TRANSFER_COUNT_H_COUNT); + val |= BF_ELCDIF_TRANSFER_COUNT_H_COUNT(h_active) | + BF_ELCDIF_TRANSFER_COUNT_V_COUNT(v_active); + __raw_writel(val, elcdif_base + HW_ELCDIF_TRANSFER_COUNT); + + /* set elcdif to DVI mode */ + __raw_writel(BM_ELCDIF_CTRL_DVI_MODE, + elcdif_base + HW_ELCDIF_CTRL_SET); + __raw_writel(BM_ELCDIF_CTRL_VSYNC_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_DOTCLK_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + + __raw_writel(BM_ELCDIF_CTRL_BYPASS_COUNT, + elcdif_base + HW_ELCDIF_CTRL_SET); + /* convert input RGB -> YCbCr */ + __raw_writel(BM_ELCDIF_CTRL_RGB_TO_YCBCR422_CSC, + elcdif_base + HW_ELCDIF_CTRL_SET); + /* interlace odd and even fields */ + __raw_writel(BM_ELCDIF_CTRL1_INTERLACE_FIELDS, + elcdif_base + HW_ELCDIF_CTRL1_SET); + + __raw_writel(BM_ELCDIF_CTRL_WORD_LENGTH | + BM_ELCDIF_CTRL_INPUT_DATA_SWIZZLE | + BM_ELCDIF_CTRL_LCD_DATABUS_WIDTH, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BF_ELCDIF_CTRL_WORD_LENGTH(3) | /* 24 bit */ + BM_ELCDIF_CTRL_DATA_SELECT | /* data mode */ + BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(0) | /* no swap */ + BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(1), /* 8 bit */ + elcdif_base + HW_ELCDIF_CTRL_SET); + + /* ELCDIF_DVI */ + /* set frame size */ + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL0); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL0); + + /* set start/end of field-1 and start of field-2 */ + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL1); + val &= ~(BM_ELCDIF_DVICTRL1_F1_START_LINE | + BM_ELCDIF_DVICTRL1_F1_END_LINE | + BM_ELCDIF_DVICTRL1_F2_START_LINE); + val |= BF_ELCDIF_DVICTRL1_F1_START_LINE(f1_start) | + BF_ELCDIF_DVICTRL1_F1_END_LINE(f1_end) | + BF_ELCDIF_DVICTRL1_F2_START_LINE(f2_start); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL1); + + /* set first vertical blanking interval and end of filed-2 */ + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL2); + val &= ~(BM_ELCDIF_DVICTRL2_F2_END_LINE | + BM_ELCDIF_DVICTRL2_V1_BLANK_START_LINE | + BM_ELCDIF_DVICTRL2_V1_BLANK_END_LINE); + val |= BF_ELCDIF_DVICTRL2_F2_END_LINE(f2_end) | + BF_ELCDIF_DVICTRL2_V1_BLANK_START_LINE(v1_blank_start) | + BF_ELCDIF_DVICTRL2_V1_BLANK_END_LINE(v1_blank_end); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL2); + + /* set second vertical blanking interval */ + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL3); + val &= ~(BM_ELCDIF_DVICTRL3_V2_BLANK_START_LINE | + BM_ELCDIF_DVICTRL3_V2_BLANK_END_LINE); + val |= BF_ELCDIF_DVICTRL3_V2_BLANK_START_LINE(v2_blank_start) | + BF_ELCDIF_DVICTRL3_V2_BLANK_END_LINE(v2_blank_end); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL3); + + /* fill the rest area black color if the input frame + * is not 720 pixels/line + */ + if (h_active != 720) { + /* the input frame can't be less then (720-256) pixels/line */ + if (720 - h_active > 0xff) + h_active = 720 - 0xff; + + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL4); + val &= ~(BM_ELCDIF_DVICTRL4_H_FILL_CNT | + BM_ELCDIF_DVICTRL4_Y_FILL_VALUE | + BM_ELCDIF_DVICTRL4_CB_FILL_VALUE | + BM_ELCDIF_DVICTRL4_CR_FILL_VALUE); + val |= BF_ELCDIF_DVICTRL4_H_FILL_CNT(720 - h_active) | + BF_ELCDIF_DVICTRL4_Y_FILL_VALUE(16) | + BF_ELCDIF_DVICTRL4_CB_FILL_VALUE(128) | + BF_ELCDIF_DVICTRL4_CR_FILL_VALUE(128); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL4); + } + + /* Color Space Conversion RGB->YCbCr */ + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF0); + val &= ~(BM_ELCDIF_CSC_COEFF0_C0 | + BM_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER); + val |= BF_ELCDIF_CSC_COEFF0_C0(0x41) | + BF_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER(3); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF0); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF1); + val &= ~(BM_ELCDIF_CSC_COEFF1_C1 | BM_ELCDIF_CSC_COEFF1_C2); + val |= BF_ELCDIF_CSC_COEFF1_C1(0x81) | + BF_ELCDIF_CSC_COEFF1_C2(0x19); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF1); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF2); + val &= ~(BM_ELCDIF_CSC_COEFF2_C3 | BM_ELCDIF_CSC_COEFF2_C4); + val |= BF_ELCDIF_CSC_COEFF2_C3(0x3DB) | + BF_ELCDIF_CSC_COEFF2_C4(0x3B6); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF2); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF3); + val &= ~(BM_ELCDIF_CSC_COEFF3_C5 | BM_ELCDIF_CSC_COEFF3_C6); + val |= BF_ELCDIF_CSC_COEFF3_C5(0x70) | + BF_ELCDIF_CSC_COEFF3_C6(0x70); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF3); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF4); + val &= ~(BM_ELCDIF_CSC_COEFF4_C7 | BM_ELCDIF_CSC_COEFF4_C8); + val |= BF_ELCDIF_CSC_COEFF4_C7(0x3A2) | + BF_ELCDIF_CSC_COEFF4_C8(0x3EE); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF4); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_OFFSET); + val &= ~(BM_ELCDIF_CSC_OFFSET_CBCR_OFFSET | + BM_ELCDIF_CSC_OFFSET_Y_OFFSET); + val |= BF_ELCDIF_CSC_OFFSET_CBCR_OFFSET(0x80) | + BF_ELCDIF_CSC_OFFSET_Y_OFFSET(0x10); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_OFFSET); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_LIMIT); + val &= ~(BM_ELCDIF_CSC_LIMIT_CBCR_MIN | + BM_ELCDIF_CSC_LIMIT_CBCR_MAX | + BM_ELCDIF_CSC_LIMIT_Y_MIN | + BM_ELCDIF_CSC_LIMIT_Y_MAX); + val |= BF_ELCDIF_CSC_LIMIT_CBCR_MIN(16) | + BF_ELCDIF_CSC_LIMIT_CBCR_MAX(240) | + BF_ELCDIF_CSC_LIMIT_Y_MIN(16) | + BF_ELCDIF_CSC_LIMIT_Y_MAX(235); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_LIMIT); + + return; +} + +static inline void release_dvi_panel(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_DVI_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + return; +} + +static inline void mxc_init_elcdif(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_CLKGATE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + /* Reset controller */ + __raw_writel(BM_ELCDIF_CTRL_SFTRST, + elcdif_base + HW_ELCDIF_CTRL_SET); + udelay(10); + + /* Take controller out of reset */ + __raw_writel(BM_ELCDIF_CTRL_SFTRST | BM_ELCDIF_CTRL_CLKGATE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + + /* Setup the bus protocol */ + __raw_writel(BM_ELCDIF_CTRL1_MODE86, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + __raw_writel(BM_ELCDIF_CTRL1_BUSY_ENABLE, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + + /* Take display out of reset */ + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_SET); + + /* VSYNC is an input by default */ + __raw_writel(BM_ELCDIF_VDCTRL0_VSYNC_OEB, + elcdif_base + HW_ELCDIF_VDCTRL0_SET); + + /* Reset display */ + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + udelay(10); + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_SET); + udelay(10); + + return; +} + +int mxc_elcdif_frame_addr_setup(dma_addr_t phys) +{ + int ret = 0; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_ELCDIF_MASTER, + elcdif_base + HW_ELCDIF_CTRL_SET); + + __raw_writel(phys, elcdif_base + HW_ELCDIF_CUR_BUF); + __raw_writel(phys, elcdif_base + HW_ELCDIF_NEXT_BUF); + return ret; +} + +static inline void mxc_elcdif_dma_release(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_ELCDIF_MASTER, + elcdif_base + HW_ELCDIF_CTRL_CLR); + return; +} + +static inline void mxc_elcdif_run(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_ELCDIF_MASTER, + elcdif_base + HW_ELCDIF_CTRL_SET); + __raw_writel(BM_ELCDIF_CTRL_RUN, + elcdif_base + HW_ELCDIF_CTRL_SET); + return; +} + +static inline void mxc_elcdif_stop(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_RUN, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_ELCDIF_MASTER, + elcdif_base + HW_ELCDIF_CTRL_CLR); + msleep(1); + __raw_writel(BM_ELCDIF_CTRL_CLKGATE, elcdif_base + HW_ELCDIF_CTRL_SET); + return; +} + +static int mxc_elcdif_blank_panel(int blank) +{ + int ret = 0, count; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + switch (blank) { + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + __raw_writel(BM_ELCDIF_CTRL_BYPASS_COUNT, + elcdif_base + HW_ELCDIF_CTRL_CLR); + for (count = 10000; count; count--) { + if (__raw_readl(elcdif_base + HW_ELCDIF_STAT) & + BM_ELCDIF_STAT_TXFIFO_EMPTY) + break; + msleep(1); + } + break; + + case FB_BLANK_UNBLANK: + __raw_writel(BM_ELCDIF_CTRL_BYPASS_COUNT, + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + + default: + dev_err(g_elcdif_dev, "unknown blank parameter\n"); + ret = -EINVAL; + break; + } + return ret; +} + +static int mxc_elcdif_init_panel(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + /* + * Make sure we do a high-to-low transition to reset the panel. + * First make it low for 100 msec, hi for 10 msec, low for 10 msec, + * then hi. + */ + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_CLR); /* low */ + msleep(100); + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_SET); /* high */ + msleep(10); + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_CLR); /* low */ + + /* For the Samsung, Reset must be held low at least 30 uSec + * Therefore, we'll hold it low for about 10 mSec just to be sure. + * Then we'll wait 1 mSec afterwards. + */ + msleep(10); + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_SET); /* high */ + msleep(1); + + return 0; +} + +static uint32_t bpp_to_pixfmt(struct fb_info *fbi) +{ + uint32_t pixfmt = 0; + + if (fbi->var.nonstd) + return fbi->var.nonstd; + + switch (fbi->var.bits_per_pixel) { + case 32: + pixfmt = ELCDIF_PIX_FMT_RGB32; + break; + case 24: + pixfmt = ELCDIF_PIX_FMT_RGB24; + break; + case 18: + pixfmt = ELCDIF_PIX_FMT_RGB666; + break; + case 16: + pixfmt = ELCDIF_PIX_FMT_RGB565; + break; + case 8: + pixfmt = ELCDIF_PIX_FMT_RGB332; + break; + } + return pixfmt; +} + +static int mxc_elcdif_fb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static irqreturn_t lcd_irq_handler(int irq, void *dev_id) +{ + struct mxc_elcdif_fb_data *data = dev_id; + u32 status_lcd = __raw_readl(elcdif_base + HW_ELCDIF_CTRL1); + dev_dbg(g_elcdif_dev, "%s: irq %d\n", __func__, irq); + + if ((status_lcd & BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ) && + data->wait4vsync) { + dev_dbg(g_elcdif_dev, "%s: VSYNC irq\n", __func__); + __raw_writel(BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ_EN, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + data->wait4vsync = 0; + complete(&data->vsync_complete); + } + if ((status_lcd & BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ) && + data->wait4framedone) { + dev_dbg(g_elcdif_dev, "%s: frame done irq\n", __func__); + __raw_writel(BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ_EN, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + if (data->panning) { + up(&data->flip_sem); + data->panning = 0; + } + data->wait4framedone = 0; + complete(&data->frame_done_complete); + } + if (status_lcd & BM_ELCDIF_CTRL1_UNDERFLOW_IRQ) { + dev_dbg(g_elcdif_dev, "%s: underflow irq\n", __func__); + __raw_writel(BM_ELCDIF_CTRL1_UNDERFLOW_IRQ, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + } + if (status_lcd & BM_ELCDIF_CTRL1_OVERFLOW_IRQ) { + dev_dbg(g_elcdif_dev, "%s: overflow irq\n", __func__); + __raw_writel(BM_ELCDIF_CTRL1_OVERFLOW_IRQ, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + } + return IRQ_HANDLED; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxc_elcdif_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + return ret; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + * + */ +static int mxc_elcdif_fb_set_par(struct fb_info *fbi) +{ + struct mxc_elcdif_fb_data *data = (struct mxc_elcdif_fb_data *)fbi->par; + struct elcdif_signal_cfg sig_cfg; + int mem_len; + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + sema_init(&data->flip_sem, 1); + + /* release prev panel */ + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + mxc_elcdif_blank_panel(FB_BLANK_POWERDOWN); + mxc_elcdif_stop(); + release_dotclk_panel(); + mxc_elcdif_dma_release(); + mxc_elcdif_fb_set_fix(fbi); + if (g_elcdif_pix_clk_enable) { + clk_disable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = false; + } + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) { + if (fbi->fix.smem_start) + mxc_elcdif_fb_unmap_video_memory(fbi); + + if (mxc_elcdif_fb_map_video_memory(fbi) < 0) + return -ENOMEM; + } + + if (data->next_blank != FB_BLANK_UNBLANK) + return 0; + + /* init next panel */ + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + mxc_init_elcdif(); + mxc_elcdif_init_panel(); + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_LAT_FALL) + sig_cfg.clk_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + + setup_dotclk_panel((PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.vsync_len, + fbi->var.upper_margin + + fbi->var.yres + fbi->var.lower_margin, + fbi->var.upper_margin, + fbi->var.yres, + fbi->var.hsync_len, + fbi->var.left_margin + + fbi->var.xres + fbi->var.right_margin, + fbi->var.left_margin, + fbi->var.xres, + bpp_to_pixfmt(fbi), + data->output_pix_fmt, + sig_cfg, + 1); + mxc_elcdif_frame_addr_setup(fbi->fix.smem_start); + mxc_elcdif_run(); + mxc_elcdif_blank_panel(FB_BLANK_UNBLANK); + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + return 0; +} + +static int mxc_elcdif_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8)) + var->bits_per_pixel = default_bpp; + + switch (var->bits_per_pixel) { + case 8: + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + return 0; +} + +static int mxc_elcdif_fb_wait_for_vsync(struct fb_info *info) +{ + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + int ret = 0; + + if (data->cur_blank != FB_BLANK_UNBLANK) { + dev_err(info->device, "can't wait for VSYNC when fb " + "is blank\n"); + return -EINVAL; + } + + init_completion(&data->vsync_complete); + + __raw_writel(BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + data->wait4vsync = 1; + __raw_writel(BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ_EN, + elcdif_base + HW_ELCDIF_CTRL1_SET); + ret = wait_for_completion_interruptible_timeout( + &data->vsync_complete, 1 * HZ); + if (ret == 0) { + dev_err(info->device, + "MXC ELCDIF wait for vsync timeout\n"); + data->wait4vsync = 0; + ret = -ETIME; + } else if (ret > 0) { + ret = 0; + } + return ret; +} + +static int mxc_elcdif_fb_wait_for_frame_done(struct fb_info *info) +{ + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + int ret = 0; + + if (data->cur_blank != FB_BLANK_UNBLANK) { + dev_err(info->device, "can't wait for frame done when fb " + "is blank\n"); + return -EINVAL; + } + + init_completion(&data->frame_done_complete); + + __raw_writel(BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + data->wait4framedone = 1; + __raw_writel(BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ_EN, + elcdif_base + HW_ELCDIF_CTRL1_SET); + ret = wait_for_completion_interruptible_timeout( + &data->frame_done_complete, 1 * HZ); + if (ret == 0) { + dev_err(info->device, + "MXC ELCDIF wait for frame done timeout\n"); + data->wait4framedone = 0; + ret = -ETIME; + } else if (ret > 0) { + ret = 0; + } + return ret; +} + +static int mxc_elcdif_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + int ret = -EINVAL; + + switch (cmd) { + case MXCFB_WAIT_FOR_VSYNC: + ret = mxc_elcdif_fb_wait_for_vsync(info); + break; + case MXCFB_GET_FB_BLANK: + { + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + + if (put_user(data->cur_blank, (__u32 __user *)arg)) + return -EFAULT; + break; + } + default: + break; + } + return ret; +} + +static int mxc_elcdif_fb_blank(int blank, struct fb_info *info) +{ + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + int ret = 0; + + if (data->cur_blank == blank) + return ret; + + data->next_blank = blank; + + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + ret = mxc_elcdif_blank_panel(blank); + if (ret == 0) + data->cur_blank = blank; + else + return ret; + + if (blank == FB_BLANK_UNBLANK) { + ret = mxc_elcdif_fb_set_par(info); + if (ret) + return ret; + } + + if (data->cur_blank != FB_BLANK_UNBLANK) { + if (g_elcdif_axi_clk_enable) { + clk_disable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = false; + } + if (g_elcdif_pix_clk_enable) { + clk_disable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = false; + } + } else { + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + } + + return ret; +} + +static int mxc_elcdif_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + unsigned long base = 0; + + if (data->cur_blank != FB_BLANK_UNBLANK) { + dev_err(info->device, "can't do pan display when fb " + "is blank\n"); + return -EINVAL; + } + + if (var->xoffset > 0) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((var->yoffset + var->yres > var->yres_virtual)) { + dev_err(info->device, "y panning exceeds\n"); + return -EINVAL; + } + + /* update framebuffer visual */ + base = (var->yoffset * var->xres_virtual + var->xoffset); + base = (var->bits_per_pixel) * base / 8; + base += info->fix.smem_start; + + down(&data->flip_sem); + + __raw_writel(base, elcdif_base + HW_ELCDIF_NEXT_BUF); + + data->panning = 1; + return mxc_elcdif_fb_wait_for_frame_done(info); +} + +static struct fb_ops mxc_elcdif_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxc_elcdif_fb_check_var, + .fb_set_par = mxc_elcdif_fb_set_par, + .fb_setcolreg = mxc_elcdif_fb_setcolreg, + .fb_ioctl = mxc_elcdif_fb_ioctl, + .fb_blank = mxc_elcdif_fb_blank, + .fb_pan_display = mxc_elcdif_fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxc_elcdif_fb_map_video_memory(struct fb_info *fbi) +{ + if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + + fbi->screen_base = dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *)&fbi->fix.smem_start, + GFP_DMA); + if (fbi->screen_base == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + return -EBUSY; + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxc_elcdif_fb_unmap_video_memory(struct fb_info *fbi) +{ + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +static int mxc_elcdif_fb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_elcdif_fb_data *data; + struct resource *res; + struct fb_info *fbi; + struct mxc_fb_platform_data *pdata = pdev->dev.platform_data; + + fbi = framebuffer_alloc(sizeof(struct mxc_elcdif_fb_data), &pdev->dev); + if (fbi == NULL) { + ret = -ENOMEM; + goto out; + } + + data = (struct mxc_elcdif_fb_data *)fbi->par; + data->cur_blank = data->next_blank = FB_BLANK_UNBLANK; + + fbi->var.activate = FB_ACTIVATE_NOW; + fbi->fbops = &mxc_elcdif_fb_ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = data->pseudo_palette; + + ret = fb_alloc_cmap(&fbi->cmap, 16, 0); + if (ret) + goto out; + + g_elcdif_dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto err0; + } + data->dma_irq = res->start; + + ret = request_irq(data->dma_irq, lcd_irq_handler, 0, + "mxc_elcdif_fb", data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + data->dma_irq, ret); + goto err0; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto err1; + } + elcdif_base = ioremap(res->start, SZ_4K); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + fbi->fix.smem_len = res->end - res->start + 1; + fbi->fix.smem_start = res->start; + fbi->screen_base = ioremap(fbi->fix.smem_start, + fbi->fix.smem_len); + } + + strcpy(fbi->fix.id, "mxc_elcdif_fb"); + + fbi->var.xres = 800; + fbi->var.yres = 480; + + if (pdata && !data->output_pix_fmt) + data->output_pix_fmt = pdata->interface_pix_fmt; + + if (pdata && pdata->mode && pdata->num_modes) + fb_videomode_to_modelist(pdata->mode, pdata->num_modes, + &fbi->modelist); + + if (!fb_mode && pdata && pdata->mode_str) + fb_mode = pdata->mode_str; + + if (fb_mode) { + ret = fb_find_mode(&fbi->var, fbi, fb_mode, NULL, 0, NULL, + default_bpp); + if ((!ret || (ret > 2)) && pdata && pdata->mode && + pdata->num_modes) + fb_find_mode(&fbi->var, fbi, fb_mode, pdata->mode, + pdata->num_modes, NULL, default_bpp); + } + + mxc_elcdif_fb_check_var(&fbi->var, fbi); + + fbi->var.xres_virtual = fbi->var.xres; + fbi->var.yres_virtual = fbi->var.yres * 3; + + mxc_elcdif_fb_set_fix(fbi); + + if (!res || !res->end) + if (mxc_elcdif_fb_map_video_memory(fbi) < 0) { + ret = -ENOMEM; + goto err2; + } + + g_elcdif_axi_clk = clk_get(g_elcdif_dev, "elcdif_axi"); + if (g_elcdif_axi_clk == NULL) { + dev_err(&pdev->dev, "can't get ELCDIF axi clk\n"); + ret = -ENODEV; + goto err3; + } + g_elcdif_pix_clk = clk_get(g_elcdif_dev, "elcdif_pix"); + if (g_elcdif_pix_clk == NULL) { + dev_err(&pdev->dev, "can't get ELCDIF pix clk\n"); + ret = -ENODEV; + goto err3; + } + /* + * Set an appropriate pixel clk rate first, so that we can + * access ELCDIF registers. + */ + clk_set_rate(g_elcdif_pix_clk, 25000000); + + ret = register_framebuffer(fbi); + if (ret) + goto err3; + + platform_set_drvdata(pdev, fbi); + + return 0; +err3: + mxc_elcdif_fb_unmap_video_memory(fbi); +err2: + iounmap(elcdif_base); +err1: + free_irq(data->dma_irq, data); +err0: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); +out: + return ret; +} + +static int mxc_elcdif_fb_remove(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxc_elcdif_fb_data *data = (struct mxc_elcdif_fb_data *)fbi->par; + + mxc_elcdif_fb_blank(FB_BLANK_POWERDOWN, fbi); + mxc_elcdif_stop(); + release_dotclk_panel(); + mxc_elcdif_dma_release(); + + if (g_elcdif_axi_clk_enable) { + clk_disable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = false; + } + if (g_elcdif_pix_clk_enable) { + clk_disable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = false; + } + clk_put(g_elcdif_axi_clk); + clk_put(g_elcdif_pix_clk); + + free_irq(data->dma_irq, data); + mxc_elcdif_fb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +static int mxc_elcdif_fb_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxc_elcdif_fb_data *data = (struct mxc_elcdif_fb_data *)fbi->par; + int saved_blank; + + acquire_console_sem(); + fb_set_suspend(fbi, 1); + saved_blank = data->cur_blank; + mxc_elcdif_fb_blank(FB_BLANK_POWERDOWN, fbi); + data->next_blank = saved_blank; + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + mxc_elcdif_stop(); + mxc_elcdif_dma_release(); + if (g_elcdif_pix_clk_enable) { + clk_disable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = false; + } + if (g_elcdif_axi_clk_enable) { + clk_disable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = false; + } + release_console_sem(); + return 0; +} + +static int mxc_elcdif_fb_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxc_elcdif_fb_data *data = (struct mxc_elcdif_fb_data *)fbi->par; + + acquire_console_sem(); + mxc_elcdif_fb_blank(data->next_blank, fbi); + fb_set_suspend(fbi, 0); + release_console_sem(); + + return 0; +} +#else +#define mxc_elcdif_fb_suspend NULL +#define mxc_elcdif_fb_resume NULL +#endif + +static struct platform_driver mxc_elcdif_fb_driver = { + .probe = mxc_elcdif_fb_probe, + .remove = mxc_elcdif_fb_remove, + .suspend = mxc_elcdif_fb_suspend, + .resume = mxc_elcdif_fb_resume, + .driver = { + .name = "mxc_elcdif_fb", + .owner = THIS_MODULE, + }, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=trident:800x600,bpp=16,noaccel + */ +int mxc_elcdif_fb_setup(char *options) +{ + char *opt; + if (!options || !*options) + return 0; + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + return 0; +} + +static int __init mxc_elcdif_fb_init(void) +{ + char *option = NULL; + + if (fb_get_options("mxc_elcdif_fb", &option)) + return -ENODEV; + mxc_elcdif_fb_setup(option); + + return platform_driver_register(&mxc_elcdif_fb_driver); +} + +static void __exit mxc_elcdif_fb_exit(void) +{ + platform_driver_unregister(&mxc_elcdif_fb_driver); +} + +module_init(mxc_elcdif_fb_init); +module_exit(mxc_elcdif_fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC ELCDIF Framebuffer Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxc_epdc_fb.c b/drivers/video/mxc/mxc_epdc_fb.c new file mode 100644 index 000000000000..bf91d4175513 --- /dev/null +++ b/drivers/video/mxc/mxc_epdc_fb.c @@ -0,0 +1,4036 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * Based on STMP378X LCDIF + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "epdc_regs.h" + +/* + * Enable this define to have a default panel + * loaded during driver initialization + */ +/*#define DEFAULT_PANEL_HW_INIT*/ + +#define NUM_SCREENS_MIN 2 +#define EPDC_NUM_LUTS 16 +#define EPDC_MAX_NUM_UPDATES 20 +#define INVALID_LUT -1 + +#define DEFAULT_TEMP_INDEX 0 +#define DEFAULT_TEMP 20 /* room temp in deg Celsius */ + +#define INIT_UPDATE_MARKER 0x12345678 +#define PAN_UPDATE_MARKER 0x12345679 + +#define POWER_STATE_OFF 0 +#define POWER_STATE_ON 1 + +#define MERGE_OK 0 +#define MERGE_FAIL 1 +#define MERGE_BLOCK 2 + +static unsigned long default_bpp = 16; + +struct update_marker_data { + u32 update_marker; + struct completion update_completion; + int lut_num; +}; + +/* This structure represents a list node containing both + * a memory region allocated as an output buffer for the PxP + * update processing task, and the update description (mode, region, etc.) */ +struct update_data_list { + struct list_head list; + struct mxcfb_update_data upd_data;/* Update parameters */ + dma_addr_t phys_addr; /* Pointer to phys address of processed Y buf */ + void *virt_addr; + u32 epdc_offs; /* Add to buffer pointer to resolve alignment */ + u32 size; + int lut_num; /* Assigned before update is processed into working buffer */ + int collision_mask; /* Set when update results in collision */ + /* Represents other LUTs that we collide with */ + struct update_marker_data *upd_marker_data; + u32 update_order; /* Numeric ordering value for update */ + dma_addr_t phys_addr_copybuf; /* Phys address of copied update data */ + void *virt_addr_copybuf; /* Used for PxP SW workaround */ +}; + +struct mxc_epdc_fb_data { + struct fb_info info; + struct fb_var_screeninfo epdc_fb_var; /* Internal copy of screeninfo + so we can sync changes to it */ + u32 pseudo_palette[16]; + char fw_str[24]; + struct list_head list; + struct mxc_epdc_fb_mode *cur_mode; + struct mxc_epdc_fb_platform_data *pdata; + int blank; + ssize_t map_size; + dma_addr_t phys_start; + u32 fb_offset; + int default_bpp; + int native_width; + int native_height; + int num_screens; + int epdc_irq; + struct device *dev; + int power_state; + struct clk *epdc_clk_axi; + struct clk *epdc_clk_pix; + struct regulator *display_regulator; + struct regulator *vcom_regulator; + bool fw_default_load; + + /* FB elements related to EPDC updates */ + bool in_init; + bool hw_ready; + bool waiting_for_idle; + u32 auto_mode; + u32 upd_scheme; + struct update_data_list *upd_buf_queue; + struct update_data_list *upd_buf_free_list; + struct update_data_list *upd_buf_collision_list; + struct update_data_list *cur_update; + spinlock_t queue_lock; + int trt_entries; + int temp_index; + u8 *temp_range_bounds; + struct mxcfb_waveform_modes wv_modes; + u32 *waveform_buffer_virt; + u32 waveform_buffer_phys; + u32 waveform_buffer_size; + u32 *working_buffer_virt; + u32 working_buffer_phys; + u32 working_buffer_size; + u32 order_cnt; + struct update_marker_data update_marker_array[EPDC_MAX_NUM_UPDATES]; + u32 lut_update_order[EPDC_NUM_LUTS]; + struct completion updates_done; + struct delayed_work epdc_done_work; + struct workqueue_struct *epdc_submit_workqueue; + struct work_struct epdc_submit_work; + bool waiting_for_wb; + bool waiting_for_lut; + struct completion update_res_free; + struct mutex power_mutex; + bool powering_down; + int pwrdown_delay; + + /* FB elements related to PxP DMA */ + struct completion pxp_tx_cmpl; + struct pxp_channel *pxp_chan; + struct pxp_config_data pxp_conf; + struct dma_async_tx_descriptor *txd; + dma_cookie_t cookie; + struct scatterlist sg[2]; + struct mutex pxp_mutex; /* protects access to PxP */ +}; + +struct waveform_data_header { + unsigned int wi0; + unsigned int wi1; + unsigned int wi2; + unsigned int wi3; + unsigned int wi4; + unsigned int wi5; + unsigned int wi6; + unsigned int xwia:24; + unsigned int cs1:8; + unsigned int wmta:24; + unsigned int fvsn:8; + unsigned int luts:8; + unsigned int mc:8; + unsigned int trc:8; + unsigned int reserved0_0:8; + unsigned int eb:8; + unsigned int sb:8; + unsigned int reserved0_1:8; + unsigned int reserved0_2:8; + unsigned int reserved0_3:8; + unsigned int reserved0_4:8; + unsigned int reserved0_5:8; + unsigned int cs2:8; +}; + +struct mxcfb_waveform_data_file { + struct waveform_data_header wdh; + u32 *data; /* Temperature Range Table + Waveform Data */ +}; + +void __iomem *epdc_base; + +struct mxc_epdc_fb_data *g_fb_data; + +/* forward declaration */ +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, + int temp); +static void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data); +static int mxc_epdc_fb_blank(int blank, struct fb_info *info); +static int mxc_epdc_fb_init_hw(struct fb_info *info); +static int pxp_process_update(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region); +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat); + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data); +static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data); + + +#ifdef DEBUG +static void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) +{ + dev_err(fb_data->dev, "S0 fmt 0x%x", + pxp_conf->s0_param.pixel_fmt); + dev_err(fb_data->dev, "S0 width 0x%x", + pxp_conf->s0_param.width); + dev_err(fb_data->dev, "S0 height 0x%x", + pxp_conf->s0_param.height); + dev_err(fb_data->dev, "S0 ckey 0x%x", + pxp_conf->s0_param.color_key); + dev_err(fb_data->dev, "S0 ckey en 0x%x", + pxp_conf->s0_param.color_key_enable); + + dev_err(fb_data->dev, "OL0 combine en 0x%x", + pxp_conf->ol_param[0].combine_enable); + dev_err(fb_data->dev, "OL0 fmt 0x%x", + pxp_conf->ol_param[0].pixel_fmt); + dev_err(fb_data->dev, "OL0 width 0x%x", + pxp_conf->ol_param[0].width); + dev_err(fb_data->dev, "OL0 height 0x%x", + pxp_conf->ol_param[0].height); + dev_err(fb_data->dev, "OL0 ckey 0x%x", + pxp_conf->ol_param[0].color_key); + dev_err(fb_data->dev, "OL0 ckey en 0x%x", + pxp_conf->ol_param[0].color_key_enable); + dev_err(fb_data->dev, "OL0 alpha 0x%x", + pxp_conf->ol_param[0].global_alpha); + dev_err(fb_data->dev, "OL0 alpha en 0x%x", + pxp_conf->ol_param[0].global_alpha_enable); + dev_err(fb_data->dev, "OL0 local alpha en 0x%x", + pxp_conf->ol_param[0].local_alpha_enable); + + dev_err(fb_data->dev, "Out fmt 0x%x", + pxp_conf->out_param.pixel_fmt); + dev_err(fb_data->dev, "Out width 0x%x", + pxp_conf->out_param.width); + dev_err(fb_data->dev, "Out height 0x%x", + pxp_conf->out_param.height); + + dev_err(fb_data->dev, + "drect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.drect.left, pxp_conf->proc_data.drect.top, + pxp_conf->proc_data.drect.width, + pxp_conf->proc_data.drect.height); + dev_err(fb_data->dev, + "srect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.srect.left, pxp_conf->proc_data.srect.top, + pxp_conf->proc_data.srect.width, + pxp_conf->proc_data.srect.height); + dev_err(fb_data->dev, "Scaling en 0x%x", pxp_conf->proc_data.scaling); + dev_err(fb_data->dev, "HFlip en 0x%x", pxp_conf->proc_data.hflip); + dev_err(fb_data->dev, "VFlip en 0x%x", pxp_conf->proc_data.vflip); + dev_err(fb_data->dev, "Rotation 0x%x", pxp_conf->proc_data.rotate); + dev_err(fb_data->dev, "BG Color 0x%x", pxp_conf->proc_data.bgcolor); +} + +static void dump_epdc_reg(void) +{ + printk(KERN_DEBUG "\n\n"); + printk(KERN_DEBUG "EPDC_CTRL 0x%x\n", __raw_readl(EPDC_CTRL)); + printk(KERN_DEBUG "EPDC_WVADDR 0x%x\n", __raw_readl(EPDC_WVADDR)); + printk(KERN_DEBUG "EPDC_WB_ADDR 0x%x\n", __raw_readl(EPDC_WB_ADDR)); + printk(KERN_DEBUG "EPDC_RES 0x%x\n", __raw_readl(EPDC_RES)); + printk(KERN_DEBUG "EPDC_FORMAT 0x%x\n", __raw_readl(EPDC_FORMAT)); + printk(KERN_DEBUG "EPDC_FIFOCTRL 0x%x\n", __raw_readl(EPDC_FIFOCTRL)); + printk(KERN_DEBUG "EPDC_UPD_ADDR 0x%x\n", __raw_readl(EPDC_UPD_ADDR)); + printk(KERN_DEBUG "EPDC_UPD_FIXED 0x%x\n", __raw_readl(EPDC_UPD_FIXED)); + printk(KERN_DEBUG "EPDC_UPD_CORD 0x%x\n", __raw_readl(EPDC_UPD_CORD)); + printk(KERN_DEBUG "EPDC_UPD_SIZE 0x%x\n", __raw_readl(EPDC_UPD_SIZE)); + printk(KERN_DEBUG "EPDC_UPD_CTRL 0x%x\n", __raw_readl(EPDC_UPD_CTRL)); + printk(KERN_DEBUG "EPDC_TEMP 0x%x\n", __raw_readl(EPDC_TEMP)); + printk(KERN_DEBUG "EPDC_TCE_CTRL 0x%x\n", __raw_readl(EPDC_TCE_CTRL)); + printk(KERN_DEBUG "EPDC_TCE_SDCFG 0x%x\n", __raw_readl(EPDC_TCE_SDCFG)); + printk(KERN_DEBUG "EPDC_TCE_GDCFG 0x%x\n", __raw_readl(EPDC_TCE_GDCFG)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN1 0x%x\n", __raw_readl(EPDC_TCE_HSCAN1)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN2 0x%x\n", __raw_readl(EPDC_TCE_HSCAN2)); + printk(KERN_DEBUG "EPDC_TCE_VSCAN 0x%x\n", __raw_readl(EPDC_TCE_VSCAN)); + printk(KERN_DEBUG "EPDC_TCE_OE 0x%x\n", __raw_readl(EPDC_TCE_OE)); + printk(KERN_DEBUG "EPDC_TCE_POLARITY 0x%x\n", __raw_readl(EPDC_TCE_POLARITY)); + printk(KERN_DEBUG "EPDC_TCE_TIMING1 0x%x\n", __raw_readl(EPDC_TCE_TIMING1)); + printk(KERN_DEBUG "EPDC_TCE_TIMING2 0x%x\n", __raw_readl(EPDC_TCE_TIMING2)); + printk(KERN_DEBUG "EPDC_TCE_TIMING3 0x%x\n", __raw_readl(EPDC_TCE_TIMING3)); + printk(KERN_DEBUG "EPDC_IRQ_MASK 0x%x\n", __raw_readl(EPDC_IRQ_MASK)); + printk(KERN_DEBUG "EPDC_IRQ 0x%x\n", __raw_readl(EPDC_IRQ)); + printk(KERN_DEBUG "EPDC_STATUS_LUTS 0x%x\n", __raw_readl(EPDC_STATUS_LUTS)); + printk(KERN_DEBUG "EPDC_STATUS_NEXTLUT 0x%x\n", __raw_readl(EPDC_STATUS_NEXTLUT)); + printk(KERN_DEBUG "EPDC_STATUS_COL 0x%x\n", __raw_readl(EPDC_STATUS_COL)); + printk(KERN_DEBUG "EPDC_STATUS 0x%x\n", __raw_readl(EPDC_STATUS)); + printk(KERN_DEBUG "EPDC_DEBUG 0x%x\n", __raw_readl(EPDC_DEBUG)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT0 0x%x\n", __raw_readl(EPDC_DEBUG_LUT0)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT1 0x%x\n", __raw_readl(EPDC_DEBUG_LUT1)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT2 0x%x\n", __raw_readl(EPDC_DEBUG_LUT2)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT3 0x%x\n", __raw_readl(EPDC_DEBUG_LUT3)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT4 0x%x\n", __raw_readl(EPDC_DEBUG_LUT4)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT5 0x%x\n", __raw_readl(EPDC_DEBUG_LUT5)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT6 0x%x\n", __raw_readl(EPDC_DEBUG_LUT6)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT7 0x%x\n", __raw_readl(EPDC_DEBUG_LUT7)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT8 0x%x\n", __raw_readl(EPDC_DEBUG_LUT8)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT9 0x%x\n", __raw_readl(EPDC_DEBUG_LUT9)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT10 0x%x\n", __raw_readl(EPDC_DEBUG_LUT10)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT11 0x%x\n", __raw_readl(EPDC_DEBUG_LUT11)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT12 0x%x\n", __raw_readl(EPDC_DEBUG_LUT12)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT13 0x%x\n", __raw_readl(EPDC_DEBUG_LUT13)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT14 0x%x\n", __raw_readl(EPDC_DEBUG_LUT14)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT15 0x%x\n", __raw_readl(EPDC_DEBUG_LUT15)); + printk(KERN_DEBUG "EPDC_GPIO 0x%x\n", __raw_readl(EPDC_GPIO)); + printk(KERN_DEBUG "EPDC_VERSION 0x%x\n", __raw_readl(EPDC_VERSION)); + printk(KERN_DEBUG "\n\n"); +} + +static void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) +{ + dev_err(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " + "LUT = %d, Coll Mask = 0x%x, order = %d\n", + upd_data_list->upd_data.update_region.left, + upd_data_list->upd_data.update_region.top, + upd_data_list->upd_data.update_region.width, + upd_data_list->upd_data.update_region.height, + upd_data_list->upd_data.waveform_mode, upd_data_list->lut_num, + upd_data_list->collision_mask, + upd_data_list->update_order); +} + +static void dump_collision_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_err(fb_data->dev, "Collision List:\n"); + if (list_empty(&fb_data->upd_buf_collision_list->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_collision_list->list, list) { + dev_err(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_free_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_err(fb_data->dev, "Free List:\n"); + if (list_empty(&fb_data->upd_buf_free_list->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_free_list->list, list) { + dev_err(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_queue(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_err(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_buf_queue->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_queue->list, list) { + dev_err(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_all_updates(struct mxc_epdc_fb_data *fb_data) +{ + dump_free_list(fb_data); + dump_queue(fb_data); + dump_collision_list(fb_data); + dev_err(fb_data->dev, "Current update being processed:\n"); + if (fb_data->cur_update == NULL) + dev_err(fb_data->dev, "No current update\n"); + else + dump_update_data(fb_data->dev, fb_data->cur_update); +} +#else +static inline void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) {} +static inline void dump_epdc_reg(void) {} +static inline void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) {} +static inline void dump_collision_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_free_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_queue(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_all_updates(struct mxc_epdc_fb_data *fb_data) {} + +#endif + + +/******************************************************** + * Start Low-Level EPDC Functions + ********************************************************/ + +static inline void epdc_lut_complete_intr(u32 lut_num, bool enable) +{ + if (enable) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_SET); + else + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_working_buf_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_clear_working_buf_irq(void) +{ + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ | EPDC_IRQ_LUT_COL_IRQ, + EPDC_IRQ_CLEAR); +} + +static inline void epdc_set_temp(u32 temp) +{ + __raw_writel(temp, EPDC_TEMP); +} + +static inline void epdc_set_screen_res(u32 width, u32 height) +{ + u32 val = (height << EPDC_RES_VERTICAL_OFFSET) | width; + __raw_writel(val, EPDC_RES); +} + +static inline void epdc_set_update_addr(u32 addr) +{ + __raw_writel(addr, EPDC_UPD_ADDR); +} + +static inline void epdc_set_update_coord(u32 x, u32 y) +{ + u32 val = (y << EPDC_UPD_CORD_YCORD_OFFSET) | x; + __raw_writel(val, EPDC_UPD_CORD); +} + +static inline void epdc_set_update_dimensions(u32 width, u32 height) +{ + u32 val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width; + __raw_writel(val, EPDC_UPD_SIZE); +} + +static void epdc_submit_update(u32 lut_num, u32 waveform_mode, u32 update_mode, + bool use_test_mode, u32 np_val) +{ + u32 reg_val = 0; + + if (use_test_mode) { + reg_val |= + ((np_val << EPDC_UPD_FIXED_FIXNP_OFFSET) & + EPDC_UPD_FIXED_FIXNP_MASK) | EPDC_UPD_FIXED_FIXNP_EN; + + __raw_writel(reg_val, EPDC_UPD_FIXED); + + reg_val = EPDC_UPD_CTRL_USE_FIXED; + } else { + __raw_writel(reg_val, EPDC_UPD_FIXED); + } + + reg_val |= + ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) & + EPDC_UPD_CTRL_LUT_SEL_MASK) | + ((waveform_mode << EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) & + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK) | + update_mode; + + __raw_writel(reg_val, EPDC_UPD_CTRL); +} + +static inline bool epdc_is_lut_complete(u32 lut_num) +{ + u32 val = __raw_readl(EPDC_IRQ); + bool is_compl = val & (1 << lut_num) ? true : false; + + return is_compl; +} + +static inline void epdc_clear_lut_complete_irq(u32 lut_num) +{ + __raw_writel(1 << lut_num, EPDC_IRQ_CLEAR); +} + +static inline bool epdc_is_lut_active(u32 lut_num) +{ + u32 val = __raw_readl(EPDC_STATUS_LUTS); + bool is_active = val & (1 << lut_num) ? true : false; + + return is_active; +} + +static inline bool epdc_any_luts_active(void) +{ + bool any_active = __raw_readl(EPDC_STATUS_LUTS) ? true : false; + + return any_active; +} + +static inline bool epdc_any_luts_available(void) +{ + bool luts_available = + (__raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false; + return luts_available; +} + +static inline int epdc_get_next_lut(void) +{ + u32 val = + __raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK; + return val; +} + +static inline bool epdc_is_working_buffer_busy(void) +{ + u32 val = __raw_readl(EPDC_STATUS); + bool is_busy = (val & EPDC_STATUS_WB_BUSY) ? true : false; + + return is_busy; +} + +static inline bool epdc_is_working_buffer_complete(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + bool is_compl = (val & EPDC_IRQ_WB_CMPLT_IRQ) ? true : false; + + return is_compl; +} + +static inline bool epdc_is_collision(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false; +} + +static inline int epdc_get_colliding_luts(void) +{ + u32 val = __raw_readl(EPDC_STATUS_COL); + return val; +} + +static void epdc_set_horizontal_timing(u32 horiz_start, u32 horiz_end, + u32 hsync_width, u32 hsync_line_length) +{ + u32 reg_val = + ((hsync_width << EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK) + | ((hsync_line_length << EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN1); + + reg_val = + ((horiz_start << EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET) & + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK) + | ((horiz_end << EPDC_TCE_HSCAN2_LINE_END_OFFSET) & + EPDC_TCE_HSCAN2_LINE_END_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN2); +} + +static void epdc_set_vertical_timing(u32 vert_start, u32 vert_end, + u32 vsync_width) +{ + u32 reg_val = + ((vert_start << EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET) & + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK) + | ((vert_end << EPDC_TCE_VSCAN_FRAME_END_OFFSET) & + EPDC_TCE_VSCAN_FRAME_END_MASK) + | ((vsync_width << EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET) & + EPDC_TCE_VSCAN_FRAME_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_VSCAN); +} + +void epdc_init_settings(struct mxc_epdc_fb_data *fb_data) +{ + struct mxc_epdc_fb_mode *epdc_mode = fb_data->cur_mode; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 reg_val; + int num_ce; + + /* Reset */ + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_SET); + while (!(__raw_readl(EPDC_CTRL) & EPDC_CTRL_CLKGATE)) + ; + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_CLEAR); + + /* Enable clock gating (clear to enable) */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + while (__raw_readl(EPDC_CTRL) & (EPDC_CTRL_SFTRST | EPDC_CTRL_CLKGATE)) + ; + + /* EPDC_CTRL */ + reg_val = __raw_readl(EPDC_CTRL); + reg_val &= ~EPDC_CTRL_UPD_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP; + reg_val &= ~EPDC_CTRL_LUT_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP; + __raw_writel(reg_val, EPDC_CTRL_SET); + + /* EPDC_FORMAT - 2bit TFT and 4bit Buf pixel format */ + reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT + | EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N + | ((0x0 << EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET) & + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK); + __raw_writel(reg_val, EPDC_FORMAT); + + /* EPDC_FIFOCTRL (disabled) */ + reg_val = + ((100 << EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK) + | ((200 << EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK) + | ((100 << EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK); + __raw_writel(reg_val, EPDC_FIFOCTRL); + + /* EPDC_TEMP - Use default temp to get index */ + epdc_set_temp(mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP)); + + /* EPDC_RES */ + epdc_set_screen_res(epdc_mode->vmode->xres, epdc_mode->vmode->yres); + + /* + * EPDC_TCE_CTRL + * VSCAN_HOLDOFF = 4 + * VCOM_MODE = MANUAL + * VCOM_VAL = 0 + * DDR_MODE = DISABLED + * LVDS_MODE_CE = DISABLED + * LVDS_MODE = DISABLED + * DUAL_SCAN = DISABLED + * SDDO_WIDTH = 8bit + * PIXELS_PER_SDCLK = 4 + */ + reg_val = + ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) & + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK) + | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4; + __raw_writel(reg_val, EPDC_TCE_CTRL); + + /* EPDC_TCE_HSCAN */ + epdc_set_horizontal_timing(screeninfo->left_margin, + screeninfo->right_margin, + screeninfo->hsync_len, + screeninfo->hsync_len); + + /* EPDC_TCE_VSCAN */ + epdc_set_vertical_timing(screeninfo->upper_margin, + screeninfo->lower_margin, + screeninfo->vsync_len); + + /* EPDC_TCE_OE */ + reg_val = + ((epdc_mode->sdoed_width << EPDC_TCE_OE_SDOED_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOED_WIDTH_MASK) + | ((epdc_mode->sdoed_delay << EPDC_TCE_OE_SDOED_DLY_OFFSET) & + EPDC_TCE_OE_SDOED_DLY_MASK) + | ((epdc_mode->sdoez_width << EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOEZ_WIDTH_MASK) + | ((epdc_mode->sdoez_delay << EPDC_TCE_OE_SDOEZ_DLY_OFFSET) & + EPDC_TCE_OE_SDOEZ_DLY_MASK); + __raw_writel(reg_val, EPDC_TCE_OE); + + /* EPDC_TCE_TIMING1 */ + __raw_writel(0x0, EPDC_TCE_TIMING1); + + /* EPDC_TCE_TIMING2 */ + reg_val = + ((epdc_mode->gdclk_hp_offs << EPDC_TCE_TIMING2_GDCLK_HP_OFFSET) & + EPDC_TCE_TIMING2_GDCLK_HP_MASK) + | ((epdc_mode->gdsp_offs << EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET) & + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING2); + + /* EPDC_TCE_TIMING3 */ + reg_val = + ((epdc_mode->gdoe_offs << EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK) + | ((epdc_mode->gdclk_offs << EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING3); + + /* + * EPDC_TCE_SDCFG + * SDCLK_HOLD = 1 + * SDSHR = 1 + * NUM_CE = 1 + * SDDO_REFORMAT = FLIP_PIXELS + * SDDO_INVERT = DISABLED + * PIXELS_PER_CE = display horizontal resolution + */ + num_ce = epdc_mode->num_ce; + if (num_ce == 0) + num_ce = 1; + reg_val = EPDC_TCE_SDCFG_SDCLK_HOLD | EPDC_TCE_SDCFG_SDSHR + | ((num_ce << EPDC_TCE_SDCFG_NUM_CE_OFFSET) & + EPDC_TCE_SDCFG_NUM_CE_MASK) + | EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS + | ((epdc_mode->vmode->xres/num_ce << EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET) & + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK); + __raw_writel(reg_val, EPDC_TCE_SDCFG); + + /* + * EPDC_TCE_GDCFG + * GDRL = 1 + * GDOE_MODE = 0; + * GDSP_MODE = 0; + */ + reg_val = EPDC_TCE_SDCFG_GDRL; + __raw_writel(reg_val, EPDC_TCE_GDCFG); + + /* + * EPDC_TCE_POLARITY + * SDCE_POL = ACTIVE LOW + * SDLE_POL = ACTIVE HIGH + * SDOE_POL = ACTIVE HIGH + * GDOE_POL = ACTIVE HIGH + * GDSP_POL = ACTIVE LOW + */ + reg_val = EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH; + __raw_writel(reg_val, EPDC_TCE_POLARITY); + + /* EPDC_IRQ_MASK */ + __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_MASK); + + /* + * EPDC_GPIO + * PWRCOM = ? + * PWRCTRL = ? + * BDR = ? + */ + reg_val = ((0 << EPDC_GPIO_PWRCTRL_OFFSET) & EPDC_GPIO_PWRCTRL_MASK) + | ((0 << EPDC_GPIO_BDR_OFFSET) & EPDC_GPIO_BDR_MASK); + __raw_writel(reg_val, EPDC_GPIO); +} + +static void epdc_powerup(struct mxc_epdc_fb_data *fb_data) +{ + int ret = 0; + mutex_lock(&fb_data->power_mutex); + + /* + * If power down request is pending, clear + * powering_down to cancel the request. + */ + if (fb_data->powering_down) + fb_data->powering_down = false; + + if (fb_data->power_state == POWER_STATE_ON) { + mutex_unlock(&fb_data->power_mutex); + return; + } + + dev_dbg(fb_data->dev, "EPDC Powerup\n"); + + /* Enable pins used by EPDC */ + if (fb_data->pdata->enable_pins) + fb_data->pdata->enable_pins(); + + /* Enable clocks to EPDC */ + clk_enable(fb_data->epdc_clk_axi); + clk_enable(fb_data->epdc_clk_pix); + + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + + /* Enable power to the EPD panel */ + ret = regulator_enable(fb_data->display_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable DISPLAY regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + ret = regulator_enable(fb_data->vcom_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable VCOM regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + + fb_data->power_state = POWER_STATE_ON; + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_powerdown(struct mxc_epdc_fb_data *fb_data) +{ + mutex_lock(&fb_data->power_mutex); + + /* If powering_down has been cleared, a powerup + * request is pre-empting this powerdown request. + */ + if (!fb_data->powering_down + || (fb_data->power_state == POWER_STATE_OFF)) { + mutex_unlock(&fb_data->power_mutex); + return; + } + + dev_dbg(fb_data->dev, "EPDC Powerdown\n"); + + /* Disable power to the EPD panel */ + regulator_disable(fb_data->vcom_regulator); + regulator_disable(fb_data->display_regulator); + + /* Disable clocks to EPDC */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); + clk_disable(fb_data->epdc_clk_pix); + clk_disable(fb_data->epdc_clk_axi); + + /* Disable pins used by EPDC (to prevent leakage current) */ + if (fb_data->pdata->disable_pins) + fb_data->pdata->disable_pins(); + + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_init_sequence(struct mxc_epdc_fb_data *fb_data) +{ + /* Initialize EPDC, passing pointer to EPDC registers */ + epdc_init_settings(fb_data); + __raw_writel(fb_data->waveform_buffer_phys, EPDC_WVADDR); + __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR); + epdc_powerup(fb_data); + draw_mode0(fb_data); + epdc_powerdown(fb_data); +} + +static int mxc_epdc_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset < info->fix.smem_len) { + /* mapping framebuffer memory */ + len = info->fix.smem_len - offset; + vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT; + } else + return -EINVAL; + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(info->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +static int mxc_epdc_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return 1; + /* + * Program hardware... do anything you want with transp + */ + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_PSEUDOCOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + break; + case FB_VISUAL_DIRECTCOLOR: + red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */ + green = CNVT_TOHW(green, 8); + blue = CNVT_TOHW(blue, 8); + /* hey, there is bug in transp handling... */ + transp = CNVT_TOHW(transp, 8); + break; + } +#undef CNVT_TOHW + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + + if (regno >= 16) + return 1; + + ((u32 *) (info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + } + return 0; +} + +static void adjust_coordinates(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region) +{ + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 rotation = fb_data->epdc_fb_var.rotate; + u32 temp; + + /* If adj_update_region == NULL, pass result back in update_region */ + /* If adj_update_region == valid, use it to pass back result */ + if (adj_update_region) + switch (rotation) { + case FB_ROTATE_UR: + adj_update_region->top = update_region->top; + adj_update_region->left = update_region->left; + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + break; + case FB_ROTATE_CW: + adj_update_region->top = update_region->left; + adj_update_region->left = screeninfo->yres - + (update_region->top + update_region->height); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + case FB_ROTATE_UD: + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + adj_update_region->top = screeninfo->yres - + (update_region->top + update_region->height); + adj_update_region->left = screeninfo->xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + adj_update_region->left = update_region->top; + adj_update_region->top = screeninfo->xres - + (update_region->left + update_region->width); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + } + else + switch (rotation) { + case FB_ROTATE_UR: + /* No adjustment needed */ + break; + case FB_ROTATE_CW: + temp = update_region->top; + update_region->top = update_region->left; + update_region->left = screeninfo->yres - + (temp + update_region->height); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + case FB_ROTATE_UD: + update_region->top = screeninfo->yres - + (update_region->top + update_region->height); + update_region->left = screeninfo->xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + temp = update_region->left; + update_region->left = update_region->top; + update_region->top = screeninfo->xres - + (temp + update_region->width); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + } +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxc_epdc_fb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + if (var->grayscale) + fix->visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + else + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + * + */ +static int mxc_epdc_fb_set_par(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &pxp_conf->proc_data; + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + struct mxc_epdc_fb_mode *epdc_modes = fb_data->pdata->epdc_mode; + int i; + int ret; + unsigned long flags; + __u32 xoffset_old, yoffset_old; + + /* + * Can't change the FB parameters until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_epdc_fb_flush_updates(fb_data); + + spin_lock_irqsave(&fb_data->queue_lock, flags); + /* + * Set all screeninfo except for xoffset/yoffset + * Subsequent call to pan_display will handle those. + */ + xoffset_old = fb_data->epdc_fb_var.xoffset; + yoffset_old = fb_data->epdc_fb_var.yoffset; + fb_data->epdc_fb_var = *screeninfo; + fb_data->epdc_fb_var.xoffset = xoffset_old; + fb_data->epdc_fb_var.yoffset = yoffset_old; + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + mutex_lock(&fb_data->pxp_mutex); + + /* + * Update PxP config data (used to process FB regions for updates) + * based on FB info and processing tasks required + */ + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = screeninfo->xres; + proc_data->drect.height = proc_data->srect.height = screeninfo->yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = screeninfo->rotate; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + + /* + * configure S0 channel parameters + * Parameters should match FB format/width/height + */ + if (screeninfo->grayscale) + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_GREY; + else { + switch (screeninfo->bits_per_pixel) { + case 16: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + case 24: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB24; + break; + case 32: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB32; + break; + default: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + } + } + pxp_conf->s0_param.width = screeninfo->xres_virtual; + pxp_conf->s0_param.height = screeninfo->yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = screeninfo->xres; + pxp_conf->out_param.height = screeninfo->yres; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; + + mutex_unlock(&fb_data->pxp_mutex); + + /* + * If HW not yet initialized, check to see if we are being sent + * an initialization request. + */ + if (!fb_data->hw_ready) { + struct fb_videomode mode; + bool found_match = false; + u32 xres_temp; + + fb_var_to_videomode(&mode, screeninfo); + + /* When comparing requested fb mode, + we need to use unrotated dimensions */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres_temp = mode.xres; + mode.xres = mode.yres; + mode.yres = xres_temp; + } + + /* Match videomode against epdc modes */ + for (i = 0; i < fb_data->pdata->num_modes; i++) { + if (!fb_mode_is_equal(epdc_modes[i].vmode, &mode)) + continue; + fb_data->cur_mode = &epdc_modes[i]; + found_match = true; + break; + } + + if (!found_match) { + dev_err(fb_data->dev, + "Failed to match requested video mode\n"); + return EINVAL; + } + + /* Found a match - Grab timing params */ + screeninfo->left_margin = mode.left_margin; + screeninfo->right_margin = mode.right_margin; + screeninfo->upper_margin = mode.upper_margin; + screeninfo->lower_margin = mode.lower_margin; + screeninfo->hsync_len = mode.hsync_len; + screeninfo->vsync_len = mode.vsync_len; + + /* Initialize EPDC settings and init panel */ + ret = + mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(fb_data->dev, + "Failed to load panel waveform data\n"); + return ret; + } + } + + mxc_epdc_fb_set_fix(info); + + return 0; +} + +static int mxc_epdc_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8)) + var->bits_per_pixel = default_bpp; + + switch (var->bits_per_pixel) { + case 8: + if (var->grayscale != 0) { + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var->red.length = 8; + var->red.offset = 0; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 0; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } else { + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + switch (var->rotate) { + case FB_ROTATE_UR: + case FB_ROTATE_UD: + var->xres = fb_data->native_width; + var->yres = fb_data->native_height; + break; + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + var->xres = fb_data->native_height; + var->yres = fb_data->native_width; + break; + default: + /* Invalid rotation value */ + var->rotate = 0; + dev_dbg(fb_data->dev, "Invalid rotation request\n"); + return -EINVAL; + } + + var->xres_virtual = ALIGN(var->xres, 32); + var->yres_virtual = ALIGN(var->yres, 128) * fb_data->num_screens; + + var->height = -1; + var->width = -1; + + return 0; +} + +void mxc_epdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + memcpy(&fb_data->wv_modes, modes, sizeof(modes)); +} +EXPORT_SYMBOL(mxc_epdc_fb_set_waveform_modes); + +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, int temp) +{ + int i; + int index = -1; + + if (fb_data->trt_entries == 0) { + dev_err(fb_data->dev, + "No TRT exists...using default temp index\n"); + return DEFAULT_TEMP_INDEX; + } + + /* Search temperature ranges for a match */ + for (i = 0; i < fb_data->trt_entries - 1; i++) { + if ((temp >= fb_data->temp_range_bounds[i]) + && (temp < fb_data->temp_range_bounds[i+1])) { + index = i; + break; + } + } + + if (index < 0) { + dev_err(fb_data->dev, + "No TRT index match...using default temp index\n"); + return DEFAULT_TEMP_INDEX; + } + + dev_dbg(fb_data->dev, "Using temperature index %d\n", index); + + return index; +} + +int mxc_epdc_fb_set_temperature(int temperature, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + unsigned long flags; + + /* Store temp index. Used later when configuring updates. */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, temperature); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_temperature); + +int mxc_epdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + dev_dbg(fb_data->dev, "Setting auto update mode to %d\n", auto_mode); + + if ((auto_mode == AUTO_UPDATE_MODE_AUTOMATIC_MODE) + || (auto_mode == AUTO_UPDATE_MODE_REGION_MODE)) + fb_data->auto_mode = auto_mode; + else { + dev_err(fb_data->dev, "Invalid auto update mode parameter.\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_auto_update); + +int mxc_epdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + dev_dbg(fb_data->dev, "Setting optimization level to %d\n", upd_scheme); + + /* + * Can't change the scheme until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_epdc_fb_flush_updates(fb_data); + + if ((upd_scheme == UPDATE_SCHEME_SNAPSHOT) + || (upd_scheme == UPDATE_SCHEME_QUEUE) + || (upd_scheme == UPDATE_SCHEME_QUEUE_AND_MERGE)) + fb_data->upd_scheme = upd_scheme; + else { + dev_err(fb_data->dev, "Invalid update scheme specified.\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_upd_scheme); + +static void copy_before_process(struct mxc_epdc_fb_data *fb_data, + struct update_data_list *upd_data_list) +{ + int i; + unsigned char *temp_buf_ptr = upd_data_list->virt_addr_copybuf; + unsigned char *src_ptr; + struct mxcfb_rect *src_upd_region; + int temp_buf_stride; + int src_stride; + int bpp = fb_data->epdc_fb_var.bits_per_pixel; + int left_offs, right_offs; + int x_trailing_bytes, y_trailing_bytes; + + /* Set source buf pointer based on input source, panning, etc. */ + if (upd_data_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_upd_region = &upd_data_list->upd_data.alt_buffer_data.alt_update_region; + src_stride = + upd_data_list->upd_data.alt_buffer_data.width * bpp/8; + src_ptr = upd_data_list->upd_data.alt_buffer_data.virt_addr + + src_upd_region->top * src_stride; + } else { + src_upd_region = &upd_data_list->upd_data.update_region; + src_stride = fb_data->epdc_fb_var.xres_virtual * bpp/8; + src_ptr = fb_data->info.screen_base + fb_data->fb_offset + + src_upd_region->top * src_stride; + } + + temp_buf_stride = ALIGN(src_upd_region->width, 8) * bpp/8; + left_offs = src_upd_region->left * bpp/8; + right_offs = src_upd_region->width * bpp/8; + x_trailing_bytes = (ALIGN(src_upd_region->width, 8) + - src_upd_region->width) * bpp/8; + + for (i = 0; i < src_upd_region->height; i++) { + /* Copy the full line */ + memcpy(temp_buf_ptr, src_ptr + left_offs, + src_upd_region->width * bpp/8); + + /* Clear any unwanted pixels at the end of each line */ + if (src_upd_region->width & 0x7) { + memset(temp_buf_ptr + right_offs, 0x0, + x_trailing_bytes); + } + + temp_buf_ptr += temp_buf_stride; + src_ptr += src_stride; + } + + /* Clear any unwanted pixels at the bottom of the end of each line */ + if (src_upd_region->height & 0x7) { + y_trailing_bytes = (ALIGN(src_upd_region->height, 8) + - src_upd_region->height) * + ALIGN(src_upd_region->width, 8) * bpp/8; + memset(temp_buf_ptr, 0x0, y_trailing_bytes); + } +} + +static int epdc_process_update(struct update_data_list *upd_data_list, + struct mxc_epdc_fb_data *fb_data) +{ + struct mxcfb_rect *src_upd_region; /* Region of src buffer for update */ + struct mxcfb_rect pxp_upd_region; + u32 src_width, src_height; + u32 offset_from_4, bytes_per_pixel; + u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; + u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; + u32 hist_stat = 0; + int width_unaligned, height_unaligned; + bool input_unaligned = false; + bool line_overflow = false; + int pix_per_line_added; + bool use_temp_buf = false; + struct mxcfb_rect temp_buf_upd_region; + + int ret; + + /* + * Gotta do a whole bunch of buffer ptr manipulation to + * work around HW restrictions for PxP & EPDC + */ + + /* + * Are we using FB or an alternate (overlay) + * buffer for source of update? + */ + if (upd_data_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_width = upd_data_list->upd_data.alt_buffer_data.width; + src_height = upd_data_list->upd_data.alt_buffer_data.height; + src_upd_region = &upd_data_list->upd_data.alt_buffer_data.alt_update_region; + } else { + src_width = fb_data->epdc_fb_var.xres_virtual; + src_height = fb_data->epdc_fb_var.yres; + src_upd_region = &upd_data_list->upd_data.update_region; + } + + bytes_per_pixel = fb_data->epdc_fb_var.bits_per_pixel/8; + + /* + * SW workaround for PxP limitation + * + * There are 3 cases where we cannot process the update data + * directly from the input buffer: + * + * 1) PxP must process 8x8 pixel blocks, and all pixels in each block + * are considered for auto-waveform mode selection. If the + * update region is not 8x8 aligned, additional unwanted pixels + * will be considered in auto-waveform mode selection. + * + * 2) PxP input must be 32-bit aligned, so any update + * address not 32-bit aligned must be shifted to meet the + * 32-bit alignment. The PxP will thus end up processing pixels + * outside of the update region to satisfy this alignment restriction, + * which can affect auto-waveform mode selection. + * + * 3) If input fails 32-bit alignment, and the resulting expansion + * of the processed region would add at least 8 pixels more per + * line than the original update line width, the EPDC would + * cause screen artifacts by incorrectly handling the 8+ pixels + * at the end of each line. + * + * Workaround is to copy from source buffer into a temporary + * buffer, which we pad with zeros to match the 8x8 alignment + * requirement. This temp buffer becomes the input to the PxP. + */ + width_unaligned = src_upd_region->width & 0x7; + height_unaligned = src_upd_region->height & 0x7; + + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + + pix_per_line_added = offset_from_4 / bytes_per_pixel; + if ((((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || + fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) && + (ALIGN(src_upd_region->width, 8) < + ALIGN(src_upd_region->width + pix_per_line_added, 8))) + line_overflow = true; + + if (((width_unaligned || height_unaligned || input_unaligned) && + (upd_data_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) + || line_overflow) { + + dev_dbg(fb_data->dev, "Copying update before processing.\n"); + + /* Update to reflect what the new source buffer will be */ + src_width = ALIGN(src_upd_region->width, 8); + src_height = ALIGN(src_upd_region->height, 8); + + copy_before_process(fb_data, upd_data_list); + + /* + * src_upd_region should now describe + * the new update buffer attributes. + */ + temp_buf_upd_region.left = 0; + temp_buf_upd_region.top = 0; + temp_buf_upd_region.width = src_upd_region->width; + temp_buf_upd_region.height = src_upd_region->height; + src_upd_region = &temp_buf_upd_region; + + use_temp_buf = true; + } + + /* + * Compute buffer offset to account for + * PxP limitation (input must be 32-bit aligned) + */ + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + if (input_unaligned) { + /* Leave a gap between PxP input addr and update region pixels */ + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel & 0xFFFFFFFC; + /* Update region should change to reflect relative position to input ptr */ + pxp_upd_region.top = 0; + pxp_upd_region.left = offset_from_4 / bytes_per_pixel; + } else { + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel; + /* Update region should change to reflect relative position to input ptr */ + pxp_upd_region.top = 0; + pxp_upd_region.left = 0; + } + + /* Update region dimensions to meet 8x8 pixel requirement */ + pxp_upd_region.width = + ALIGN(src_upd_region->width + pxp_upd_region.left, 8); + pxp_upd_region.height = ALIGN(src_upd_region->height, 8); + + switch (fb_data->epdc_fb_var.rotate) { + case FB_ROTATE_UR: + default: + post_rotation_xcoord = pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.top; + width_pxp_blocks = pxp_upd_region.width; + break; + case FB_ROTATE_CW: + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = width_pxp_blocks - src_upd_region->height; + post_rotation_ycoord = pxp_upd_region.left; + break; + case FB_ROTATE_UD: + width_pxp_blocks = pxp_upd_region.width; + post_rotation_xcoord = width_pxp_blocks - src_upd_region->width - pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.height - src_upd_region->height - pxp_upd_region.top; + break; + case FB_ROTATE_CCW: + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = pxp_upd_region.top; + post_rotation_ycoord = pxp_upd_region.width - src_upd_region->width - pxp_upd_region.left; + break; + } + + /* Update region start coord to force PxP to process full 8x8 regions */ + pxp_upd_region.top &= ~0x7; + pxp_upd_region.left &= ~0x7; + + pxp_output_shift = ALIGN(post_rotation_xcoord, 8) + - post_rotation_xcoord; + + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + pxp_output_shift; + + upd_data_list->epdc_offs = ALIGN(pxp_output_offs, 8); + + mutex_lock(&fb_data->pxp_mutex); + + /* Source address either comes from alternate buffer + provided in update data, or from the framebuffer. */ + if (use_temp_buf) + sg_dma_address(&fb_data->sg[0]) = + upd_data_list->phys_addr_copybuf; + else if (upd_data_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) + sg_dma_address(&fb_data->sg[0]) = + upd_data_list->upd_data.alt_buffer_data.phys_addr + + pxp_input_offs; + else { + sg_dma_address(&fb_data->sg[0]) = + fb_data->info.fix.smem_start + fb_data->fb_offset + + pxp_input_offs; + sg_set_page(&fb_data->sg[0], + virt_to_page(fb_data->info.screen_base), + fb_data->info.fix.smem_len, + offset_in_page(fb_data->info.screen_base)); + } + + /* Update sg[1] to point to output of PxP proc task */ + sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr + + pxp_output_shift; + sg_set_page(&fb_data->sg[1], virt_to_page(upd_data_list->virt_addr), + upd_data_list->size, + offset_in_page(upd_data_list->virt_addr)); + + /* + * Set PxP LUT transform type based on update flags. + */ + fb_data->pxp_conf.proc_data.lut_transform = 0; + if (upd_data_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION) + fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT; + if (upd_data_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME) + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_BLACK_WHITE; + + /* + * Toggle inversion processing if 8-bit + * inverted is the current pixel format. + */ + if (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT_INVERTED) + fb_data->pxp_conf.proc_data.lut_transform ^= PXP_LUT_INVERT; + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_process_update(fb_data, src_width, src_height, + &pxp_upd_region); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + epdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &hist_stat); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + mutex_unlock(&fb_data->pxp_mutex); + + /* Update waveform mode from PxP histogram results */ + if (upd_data_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO) { + if (hist_stat & 0x1) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_du; + else if (hist_stat & 0x2) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc4; + else if (hist_stat & 0x4) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc8; + else if (hist_stat & 0x8) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; + else + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc32; + + dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", + hist_stat, upd_data_list->upd_data.waveform_mode); + } + + return 0; + +} + +static int epdc_submit_merge(struct update_data_list *upd_data_list, + struct update_data_list *update_to_merge) +{ + struct mxcfb_update_data *a, *b; + struct mxcfb_rect *arect, *brect; + struct mxcfb_rect combine; + bool use_flags = false; + + a = &upd_data_list->upd_data; + b = &update_to_merge->upd_data; + arect = &upd_data_list->upd_data.update_region; + brect = &update_to_merge->upd_data.update_region; + + /* + * Updates with different flags must be executed sequentially. + * Halt the merge process to ensure this. + */ + if (a->flags != b->flags) { + /* + * Special exception: if update regions are identical, + * we may be able to merge them. + */ + if ((arect->left != brect->left) || + (arect->top != brect->top) || + (arect->width != brect->width) || + (arect->height != brect->height)) + return MERGE_BLOCK; + + use_flags = true; + } + + if ((a->waveform_mode != b->waveform_mode + && a->waveform_mode != WAVEFORM_MODE_AUTO) || + a->update_mode != b->update_mode || + arect->left > (brect->left + brect->width) || + brect->left > (arect->left + arect->width) || + arect->top > (brect->top + brect->height) || + brect->top > (arect->top + arect->height) || + (b->update_marker != 0 && a->update_marker != 0)) + return MERGE_FAIL; + + combine.left = arect->left < brect->left ? arect->left : brect->left; + combine.top = arect->top < brect->top ? arect->top : brect->top; + combine.width = (arect->left + arect->width) > + (brect->left + brect->width) ? + (arect->left + arect->width - combine.left) : + (brect->left + brect->width - combine.left); + combine.height = (arect->top + arect->height) > + (brect->top + brect->height) ? + (arect->top + arect->height - combine.top) : + (brect->top + brect->height - combine.top); + + *arect = combine; + + /* Use flags of the later update */ + if (use_flags) + a->flags = b->flags; + + /* Preserve marker value for merged update */ + if (b->update_marker != 0) { + a->update_marker = b->update_marker; + upd_data_list->upd_marker_data = + update_to_merge->upd_marker_data; + } + + /* Merged update should take on the earliest order */ + upd_data_list->update_order = + (upd_data_list->update_order > update_to_merge->update_order) ? + upd_data_list->update_order : update_to_merge->update_order; + + return MERGE_OK; +} + +static void epdc_submit_work_func(struct work_struct *work) +{ + int temp_index; + struct update_data_list *next_update; + struct update_data_list *temp; + unsigned long flags; + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_submit_work); + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect adj_update_region; + bool end_merge = false; + + /* Protect access to buffer queues and to update HW */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry_safe(next_update, temp, + &fb_data->upd_buf_collision_list->list, list) { + + if (next_update->collision_mask != 0) + continue; + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + + /* + * We have a collision cleared, so select it for resubmission. + * If an update is already selected, attempt to merge. + */ + if (!upd_data_list) { + upd_data_list = next_update; + list_del_init(&next_update->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + /* If not merging, we have our update */ + break; + } else { + switch (epdc_submit_merge(upd_data_list, + next_update)) { + case MERGE_OK: + dev_dbg(fb_data->dev, + "Update merged [collision]\n"); + list_del_init(&next_update->list); + /* Add to free buffer list */ + list_add_tail(&next_update->list, + &fb_data->upd_buf_free_list->list); + break; + case MERGE_FAIL: + dev_dbg(fb_data->dev, + "Update not merged [collision]\n"); + break; + case MERGE_BLOCK: + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) { + end_merge = false; + break; + } + } + } + + /* + * Skip update queue only if we found a collision + * update and we are not merging + */ + if (!((fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) && + upd_data_list)) { + /* + * If we didn't find a collision update ready to go, + * we try to grab one from the update queue + */ + list_for_each_entry_safe(next_update, temp, + &fb_data->upd_buf_queue->list, list) { + + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + if (!upd_data_list) { + upd_data_list = next_update; + list_del_init(&next_update->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + /* If not merging, we have an update */ + break; + } else { + switch (epdc_submit_merge(upd_data_list, + next_update)) { + case MERGE_OK: + dev_dbg(fb_data->dev, + "Update merged [queue]\n"); + list_del_init(&next_update->list); + /* Add to free buffer list */ + list_add_tail(&next_update->list, + &fb_data->upd_buf_free_list->list); + break; + case MERGE_FAIL: + dev_dbg(fb_data->dev, + "Update not merged [queue]\n"); + break; + case MERGE_BLOCK: + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) + break; + } + } + } + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + /* Is update list empty? */ + if (!upd_data_list) + return; + + /* Perform PXP processing - EPDC power will also be enabled */ + if (epdc_process_update(upd_data_list, fb_data)) { + dev_dbg(fb_data->dev, "PXP processing error.\n"); + /* Protect access to buffer queues and to update HW */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + /* Add to free buffer list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_free_list->list); + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return; + } + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data, &upd_data_list->upd_data.update_region, + &adj_update_region); + + /* Protect access to buffer queues and to update HW */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * Is the working buffer idle? + * If the working buffer is busy, we must wait for the resource + * to become free. The IST will signal this event. + */ + if (fb_data->cur_update != NULL) { + dev_dbg(fb_data->dev, "working buf busy!\n"); + + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_wb = true; + + /* Leave spinlock while waiting for WB to complete */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + wait_for_completion(&fb_data->update_res_free); + spin_lock_irqsave(&fb_data->queue_lock, flags); + } + + /* + * If there are no LUTs available, + * then we must wait for the resource to become free. + * The IST will signal this event. + */ + if (!epdc_any_luts_available()) { + dev_dbg(fb_data->dev, "no luts available!\n"); + + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_lut = true; + + /* Leave spinlock while waiting for LUT to free up */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + wait_for_completion(&fb_data->update_res_free); + spin_lock_irqsave(&fb_data->queue_lock, flags); + } + + + /* LUTs are available, so we get one here */ + fb_data->cur_update = upd_data_list; + fb_data->cur_update->lut_num = epdc_get_next_lut(); + + /* Associate LUT with update marker */ + if ((fb_data->cur_update->upd_marker_data) + && (fb_data->cur_update->upd_marker_data->update_marker != 0)) + fb_data->cur_update->upd_marker_data->lut_num = + fb_data->cur_update->lut_num; + + /* Mark LUT with order */ + fb_data->lut_update_order[fb_data->cur_update->lut_num] = + fb_data->cur_update->update_order; + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + if (fb_data->cur_update->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + fb_data->cur_update->upd_data.temp); + epdc_set_temp(temp_index); + } + epdc_set_update_addr(fb_data->cur_update->phys_addr + + fb_data->cur_update->epdc_offs); + epdc_set_update_coord(adj_update_region.left, adj_update_region.top); + epdc_set_update_dimensions(adj_update_region.width, + adj_update_region.height); + epdc_submit_update(fb_data->cur_update->lut_num, + fb_data->cur_update->upd_data.waveform_mode, + fb_data->cur_update->upd_data.update_mode, false, 0); + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); +} + + +int mxc_epdc_fb_send_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + struct update_data_list *upd_data_list = NULL; + unsigned long flags; + int i; + struct mxcfb_rect *screen_upd_region; /* Region on screen to update */ + int temp_index; + int ret; + + /* Has EPDC HW been initialized? */ + if (!fb_data->hw_ready) { + dev_err(fb_data->dev, "Display HW not properly initialized." + " Aborting update.\n"); + return -EPERM; + } + + /* Check validity of update params */ + if ((upd_data->update_mode != UPDATE_MODE_PARTIAL) && + (upd_data->update_mode != UPDATE_MODE_FULL)) { + dev_err(fb_data->dev, + "Update mode 0x%x is invalid. Aborting update.\n", + upd_data->update_mode); + return -EINVAL; + } + if ((upd_data->waveform_mode > 255) && + (upd_data->waveform_mode != WAVEFORM_MODE_AUTO)) { + dev_err(fb_data->dev, + "Update waveform mode 0x%x is invalid." + " Aborting update.\n", + upd_data->waveform_mode); + return -EINVAL; + } + if ((upd_data->update_region.left + upd_data->update_region.width > fb_data->epdc_fb_var.xres) || + (upd_data->update_region.top + upd_data->update_region.height > fb_data->epdc_fb_var.yres)) { + dev_err(fb_data->dev, + "Update region is outside bounds of framebuffer." + "Aborting update.\n"); + return -EINVAL; + } + if ((upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) && + ((upd_data->update_region.width != upd_data->alt_buffer_data.alt_update_region.width) || + (upd_data->update_region.height != upd_data->alt_buffer_data.alt_update_region.height))) { + dev_err(fb_data->dev, + "Alternate update region dimensions must match screen update region dimensions.\n"); + return -EINVAL; + } + + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * If we are waiting to go into suspend, or the FB is blanked, + * we do not accept new updates + */ + if ((fb_data->waiting_for_idle) || + (fb_data->blank != FB_BLANK_UNBLANK)) { + dev_dbg(fb_data->dev, "EPDC not active." + "Update request abort.\n"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return -EPERM; + } + + /* + * Get available intermediate (PxP output) buffer to hold + * processed update region + */ + if (list_empty(&fb_data->upd_buf_free_list->list)) { + dev_err(fb_data->dev, + "No free intermediate buffers available.\n"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return -ENOMEM; + } + + /* Grab first available buffer and delete it from the free list */ + upd_data_list = + list_entry(fb_data->upd_buf_free_list->list.next, + struct update_data_list, list); + + list_del_init(&upd_data_list->list); + + /* copy update parameters to the current update data object */ + memcpy(&upd_data_list->upd_data, upd_data, + sizeof(struct mxcfb_update_data)); + memcpy(&upd_data_list->upd_data.update_region, &upd_data->update_region, + sizeof(struct mxcfb_rect)); + + /* If marker specified, associate it with a completion */ + if (upd_data->update_marker != 0) { + /* Find available update marker and set it up */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + /* Marker value set to 0 signifies it is not currently in use */ + if (fb_data->update_marker_array[i].update_marker == 0) { + fb_data->update_marker_array[i].update_marker = upd_data->update_marker; + init_completion(&fb_data->update_marker_array[i].update_completion); + upd_data_list->upd_marker_data = &fb_data->update_marker_array[i]; + break; + } + } + } else { + if (upd_data_list->upd_marker_data) + upd_data_list->upd_marker_data->update_marker = 0; + } + + upd_data_list->update_order = fb_data->order_cnt++; + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_queue->list); + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + /* Signal workqueue to handle new update */ + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + return 0; + } + + /* Snapshot update scheme processing */ + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + /* + * Hold on to original screen update region, which we + * will ultimately use when telling EPDC where to update on panel + */ + screen_upd_region = &upd_data_list->upd_data.update_region; + + ret = epdc_process_update(upd_data_list, fb_data); + if (ret) { + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* Pass selected waveform mode back to user */ + upd_data->waveform_mode = upd_data_list->upd_data.waveform_mode; + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data, &upd_data_list->upd_data.update_region, + NULL); + + /* Grab lock for queue manipulation and update submission */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * Is the working buffer idle? + * If either the working buffer is busy, or there are no LUTs available, + * then we return and let the ISR handle the update later + */ + if ((fb_data->cur_update != NULL) || !epdc_any_luts_available()) { + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_queue->list); + + /* Return and allow the update to be submitted by the ISR. */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return 0; + } + + /* Save current update */ + fb_data->cur_update = upd_data_list; + + /* LUTs are available, so we get one here */ + upd_data_list->lut_num = epdc_get_next_lut(); + + /* Associate LUT with update marker */ + if (upd_data_list->upd_marker_data) + if (upd_data_list->upd_marker_data->update_marker != 0) + upd_data_list->upd_marker_data->lut_num = upd_data_list->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[upd_data_list->lut_num] = + upd_data_list->update_order; + + /* Clear status and Enable LUT complete and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(upd_data_list->phys_addr + upd_data_list->epdc_offs); + epdc_set_update_coord(screen_upd_region->left, screen_upd_region->top); + epdc_set_update_dimensions(screen_upd_region->width, + screen_upd_region->height); + if (upd_data_list->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + upd_data_list->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + + epdc_submit_update(upd_data_list->lut_num, + upd_data_list->upd_data.waveform_mode, + upd_data_list->upd_data.update_mode, false, 0); + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_send_update); + +int mxc_epdc_fb_wait_update_complete(u32 update_marker, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + int ret; + int i; + + /* 0 is an invalid update_marker value */ + if (update_marker == 0) + return -EINVAL; + + /* + * Wait for completion associated with update_marker requested. + * Note: If update completed already, marker will have been + * cleared and we will just return + */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + if (fb_data->update_marker_array[i].update_marker == update_marker) { + dev_dbg(fb_data->dev, "Waiting for marker %d\n", update_marker); + ret = wait_for_completion_timeout(&fb_data->update_marker_array[i].update_completion, msecs_to_jiffies(5000)); + if (!ret) + dev_err(fb_data->dev, "Timed out waiting for update completion\n"); + dev_dbg(fb_data->dev, "marker %d signalled!\n", update_marker); + break; + } + } + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_wait_update_complete); + +int mxc_epdc_fb_set_pwrdown_delay(u32 pwrdown_delay, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + fb_data->pwrdown_delay = pwrdown_delay; + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_pwrdown_delay); + +int mxc_epdc_get_pwrdown_delay(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + return fb_data->pwrdown_delay; +} +EXPORT_SYMBOL(mxc_epdc_get_pwrdown_delay); + +static int mxc_epdc_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int ret = -EINVAL; + + switch (cmd) { + case MXCFB_SET_WAVEFORM_MODES: + { + struct mxcfb_waveform_modes modes; + if (!copy_from_user(&modes, argp, sizeof(modes))) { + mxc_epdc_fb_set_waveform_modes(&modes, info); + ret = 0; + } + break; + } + case MXCFB_SET_TEMPERATURE: + { + int temperature; + if (!get_user(temperature, (int32_t __user *) arg)) + ret = mxc_epdc_fb_set_temperature(temperature, + info); + break; + } + case MXCFB_SET_AUTO_UPDATE_MODE: + { + u32 auto_mode = 0; + if (!get_user(auto_mode, (__u32 __user *) arg)) + ret = mxc_epdc_fb_set_auto_update(auto_mode, + info); + break; + } + case MXCFB_SET_UPDATE_SCHEME: + { + u32 upd_scheme = 0; + if (!get_user(upd_scheme, (__u32 __user *) arg)) + ret = mxc_epdc_fb_set_upd_scheme(upd_scheme, + info); + break; + } + case MXCFB_SEND_UPDATE: + { + struct mxcfb_update_data upd_data; + if (!copy_from_user(&upd_data, argp, + sizeof(upd_data))) { + ret = mxc_epdc_fb_send_update(&upd_data, info); + if (ret == 0 && copy_to_user(argp, &upd_data, + sizeof(upd_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + break; + } + case MXCFB_WAIT_FOR_UPDATE_COMPLETE: + { + u32 update_marker = 0; + if (!get_user(update_marker, (__u32 __user *) arg)) + ret = + mxc_epdc_fb_wait_update_complete(update_marker, + info); + break; + } + + case MXCFB_SET_PWRDOWN_DELAY: + { + int delay = 0; + if (!get_user(delay, (__u32 __user *) arg)) + ret = + mxc_epdc_fb_set_pwrdown_delay(delay, info); + break; + } + + case MXCFB_GET_PWRDOWN_DELAY: + { + int pwrdown_delay = mxc_epdc_get_pwrdown_delay(info); + if (put_user(pwrdown_delay, + (int __user *)argp)) + ret = -EFAULT; + ret = 0; + break; + } + default: + break; + } + return ret; +} + +static void mxc_epdc_fb_update_pages(struct mxc_epdc_fb_data *fb_data, + u16 y1, u16 y2) +{ + struct mxcfb_update_data update; + + /* Do partial screen update, Update full horizontal lines */ + update.update_region.left = 0; + update.update_region.width = fb_data->epdc_fb_var.xres; + update.update_region.top = y1; + update.update_region.height = y2 - y1; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_mode = UPDATE_MODE_FULL; + update.update_marker = 0; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, &fb_data->info); +} + +/* this is called back from the deferred io workqueue */ +static void mxc_epdc_fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct page *page; + unsigned long beg, end; + int y1, y2, miny, maxy; + + if (fb_data->auto_mode != AUTO_UPDATE_MODE_AUTOMATIC_MODE) + return; + + miny = INT_MAX; + maxy = 0; + list_for_each_entry(page, pagelist, lru) { + beg = page->index << PAGE_SHIFT; + end = beg + PAGE_SIZE - 1; + y1 = beg / info->fix.line_length; + y2 = end / info->fix.line_length; + if (y2 >= fb_data->epdc_fb_var.yres) + y2 = fb_data->epdc_fb_var.yres - 1; + if (miny > y1) + miny = y1; + if (maxy < y2) + maxy = y2; + } + + mxc_epdc_fb_update_pages(fb_data, miny, maxy); +} + +void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data) +{ + unsigned long flags; + /* Grab queue lock to prevent any new updates from being submitted */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + if (!is_free_list_full(fb_data)) { + /* Initialize event signalling updates are done */ + init_completion(&fb_data->updates_done); + fb_data->waiting_for_idle = true; + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + /* Wait for any currently active updates to complete */ + wait_for_completion_timeout(&fb_data->updates_done, msecs_to_jiffies(2000)); + spin_lock_irqsave(&fb_data->queue_lock, flags); + fb_data->waiting_for_idle = false; + } + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); +} + +static int mxc_epdc_fb_blank(int blank, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + dev_dbg(fb_data->dev, "blank = %d\n", blank); + + if (fb_data->blank == blank) + return 0; + + fb_data->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxc_epdc_fb_flush_updates(fb_data); + break; + } + return 0; +} + +static int mxc_epdc_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + u_int y_bottom; + unsigned long flags; + + dev_dbg(info->device, "%s: var->yoffset %d, info->var.yoffset %d\n", + __func__, var->yoffset, info->var.yoffset); + /* check if var is valid; also, xpan is not supported */ + if (!var || (var->xoffset != info->var.xoffset) || + (var->yoffset + var->yres > var->yres_virtual)) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((fb_data->epdc_fb_var.xoffset == var->xoffset) && + (fb_data->epdc_fb_var.yoffset == var->yoffset)) + return 0; /* No change, do nothing */ + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) { + return -EINVAL; + } + + spin_lock_irqsave(&fb_data->queue_lock, flags); + + fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset) + * (var->bits_per_pixel) / 8; + + fb_data->epdc_fb_var.xoffset = var->xoffset; + fb_data->epdc_fb_var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + return 0; +} + +static struct fb_ops mxc_epdc_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxc_epdc_fb_check_var, + .fb_set_par = mxc_epdc_fb_set_par, + .fb_setcolreg = mxc_epdc_fb_setcolreg, + .fb_pan_display = mxc_epdc_fb_pan_display, + .fb_ioctl = mxc_epdc_fb_ioctl, + .fb_mmap = mxc_epdc_fb_mmap, + .fb_blank = mxc_epdc_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_deferred_io mxc_epdc_fb_defio = { + .delay = HZ, + .deferred_io = mxc_epdc_fb_deferred_io, +}; + +static void epdc_done_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, + epdc_done_work.work); + epdc_powerdown(fb_data); +} + +static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data) +{ + int count = 0; + struct update_data_list *plist; + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list->list, list) + count++; + + /* Check to see if all buffers are in this list */ + if (count == EPDC_MAX_NUM_UPDATES) + return true; + else + return false; +} + +static bool do_updates_overlap(struct update_data_list *update1, + struct update_data_list *update2) +{ + struct mxcfb_rect *rect1 = &update1->upd_data.update_region; + struct mxcfb_rect *rect2 = &update2->upd_data.update_region; + __u32 bottom1, bottom2, right1, right2; + bottom1 = rect1->top + rect1->height; + bottom2 = rect2->top + rect2->height; + right1 = rect1->left + rect1->width; + right2 = rect2->left + rect2->width; + + if ((rect1->top < bottom2) && + (bottom1 > rect2->top) && + (rect1->left < right2) && + (right1 > rect2->left)) { + return true; + } else + return false; +} +static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id) +{ + struct mxc_epdc_fb_data *fb_data = dev_id; + struct update_data_list *collision_update; + struct mxcfb_rect *next_upd_region; + unsigned long flags; + int temp_index; + u32 luts_completed_mask; + u32 temp_mask; + u32 missed_coll_mask = 0; + u32 lut; + bool ignore_collision = false; + int i, j; + + /* + * If we just completed one-time panel init, bypass + * queue handling, clear interrupt and return + */ + if (fb_data->in_init) { + if (epdc_is_working_buffer_complete()) { + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + dev_dbg(fb_data->dev, "Cleared WB for init update\n"); + } + + if (epdc_is_lut_complete(0)) { + epdc_lut_complete_intr(0, false); + epdc_clear_lut_complete_irq(0); + fb_data->in_init = false; + dev_dbg(fb_data->dev, "Cleared LUT complete for init update\n"); + } + + return IRQ_HANDLED; + } + + if (!(__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ))) + return IRQ_HANDLED; + + if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) { + dev_err(fb_data->dev, "TCE underrun! Panel may lock up.\n"); + return IRQ_HANDLED; + } + + /* Protect access to buffer queues and to update HW */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* Free any LUTs that have completed */ + luts_completed_mask = 0; + for (i = 0; i < EPDC_NUM_LUTS; i++) { + if (!epdc_is_lut_complete(i)) + continue; + + dev_dbg(fb_data->dev, "\nLUT %d completed\n", i); + + /* Disable IRQ for completed LUT */ + epdc_lut_complete_intr(i, false); + + /* + * Go through all updates in the collision list and + * unmask any updates that were colliding with + * the completed LUT. + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list-> + list, list) { + collision_update->collision_mask = + collision_update->collision_mask & ~(1 << i); + } + + epdc_clear_lut_complete_irq(i); + + luts_completed_mask |= 1 << i; + + fb_data->lut_update_order[i] = 0; + + /* Signal completion if submit workqueue needs a LUT */ + if (fb_data->waiting_for_lut) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + /* Signal completion if anyone waiting on this LUT */ + for (j = 0; j < EPDC_MAX_NUM_UPDATES; j++) { + if (fb_data->update_marker_array[j].lut_num != i) + continue; + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker %d\n", + fb_data->update_marker_array[j].update_marker); + complete(&fb_data->update_marker_array[j].update_completion); + /* Ensure this doesn't get signaled again inadvertently */ + fb_data->update_marker_array[j].lut_num = INVALID_LUT; + /* + * Setting marker to 0 is OK - any wait call will + * return when marker doesn't match any in array + */ + fb_data->update_marker_array[j].update_marker = 0; + } + } + + /* Check to see if all updates have completed */ + if (is_free_list_full(fb_data) && + (fb_data->cur_update == NULL) && + !epdc_any_luts_active()) { + + if (fb_data->pwrdown_delay != FB_POWERDOWN_DISABLE) { + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule task to disable EPDC HW until next update */ + schedule_delayed_work(&fb_data->epdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) + complete(&fb_data->updates_done); + } + + /* Is Working Buffer busy? */ + if (epdc_is_working_buffer_busy()) { + /* Can't submit another update until WB is done */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } + + /* + * Were we waiting on working buffer? + * If so, update queues and check for collisions + */ + if (fb_data->cur_update != NULL) { + dev_dbg(fb_data->dev, "\nWorking buffer completed\n"); + + /* Signal completion if submit workqueue was waiting on WB */ + if (fb_data->waiting_for_wb) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + /* + * Check for "missed collision" conditions: + * - Current update overlaps one or more updates + * in collision list + * - No collision reported with current active updates + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list->list, + list) + if (do_updates_overlap(collision_update, + fb_data->cur_update)) + missed_coll_mask |= + collision_update->collision_mask; + + /* Was there a collision? */ + if (epdc_is_collision() || missed_coll_mask) { + /* Check list of colliding LUTs, and add to our collision mask */ + fb_data->cur_update->collision_mask = + epdc_get_colliding_luts(); + + if (!fb_data->cur_update->collision_mask) { + fb_data->cur_update->collision_mask = + missed_coll_mask; + dev_dbg(fb_data->dev, "Missed collision " + "possible. Mask = 0x%x\n", + missed_coll_mask); + } + + /* Clear collisions that just completed */ + fb_data->cur_update->collision_mask &= ~luts_completed_mask; + + dev_dbg(fb_data->dev, "\nCollision mask = 0x%x\n", + fb_data->cur_update->collision_mask); + + /* + * If we collide with newer updates, then + * we don't need to re-submit the update. The + * idea is that the newer updates should take + * precedence anyways, so we don't want to + * overwrite them. + */ + for (temp_mask = fb_data->cur_update->collision_mask, lut = 0; + temp_mask != 0; + lut++, temp_mask = temp_mask >> 1) { + if (!(temp_mask & 0x1)) + continue; + + if (fb_data->lut_update_order[lut] >= + fb_data->cur_update->update_order) { + dev_dbg(fb_data->dev, "Ignoring collision with newer update.\n"); + ignore_collision = true; + break; + } + } + + if (ignore_collision) { + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list->list); + } else { + /* + * If update has a marker, clear the LUT, since we + * don't want to signal that it is complete. + */ + if (fb_data->cur_update->upd_marker_data) + if (fb_data->cur_update->upd_marker_data->update_marker != 0) + fb_data->cur_update->upd_marker_data->lut_num = INVALID_LUT; + + /* Move to collision list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_collision_list->list); + } + } else { + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list->list); + } + /* Clear current update */ + fb_data->cur_update = NULL; + + /* Clear IRQ for working buffer */ + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + /* Schedule task to submit collision and pending update */ + if (!fb_data->powering_down) + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + return IRQ_HANDLED; + } + + /* Snapshot update scheme processing */ + + /* Check to see if any LUTs are free */ + if (!epdc_any_luts_available()) { + dev_dbg(fb_data->dev, "No luts available.\n"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list->list, list) { + + if (collision_update->collision_mask != 0) + continue; + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + /* + * We have a collision cleared, so select it + * and we will retry the update + */ + fb_data->cur_update = collision_update; + list_del_init(&fb_data->cur_update->list); + break; + } + + /* + * If we didn't find a collision update ready to go, + * we try to grab one from the update queue + */ + if (fb_data->cur_update == NULL) { + /* Is update list empty? */ + if (list_empty(&fb_data->upd_buf_queue->list)) { + dev_dbg(fb_data->dev, "No pending updates.\n"); + + /* No updates pending, so we are done */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } else { + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + /* Process next item in update list */ + fb_data->cur_update = + list_entry(fb_data->upd_buf_queue->list.next, + struct update_data_list, list); + list_del_init(&fb_data->cur_update->list); + } + } + + /* LUTs are available, so we get one here */ + fb_data->cur_update->lut_num = epdc_get_next_lut(); + + /* Associate LUT with update marker */ + if ((fb_data->cur_update->upd_marker_data) + && (fb_data->cur_update->upd_marker_data->update_marker != 0)) + fb_data->cur_update->upd_marker_data->lut_num = + fb_data->cur_update->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[fb_data->cur_update->lut_num] = + fb_data->cur_update->update_order; + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + next_upd_region = &fb_data->cur_update->upd_data.update_region; + if (fb_data->cur_update->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, fb_data->cur_update->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + epdc_set_update_addr(fb_data->cur_update->phys_addr + fb_data->cur_update->epdc_offs); + epdc_set_update_coord(next_upd_region->left, next_upd_region->top); + epdc_set_update_dimensions(next_upd_region->width, + next_upd_region->height); + + epdc_submit_update(fb_data->cur_update->lut_num, + fb_data->cur_update->upd_data.waveform_mode, + fb_data->cur_update->upd_data.update_mode, false, 0); + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + return IRQ_HANDLED; +} + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data) +{ + u32 *upd_buf_ptr; + int i; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + + upd_buf_ptr = (u32 *)fb_data->info.screen_base; + + epdc_working_buf_intr(true); + epdc_lut_complete_intr(0, true); + fb_data->in_init = true; + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + xres = screeninfo->xres; + yres = screeninfo->yres; + } + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(fb_data->phys_start); + epdc_set_update_coord(0, 0); + epdc_set_update_dimensions(xres, yres); + epdc_submit_update(0, fb_data->wv_modes.mode_init, UPDATE_MODE_FULL, true, 0xFF); + + dev_dbg(fb_data->dev, "Mode0 update - Waiting for LUT to complete...\n"); + + /* Will timeout after ~4-5 seconds */ + + for (i = 0; i < 40; i++) { + if (!epdc_is_lut_active(0)) { + dev_dbg(fb_data->dev, "Mode0 init complete\n"); + return; + } + msleep(100); + } + + dev_err(fb_data->dev, "Mode0 init failed!\n"); + + return; +} + + +static void mxc_epdc_fb_fw_handler(const struct firmware *fw, + void *context) +{ + struct mxc_epdc_fb_data *fb_data = context; + int ret; + struct mxcfb_waveform_data_file *wv_file; + int wv_data_offs; + int i; + struct mxcfb_update_data update; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + + if (fw == NULL) { + /* If default FW file load failed, we give up */ + if (fb_data->fw_default_load) + return; + + /* Try to load default waveform */ + dev_dbg(fb_data->dev, + "Can't find firmware. Trying fallback fw\n"); + fb_data->fw_default_load = true; + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "imx/epdc.fw", fb_data->dev, GFP_KERNEL, fb_data, + mxc_epdc_fb_fw_handler); + if (ret) + dev_err(fb_data->dev, + "Failed request_firmware_nowait err %d\n", ret); + + return; + } + + wv_file = (struct mxcfb_waveform_data_file *)fw->data; + + /* Get size and allocate temperature range table */ + fb_data->trt_entries = wv_file->wdh.trc + 1; + fb_data->temp_range_bounds = kzalloc(fb_data->trt_entries, GFP_KERNEL); + + for (i = 0; i < fb_data->trt_entries; i++) + dev_dbg(fb_data->dev, "trt entry #%d = 0x%x\n", i, *((u8 *)&wv_file->data + i)); + + /* Copy TRT data */ + memcpy(fb_data->temp_range_bounds, &wv_file->data, fb_data->trt_entries); + + /* Set default temperature index using TRT and room temp */ + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP); + + /* Get offset and size for waveform data */ + wv_data_offs = sizeof(wv_file->wdh) + fb_data->trt_entries + 1; + fb_data->waveform_buffer_size = fw->size - wv_data_offs; + + /* Allocate memory for waveform data */ + fb_data->waveform_buffer_virt = dma_alloc_coherent(fb_data->dev, + fb_data->waveform_buffer_size, + &fb_data->waveform_buffer_phys, + GFP_DMA); + if (fb_data->waveform_buffer_virt == NULL) { + dev_err(fb_data->dev, "Can't allocate mem for waveform!\n"); + return; + } + + memcpy(fb_data->waveform_buffer_virt, (u8 *)(fw->data) + wv_data_offs, + fb_data->waveform_buffer_size); + + release_firmware(fw); + + /* Enable clocks to access EPDC regs */ + clk_enable(fb_data->epdc_clk_axi); + + /* Enable pix clk for EPDC */ + clk_enable(fb_data->epdc_clk_pix); + clk_set_rate(fb_data->epdc_clk_pix, fb_data->cur_mode->vmode->pixclock); + + epdc_init_sequence(fb_data); + + /* Disable clocks */ + clk_disable(fb_data->epdc_clk_axi); + clk_disable(fb_data->epdc_clk_pix); + + fb_data->hw_ready = true; + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + xres = screeninfo->xres; + yres = screeninfo->yres; + } + + update.update_region.left = 0; + update.update_region.width = xres; + update.update_region.top = 0; + update.update_region.height = yres; + update.update_mode = UPDATE_MODE_FULL; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, &fb_data->info); + + /* Block on initial update */ + ret = mxc_epdc_fb_wait_update_complete(update.update_marker, + &fb_data->info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for update complete failed. Error = 0x%x", ret); +} + +static int mxc_epdc_fb_init_hw(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int ret; + + /* + * Create fw search string based on ID string in selected videomode. + * Format is "imx/epdc_[panel string].fw" + */ + if (fb_data->cur_mode) { + strcat(fb_data->fw_str, "imx/epdc_"); + strcat(fb_data->fw_str, fb_data->cur_mode->vmode->name); + strcat(fb_data->fw_str, ".fw"); + } + + fb_data->fw_default_load = false; + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fb_data->fw_str, fb_data->dev, GFP_KERNEL, + fb_data, mxc_epdc_fb_fw_handler); + if (ret) + dev_dbg(fb_data->dev, + "Failed request_firmware_nowait err %d\n", ret); + + return ret; +} + +static ssize_t store_update(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxcfb_update_data update; + struct fb_info *info = dev_get_drvdata(device); + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + if (strncmp(buf, "direct", 6) == 0) + update.waveform_mode = fb_data->wv_modes.mode_du; + else if (strncmp(buf, "gc16", 4) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc16; + else if (strncmp(buf, "gc4", 3) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc4; + + /* Now, request full screen update */ + update.update_region.left = 0; + update.update_region.width = fb_data->epdc_fb_var.xres; + update.update_region.top = 0; + update.update_region.height = fb_data->epdc_fb_var.yres; + update.update_mode = UPDATE_MODE_FULL; + update.temp = TEMP_USE_AMBIENT; + update.update_marker = 0; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, info); + + return count; +} + +static struct device_attribute fb_attrs[] = { + __ATTR(update, S_IRUGO|S_IWUSR, NULL, store_update), +}; + +int __devinit mxc_epdc_fb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_epdc_fb_data *fb_data; + struct resource *res; + struct fb_info *info; + char *options, *opt; + char *panel_str = NULL; + char name[] = "mxcepdcfb"; + struct fb_videomode *vmode; + int xres_virt, yres_virt, max_pix_size, buf_size; + int xres_virt_rot, yres_virt_rot, pix_size_rot; + struct fb_var_screeninfo *var_info; + struct fb_fix_screeninfo *fix_info; + struct pxp_config_data *pxp_conf; + struct pxp_proc_data *proc_data; + struct scatterlist *sg; + struct update_data_list *upd_list; + struct update_data_list *plist, *temp_list; + int i; + unsigned long x_mem_size = 0; +#ifdef CONFIG_FRAMEBUFFER_CONSOLE + struct mxcfb_update_data update; +#endif + + fb_data = (struct mxc_epdc_fb_data *)framebuffer_alloc( + sizeof(struct mxc_epdc_fb_data), &pdev->dev); + if (fb_data == NULL) { + ret = -ENOMEM; + goto out; + } + + /* Get platform data and check validity */ + fb_data->pdata = pdev->dev.platform_data; + if ((fb_data->pdata == NULL) || (fb_data->pdata->num_modes < 1) + || (fb_data->pdata->epdc_mode == NULL) + || (fb_data->pdata->epdc_mode->vmode == NULL)) { + ret = -EINVAL; + goto out_fbdata; + } + + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto out_fbdata; + } + + if (options) + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + fb_data->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + else if (!strncmp(opt, "x_mem=", 6)) + x_mem_size = memparse(opt + 6, NULL); + else + panel_str = opt; + } + + fb_data->dev = &pdev->dev; + + if (!fb_data->default_bpp) + fb_data->default_bpp = 16; + + /* Set default (first defined mode) before searching for a match */ + fb_data->cur_mode = &fb_data->pdata->epdc_mode[0]; + + if (panel_str) + for (i = 0; i < fb_data->pdata->num_modes; i++) + if (!strcmp(fb_data->pdata->epdc_mode[i].vmode->name, + panel_str)) { + fb_data->cur_mode = + &fb_data->pdata->epdc_mode[i]; + break; + } + + vmode = fb_data->cur_mode->vmode; + + platform_set_drvdata(pdev, fb_data); + info = &fb_data->info; + + /* Allocate color map for the FB */ + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) + goto out_fbdata; + + dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n", + vmode->xres, vmode->yres, fb_data->default_bpp); + + /* + * GPU alignment restrictions dictate framebuffer parameters: + * - 32-byte alignment for buffer width + * - 128-byte alignment for buffer height + * => 4K buffer alignment for buffer start + */ + xres_virt = ALIGN(vmode->xres, 32); + yres_virt = ALIGN(vmode->yres, 128); + max_pix_size = PAGE_ALIGN(xres_virt * yres_virt); + + /* + * Have to check to see if aligned buffer size when rotated + * is bigger than when not rotated, and use the max + */ + xres_virt_rot = ALIGN(vmode->yres, 32); + yres_virt_rot = ALIGN(vmode->xres, 128); + pix_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot); + max_pix_size = (max_pix_size > pix_size_rot) ? + max_pix_size : pix_size_rot; + + buf_size = max_pix_size * fb_data->default_bpp/8; + + /* Compute the number of screens needed based on X memory requested */ + if (x_mem_size > 0) { + fb_data->num_screens = DIV_ROUND_UP(x_mem_size, buf_size); + if (fb_data->num_screens < NUM_SCREENS_MIN) + fb_data->num_screens = NUM_SCREENS_MIN; + else if (buf_size * fb_data->num_screens > SZ_16M) + fb_data->num_screens = SZ_16M / buf_size; + } else + fb_data->num_screens = NUM_SCREENS_MIN; + + fb_data->map_size = buf_size * fb_data->num_screens; + dev_dbg(&pdev->dev, "memory to allocate: %d\n", fb_data->map_size); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto out_cmap; + } + + epdc_base = ioremap(res->start, SZ_4K); + if (epdc_base == NULL) { + ret = -ENOMEM; + goto out_cmap; + } + + /* Allocate FB memory */ + info->screen_base = dma_alloc_writecombine(&pdev->dev, + fb_data->map_size, + &fb_data->phys_start, + GFP_DMA); + + if (info->screen_base == NULL) { + ret = -ENOMEM; + goto out_mapregs; + } + dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", info->screen_base, + fb_data->phys_start); + + var_info = &info->var; + var_info->activate = FB_ACTIVATE_TEST; + var_info->bits_per_pixel = fb_data->default_bpp; + var_info->xres = vmode->xres; + var_info->yres = vmode->yres; + var_info->xres_virtual = xres_virt; + /* Additional screens allow for panning and buffer flipping */ + var_info->yres_virtual = yres_virt * fb_data->num_screens; + + var_info->pixclock = vmode->pixclock; + var_info->left_margin = vmode->left_margin; + var_info->right_margin = vmode->right_margin; + var_info->upper_margin = vmode->upper_margin; + var_info->lower_margin = vmode->lower_margin; + var_info->hsync_len = vmode->hsync_len; + var_info->vsync_len = vmode->vsync_len; + var_info->vmode = FB_VMODE_NONINTERLACED; + + switch (fb_data->default_bpp) { + case 32: + case 24: + var_info->red.offset = 16; + var_info->red.length = 8; + var_info->green.offset = 8; + var_info->green.length = 8; + var_info->blue.offset = 0; + var_info->blue.length = 8; + break; + + case 16: + var_info->red.offset = 11; + var_info->red.length = 5; + var_info->green.offset = 5; + var_info->green.length = 6; + var_info->blue.offset = 0; + var_info->blue.length = 5; + break; + + case 8: + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var_info->grayscale = GRAYSCALE_8BIT; + + var_info->red.length = 8; + var_info->red.offset = 0; + var_info->red.msb_right = 0; + var_info->green.length = 8; + var_info->green.offset = 0; + var_info->green.msb_right = 0; + var_info->blue.length = 8; + var_info->blue.offset = 0; + var_info->blue.msb_right = 0; + break; + + default: + dev_err(&pdev->dev, "unsupported bitwidth %d\n", + fb_data->default_bpp); + ret = -EINVAL; + goto out_dma_fb; + } + + fix_info = &info->fix; + + strcpy(fix_info->id, "mxc_epdc_fb"); + fix_info->type = FB_TYPE_PACKED_PIXELS; + fix_info->visual = FB_VISUAL_TRUECOLOR; + fix_info->xpanstep = 0; + fix_info->ypanstep = 0; + fix_info->ywrapstep = 0; + fix_info->accel = FB_ACCEL_NONE; + fix_info->smem_start = fb_data->phys_start; + fix_info->smem_len = fb_data->map_size; + fix_info->ypanstep = 0; + + fb_data->native_width = vmode->xres; + fb_data->native_height = vmode->yres; + + info->fbops = &mxc_epdc_fb_ops; + info->var.activate = FB_ACTIVATE_NOW; + info->pseudo_palette = fb_data->pseudo_palette; + info->screen_size = info->fix.smem_len; + info->flags = FBINFO_FLAG_DEFAULT; + + mxc_epdc_fb_set_fix(info); + + fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE; + fb_data->upd_scheme = UPDATE_SCHEME_SNAPSHOT; + + /* Initialize our internal copy of the screeninfo */ + fb_data->epdc_fb_var = *var_info; + fb_data->fb_offset = 0; + + /* Allocate head objects for our lists */ + fb_data->upd_buf_queue = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + fb_data->upd_buf_collision_list = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + fb_data->upd_buf_free_list = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + if ((fb_data->upd_buf_queue == NULL) || (fb_data->upd_buf_free_list == NULL) + || (fb_data->upd_buf_collision_list == NULL)) { + ret = -ENOMEM; + goto out_dma_fb; + } + + /* + * Initialize lists for update requests, update collisions, + * and available update (PxP output) buffers + */ + INIT_LIST_HEAD(&fb_data->upd_buf_queue->list); + INIT_LIST_HEAD(&fb_data->upd_buf_free_list->list); + INIT_LIST_HEAD(&fb_data->upd_buf_collision_list->list); + + /* Allocate update buffers and add them to the list */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL); + if (upd_list == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Clear update data structure */ + memset(&upd_list->upd_data, 0, + sizeof(struct mxcfb_update_data)); + + /* + * Each update buffer is 1 byte per pixel, and can + * be as big as the full-screen frame buffer + */ + upd_list->size = max_pix_size; + + /* Allocate memory for PxP output buffer */ + upd_list->virt_addr = + dma_alloc_coherent(fb_data->info.device, upd_list->size, + &upd_list->phys_addr, GFP_DMA); + if (upd_list->virt_addr == NULL) { + kfree(upd_list); + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Add newly allocated buffer to free list */ + list_add(&upd_list->list, &fb_data->upd_buf_free_list->list); + + dev_dbg(fb_data->info.device, "allocated %d bytes @ 0x%08X\n", + upd_list->size, upd_list->phys_addr); + + /* Allocate memory for PxP SW workaround buffers */ + /* These buffers are used to hold copy of the update region */ + upd_list->virt_addr_copybuf = + dma_alloc_coherent(fb_data->info.device, upd_list->size*2, + &upd_list->phys_addr_copybuf, GFP_DMA); + if (upd_list->virt_addr_copybuf == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + } + + fb_data->working_buffer_size = vmode->yres * vmode->xres * 2; + /* Allocate memory for EPDC working buffer */ + fb_data->working_buffer_virt = + dma_alloc_coherent(&pdev->dev, fb_data->working_buffer_size, + &fb_data->working_buffer_phys, GFP_DMA); + if (fb_data->working_buffer_virt == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for working buf!\n"); + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Initialize EPDC pins */ + if (fb_data->pdata->get_pins) + fb_data->pdata->get_pins(); + + fb_data->epdc_clk_axi = clk_get(fb_data->dev, "epdc_axi"); + if (IS_ERR(fb_data->epdc_clk_axi)) { + dev_err(&pdev->dev, "Unable to get EPDC AXI clk." + "err = 0x%x\n", (int)fb_data->epdc_clk_axi); + ret = -ENODEV; + goto out_upd_buffers; + } + fb_data->epdc_clk_pix = clk_get(fb_data->dev, "epdc_pix"); + if (IS_ERR(fb_data->epdc_clk_pix)) { + dev_err(&pdev->dev, "Unable to get EPDC pix clk." + "err = 0x%x\n", (int)fb_data->epdc_clk_pix); + ret = -ENODEV; + goto out_upd_buffers; + } + + fb_data->in_init = false; + + fb_data->hw_ready = false; + + /* + * Set default waveform mode values. + * Should be overwritten via ioctl. + */ + fb_data->wv_modes.mode_init = 0; + fb_data->wv_modes.mode_du = 1; + fb_data->wv_modes.mode_gc4 = 3; + fb_data->wv_modes.mode_gc8 = 2; + fb_data->wv_modes.mode_gc16 = 2; + fb_data->wv_modes.mode_gc32 = 2; + + /* Initialize markers */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + fb_data->update_marker_array[i].update_marker = 0; + fb_data->update_marker_array[i].lut_num = INVALID_LUT; + } + + /* Initialize all LUTs to inactive */ + for (i = 0; i < EPDC_NUM_LUTS; i++) + fb_data->lut_update_order[i] = 0; + + /* Retrieve EPDC IRQ num */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto out_dma_work_buf; + } + fb_data->epdc_irq = res->start; + + /* Register IRQ handler */ + ret = request_irq(fb_data->epdc_irq, mxc_epdc_irq_handler, 0, + "fb_dma", fb_data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + fb_data->epdc_irq, ret); + ret = -ENODEV; + goto out_dma_work_buf; + } + + INIT_DELAYED_WORK(&fb_data->epdc_done_work, epdc_done_work_func); + fb_data->epdc_submit_workqueue = create_rt_workqueue("submit"); + INIT_WORK(&fb_data->epdc_submit_work, epdc_submit_work_func); + + info->fbdefio = &mxc_epdc_fb_defio; +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_init(info); +#endif + + /* get pmic regulators */ + fb_data->display_regulator = regulator_get(NULL, "DISPLAY"); + if (IS_ERR(fb_data->display_regulator)) { + dev_err(&pdev->dev, "Unable to get display PMIC regulator." + "err = 0x%x\n", (int)fb_data->display_regulator); + ret = -ENODEV; + goto out_irq; + } + fb_data->vcom_regulator = regulator_get(NULL, "VCOM"); + if (IS_ERR(fb_data->vcom_regulator)) { + regulator_put(fb_data->display_regulator); + dev_err(&pdev->dev, "Unable to get VCOM regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto out_irq; + } + + if (device_create_file(info->dev, &fb_attrs[0])) + dev_err(&pdev->dev, "Unable to create file from fb_attrs\n"); + + fb_data->cur_update = NULL; + + spin_lock_init(&fb_data->queue_lock); + + mutex_init(&fb_data->pxp_mutex); + + mutex_init(&fb_data->power_mutex); + + /* PxP DMA interface */ + dmaengine_get(); + + /* + * Fill out PxP config data structure based on FB info and + * processing tasks required + */ + pxp_conf = &fb_data->pxp_conf; + proc_data = &pxp_conf->proc_data; + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = fb_data->info.var.xres; + proc_data->drect.height = proc_data->srect.height = fb_data->info.var.yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = 0; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + + /* + * We initially configure PxP for RGB->YUV conversion, + * and only write out Y component of the result. + */ + + /* + * Initialize S0 channel parameters + * Parameters should match FB format/width/height + */ + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->s0_param.width = fb_data->info.var.xres_virtual; + pxp_conf->s0_param.height = fb_data->info.var.yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize OL0 channel parameters + * No overlay will be used for PxP operation + */ + for (i = 0; i < 8; i++) { + pxp_conf->ol_param[i].combine_enable = false; + pxp_conf->ol_param[i].width = 0; + pxp_conf->ol_param[i].height = 0; + pxp_conf->ol_param[i].pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->ol_param[i].color_key_enable = false; + pxp_conf->ol_param[i].color_key = -1; + pxp_conf->ol_param[i].global_alpha_enable = false; + pxp_conf->ol_param[i].global_alpha = 0; + pxp_conf->ol_param[i].local_alpha_enable = false; + } + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = fb_data->info.var.xres; + pxp_conf->out_param.height = fb_data->info.var.yres; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; + + /* + * Ensure this is set to NULL here...we will initialize pxp_chan + * later in our thread. + */ + fb_data->pxp_chan = NULL; + + /* Initialize Scatter-gather list containing 2 buffer addresses. */ + sg = fb_data->sg; + sg_init_table(sg, 2); + + /* + * For use in PxP transfers: + * sg[0] holds the FB buffer pointer + * sg[1] holds the Output buffer pointer (configured before TX request) + */ + sg_dma_address(&sg[0]) = info->fix.smem_start; + sg_set_page(&sg[0], virt_to_page(info->screen_base), + info->fix.smem_len, offset_in_page(info->screen_base)); + + fb_data->order_cnt = 0; + fb_data->waiting_for_wb = false; + fb_data->waiting_for_lut = false; + fb_data->waiting_for_idle = false; + fb_data->blank = FB_BLANK_UNBLANK; + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + fb_data->pwrdown_delay = 0; + + /* Register FB */ + ret = register_framebuffer(info); + if (ret) { + dev_err(&pdev->dev, + "register_framebuffer failed with error %d\n", ret); + goto out_dmaengine; + } + + g_fb_data = fb_data; + +#ifdef DEFAULT_PANEL_HW_INIT + ret = mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HW!\n"); + } +#endif + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE + /* If FB console included, update display to show logo */ + update.update_region.left = 0; + update.update_region.width = info->var.xres; + update.update_region.top = 0; + update.update_region.height = info->var.yres; + update.update_mode = UPDATE_MODE_PARTIAL; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, info); + + ret = mxc_epdc_fb_wait_update_complete(update.update_marker, info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for update complete failed. Error = 0x%x", ret); +#endif + + goto out; + +out_dmaengine: + dmaengine_put(); +out_irq: + free_irq(fb_data->epdc_irq, fb_data); +out_dma_work_buf: + dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, fb_data->working_buffer_phys); + if (fb_data->pdata->put_pins) + fb_data->pdata->put_pins(); +out_upd_buffers: + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list->list, list) { + list_del(&plist->list); + dma_free_writecombine(&pdev->dev, plist->size, plist->virt_addr, + plist->phys_addr); + dma_free_writecombine(&pdev->dev, plist->size*2, + plist->virt_addr_copybuf, + plist->phys_addr_copybuf); + kfree(plist); + } +out_dma_fb: + dma_free_writecombine(&pdev->dev, fb_data->map_size, info->screen_base, + fb_data->phys_start); + +out_mapregs: + iounmap(epdc_base); +out_cmap: + fb_dealloc_cmap(&info->cmap); +out_fbdata: + kfree(fb_data); +out: + return ret; +} + +static int mxc_epdc_fb_remove(struct platform_device *pdev) +{ + struct update_data_list *plist, *temp_list; + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + + mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info); + + flush_workqueue(fb_data->epdc_submit_workqueue); + destroy_workqueue(fb_data->epdc_submit_workqueue); + + regulator_put(fb_data->display_regulator); + regulator_put(fb_data->vcom_regulator); + + unregister_framebuffer(&fb_data->info); + free_irq(fb_data->epdc_irq, fb_data); + + dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, + fb_data->working_buffer_phys); + if (fb_data->waveform_buffer_virt != NULL) + dma_free_writecombine(&pdev->dev, fb_data->waveform_buffer_size, + fb_data->waveform_buffer_virt, + fb_data->waveform_buffer_phys); + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list->list, list) { + list_del(&plist->list); + dma_free_writecombine(&pdev->dev, plist->size, plist->virt_addr, + plist->phys_addr); + dma_free_writecombine(&pdev->dev, plist->size*2, + plist->virt_addr_copybuf, + plist->phys_addr_copybuf); + kfree(plist); + } +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_cleanup(&fb_data->info); +#endif + + dma_free_writecombine(&pdev->dev, fb_data->map_size, fb_data->info.screen_base, + fb_data->phys_start); + + if (fb_data->pdata->put_pins) + fb_data->pdata->put_pins(); + + /* Release PxP-related resources */ + if (fb_data->pxp_chan != NULL) + dma_release_channel(&fb_data->pxp_chan->dma_chan); + + dmaengine_put(); + + iounmap(epdc_base); + + fb_dealloc_cmap(&fb_data->info.cmap); + + framebuffer_release(&fb_data->info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int mxc_epdc_fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxc_epdc_fb_data *data = platform_get_drvdata(pdev); + int ret; + + ret = mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &data->info); + if (ret) + goto out; + +out: + return ret; +} + +static int mxc_epdc_fb_resume(struct platform_device *pdev) +{ + struct mxc_epdc_fb_data *data = platform_get_drvdata(pdev); + + mxc_epdc_fb_blank(FB_BLANK_UNBLANK, &data->info); + return 0; +} +#else +#define mxc_epdc_fb_suspend NULL +#define mxc_epdc_fb_resume NULL +#endif + +static struct platform_driver mxc_epdc_fb_driver = { + .probe = mxc_epdc_fb_probe, + .remove = mxc_epdc_fb_remove, + .suspend = mxc_epdc_fb_suspend, + .resume = mxc_epdc_fb_resume, + .driver = { + .name = "mxc_epdc_fb", + .owner = THIS_MODULE, + }, +}; + +/* Callback function triggered after PxP receives an EOF interrupt */ +static void pxp_dma_done(void *arg) +{ + struct pxp_tx_desc *tx_desc = to_tx_desc(arg); + struct dma_chan *chan = tx_desc->txd.chan; + struct pxp_channel *pxp_chan = to_pxp_channel(chan); + struct mxc_epdc_fb_data *fb_data = pxp_chan->client; + + /* This call will signal wait_for_completion_timeout() in send_buffer_to_pxp */ + complete(&fb_data->pxp_tx_cmpl); +} + +/* Function to request PXP DMA channel */ +static int pxp_chan_init(struct mxc_epdc_fb_data *fb_data) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + + /* + * Request a free channel + */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + chan = dma_request_channel(mask, NULL, NULL); + if (!chan) { + dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n"); + return -EBUSY; + } + + dev_dbg(fb_data->dev, "Successfully received channel.\n"); + + fb_data->pxp_chan = to_pxp_channel(chan); + + fb_data->pxp_chan->client = fb_data; + + init_completion(&fb_data->pxp_tx_cmpl); + + return 0; +} + +/* + * Function to call PxP DMA driver and send our latest FB update region + * through the PxP and out to an intermediate buffer. + * Note: This is a blocking call, so upon return the PxP tx should be complete. + */ +static int pxp_process_update(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + int i, ret; + int length; + + dev_dbg(fb_data->dev, "Starting PxP Send Buffer\n"); + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dev_dbg(fb_data->dev, "sg[0] = 0x%x, sg[1] = 0x%x\n", + sg_dma_address(&sg[0]), sg_dma_address(&sg[1])); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + /* + * Configure PxP for processing of new update region + * The rest of our config params were set up in + * probe() and should not need to be changed. + */ + pxp_conf->s0_param.width = src_width; + pxp_conf->s0_param.height = src_height; + proc_data->srect.top = update_region->top; + proc_data->srect.left = update_region->left; + proc_data->srect.width = update_region->width; + proc_data->srect.height = update_region->height; + + /* + * Because only YUV/YCbCr image can be scaled, configure + * drect equivalent to srect, as such do not perform scaling. + */ + proc_data->drect.top = 0; + proc_data->drect.left = 0; + proc_data->drect.width = proc_data->srect.width; + proc_data->drect.height = proc_data->srect.height; + + /* PXP expects rotation in terms of degrees */ + proc_data->rotate = fb_data->epdc_fb_var.rotate * 90; + if (proc_data->rotate > 270) + proc_data->rotate = 0; + + pxp_conf->out_param.width = update_region->width; + pxp_conf->out_param.height = update_region->height; + + desc = to_tx_desc(txd); + length = desc->len; + for (i = 0; i < length; i++) { + if (i == 0) {/* S0 */ + memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); + pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]); + memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param, + sizeof(struct pxp_layer_param)); + } else if (i == 1) { + pxp_conf->out_param.paddr = sg_dma_address(&sg[1]); + memcpy(&desc->layer_param.out_param, &pxp_conf->out_param, + sizeof(struct pxp_layer_param)); + } + /* TODO: OverLay */ + + desc = desc->next; + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + dev_dbg(fb_data->info.device, "%d: Submit %p #%d\n", __LINE__, txd, + cookie); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + return 0; +} + +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat) +{ + int ret; + /* + * Wait for completion event, which will be set + * through our TX callback function. + */ + ret = wait_for_completion_timeout(&fb_data->pxp_tx_cmpl, HZ / 10); + if (ret <= 0) { + dev_info(fb_data->info.device, + "PxP operation failed due to %s\n", + ret < 0 ? "user interrupt" : "timeout"); + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + return ret ? : -ETIMEDOUT; + } + + *hist_stat = to_tx_desc(fb_data->txd)->hist_status; + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + + dev_dbg(fb_data->dev, "TX completed\n"); + + return 0; +} + +static int __init mxc_epdc_fb_init(void) +{ + return platform_driver_register(&mxc_epdc_fb_driver); +} +late_initcall(mxc_epdc_fb_init); + + +static void __exit mxc_epdc_fb_exit(void) +{ + platform_driver_unregister(&mxc_epdc_fb_driver); +} +module_exit(mxc_epdc_fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC EPDC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c new file mode 100644 index 000000000000..7631445d65d2 --- /dev/null +++ b/drivers/video/mxc/mxc_ipuv3_fb.c @@ -0,0 +1,2075 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" + +/* Display port number */ +#define MXCFB_PORT_NUM 2 +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + char *fb_mode_str; + int default_bpp; + int cur_blank; + int next_blank; + ipu_channel_t ipu_ch; + int ipu_di; + u32 ipu_di_pix_fmt; + bool ipu_int_clk; + bool overlay; + bool alpha_chan_en; + dma_addr_t alpha_phy_addr0; + dma_addr_t alpha_phy_addr1; + void *alpha_virt_addr0; + void *alpha_virt_addr1; + uint32_t alpha_mem_len; + uint32_t ipu_ch_irq; + uint32_t ipu_alp_ch_irq; + uint32_t cur_ipu_buf; + uint32_t cur_ipu_alpha_buf; + + u32 pseudo_palette[16]; + + bool wait4vsync; + uint32_t waitcnt; + struct semaphore flip_sem; + struct semaphore alpha_flip_sem; + struct completion vsync_complete; +}; + +struct mxcfb_mode { + int dev_mode; + int num_modes; + struct fb_videomode *mode; +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +enum { + BOTH_ON, + SRC_ON, + TGT_ON, + BOTH_OFF +}; + +static bool g_dp_in_use; +LIST_HEAD(fb_alloc_list); +static struct fb_info *mxcfb_info[3]; +static __initdata struct mxcfb_mode mxc_disp_mode[MXCFB_PORT_NUM]; +static __initdata int (*mxcfb_pre_setup[MXCFB_PORT_NUM])(struct fb_info *info); + +/* + * register pre-setup callback for some display + * driver which need prepare clk etc. + */ +void mxcfb_register_presetup(int disp_port, + int (*pre_setup)(struct fb_info *info)) +{ + if (pre_setup) + mxcfb_pre_setup[disp_port] = pre_setup; +} +EXPORT_SYMBOL(mxcfb_register_presetup); + +/* + * mode register from each display driver before + * primary fb setting. + */ +void mxcfb_register_mode(int disp_port, + const struct fb_videomode *modedb, + int num_modes, int dev_mode) +{ + struct fb_videomode *mode; + int mode_sum; + + if (disp_port > MXCFB_PORT_NUM) + return; + + /* + * if there is new DDC device, overwrite old modes. + * if there is old DDC device while new device is not DDC, + * just keep old DDC modes. + */ + if (dev_mode & MXC_DISP_DDC_DEV) { + if (mxc_disp_mode[disp_port].num_modes) { + kfree(mxc_disp_mode[disp_port].mode); + mxc_disp_mode[disp_port].num_modes = 0; + } + } else if (mxc_disp_mode[disp_port].dev_mode & MXC_DISP_DDC_DEV) + return; + + mode_sum = mxc_disp_mode[disp_port].num_modes + num_modes; + mode = kzalloc(mode_sum * sizeof(struct fb_videomode), GFP_KERNEL); + + if (mxc_disp_mode[disp_port].num_modes) + memcpy(mode, mxc_disp_mode[disp_port].mode, + mxc_disp_mode[disp_port].num_modes + * sizeof(struct fb_videomode)); + if (modedb) + memcpy(mode + mxc_disp_mode[disp_port].num_modes, + modedb, num_modes * sizeof(struct fb_videomode)); + + if (mxc_disp_mode[disp_port].num_modes) + kfree(mxc_disp_mode[disp_port].mode); + + mxc_disp_mode[disp_port].mode = mode; + mxc_disp_mode[disp_port].num_modes += num_modes; + mxc_disp_mode[disp_port].dev_mode = dev_mode; + + return; +} +EXPORT_SYMBOL(mxcfb_register_mode); + +static uint32_t bpp_to_pixfmt(struct fb_info *fbi) +{ + uint32_t pixfmt = 0; + + if (fbi->var.nonstd) + return fbi->var.nonstd; + + switch (fbi->var.bits_per_pixel) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); +static int mxcfb_option_setup(struct fb_info *info, char *options); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ywrapstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static int _setup_disp_channel1(struct fb_info *fbi) +{ + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + memset(¶ms, 0, sizeof(params)); + params.mem_dp_bg_sync.di = mxc_fbi->ipu_di; + + /* + * Assuming interlaced means yuv output, below setting also + * valid for mem_dc_sync. FG should have the same vmode as BG. + */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *mxc_fbi_tmp; + int i; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi_tmp = (struct mxcfb_info *) + (registered_fb[i]->par); + if (mxc_fbi_tmp->ipu_ch == MEM_BG_SYNC) { + fbi->var.vmode = + registered_fb[i]->var.vmode; + mxc_fbi->ipu_di_pix_fmt = + mxc_fbi_tmp->ipu_di_pix_fmt; + break; + } + } + } + if (mxc_fbi->ipu_ch == MEM_DC_SYNC) { + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + params.mem_dc_sync.interlaced = true; + params.mem_dc_sync.out_pixel_fmt = + IPU_PIX_FMT_YUV444; + } else { + if (mxc_fbi->ipu_di_pix_fmt) + params.mem_dc_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + else + params.mem_dc_sync.out_pixel_fmt = IPU_PIX_FMT_RGB666; + } + params.mem_dc_sync.in_pixel_fmt = bpp_to_pixfmt(fbi); + } else { + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + params.mem_dp_bg_sync.interlaced = true; + params.mem_dp_bg_sync.out_pixel_fmt = + IPU_PIX_FMT_YUV444; + } else { + if (mxc_fbi->ipu_di_pix_fmt) + params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + else + params.mem_dp_bg_sync.out_pixel_fmt = IPU_PIX_FMT_RGB666; + } + params.mem_dp_bg_sync.in_pixel_fmt = bpp_to_pixfmt(fbi); + if (mxc_fbi->alpha_chan_en) + params.mem_dp_bg_sync.alpha_chan_en = true; + } + ipu_init_channel(mxc_fbi->ipu_ch, ¶ms); + + return 0; +} + +static int _setup_disp_channel2(struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int fb_stride; + unsigned long base; + + switch (bpp_to_pixfmt(fbi)) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P: + fb_stride = fbi->var.xres_virtual; + break; + default: + fb_stride = fbi->fix.line_length; + } + + mxc_fbi->cur_ipu_buf = 1; + sema_init(&mxc_fbi->flip_sem, 1); + if (mxc_fbi->alpha_chan_en) { + mxc_fbi->cur_ipu_alpha_buf = 1; + sema_init(&mxc_fbi->alpha_flip_sem, 1); + } + fbi->var.xoffset = 0; + + base = (fbi->var.yoffset * fbi->var.xres_virtual + fbi->var.xoffset); + base = (fbi->var.bits_per_pixel) * base / 8; + base += fbi->fix.smem_start; + + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi), + fbi->var.xres, fbi->var.yres, + fb_stride, + IPU_ROTATE_NONE, + base, + base, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + } + + if (mxc_fbi->alpha_chan_en) { + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + IPU_PIX_FMT_GENERIC, + fbi->var.xres, fbi->var.yres, + fbi->var.xres, + IPU_ROTATE_NONE, + mxc_fbi->alpha_phy_addr1, + mxc_fbi->alpha_phy_addr0, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + } + + return retval; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + u32 mem_len, alpha_mem_len; + ipu_di_signal_cfg_t sig_cfg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + ipu_disable_irq(mxc_fbi->ipu_ch_irq); + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + mxcfb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + + if (mxcfb_map_video_memory(fbi) < 0) + return -ENOMEM; + } + if (mxc_fbi->alpha_chan_en) { + alpha_mem_len = fbi->var.xres * fbi->var.yres; + if ((!mxc_fbi->alpha_phy_addr0 && !mxc_fbi->alpha_phy_addr1) || + (alpha_mem_len > mxc_fbi->alpha_mem_len)) { + if (mxc_fbi->alpha_phy_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_phy_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + + mxc_fbi->alpha_virt_addr0 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr0, + GFP_DMA | GFP_KERNEL); + + mxc_fbi->alpha_virt_addr1 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr1, + GFP_DMA | GFP_KERNEL); + if (mxc_fbi->alpha_virt_addr0 == NULL || + mxc_fbi->alpha_virt_addr1 == NULL) { + dev_err(fbi->device, "mxcfb: dma alloc for" + " alpha buffer failed.\n"); + if (mxc_fbi->alpha_virt_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_virt_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + return -ENOMEM; + } + mxc_fbi->alpha_mem_len = alpha_mem_len; + } + } + + if (mxc_fbi->next_blank != FB_BLANK_UNBLANK) + return retval; + + _setup_disp_channel1(fbi); + + if (!mxc_fbi->overlay) { + uint32_t out_pixel_fmt; + + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + sig_cfg.interlaced = true; + out_pixel_fmt = IPU_PIX_FMT_YUV444; + } else { + if (mxc_fbi->ipu_di_pix_fmt) + out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + else + out_pixel_fmt = IPU_PIX_FMT_RGB666; + } + if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */ + sig_cfg.odd_field_first = true; + if (mxc_fbi->ipu_int_clk) + sig_cfg.int_clk = true; + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_init_sync_panel(mxc_fbi->ipu_di, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + out_pixel_fmt, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin, + 0, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + + fbi->mode = + (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + ipu_disp_set_window_pos(mxc_fbi->ipu_ch, 0, 0); + } + + retval = _setup_disp_channel2(fbi); + if (retval) + return retval; + + ipu_enable_channel(mxc_fbi->ipu_ch); + + return retval; +} + +static int _swap_channels(struct fb_info *fbi, + struct fb_info *fbi_to, bool both_on) +{ + int retval, tmp; + ipu_channel_t old_ch; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi->par; + struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + if (both_on) { + ipu_disable_channel(mxc_fbi_to->ipu_ch, true); + ipu_uninit_channel(mxc_fbi_to->ipu_ch); + } + + /* switch the mxc fbi parameters */ + old_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = mxc_fbi_to->ipu_ch; + mxc_fbi_to->ipu_ch = old_ch; + tmp = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = tmp; + + _setup_disp_channel1(fbi); + retval = _setup_disp_channel2(fbi); + if (retval) + return retval; + + /* switch between dp and dc, disable old idmac, enable new idmac */ + retval = ipu_swap_channel(old_ch, mxc_fbi_from->ipu_ch); + ipu_uninit_channel(old_ch); + + if (both_on) { + _setup_disp_channel1(fbi_to); + retval = _setup_disp_channel2(fbi_to); + if (retval) + return retval; + ipu_enable_channel(mxc_fbi_to->ipu_ch); + } + + return retval; +} + +static int swap_channels(struct fb_info *fbi) +{ + int i; + int swap_mode; + ipu_channel_t ch_to; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi->par; + struct fb_info *fbi_to = NULL; + struct mxcfb_info *mxc_fbi_to; + + /* what's the target channel? */ + if (mxc_fbi_from->ipu_ch == MEM_BG_SYNC) + ch_to = MEM_DC_SYNC; + else + ch_to = MEM_BG_SYNC; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi_to = + (struct mxcfb_info *)mxcfb_info[i]->par; + if (mxc_fbi_to->ipu_ch == ch_to) { + fbi_to = mxcfb_info[i]; + break; + } + } + if (fbi_to == NULL) + return -1; + + ipu_clear_irq(mxc_fbi_from->ipu_ch_irq); + ipu_clear_irq(mxc_fbi_to->ipu_ch_irq); + ipu_free_irq(mxc_fbi_from->ipu_ch_irq, fbi); + ipu_free_irq(mxc_fbi_to->ipu_ch_irq, fbi_to); + + if (mxc_fbi_from->cur_blank == FB_BLANK_UNBLANK) { + if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK) + swap_mode = BOTH_ON; + else + swap_mode = SRC_ON; + } else { + if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK) + swap_mode = TGT_ON; + else + swap_mode = BOTH_OFF; + } + + /* tvout di-1: for DC use UYVY, for DP use RGB */ + if (mxc_fbi_from->ipu_di == 1 && ch_to == MEM_DC_SYNC) { + fbi->var.bits_per_pixel = 16; + fbi->var.nonstd = IPU_PIX_FMT_UYVY; + } else if (mxc_fbi_from->ipu_di == 1 && ch_to == MEM_BG_SYNC) { + fbi->var.nonstd = 0; + } else if (mxc_fbi_from->ipu_di == 0 && ch_to == MEM_DC_SYNC) { + fbi_to->var.nonstd = 0; + } else if (mxc_fbi_from->ipu_di == 0 && ch_to == MEM_BG_SYNC) { + fbi->var.bits_per_pixel = 16; + fbi->var.nonstd = IPU_PIX_FMT_UYVY; + } + + switch (swap_mode) { + case BOTH_ON: + /* disable target->switch src->enable target */ + _swap_channels(fbi, fbi_to, true); + break; + case SRC_ON: + /* just switch src */ + _swap_channels(fbi, fbi_to, false); + break; + case TGT_ON: + /* just switch target */ + _swap_channels(fbi_to, fbi, false); + break; + case BOTH_OFF: + /* switch directly, no more need to do */ + mxc_fbi_to->ipu_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = ch_to; + i = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = i; + break; + default: + break; + } + + if (ipu_request_irq(mxc_fbi_from->ipu_ch_irq, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering irq %d\n", + mxc_fbi_from->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_from->ipu_ch_irq); + if (ipu_request_irq(mxc_fbi_to->ipu_ch_irq, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi_to) != 0) { + dev_err(fbi_to->device, "Error registering irq %d\n", + mxc_fbi_to->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_to->ipu_ch_irq); + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + /* fg should not bigger than bg */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct fb_info *fbi_tmp; + struct mxcfb_info *mxc_fbi_tmp; + int i, bg_xres, bg_yres; + int16_t pos_x, pos_y; + + bg_xres = var->xres; + bg_yres = var->yres; + + for (i = 0; i < num_registered_fb; i++) { + fbi_tmp = registered_fb[i]; + mxc_fbi_tmp = (struct mxcfb_info *) + (fbi_tmp->par); + if (mxc_fbi_tmp->ipu_ch == MEM_BG_SYNC) { + bg_xres = fbi_tmp->var.xres; + bg_yres = fbi_tmp->var.yres; + break; + } + } + + ipu_disp_get_window_pos(mxc_fbi->ipu_ch, &pos_x, &pos_y); + + if ((var->xres + pos_x) > bg_xres) + var->xres = bg_xres - pos_x; + if ((var->yres + pos_y) > bg_yres) + var->yres = bg_yres - pos_y; + } + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 12) && + (var->bits_per_pixel != 8)) + var->bits_per_pixel = 16; + + switch (var->bits_per_pixel) { + case 8: + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu_ch, + (bool)ga.enable, + ga.alpha)) { + retval = -EINVAL; + break; + } + + if (ga.enable) + mxc_fbi->alpha_chan_en = false; + + if (ga.enable) + dev_dbg(fbi->device, + "Set global alpha of %s to %d\n", + fbi->fix.id, ga.alpha); + break; + } + case MXCFB_SET_LOC_ALPHA: + { + struct mxcfb_loc_alpha la; + int i; + char *video_plane_idstr = ""; + + if (copy_from_user(&la, (void *)arg, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu_ch, + !(bool)la.enable, 0)) { + retval = -EINVAL; + break; + } + + if (la.enable && !la.alpha_in_pixel) { + mxc_fbi->alpha_chan_en = true; + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + video_plane_idstr = "DISP3 BG"; + else if (mxc_fbi->ipu_ch == MEM_BG_SYNC) + video_plane_idstr = "DISP3 FG"; + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, video_plane_idstr) == 0) { + ((struct mxcfb_info *)(registered_fb[i]->par))->alpha_chan_en = false; + break; + } + } + } else + mxc_fbi->alpha_chan_en = false; + + mxcfb_set_par(fbi); + + la.alpha_phy_addr0 = mxc_fbi->alpha_phy_addr0; + la.alpha_phy_addr1 = mxc_fbi->alpha_phy_addr1; + if (copy_to_user((void *)arg, &la, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (la.enable) + dev_dbg(fbi->device, + "Enable DP local alpha for %s\n", + fbi->fix.id); + break; + } + case MXCFB_SET_LOC_ALP_BUF: + { + unsigned long base; + uint32_t ipu_alp_ch_irq; + + if (!(((mxc_fbi->ipu_ch == MEM_FG_SYNC) || + (mxc_fbi->ipu_ch == MEM_BG_SYNC)) && + (mxc_fbi->alpha_chan_en))) { + dev_err(fbi->device, + "Should use background or overlay " + "framebuffer to set the alpha buffer " + "number\n"); + return -EINVAL; + } + + if (get_user(base, argp)) + return -EFAULT; + + if (base != mxc_fbi->alpha_phy_addr0 && + base != mxc_fbi->alpha_phy_addr1) { + dev_err(fbi->device, + "Wrong alpha buffer physical address " + "%lu\n", base); + return -EINVAL; + } + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + else + ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + + down(&mxc_fbi->alpha_flip_sem); + + mxc_fbi->cur_ipu_alpha_buf = + !mxc_fbi->cur_ipu_alpha_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi-> + cur_ipu_alpha_buf, + base) == 0) { + ipu_select_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + ipu_clear_irq(ipu_alp_ch_irq); + ipu_enable_irq(ipu_alp_ch_irq); + } else { + dev_err(fbi->device, + "Error updating %s SDC alpha buf %d " + "to address=0x%08lX\n", + fbi->fix.id, + mxc_fbi->cur_ipu_alpha_buf, base); + } + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_color_key(mxc_fbi->ipu_ch, + key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_SET_GAMMA: + { + struct mxcfb_gamma gamma; + if (copy_from_user(&gamma, (void *)arg, sizeof(gamma))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_gamma_correction(mxc_fbi->ipu_ch, + gamma.enable, + gamma.constk, + gamma.slopek); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *bg_mxcfbi = NULL; + int i; + for (i = 0; i < num_registered_fb; i++) { + bg_mxcfbi = + ((struct mxcfb_info *)(registered_fb[i]->par)); + + if (bg_mxcfbi->ipu_ch == MEM_BG_SYNC) + break; + } + if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) { + retval = -EINVAL; + break; + } + } + if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) { + retval = -EINVAL; + break; + } + + init_completion(&mxc_fbi->vsync_complete); + + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + mxc_fbi->wait4vsync = 1; + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + retval = wait_for_completion_interruptible_timeout( + &mxc_fbi->vsync_complete, 1 * HZ); + if (retval == 0) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout %d\n", + retval); + mxc_fbi->wait4vsync = 0; + retval = -ETIME; + } else if (retval > 0) { + retval = 0; + } + break; + } + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) + return -EFAULT; + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_DMA); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + if (put_user(mem->phy_addr, argp)) + return -EFAULT; + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + struct fb_info *bg_fbi = NULL; + struct mxcfb_info *bg_mxcfbi = NULL; + int i; + + if (mxc_fbi->ipu_ch != MEM_FG_SYNC) { + dev_err(fbi->device, "Should use the overlay " + "framebuffer to set the position of " + "the overlay window\n"); + retval = -EINVAL; + break; + } + + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + + for (i = 0; i < num_registered_fb; i++) { + bg_mxcfbi = + ((struct mxcfb_info *)(registered_fb[i]->par)); + + if (bg_mxcfbi->ipu_ch == MEM_BG_SYNC) { + bg_fbi = registered_fb[i]; + break; + } + } + + if (bg_fbi == NULL) { + dev_err(fbi->device, "Cannot find the " + "background framebuffer\n"); + retval = -ENOENT; + break; + } + + if (fbi->var.xres + pos.x > bg_fbi->var.xres) { + if (bg_fbi->var.xres < fbi->var.xres) + pos.x = 0; + else + pos.x = bg_fbi->var.xres - fbi->var.xres; + } + if (fbi->var.yres + pos.y > bg_fbi->var.yres) { + if (bg_fbi->var.yres < fbi->var.yres) + pos.y = 0; + else + pos.y = bg_fbi->var.yres - fbi->var.yres; + } + + retval = ipu_disp_set_window_pos(mxc_fbi->ipu_ch, + pos.x, pos.y); + + if (copy_to_user((void *)arg, &pos, sizeof(pos))) { + retval = -EFAULT; + break; + } + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_DIFMT: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_di_pix_fmt, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_FB_IPU_DI: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_di, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_FB_BLANK: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->cur_blank, argp)) + return -EFAULT; + break; + } + case MXCFB_SET_DIFMT: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (get_user(mxc_fbi->ipu_di_pix_fmt, argp)) + return -EFAULT; + + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (mxc_fbi->cur_blank == blank) + return 0; + + mxc_fbi->next_blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_par(info); + break; + } + mxc_fbi->cur_blank = blank; + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par, + *mxc_graphic_fbi = NULL; + u_int y_bottom; + unsigned long base, active_alpha_phy_addr = 0; + bool loc_alpha_en = false; + int i = 0; + + if (info->var.yoffset == var->yoffset) + return 0; /* No change, do nothing */ + + /* no pan display during fb blank */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *bg_mxcfbi = NULL; + int j; + for (j = 0; j < num_registered_fb; j++) { + bg_mxcfbi = + ((struct mxcfb_info *)(registered_fb[j]->par)); + + if (bg_mxcfbi->ipu_ch == MEM_BG_SYNC) + break; + } + if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) + return -EINVAL; + } + if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) + return -EINVAL; + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base = (var->bits_per_pixel) * base / 8; + base += info->fix.smem_start; + + /* Check if DP local alpha is enabled and find the graphic fb */ + if (mxc_fbi->ipu_ch == MEM_BG_SYNC || mxc_fbi->ipu_ch == MEM_FG_SYNC) { + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if ((strcmp(idstr, "DISP3 BG") == 0 || + strcmp(idstr, "DISP3 FG") == 0) && + ((struct mxcfb_info *) + (registered_fb[i]->par))->alpha_chan_en) { + loc_alpha_en = true; + mxc_graphic_fbi = (struct mxcfb_info *) + (registered_fb[i]->par); + active_alpha_phy_addr = mxc_fbi->cur_ipu_buf ? + mxc_graphic_fbi->alpha_phy_addr1 : + mxc_graphic_fbi->alpha_phy_addr0; + dev_dbg(info->device, "Updating SDC graphic " + "buf %d address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, + active_alpha_phy_addr); + break; + } + } + } + + down(&mxc_fbi->flip_sem); + + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + + dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n", + info->fix.id, mxc_fbi->cur_ipu_buf, base); + + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, base) == 0) { + /* Update the DP local alpha buffer only for graphic plane */ + if (loc_alpha_en && mxc_graphic_fbi == mxc_fbi && + ipu_update_channel_buffer(mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_buf, + active_alpha_phy_addr) == 0) { + ipu_select_buffer(mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_buf); + } + + ipu_select_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + return -EBUSY; + } + + dev_dbg(info->device, "Update complete\n"); + + info->var.yoffset = var->yoffset; + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else if ((vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) || + (vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) { + len = mxc_fbi->alpha_mem_len; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) + return -EINVAL; + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + if (mxc_fbi->wait4vsync) { + complete(&mxc_fbi->vsync_complete); + ipu_disable_irq(irq); + mxc_fbi->wait4vsync = 0; + } else { + if (!ipu_check_buffer_ready(mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, mxc_fbi->cur_ipu_buf) + || (mxc_fbi->waitcnt > 1)) { + /* + * This code wait for EOF irq to make sure current + * buffer showed. + * + * Buffer ready will be clear after this buffer + * begin to show. If it keep 1, it represents this + * irq come from previous buffer. If so, wait for + * EOF irq again. + * + * Normally, waitcnt will not > 1, if so, something + * is wrong, then clear it manually. + */ + if (mxc_fbi->waitcnt > 1) + ipu_clear_buffer_ready(mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + up(&mxc_fbi->flip_sem); + ipu_disable_irq(irq); + mxc_fbi->waitcnt = 0; + } else + mxc_fbi->waitcnt++; + } + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + up(&mxc_fbi->alpha_flip_sem); + ipu_disable_irq(irq); + return IRQ_HANDLED; +} + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int saved_blank; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + console_lock(); + fb_set_suspend(fbi, 1); + saved_blank = mxc_fbi->cur_blank; + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxc_fbi->next_blank = saved_blank; + console_unlock(); + + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + console_lock(); + mxcfb_blank(mxc_fbi->next_blank, fbi); + fb_set_suspend(fbi, 0); + console_unlock(); + + return 0; +} + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + + fbi->screen_base = dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *)&fbi->fix.smem_start, + GFP_DMA); + if (fbi->screen_base == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + return -EBUSY; + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +static ssize_t show_disp_chan(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + + if (mxcfbi->ipu_ch == MEM_BG_SYNC) + return sprintf(buf, "2-layer-fb-bg\n"); + else if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return sprintf(buf, "2-layer-fb-fg\n"); + else if (mxcfbi->ipu_ch == MEM_DC_SYNC) + return sprintf(buf, "1-layer-fb\n"); + else + return sprintf(buf, "err: no display chan\n"); +} + +static ssize_t swap_disp_chan(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + struct mxcfb_info *fg_mxcfbi = NULL; + + console_lock(); + /* swap only happen between DP-BG and DC, while DP-FG disable */ + if (((mxcfbi->ipu_ch == MEM_BG_SYNC) && + (strstr(buf, "1-layer-fb") != NULL)) || + ((mxcfbi->ipu_ch == MEM_DC_SYNC) && + (strstr(buf, "2-layer-fb-bg") != NULL))) { + int i; + + for (i = 0; i < num_registered_fb; i++) { + fg_mxcfbi = + (struct mxcfb_info *)mxcfb_info[i]->par; + if (fg_mxcfbi->ipu_ch == MEM_FG_SYNC) + break; + else + fg_mxcfbi = NULL; + } + if (!fg_mxcfbi || + fg_mxcfbi->cur_blank == FB_BLANK_UNBLANK) { + dev_err(dev, + "Can not switch while fb2(fb-fg) is on.\n"); + console_unlock(); + return count; + } + + if (swap_channels(info) < 0) + dev_err(dev, "Swap display channel failed.\n"); + } + + console_unlock(); + return count; +} +DEVICE_ATTR(fsl_disp_property, 644, show_disp_chan, swap_disp_chan); + +static int mxcfb_setup(struct fb_info *fbi, struct platform_device *pdev) +{ + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data; + int ret = 0; + + /* Need dummy values until real panel is configured */ + fbi->var.xres = 240; + fbi->var.yres = 320; + + if (!mxcfbi->default_bpp) + mxcfbi->default_bpp = 16; + + if (plat_data && !mxcfbi->ipu_di_pix_fmt) + mxcfbi->ipu_di_pix_fmt = plat_data->interface_pix_fmt; + + if (!mxcfbi->fb_mode_str && plat_data && plat_data->mode_str) + mxcfbi->fb_mode_str = plat_data->mode_str; + + if (mxcfbi->fb_mode_str) { + if (mxcfbi->ipu_di >= 0) { + const struct fb_videomode *mode = NULL; + struct fb_videomode m; + int num = 0, found = 0; + + dev_dbg(fbi->device, "Config display port %d\n", + mxcfbi->ipu_di); + + INIT_LIST_HEAD(&fbi->modelist); + + if (mxc_disp_mode[mxcfbi->ipu_di].num_modes) { + mode = mxc_disp_mode[mxcfbi->ipu_di].mode; + num = mxc_disp_mode[mxcfbi->ipu_di].num_modes; + fb_videomode_to_modelist(mode, num, &fbi->modelist); + } + + if ((mxc_disp_mode[mxcfbi->ipu_di].dev_mode + & MXC_DISP_DDC_DEV) && + !list_empty(&fbi->modelist)) { + dev_dbg(fbi->device, + "Look for video mode %s in ddc modelist\n", + mxcfbi->fb_mode_str); + /* + * For DDC mode, try to get compatible mode first. + * If get one, try to find nearest mode, otherwise, + * use first mode provide by DDC. + */ + ret = fb_find_mode(&fbi->var, fbi, + mxcfbi->fb_mode_str, NULL, 0, + NULL, mxcfbi->default_bpp); + if (ret) { + fb_var_to_videomode(&m, &fbi->var); + mode = fb_find_nearest_mode(&m, + &fbi->modelist); + fb_videomode_to_var(&fbi->var, mode); + } else { + struct list_head *pos, *head; + struct fb_modelist *modelist; + + head = &fbi->modelist; + list_for_each(pos, head) { + modelist = list_entry(pos, + struct fb_modelist, list); + m = modelist->mode; + if (m.flag & FB_MODE_IS_FIRST) + break; + } + /* if no first mode, use last one */ + mode = &m; + fb_videomode_to_var(&fbi->var, mode); + } + found = 1; + } else if (!list_empty(&fbi->modelist)) { + dev_dbg(fbi->device, + "Look for video mode %s in spec modelist\n", + mxcfbi->fb_mode_str); + /* + * For specific mode, try to get specified mode + * from fbi modelist. + */ + ret = fb_find_mode(&fbi->var, fbi, + mxcfbi->fb_mode_str, mode, num, + NULL, mxcfbi->default_bpp); + if (ret == 1) + found = 1; + + } + /* + * if no DDC mode and spec mode found, + * try plat_data mode. + */ + if (!found) { + dev_dbg(fbi->device, + "Look for video mode %s in plat modelist\n", + mxcfbi->fb_mode_str); + if (plat_data && plat_data->modes + && plat_data->num_modes) + ret = fb_find_mode(&fbi->var, fbi, + mxcfbi->fb_mode_str, + plat_data->modes, + plat_data->num_modes, + NULL, + mxcfbi->default_bpp); + else + ret = fb_find_mode(&fbi->var, fbi, + mxcfbi->fb_mode_str, NULL, 0, + NULL, mxcfbi->default_bpp); + if (ret) + found = 1; + } + + if (!found) { + dev_err(fbi->device, + "Not found any valid video mode"); + ret = -EINVAL; + goto done; + } + + /*added found mode to fbi modelist*/ + fb_var_to_videomode(&m, &fbi->var); + fb_add_videomode(&m, &fbi->modelist); + } + } + + mxcfb_check_var(&fbi->var, fbi); + + /* Default Y virtual size is 3x panel size */ + fbi->var.yres_virtual = fbi->var.yres * 3; + + mxcfb_set_fix(fbi); + + /* setup display */ + if (mxcfbi->ipu_di >= 0) + if (mxcfb_pre_setup[mxcfbi->ipu_di]) + (mxcfb_pre_setup[mxcfbi->ipu_di])(fbi); + + fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + fbi->flags |= FBINFO_MISC_USEREVENT; + ret = fb_set_var(fbi, &fbi->var); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +done: + return ret; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct resource *res; + char *options; + char name[] = "mxcdi0fb"; + int ret = 0; + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfbi = (struct mxcfb_info *)fbi->par; + + name[5] += pdev->id; + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto err1; + } + + if (options) + mxcfb_option_setup(fbi, options); + + if (!g_dp_in_use) { + mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF; + mxcfbi->ipu_ch = MEM_BG_SYNC; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK; + } else { + mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF; + mxcfbi->ipu_ch = MEM_DC_SYNC; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + } + + mxcfbi->ipu_di = pdev->id; + mxcfbi->ipu_alp_ch_irq = -1; + + if (pdev->id == 0) { + ipu_disp_set_global_alpha(mxcfbi->ipu_ch, true, 0x80); + ipu_disp_set_color_key(mxcfbi->ipu_ch, false, 0); + strcpy(fbi->fix.id, "DISP3 BG"); + + if (!g_dp_in_use) + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + g_dp_in_use = true; + } else if (pdev->id == 1) { + strcpy(fbi->fix.id, "DISP3 BG - DI1"); + + if (!g_dp_in_use) + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + g_dp_in_use = true; + } else if (pdev->id == 2) { /* Overlay */ + mxcfbi->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF; + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + mxcfbi->ipu_ch = MEM_FG_SYNC; + mxcfbi->ipu_di = -1; + mxcfbi->overlay = true; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + + strcpy(fbi->fix.id, "DISP3 FG"); + } + + mxcfb_info[pdev->id] = fbi; + + if (ipu_request_irq(mxcfbi->ipu_ch_irq, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering BG irq handler.\n"); + ret = -EBUSY; + goto err1; + } + ipu_disable_irq(mxcfbi->ipu_ch_irq); + + if (mxcfbi->ipu_alp_ch_irq != -1) + if (ipu_request_irq(mxcfbi->ipu_alp_ch_irq, + mxcfb_alpha_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering alpha irq " + "handler.\n"); + ret = -EBUSY; + goto err2; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res && res->end) { + fbi->fix.smem_len = res->end - res->start + 1; + fbi->fix.smem_start = res->start; + fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len); + } + + ret = mxcfb_setup(fbi, pdev); + if (ret < 0) + goto err3; + + ret = register_framebuffer(fbi); + if (ret < 0) + goto err3; + + platform_set_drvdata(pdev, fbi); + + ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property); + if (ret) + dev_err(&pdev->dev, "Error %d on creating file\n", ret); + +#ifdef CONFIG_LOGO + fb_prepare_logo(fbi, 0); + fb_show_logo(fbi, 0); +#endif + + return 0; +err3: + if (mxcfbi->ipu_alp_ch_irq != -1) + ipu_free_irq(mxcfbi->ipu_alp_ch_irq, fbi); +err2: + ipu_free_irq(mxcfbi->ipu_ch_irq, fbi); +err1: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); +err0: + return ret; +} + +static int mxcfb_remove(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = fbi->par; + + if (!fbi) + return 0; + + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + ipu_free_irq(mxc_fbi->ipu_ch_irq, fbi); + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .remove = mxcfb_remove, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=mxcdi0fb:RGB24, 1024x768M-16@60,bpp=16,noaccel + */ +static int mxcfb_option_setup(struct fb_info *info, char *options) +{ + struct mxcfb_info *mxcfbi = info->par; + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "RGB24", 5)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_RGB24; + continue; + } + if (!strncmp(opt, "BGR24", 5)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_BGR24; + continue; + } + if (!strncmp(opt, "GBR24", 5)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_GBR24; + continue; + } + if (!strncmp(opt, "RGB565", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_RGB565; + continue; + } + if (!strncmp(opt, "RGB666", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_RGB666; + continue; + } + if (!strncmp(opt, "YUV444", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_YUV444; + continue; + } + if (!strncmp(opt, "LVDS666", 7)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_LVDS666; + continue; + } + if (!strncmp(opt, "YUYV16", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_YUYV; + continue; + } + if (!strncmp(opt, "UYVY16", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_UYVY; + continue; + } + if (!strncmp(opt, "YVYU16", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_YVYU; + continue; + } + if (!strncmp(opt, "VYUY16", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_VYUY; + continue; + } + if (!strncmp(opt, "int_clk", 7)) { + mxcfbi->ipu_int_clk = true; + continue; + } + if (!strncmp(opt, "bpp=", 4)) + mxcfbi->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + else + mxcfbi->fb_mode_str = opt; + } + + return 0; +} + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + return platform_driver_register(&mxcfb_driver); +} + +void mxcfb_exit(void) +{ + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb.c b/drivers/video/mxc/mxcfb.c new file mode 100644 index 000000000000..52694e49e69d --- /dev/null +++ b/drivers/video/mxc/mxcfb.c @@ -0,0 +1,1372 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + int blank; + ipu_channel_t ipu_ch; + uint32_t ipu_ch_irq; + uint32_t cur_ipu_buf; + + u32 pseudo_palette[16]; + + struct semaphore flip_sem; + spinlock_t fb_lock; +}; + +struct mxcfb_data { + struct fb_info *fbi; + struct fb_info *fbi_ovl; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; + int backlight_level; +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +static struct mxcfb_data mxcfb_drv_data; + +static char *fb_mode; +static unsigned long default_bpp = 16; +#ifdef CONFIG_FB_MXC_INTERNAL_MEM +static struct clk *iram_clk; +#endif +LIST_HEAD(fb_alloc_list); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +extern void gpio_lcd_active(void); +extern void gpio_lcd_inactive(void); +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + if (mxc_fbi->ipu_ch == MEM_SDC_FG) + strncpy(fix->id, "DISP3 FG", 8); + else + strncpy(fix->id, "DISP3 BG", 8); + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval; + bool use_iram = false; + u32 mem_len; + ipu_di_signal_cfg_t sig_cfg; + ipu_panel_t mode = IPU_PANEL_TFT; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + ipu_disable_irq(mxc_fbi->ipu_ch_irq); + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + mxcfb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (mem_len > fbi->fix.smem_len) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (mxc_fbi->ipu_ch == MEM_SDC_BG) { + use_iram = true; + } +#endif + if (mxcfb_map_video_memory(fbi, use_iram) < 0) + return -ENOMEM; + } + + ipu_init_channel(mxc_fbi->ipu_ch, NULL); + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + if (mxc_fbi->ipu_ch == MEM_SDC_BG) { + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + if (fbi->var.sync & FB_SYNC_SHARP_MODE) + mode = IPU_PANEL_SHARP_TFT; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_sdc_init_panel(mode, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + (fbi->var.sync & FB_SYNC_SWAP_RGB) ? + IPU_PIX_FMT_BGR666 : IPU_PIX_FMT_RGB666, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + + fbi->mode = + (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + } + + ipu_disp_set_window_pos(mxc_fbi->ipu_ch, 0, 0); + + mxc_fbi->cur_ipu_buf = 1; + sema_init(&mxc_fbi->flip_sem, 1); + fbi->var.xoffset = fbi->var.yoffset = 0; + + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + fbi->var.xres, fbi->var.yres, + fbi->var.xres_virtual, + IPU_ROTATE_NONE, + fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres), + fbi->fix.smem_start, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { + ipu_enable_channel(mxc_fbi->ipu_ch); + } + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if ((info->fix.smem_start == FB_RAM_BASE_ADDR) && + ((var->yres_virtual * var->xres_virtual * var->bits_per_pixel / 8) > + FB_RAM_SIZE)) { + return -EINVAL; + } +#endif + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* nonstd used for YUV formats, but only RGB supported */ + var->nonstd = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false)); + if (retval < 0) + return retval; + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + retval = + ipu_sdc_set_global_alpha((bool) ga.enable, + ga.alpha); + dev_dbg(fbi->device, "Set global alpha to %d\n", + ga.alpha); + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_sdc_set_color_key(MEM_SDC_BG, key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { +#ifndef CONFIG_ARCH_MX3 + mxcfb_drv_data.vsync_flag = 0; + ipu_enable_irq(IPU_IRQ_SDC_DISP3_VSYNC); + if (!wait_event_interruptible_timeout + (mxcfb_drv_data.vsync_wq, + mxcfb_drv_data.vsync_flag != 0, 1 * HZ)) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } +#endif + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl_ovl(struct fb_info *fbi, unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + switch (cmd) { + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) + return -EFAULT; + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_DMA); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + if (put_user(mem->phy_addr, argp)) + return -EFAULT; + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_window_pos(mxc_fbi->ipu_ch, + pos.x, pos.y); + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + int retval; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(MEM_SDC_BG, true); + gpio_lcd_inactive(); + break; + case FB_BLANK_UNBLANK: + gpio_lcd_active(); + ipu_enable_channel(MEM_SDC_BG); + break; + } + return 0; +} + +/* + * mxcfb_blank_ovl(): + * Blank the display. + */ +static int mxcfb_blank_ovl(int blank, struct fb_info *info) +{ + int retval; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "ovl blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(MEM_SDC_FG, true); + break; + case FB_BLANK_UNBLANK: + ipu_enable_channel(MEM_SDC_FG); + break; + } + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + unsigned long lock_flags = 0; + int retval; + u_int y_bottom; + unsigned long base; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + if (var->xoffset > 0) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) { + /* No change, do nothing */ + return 0; + } + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) { + y_bottom += var->yres; + } + + if (y_bottom > info->var.yres_virtual) { + return -EINVAL; + } + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + down(&mxc_fbi->flip_sem); + + spin_lock_irqsave(&mxc_fbi->fb_lock, lock_flags); + + dev_dbg(info->device, "Updating SDC BG buf %d address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, base) == 0) { + ipu_select_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + } + + spin_unlock_irqrestore(&mxc_fbi->fb_lock, lock_flags); + + dev_dbg(info->device, "Update complete\n"); + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) { + info->var.vmode |= FB_VMODE_YWRAP; + } else { + info->var.vmode &= ~FB_VMODE_YWRAP; + } + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) { + return -EINVAL; + } + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) { + return -EINVAL; + } + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static struct fb_ops mxcfb_ovl_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl_ovl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank_ovl, +}; + +static irqreturn_t mxcfb_vsync_irq_handler(int irq, void *dev_id) +{ + struct mxcfb_data *fb_data = dev_id; + + ipu_disable_irq(irq); + + fb_data->vsync_flag = 1; + wake_up_interruptible(&fb_data->vsync_wq); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + up(&mxc_fbi->flip_sem); + ipu_disable_irq(irq); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par; + struct mxcfb_info *mxc_fbi_ovl = + (struct mxcfb_info *)drv_data->fbi_ovl->par; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + drv_data->suspended = true; + + console_lock(); + fb_set_suspend(drv_data->fbi, 1); + fb_set_suspend(drv_data->fbi_ovl, 1); + console_unlock(); + + if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) { + ipu_disable_channel(MEM_SDC_FG, true); + } + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) { + fbmem = ioremap(FB_RAM_BASE_ADDR, FB_RAM_SIZE); + memcpy(fbmem, drv_data->fbi->screen_base, FB_RAM_SIZE); + iounmap(fbmem); + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + FB_RAM_BASE_ADDR); + ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + } + ipu_lowpwr_display_enable(); +#else + ipu_disable_channel(MEM_SDC_BG, true); + gpio_lcd_inactive(); +#endif + } + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par; + struct mxcfb_info *mxc_fbi_ovl = + (struct mxcfb_info *)drv_data->fbi_ovl->par; + + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + ipu_lowpwr_display_disable(); + if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) { + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + drv_data->fbi->fix. + smem_start); + ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + } +#else + gpio_lcd_active(); + ipu_enable_channel(MEM_SDC_BG); +#endif + } + + if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) { + ipu_enable_channel(MEM_SDC_FG); + } + + console_lock(); + fb_set_suspend(drv_data->fbi, 0); + fb_set_suspend(drv_data->fbi_ovl, 0); + console_unlock(); + + wake_up_interruptible(&drv_data->suspend_wq); + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @param use_internal_ram flag on whether to use internal RAM for memory + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram) +{ + int retval = 0; + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (use_internal_ram) { + fbi->fix.smem_len = FB_RAM_SIZE; + fbi->fix.smem_start = FB_RAM_BASE_ADDR; + if (fbi->fix.smem_len < + (fbi->var.yres_virtual * fbi->fix.line_length)) { + dev_err(fbi->device, + "Not enough internal RAM for framebuffer configuration\n"); + retval = -EINVAL; + goto err0; + } + + if (request_mem_region(fbi->fix.smem_start, fbi->fix.smem_len, + fbi->device->driver->name) == NULL) { + dev_err(fbi->device, + "Unable to request internal RAM\n"); + retval = -ENOMEM; + goto err0; + } + + fbi->screen_base = ioremap(fbi->fix.smem_start, + fbi->fix.smem_len); + if (!fbi->screen_base) { + dev_err(fbi->device, + "Unable to map fb memory to virtual address\n"); + release_mem_region(fbi->fix.smem_start, + fbi->fix.smem_len); + retval = -EIO; + goto err0; + } + + iram_clk = clk_get(NULL, "iram_clk"); + clk_enable(iram_clk); + } else +#endif + { + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + fbi->screen_base = + dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *) &fbi->fix.smem_start, + GFP_DMA); + + if (fbi->screen_base == 0) { + dev_err(fbi->device, + "Unable to allocate framebuffer memory\n"); + retval = -EBUSY; + goto err0; + } + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; + + err0: + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + fbi->screen_base = NULL; + return retval; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (fbi->fix.smem_start == FB_RAM_BASE_ADDR) { + iounmap(fbi->screen_base); + release_mem_region(fbi->fix.smem_start, fbi->fix.smem_len); + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + clk_disable(iram_clk); + } else +#endif + { + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + } + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + spin_lock_init(&mxcfbi->fb_lock); + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + char *mode = pdev->dev.platform_data; + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct fb_info *fbi_ovl; + int ret = 0; + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfbi = (struct mxcfb_info *)fbi->par; + + mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_BG_EOF; + mxcfbi->cur_ipu_buf = 0; + mxcfbi->ipu_ch = MEM_SDC_BG; + + ipu_sdc_set_global_alpha(true, 0xFF); + ipu_sdc_set_color_key(MEM_SDC_BG, false, 0); + + if (ipu_request_irq(IPU_IRQ_SDC_BG_EOF, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering BG irq handler.\n"); + ret = -EBUSY; + goto err1; + } + ipu_disable_irq(IPU_IRQ_SDC_BG_EOF); + + if (fb_mode == NULL) { + fb_mode = mode; + } + + if (!fb_find_mode(&fbi->var, fbi, fb_mode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp)) { + ret = -EBUSY; + goto err2; + } + fb_videomode_to_modelist(mxcfb_modedb, mxcfb_modedb_sz, &fbi->modelist); + + /* Default Y virtual size is 2x panel size */ +#ifndef CONFIG_FB_MXC_INTERNAL_MEM + fbi->var.yres_virtual = fbi->var.yres * 2; +#endif + + mxcfb_drv_data.fbi = fbi; + mxcfb_drv_data.backlight_level = 255; + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + mxcfbi->blank = FB_BLANK_NORMAL; + ret = mxcfb_set_par(fbi); + if (ret < 0) { + goto err2; + } + mxcfb_blank(FB_BLANK_UNBLANK, fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + /* + * Initialize Overlay FB structures + */ + fbi_ovl = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ovl_ops); + if (!fbi_ovl) { + ret = -ENOMEM; + goto err3; + } + mxcfb_drv_data.fbi_ovl = fbi_ovl; + mxcfbi = (struct mxcfb_info *)fbi_ovl->par; + + mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_FG_EOF; + mxcfbi->cur_ipu_buf = 0; + mxcfbi->ipu_ch = MEM_SDC_FG; + + if (ipu_request_irq(IPU_IRQ_SDC_FG_EOF, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi_ovl) != 0) { + dev_err(fbi->device, "Error registering FG irq handler.\n"); + ret = -EBUSY; + goto err4; + } + ipu_disable_irq(mxcfbi->ipu_ch_irq); + + /* Default Y virtual size is 2x panel size */ + fbi_ovl->var = fbi->var; + fbi_ovl->var.yres_virtual = fbi->var.yres * 2; + + /* Overlay is blanked by default */ + mxcfbi->blank = FB_BLANK_NORMAL; + + ret = mxcfb_set_par(fbi_ovl); + if (ret < 0) { + goto err5; + } + + /* + * Register overlay framebuffer + */ + ret = register_framebuffer(fbi_ovl); + if (ret < 0) { + goto err5; + } + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + init_waitqueue_head(&mxcfb_drv_data.vsync_wq); + if (!cpu_is_mx31() && !cpu_is_mx32()) { + ret = ipu_request_irq(IPU_IRQ_SDC_DISP3_VSYNC, + mxcfb_vsync_irq_handler, + 0, MXCFB_NAME, + &mxcfb_drv_data); + if (ret < 0) + goto err6; + ipu_disable_irq(IPU_IRQ_SDC_DISP3_VSYNC); + } + + printk(KERN_INFO "mxcfb: fb registered, using mode %s\n", fb_mode); + return 0; + + err6: + unregister_framebuffer(fbi_ovl); + err5: + ipu_free_irq(IPU_IRQ_SDC_FG_EOF, fbi_ovl); + err4: + fb_dealloc_cmap(&fbi_ovl->cmap); + framebuffer_release(fbi_ovl); + err3: + unregister_framebuffer(fbi); + err2: + ipu_free_irq(IPU_IRQ_SDC_BG_EOF, fbi); + err1: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + printk(KERN_ERR "mxcfb: failed to register fb\n"); + return ret; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=trident:800x600,bpp=16,noaccel + */ +int mxcfb_setup(char *options) +{ + char *opt; + if (!options || !*options) + return 0; + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + return 0; +} + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + int ret = 0; +#ifndef MODULE + char *option = NULL; +#endif + +#ifndef MODULE + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mxcfb_setup(option); +#endif + + ret = platform_driver_register(&mxcfb_driver); + return ret; +} + +void mxcfb_exit(void) +{ + struct fb_info *fbi = mxcfb_drv_data.fbi; + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + fbi = mxcfb_drv_data.fbi_ovl; + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } +#ifndef CONFIG_ARCH_MX3 + ipu_free_irq(IPU_IRQ_SDC_DISP3_VSYNC, &mxcfb_drv_data); +#endif + + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_ch7026.c b/drivers/video/mxc/mxcfb_ch7026.c new file mode 100644 index 000000000000..6668b88b5390 --- /dev/null +++ b/drivers/video/mxc/mxcfb_ch7026.c @@ -0,0 +1,370 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_epson_vga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct i2c_client *ch7026_client; + +static int lcd_init(void); +static void lcd_poweron(struct fb_info *info); +static void lcd_poweroff(void); + +static void (*lcd_reset) (void); +static struct regulator *io_reg; +static struct regulator *core_reg; +static struct regulator *analog_reg; + + /* 8 800x600-60 VESA */ +static struct fb_videomode mode = { + NULL, 60, 800, 600, 25000, 88, 40, 23, 1, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &mode); + + var.activate = FB_ACTIVATE_ALL; + + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + fb_blank(info, FB_BLANK_UNBLANK); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG - DI1")) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + lcd_poweron(event->info); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) + lcd_poweron(event->info); + else + lcd_poweroff(); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct device *dev) +{ + int ret = 0; + int i; + struct mxc_lcd_platform_data *plat = dev->platform_data; + + if (plat) { + + io_reg = regulator_get(dev, plat->io_reg); + if (!IS_ERR(io_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + regulator_enable(io_reg); + } else { + io_reg = NULL; + } + + core_reg = regulator_get(dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(core_reg, 2500000, 2500000); + regulator_enable(core_reg); + } else { + core_reg = NULL; + } + analog_reg = regulator_get(dev, plat->analog_reg); + if (!IS_ERR(analog_reg)) { + regulator_set_voltage(analog_reg, 2775000, 2775000); + regulator_enable(analog_reg); + } else { + analog_reg = NULL; + } + msleep(100); + + lcd_reset = plat->reset; + if (lcd_reset) + lcd_reset(); + } + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG - DI1") == 0) { + ret = lcd_init(); + if (ret < 0) + goto err; + + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(registered_fb[i]); + } + } + + fb_register_client(&nb); + return 0; +err: + if (io_reg) + regulator_disable(io_reg); + if (core_reg) + regulator_disable(core_reg); + if (analog_reg) + regulator_disable(analog_reg); + + return ret; +} + +static int __devinit ch7026_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + ch7026_client = client; + + return lcd_probe(&client->dev); +} + +static int __devexit ch7026_remove(struct i2c_client *client) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + regulator_put(io_reg); + regulator_put(core_reg); + regulator_put(analog_reg); + + return 0; +} + +static int ch7026_suspend(struct i2c_client *client, pm_message_t message) +{ + return 0; +} + +static int ch7026_resume(struct i2c_client *client) +{ + return 0; +} + +u8 reg_init[][2] = { + { 0x02, 0x01 }, + { 0x02, 0x03 }, + { 0x03, 0x00 }, + { 0x06, 0x6B }, + { 0x08, 0x08 }, + { 0x09, 0x80 }, + { 0x0C, 0x0A }, + { 0x0D, 0x89 }, + { 0x0F, 0x23 }, + { 0x10, 0x20 }, + { 0x11, 0x20 }, + { 0x12, 0x40 }, + { 0x13, 0x28 }, + { 0x14, 0x80 }, + { 0x15, 0x52 }, + { 0x16, 0x58 }, + { 0x17, 0x74 }, + { 0x19, 0x01 }, + { 0x1A, 0x04 }, + { 0x1B, 0x23 }, + { 0x1C, 0x20 }, + { 0x1D, 0x20 }, + { 0x1F, 0x28 }, + { 0x20, 0x80 }, + { 0x21, 0x12 }, + { 0x22, 0x58 }, + { 0x23, 0x74 }, + { 0x25, 0x01 }, + { 0x26, 0x04 }, + { 0x37, 0x20 }, + { 0x39, 0x20 }, + { 0x3B, 0x20 }, + { 0x41, 0xA2 }, + { 0x4D, 0x03 }, + { 0x4E, 0x13 }, + { 0x4F, 0xB1 }, + { 0x50, 0x3B }, + { 0x51, 0x54 }, + { 0x52, 0x12 }, + { 0x53, 0x13 }, + { 0x55, 0xE5 }, + { 0x5E, 0x80 }, + { 0x69, 0x64 }, + { 0x7D, 0x62 }, + { 0x04, 0x00 }, + { 0x06, 0x69 }, + + /* + NOTE: The following five repeated sentences are used here to wait memory initial complete, please don't remove...(you could refer to Appendix A of programming guide document (CH7025(26)B Programming Guide Rev2.03.pdf) for detailed information about memory initialization! + */ + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + + { 0x06, 0x68 }, + { 0x02, 0x02 }, + { 0x02, 0x03 }, +}; + +#define REGMAP_LENGTH (sizeof(reg_init) / (2*sizeof(u8))) + +/* + * Send init commands to L4F00242T03 + * + */ +static int lcd_init(void) +{ + int i; + int dat; + + dev_dbg(&ch7026_client->dev, "initializing CH7026\n"); + + /* read device ID */ + msleep(100); + dat = i2c_smbus_read_byte_data(ch7026_client, 0x00); + dev_dbg(&ch7026_client->dev, "read id = 0x%02X\n", dat); + if (dat != 0x54) + return -ENODEV; + + for (i = 0; i < REGMAP_LENGTH; ++i) { + if (i2c_smbus_write_byte_data + (ch7026_client, reg_init[i][0], reg_init[i][1]) < 0) + return -EIO; + } + + return 0; +} + +static int lcd_on; +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(struct fb_info *info) +{ + u16 data[4]; + u32 refresh; + + if (lcd_on) + return; + + dev_dbg(&ch7026_client->dev, "turning on LCD\n"); + + data[0] = PICOS2KHZ(info->var.pixclock) / 10; + data[2] = info->var.hsync_len + info->var.left_margin + + info->var.xres + info->var.right_margin; + data[3] = info->var.vsync_len + info->var.upper_margin + + info->var.yres + info->var.lower_margin; + + refresh = data[2] * data[3]; + refresh = (PICOS2KHZ(info->var.pixclock) * 1000) / refresh; + data[1] = refresh * 100; + + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + if (!lcd_on) + return; + + dev_dbg(&ch7026_client->dev, "turning off LCD\n"); + + lcd_on = 0; +} + +static const struct i2c_device_id ch7026_id[] = { + {"ch7026", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ch7026_id); + +static struct i2c_driver ch7026_driver = { + .driver = { + .name = "ch7026", + }, + .probe = ch7026_probe, + .remove = ch7026_remove, + .suspend = ch7026_suspend, + .resume = ch7026_resume, + .id_table = ch7026_id, +}; + +static int __init ch7026_init(void) +{ + return i2c_add_driver(&ch7026_driver); +} + +static void __exit ch7026_exit(void) +{ + i2c_del_driver(&ch7026_driver); +} + +module_init(ch7026_init); +module_exit(ch7026_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CH7026 VGA driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_claa_wvga.c b/drivers/video/mxc/mxcfb_claa_wvga.c new file mode 100644 index 000000000000..3dbad0dcb9f3 --- /dev/null +++ b/drivers/video/mxc/mxcfb_claa_wvga.c @@ -0,0 +1,240 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_claa_wvga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void lcd_poweron(void); +static void lcd_poweroff(void); + +static struct platform_device *plcd_dev; +static struct regulator *io_reg; +static struct regulator *core_reg; +static int lcd_on; + +static struct fb_videomode video_modes[] = { + { + /* 800x480 @ 57 Hz , pixel clk @ 27MHz */ + "CLAA-WVGA", 57, 800, 480, 37037, 40, 60, 10, 10, 20, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &video_modes[0]); + + var.activate = FB_ACTIVATE_ALL; + var.yres_virtual = var.yres * 3; + + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG") && + strcmp(event->info->fix.id, "mxc_elcdif_fb")) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + fb_show_logo(event->info, 0); + lcd_poweron(); + break; + case FB_EVENT_BLANK: + if ((event->info->var.xres != 800) || + (event->info->var.yres != 480)) { + break; + } + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + lcd_poweron(); + } else { + lcd_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct platform_device *pdev) +{ + int i; + struct mxc_lcd_platform_data *plat = pdev->dev.platform_data; + + if (plat) { + if (plat->reset) + plat->reset(); + + io_reg = regulator_get(&pdev->dev, plat->io_reg); + if (IS_ERR(io_reg)) + io_reg = NULL; + core_reg = regulator_get(&pdev->dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + } else { + core_reg = NULL; + } + } + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0 || + strcmp(registered_fb[i]->fix.id, "mxc_elcdif_fb") == 0) { + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(); + } else if (strcmp(registered_fb[i]->fix.id, "DISP3 FG") == 0) { + lcd_init_fb(registered_fb[i]); + } + } + + fb_register_client(&nb); + + plcd_dev = pdev; + + return 0; +} + +static int __devexit lcd_remove(struct platform_device *pdev) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + if (io_reg) + regulator_put(io_reg); + if (core_reg) + regulator_put(core_reg); + + return 0; +} + +#ifdef CONFIG_PM +static int lcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int lcd_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define lcd_suspend NULL +#define lcd_resume NULL +#endif + +/*! + * platform driver structure for CLAA WVGA + */ +static struct platform_driver lcd_driver = { + .driver = { + .name = "lcd_claa"}, + .probe = lcd_probe, + .remove = __devexit_p(lcd_remove), + .suspend = lcd_suspend, + .resume = lcd_resume, +}; + +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(void) +{ + if (lcd_on) + return; + + dev_dbg(&plcd_dev->dev, "turning on LCD\n"); + if (core_reg) + regulator_enable(core_reg); + if (io_reg) + regulator_enable(io_reg); + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + lcd_on = 0; + dev_dbg(&plcd_dev->dev, "turning off LCD\n"); + if (io_reg) + regulator_disable(io_reg); + if (core_reg) + regulator_disable(core_reg); +} + +static int __init claa_lcd_init(void) +{ + return platform_driver_register(&lcd_driver); +} + +static void __exit claa_lcd_exit(void) +{ + platform_driver_unregister(&lcd_driver); +} + +module_init(claa_lcd_init); +module_exit(claa_lcd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CLAA WVGA LCD init driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_epson.c b/drivers/video/mxc/mxcfb_epson.c new file mode 100644 index 000000000000..f7d191fa798b --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson.c @@ -0,0 +1,1153 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxcfb_epson.c + * + * @brief MXC Frame buffer driver for ADC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PARTIAL_REFRESH +#define MXCFB_REFRESH_DEFAULT MXCFB_REFRESH_PARTIAL +/* + * Driver name + */ +#define MXCFB_NAME "MXCFB_EPSON" + +#define MXCFB_SCREEN_TOP_OFFSET 0 +#define MXCFB_SCREEN_LEFT_OFFSET 2 +#define MXCFB_SCREEN_WIDTH 176 +#define MXCFB_SCREEN_HEIGHT 220 + +/*! + * Enum defining Epson panel commands. + */ +enum { + DISON = 0xAF, + DISOFF = 0xAE, + DISCTL = 0xCA, + SD_CSET = 0x15, + SD_PSET = 0x75, + DATCTL = 0xBC, + SLPIN = 0x95, + SLPOUT = 0x94, + DISNOR = 0xA6, + RAMWR = 0x5C, + VOLCTR = 0xC6, + GCP16 = 0xCC, + GCP64 = 0xCB, +}; + +struct mxcfb_info { + int open_count; + int blank; + uint32_t disp_num; + + u32 pseudo_palette[16]; + + int32_t cur_update_mode; + dma_addr_t alloc_start_paddr; + void *alloc_start_vaddr; + u32 alloc_size; + uint32_t snoop_window_size; +}; + +struct mxcfb_data { + struct fb_info *fbi; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; +}; + +static struct mxcfb_data mxcfb_drv_data; +static unsigned long default_bpp = 16; + +void slcd_gpio_config(void); +extern void gpio_lcd_active(void); +static int mxcfb_blank(int blank, struct fb_info *fbi); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +/*! + * This function sets display region in the Epson panel + * + * @param disp display panel to config + * @param x1 x-coordinate of one vertex. + * @param x2 x-coordinate of second vertex. + * @param y1 y-coordinate of one vertex. + * @param y2 y-coordinate of second vertex. + */ +void set_panel_region(int disp, uint32_t x1, uint32_t x2, + uint32_t y1, uint32_t y2) +{ + uint32_t param[8]; + + memset(param, 0, sizeof(uint32_t) * 8); + param[0] = x1; + param[2] = x2; + param[4] = y1; + param[6] = y2; + + /* SD_CSET */ + ipu_adc_write_cmd(disp, CMD, SD_CSET, param, 4); + + /* SD_PSET */ + ipu_adc_write_cmd(disp, CMD, SD_PSET, &(param[4]), 4); +} + +/*! + * Function to create and initiate template command buffer for ADC. This + * template will be written to Panel memory. + */ +static void init_channel_template(int disp) +{ + /* template command buffer for ADC is 32 */ + uint32_t tempCmd[TEMPLATE_BUF_SIZE]; + uint32_t i = 0; + + memset(tempCmd, 0, sizeof(uint32_t) * TEMPLATE_BUF_SIZE); + /* setup update display region */ + /* whole the screen during init */ + /*WRITE Y COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_PSET); + /*WRITE Y START ADDRESS CMND LSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x01); + /*WRITE Y START ADDRESS CMND MSB[22:16] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x09); + /*WRITE Y STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_HEIGHT - 1); + /*WRITE Y STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_CSET); + /*WRITE X ADDRESS CMND LSB[7:0] */ + tempCmd[i++] = ipu_adc_template_gen(WR_XADDR, 1, SINGLE_STEP, 0x01); + /*WRITE X ADDRESS CMND MSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_WIDTH + 1); + /*WRITE X STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE MEMORY CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, RAMWR); + /*WRITE DATA CMND and STP */ + tempCmd[i++] = ipu_adc_template_gen(WR_DATA, 1, STOP, 0); + + ipu_adc_write_template(disp, tempCmd, true); +} + +/*! + * Function to initialize the panel. First it resets the panel and then + * initilizes panel. + */ +static void _init_panel(int disp) +{ + uint32_t cmd_param; + uint32_t i; + + gpio_lcd_active(); + slcd_gpio_config(); + + /* DATCTL */ +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + /* 16-bit 565 mode */ + cmd_param = 0x28; +#else + /* 8-bit 666 mode */ + cmd_param = 0x08; +#endif + ipu_adc_write_cmd(disp, CMD, DATCTL, &cmd_param, 1); + + /* Sleep OUT */ + ipu_adc_write_cmd(disp, CMD, SLPOUT, 0, 0); + + /* Set display to white + Setup page and column addresses */ + set_panel_region(disp, MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET - 1, + 0, MXCFB_SCREEN_HEIGHT - 1); + /* Do RAM write cmd */ + ipu_adc_write_cmd(disp, CMD, RAMWR, 0, 0); +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT); i++) +#else + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT * 3); i++) +#endif + ipu_adc_write_cmd(disp, DAT, 0xFFFF, 0, 0); + + /* Pause 80 ms */ + mdelay(80); + + /* Display ON */ + ipu_adc_write_cmd(disp, CMD, DISON, 0, 0); + /* Pause 200 ms */ + mdelay(200); + + pr_debug("initialized panel\n"); +} + +#ifdef PARTIAL_REFRESH +static irqreturn_t mxcfb_sys2_eof_irq_handler(int irq, void *dev_id) +{ + ipu_channel_params_t params; + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + uint32_t stat[2], seg_size; + uint32_t lsb, msb; + uint32_t update_height, start_line, start_addr, end_line, end_addr; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + + ipu_adc_get_snooping_status(&stat[0], &stat[1]); + + if (!stat[0] && !stat[1]) { + dev_err(fbi->device, "error no bus snooping bits set\n"); + return IRQ_HANDLED; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + + lsb = ffs(stat[0]); + if (lsb) { + lsb--; + } else { + lsb = ffs(stat[1]); + lsb += 32 - 1; + } + msb = fls(stat[1]); + if (msb) { + msb += 32; + } else { + msb = fls(stat[0]); + } + + seg_size = mxc_fbi->snoop_window_size / 64; + + start_addr = lsb * seg_size; /* starting address offset */ + start_line = start_addr / fbi->fix.line_length; + start_addr = start_line * fbi->fix.line_length; /* Addr aligned to line */ + start_addr += fbi->fix.smem_start; + + end_addr = msb * seg_size; /* ending address offset */ + end_line = end_addr / fbi->fix.line_length; + end_line++; + + if (end_line > fbi->var.yres) { + end_line = fbi->var.yres; + } + + update_height = end_line - start_line; + dev_dbg(fbi->device, "updating rows %d to %d, start addr = 0x%08X\n", + start_line, end_line, start_addr); + + ipu_uninit_channel(ADC_SYS1); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = start_line; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, + update_height, + stride_pixels, + IPU_ROTATE_NONE, (dma_addr_t) start_addr, 0, + 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_sys1_eof_irq_handler(int irq, void *dev_id) +{ + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_channel(ADC_SYS1, false); + + ipu_enable_channel(ADC_SYS2); + ipu_enable_irq(IPU_IRQ_ADC_SYS2_EOF); + + return IRQ_HANDLED; +} +#endif + +/*! + * Function to initialize Asynchronous Display Controller. It also initilizes + * the ADC System 1 channel. Configure ADC display 0 parallel interface for + * the panel. + * + * @param fbi framebuffer information pointer + */ +static void mxcfb_init_panel(struct fb_info *fbi) +{ + int msb; + int panel_stride; + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#elif defined(CONFIG_FB_MXC_ASYNC_PANEL_IFC_8_BIT) + uint32_t pix_fmt = IPU_PIX_FMT_RGB666; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 8, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#else + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 1, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_SERIAL, + IPU_ADC_IFC_MODE_5WIRE_SERIAL_CLK, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + fbi->disp_num = DISP1; +#endif + +#ifdef PARTIAL_REFRESH + if (ipu_request_irq(IPU_IRQ_ADC_SYS2_EOF, mxcfb_sys2_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS2 irq handler.\n"); + return; + } + + if (ipu_request_irq(IPU_IRQ_ADC_SYS1_EOF, mxcfb_sys1_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS1 irq handler.\n"); + return; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + /* Init DI interface */ + msb = fls(MXCFB_SCREEN_WIDTH); + if (!(MXCFB_SCREEN_WIDTH & ((1UL << msb) - 1))) + msb--; /* Already aligned to power 2 */ + panel_stride = 1UL << msb; + ipu_adc_init_panel(mxc_fbi->disp_num, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_HEIGHT, + pix_fmt, panel_stride, sig, XY, 0, VsyncInternal); + + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, true, + 190, 17, 104, 190, 5000000); + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, false, 123, 17, 68, 0, 0); + + /* Needed to turn on ADC clock for panel init */ + memset(¶ms, 0, sizeof(params)); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + _init_panel(mxc_fbi->disp_num); + init_channel_template(mxc_fbi->disp_num); +} + +int mxcfb_set_refresh_mode(struct fb_info *fbi, int mode, + struct mxcfb_rect *update_region) +{ + unsigned long start_addr; + int ret_mode; + uint32_t dummy; + ipu_channel_params_t params; + struct mxcfb_rect rect; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + uint32_t memsize = fbi->fix.smem_len; + + if (mxc_fbi->cur_update_mode == mode) + return mode; + + ret_mode = mxc_fbi->cur_update_mode; + + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#ifdef PARTIAL_REFRESH + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#endif + + ipu_disable_channel(ADC_SYS1, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS1_EOF); +#ifdef PARTIAL_REFRESH + ipu_disable_channel(ADC_SYS2, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + ipu_adc_get_snooping_status(&dummy, &dummy); + + mxc_fbi->cur_update_mode = mode; + + switch (mode) { + case MXCFB_REFRESH_OFF: + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + if (ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); +#if 0 + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, + fbi->fix.smem_start, 0, 0); + ipu_enable_channel(ADC_SYS2); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 1); + msleep(10); +#endif + ipu_uninit_channel(ADC_SYS1); +#ifdef PARTIAL_REFRESH + ipu_uninit_channel(ADC_SYS2); +#endif + break; + case MXCFB_REFRESH_PARTIAL: +#ifdef PARTIAL_REFRESH + ipu_adc_get_snooping_status(&dummy, &dummy); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = 0; + params.adc_sys2.out_top = 0; + ipu_init_channel(ADC_SYS2, ¶ms); + + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + if (ipu_adc_set_update_mode + (ADC_SYS2, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + mxc_fbi->snoop_window_size = memsize; + + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, MXCFB_SCREEN_HEIGHT, + stride_pixels, IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + break; +#endif + case MXCFB_REFRESH_AUTO: + if (update_region == NULL) { + update_region = ▭ + rect.top = 0; + rect.left = 0; + rect.height = MXCFB_SCREEN_HEIGHT; + rect.width = MXCFB_SCREEN_WIDTH; + } + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET + + update_region->left; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET + + update_region->top; + ipu_init_channel(ADC_SYS1, ¶ms); + + /* Address aligned to line */ + start_addr = update_region->top * fbi->fix.line_length; + start_addr += fbi->fix.smem_start; + start_addr += update_region->left * fbi->var.bits_per_pixel / 8; + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + update_region->width, + update_region->height, stride_pixels, + IPU_ROTATE_NONE, start_addr, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + + if (ipu_adc_set_update_mode + (ADC_SYS1, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + + mxc_fbi->snoop_window_size = memsize; + + break; + } + return ret_mode; +} + +/* + * Open the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_open(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mxc_fbi->open_count++; + + retval = mxcfb_blank(FB_BLANK_UNBLANK, fbi); + return retval; +} + +/* + * Close the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_release(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + --mxc_fbi->open_count; + if (mxc_fbi->open_count == 0) { + retval = mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + } + return retval; +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + /* Set framebuffer id to IPU display number. */ + strcpy(fix->id, "DISP0 FB"); + fix->id[4] = '0' + mxc_fbi->disp_num; + + /* Init settings based on the panel size */ + fix->line_length = MXCFB_SCREEN_WIDTH * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ypanstep = 0; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + int mode; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mode = mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + + mxcfb_set_fix(fbi); + + if (mode != MXCFB_REFRESH_OFF) { +#ifdef PARTIAL_REFRESH + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_PARTIAL, NULL); +#else + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_AUTO, NULL); +#endif + } + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + if (var->xres > MXCFB_SCREEN_WIDTH) + var->xres = MXCFB_SCREEN_WIDTH; + if (var->yres > MXCFB_SCREEN_HEIGHT) + var->yres = MXCFB_SCREEN_HEIGHT; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + var->nonstd = 0; + + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + dev_dbg(fbi->device, "blank = %d\n", blank); + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + break; + } + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_open = mxcfb_open, + .fb_release = mxcfb_release, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + u32 msb; + u32 offset; + struct mxcfb_info *mxcfbi = fbi->par; + + fbi->fix.smem_len = fbi->var.xres_virtual * fbi->var.yres_virtual * 4; + + /* Set size to power of 2. */ + msb = fls(fbi->fix.smem_len); + if (!(fbi->fix.smem_len & ((1UL << msb) - 1))) + msb--; /* Already aligned to power 2 */ + if (msb < 11) + msb = 11; + mxcfbi->alloc_size = (1UL << msb) * 2; + + mxcfbi->alloc_start_vaddr = dma_alloc_coherent(fbi->device, + mxcfbi->alloc_size, + &mxcfbi-> + alloc_start_paddr, + GFP_KERNEL | GFP_DMA); + + if (mxcfbi->alloc_start_vaddr == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + return -ENOMEM; + } + dev_dbg(fbi->device, "allocated fb memory @ paddr=0x%08X, size=%d.\n", + (uint32_t) mxcfbi->alloc_start_paddr, mxcfbi->alloc_size); + + offset = + ((mxcfbi->alloc_size / 2) - 1) & ~((mxcfbi->alloc_size / 2) - 1); + fbi->fix.smem_start = mxcfbi->alloc_start_paddr + offset; + dev_dbg(fbi->device, "aligned fb start @ paddr=0x%08lX, size=%u.\n", + fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_base = mxcfbi->alloc_start_vaddr + offset; + + /* Clear the screen */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dma_free_coherent(fbi->device, mxc_fbi->alloc_size, + mxc_fbi->alloc_start_vaddr, + mxc_fbi->alloc_start_paddr); + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + /* + * Fill in fb_info structure information + */ + fbi->var.xres = fbi->var.xres_virtual = MXCFB_SCREEN_WIDTH; + fbi->var.yres = fbi->var.yres_virtual = MXCFB_SCREEN_HEIGHT; + fbi->var.activate = FB_ACTIVATE_NOW; + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxc_fbi; + int ret; + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfb_drv_data.fbi = fbi; + mxc_fbi = fbi->par; + + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + /* + * Allocate memory + */ + ret = mxcfb_map_video_memory(fbi); + if (ret < 0) { + goto err1; + } + + mxcfb_init_panel(fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + dev_info(&pdev->dev, "%s registered\n", MXCFB_NAME); + + return 0; + + err2: + mxcfb_unmap_video_memory(fbi); + err1: + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + return ret; +} + +#ifdef CONFIG_PM +/*! + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * Suspends the framebuffer and blanks the screen. Power management support + * + * @param pdev pointer to device structure. + * @param state state of the device. + * + * @return success + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + drv_data->suspended = true; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + /* Display OFF */ + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISOFF, 0, 0); + + return 0; +} + +/*! + * Resumes the framebuffer and unblanks the screen. Power management support + * + * @param pdev pointer to device structure. + * + * @return success + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + /* Display ON */ + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISON, 0, 0); + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + wake_up_interruptible(&drv_data->suspend_wq); + + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Device definition for the Framebuffer + */ +static struct platform_device mxcfb_device = { + .name = MXCFB_NAME, + .id = 0, + .dev = { + .coherent_dma_mask = 0xFFFFFFFF, + } +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +static int mxcfb_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&mxcfb_driver); + if (ret == 0) { + ret = platform_device_register(&mxcfb_device); + if (ret != 0) { + platform_driver_unregister(&mxcfb_driver); + } + } + return ret; +} + +static void mxcfb_exit(void) +{ + struct fb_info *fbi = dev_get_drvdata(&mxcfb_device.dev); + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + platform_device_unregister(&mxcfb_device); + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +EXPORT_SYMBOL(mxcfb_set_refresh_mode); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Epson framebuffer driver"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_epson_vga.c b/drivers/video/mxc/mxcfb_epson_vga.c new file mode 100644 index 000000000000..d7cf7d3724ba --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson_vga.c @@ -0,0 +1,362 @@ +/* + * Copyright 2007-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_epson_vga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct spi_device *lcd_spi; +static struct device *lcd_dev; + +static void lcd_init(void); +static void lcd_poweron(void); +static void lcd_poweroff(void); + +static void (*lcd_reset) (void); +static struct regulator *io_reg; +static struct regulator *core_reg; + +static struct fb_videomode video_modes[] = { + { + /* 480x640 @ 60 Hz */ + "Epson-VGA", 60, 480, 640, 41701, 60, 41, 10, 5, 20, 10, + 0, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &video_modes[0]); + + if (machine_is_mx31_3ds()) { + var.upper_margin = 0; + var.left_margin = 0; + } + + var.activate = FB_ACTIVATE_ALL; + var.yres_virtual = var.yres * 3; + + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG")) { + return 0; + } + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + lcd_poweron(); + break; + case FB_EVENT_BLANK: + if ((event->info->var.xres != 480) || + (event->info->var.yres != 640)) { + break; + } + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + lcd_poweron(); + } else { + lcd_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct device *dev) +{ + int i; + struct mxc_lcd_platform_data *plat = dev->platform_data; + + lcd_dev = dev; + + if (plat) { + io_reg = regulator_get(dev, plat->io_reg); + if (!IS_ERR(io_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + regulator_enable(io_reg); + } + core_reg = regulator_get(dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(core_reg, 2800000, 2800000); + regulator_enable(core_reg); + } + + lcd_reset = plat->reset; + if (lcd_reset) + lcd_reset(); + } + + lcd_init(); + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0) { + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(); + } + } + + fb_register_client(&nb); + + return 0; +} + +static int __devinit lcd_plat_probe(struct platform_device *pdev) +{ + ipu_adc_sig_cfg_t sig; + ipu_channel_params_t param; + + memset(&sig, 0, sizeof(sig)); + sig.ifc_width = 9; + sig.clk_pol = 1; + ipu_init_async_panel(0, IPU_PANEL_SERIAL, 90, IPU_PIX_FMT_GENERIC, sig); + + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + + return lcd_probe(&pdev->dev); +} + +static int __devinit lcd_spi_probe(struct spi_device *spi) +{ + lcd_spi = spi; + + spi->bits_per_word = 9; + spi_setup(spi); + + return lcd_probe(&spi->dev); +} + +static int __devexit lcd_remove(struct device *dev) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + regulator_put(io_reg); + regulator_put(core_reg); + + return 0; +} + +static int __devexit lcd_spi_remove(struct spi_device *spi) +{ + int ret = lcd_remove(&spi->dev); + lcd_spi = NULL; + return ret; +} + +static int __devexit lcd_plat_remove(struct platform_device *pdev) +{ + return lcd_remove(&pdev->dev); +} + +static int lcd_suspend(struct spi_device *spi, pm_message_t message) +{ + lcd_poweroff(); + return 0; +} + +static int lcd_resume(struct spi_device *spi) +{ + if (lcd_reset) + lcd_reset(); + + lcd_init(); + lcd_poweron(); + return 0; +} + +/*! + * spi driver structure for LTV350QV + */ +static struct spi_driver lcd_spi_dev_driver = { + + .driver = { + .name = "lcd_spi", + .owner = THIS_MODULE, + }, + .probe = lcd_spi_probe, + .remove = __devexit_p(lcd_spi_remove), + .suspend = lcd_suspend, + .resume = lcd_resume, +}; + +static struct platform_driver lcd_plat_driver = { + .driver = { + .name = "lcd_spi", + .owner = THIS_MODULE, + }, + .probe = lcd_plat_probe, + .remove = __devexit_p(lcd_plat_remove), +}; + +#define param(x) ((x) | 0x100) + +/* + * Send init commands to L4F00242T03 + * + */ +static void lcd_init(void) +{ + const u16 cmd[] = { 0x36, param(0), 0x3A, param(0x60) }; + + dev_dbg(lcd_dev, "initializing LCD\n"); + if (lcd_spi) { + spi_write(lcd_spi, (const u8 *)cmd, ARRAY_SIZE(cmd)); + } else { + ipu_disp_direct_write(DIRECT_ASYNC1, 0x36, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x100, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x3A, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x160, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } +} + +static int lcd_on; +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(void) +{ + const u16 slpout = 0x11; + const u16 dison = 0x29; + ipu_channel_params_t param; + if (lcd_on) + return; + + dev_dbg(lcd_dev, "turning on LCD\n"); + + if (lcd_spi) { + msleep(60); + spi_write(lcd_spi, (const u8 *)&slpout, 1); + msleep(60); + spi_write(lcd_spi, (const u8 *)&dison, 1); + } else { + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + ipu_disp_direct_write(DIRECT_ASYNC1, slpout, 0); + msleep(60); + ipu_disp_direct_write(DIRECT_ASYNC1, dison, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + const u16 slpin = 0x10; + const u16 disoff = 0x28; + ipu_channel_params_t param; + if (!lcd_on) + return; + + dev_dbg(lcd_dev, "turning off LCD\n"); + + if (lcd_spi) { + msleep(60); + spi_write(lcd_spi, (const u8 *)&disoff, 1); + msleep(60); + spi_write(lcd_spi, (const u8 *)&slpin, 1); + } else { + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + ipu_disp_direct_write(DIRECT_ASYNC1, disoff, 0); + msleep(60); + ipu_disp_direct_write(DIRECT_ASYNC1, slpin, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } + lcd_on = 0; +} + +static int __init epson_lcd_init(void) +{ + int ret; + + ret = platform_driver_register(&lcd_plat_driver); + if (ret) + return ret; + + return spi_register_driver(&lcd_spi_dev_driver); + +} + +static void __exit epson_lcd_exit(void) +{ + spi_unregister_driver(&lcd_spi_dev_driver); +} + +module_init(epson_lcd_init); +module_exit(epson_lcd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Epson VGA LCD init driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_modedb.c b/drivers/video/mxc/mxcfb_modedb.c new file mode 100644 index 000000000000..acaaef2cbff1 --- /dev/null +++ b/drivers/video/mxc/mxcfb_modedb.c @@ -0,0 +1,69 @@ +/* + * Copyright 2007-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include + +struct fb_videomode mxcfb_modedb[] = { + { + /* 240x320 @ 60 Hz */ + "Sharp-QVGA", 60, 240, 320, 185925, 9, 16, 7, 9, 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_DATA_INVERT | FB_SYNC_CLK_IDLE_EN, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 240x33 @ 60 Hz */ + "Sharp-CLI", 60, 240, 33, 185925, 9, 16, 7, 9 + 287, 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_DATA_INVERT | FB_SYNC_CLK_IDLE_EN, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 640x480 @ 60 Hz */ + "NEC-VGA", 60, 640, 480, 38255, 144, 0, 34, 40, 1, 1, + FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 640x480 @ 60 Hz */ + "CPT-VGA", 60, 640, 480, 39683, 45, 114, 33, 11, 1, 1, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* NTSC TV output */ + "TV-NTSC", 60, 640, 480, 37538, + 38, 858 - 640 - 38 - 3, + 36, 518 - 480 - 36 - 1, + 3, 1, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* PAL TV output */ + "TV-PAL", 50, 640, 480, 37538, + 38, 960 - 640 - 38 - 32, + 32, 555 - 480 - 32 - 3, + 32, 3, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* TV output VGA mode, 640x480 @ 65 Hz */ + "TV-VGA", 60, 640, 480, 40574, 35, 45, 9, 1, 46, 5, + 0, FB_VMODE_NONINTERLACED, 0, + }, +}; + +int mxcfb_modedb_sz = ARRAY_SIZE(mxcfb_modedb); diff --git a/drivers/video/mxc/mxcfb_seiko_wvga.c b/drivers/video/mxc/mxcfb_seiko_wvga.c new file mode 100644 index 000000000000..0ba4d9ed064f --- /dev/null +++ b/drivers/video/mxc/mxcfb_seiko_wvga.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2011 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_seiko_wvga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void lcd_poweron(void); +static void lcd_poweroff(void); + +static struct platform_device *plcd_dev; +static struct regulator *io_reg; +static struct regulator *core_reg; +static int lcd_on; + +static struct fb_videomode video_modes[] = { + { + /* 800x480 @ 57 Hz , pixel clk @ 32MHz */ + "SEIKO-WVGA", 60, 800, 480, 29850, 99, 164, 33, 10, 10, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &video_modes[0]); + + var.activate = FB_ACTIVATE_ALL; + var.yres_virtual = var.yres * 3; + + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "mxc_elcdif_fb")) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + fb_show_logo(event->info, 0); + lcd_poweron(); + break; + case FB_EVENT_BLANK: + if ((event->info->var.xres != 800) || + (event->info->var.yres != 480)) { + break; + } + if (*((int *)event->data) == FB_BLANK_UNBLANK) + lcd_poweron(); + else + lcd_poweroff(); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the platform device is detected. + * + * @param pdev the platform device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct platform_device *pdev) +{ + int i; + struct mxc_lcd_platform_data *plat = pdev->dev.platform_data; + + if (plat) { + if (plat->reset) + plat->reset(); + + io_reg = regulator_get(&pdev->dev, plat->io_reg); + if (IS_ERR(io_reg)) + io_reg = NULL; + core_reg = regulator_get(&pdev->dev, plat->core_reg); + if (!IS_ERR(core_reg)) + regulator_set_voltage(io_reg, 1800000, 1800000); + else + core_reg = NULL; + } + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "mxc_elcdif_fb") == 0) { + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(); + } + } + + fb_register_client(&nb); + + plcd_dev = pdev; + + return 0; +} + +static int __devexit lcd_remove(struct platform_device *pdev) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + if (io_reg) + regulator_put(io_reg); + if (core_reg) + regulator_put(core_reg); + + return 0; +} + +#ifdef CONFIG_PM +static int lcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int lcd_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define lcd_suspend NULL +#define lcd_resume NULL +#endif + +/*! + * platform driver structure for SEIKO WVGA + */ +static struct platform_driver lcd_driver = { + .driver = { + .name = "lcd_seiko"}, + .probe = lcd_probe, + .remove = __devexit_p(lcd_remove), + .suspend = lcd_suspend, + .resume = lcd_resume, +}; + +/* + * Send Power + * + */ +static void lcd_poweron(void) +{ + if (lcd_on) + return; + + dev_dbg(&plcd_dev->dev, "turning on LCD\n"); + if (core_reg) + regulator_enable(core_reg); + if (io_reg) + regulator_enable(io_reg); + lcd_on = 1; +} + +/* + * Send Power Off + * + */ +static void lcd_poweroff(void) +{ + lcd_on = 0; + dev_dbg(&plcd_dev->dev, "turning off LCD\n"); + if (io_reg) + regulator_disable(io_reg); + if (core_reg) + regulator_disable(core_reg); +} + +static int __init seiko_wvga_lcd_init(void) +{ + return platform_driver_register(&lcd_driver); +} + +static void __exit seiko_wvga_lcd_exit(void) +{ + platform_driver_unregister(&lcd_driver); +} + +module_init(seiko_wvga_lcd_init); +module_exit(seiko_wvga_lcd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SEIKO WVGA LCD init driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_sii902x.c b/drivers/video/mxc/mxcfb_sii902x.c new file mode 100644 index 000000000000..67f20c60c16e --- /dev/null +++ b/drivers/video/mxc/mxcfb_sii902x.c @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2010-2011 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_sii902x.c + * + * @brief MXC Frame buffer driver for SII902x + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPU_DISP_PORT 0 +#define SII_EDID_LEN 256 +static bool g_enable_hdmi; + +struct sii902x_data { + struct platform_device *pdev; + struct i2c_client *client; + struct delayed_work det_work; + struct fb_info *fbi; + struct mxc_edid_cfg edid_cfg; + u8 cable_plugin; + u8 edid[SII_EDID_LEN]; +} sii902x; + +static void sii902x_poweron(void); +static void sii902x_poweroff(void); +static void (*sii902x_reset) (void); + +static __attribute__ ((unused)) void dump_regs(u8 reg, int len) +{ + u8 buf[50]; + int i; + + i2c_smbus_read_i2c_block_data(sii902x.client, reg, len, buf); + for (i = 0; i < len; i++) + dev_dbg(&sii902x.client->dev, "reg[0x%02X]: 0x%02X\n", + i+reg, buf[i]); +} + +static ssize_t sii902x_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (sii902x.cable_plugin == 0) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, sii902x_show_state, NULL); + +static ssize_t sii902x_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, j, len = 0; + + for (j = 0; j < SII_EDID_LEN/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + sii902x.edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, sii902x_show_edid, NULL); + +static void sii902x_setup(struct fb_info *fbi) +{ + u16 data[4]; + u32 refresh; + u8 *tmp; + int i; + + dev_dbg(&sii902x.client->dev, "Sii902x: setup..\n"); + + /* Power up */ + i2c_smbus_write_byte_data(sii902x.client, 0x1E, 0x00); + + /* set TPI video mode */ + data[0] = PICOS2KHZ(fbi->var.pixclock) / 10; + data[2] = fbi->var.hsync_len + fbi->var.left_margin + + fbi->var.xres + fbi->var.right_margin; + data[3] = fbi->var.vsync_len + fbi->var.upper_margin + + fbi->var.yres + fbi->var.lower_margin; + refresh = data[2] * data[3]; + refresh = (PICOS2KHZ(fbi->var.pixclock) * 1000) / refresh; + data[1] = refresh * 100; + tmp = (u8 *)data; + for (i = 0; i < 8; i++) + i2c_smbus_write_byte_data(sii902x.client, i, tmp[i]); + + /* input bus/pixel: full pixel wide (24bit), rising edge */ + i2c_smbus_write_byte_data(sii902x.client, 0x08, 0x70); + /* Set input format to RGB */ + i2c_smbus_write_byte_data(sii902x.client, 0x09, 0x00); + /* set output format to RGB */ + i2c_smbus_write_byte_data(sii902x.client, 0x0A, 0x00); + /* audio setup */ + i2c_smbus_write_byte_data(sii902x.client, 0x25, 0x00); + i2c_smbus_write_byte_data(sii902x.client, 0x26, 0x40); + i2c_smbus_write_byte_data(sii902x.client, 0x27, 0x00); +} + +#ifdef CONFIG_FB_MODE_HELPERS +static int sii902x_read_edid(struct fb_info *fbi) +{ + int old, dat, ret, cnt = 100; + unsigned short addr = 0x50; + + old = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x4); + do { + cnt--; + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + } while ((!(dat & 0x2)) && cnt); + + if (!cnt) { + ret = -1; + goto done; + } + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x06); + + /* edid reading */ + ret = mxc_edid_read(sii902x.client->adapter, addr, + sii902x.edid, &sii902x.edid_cfg, fbi); + + cnt = 100; + do { + cnt--; + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old & ~0x6); + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + } while ((dat & 0x6) && cnt); + + if (!cnt) + ret = -1; + +done: + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old); + return ret; +} +#else +static int sii902x_read_edid(struct fb_info *fbi) +{ + return -1; +} +#endif + +static void det_worker(struct work_struct *work) +{ + int dat; + char event_string[16]; + char *envp[] = { event_string, NULL }; + + dat = i2c_smbus_read_byte_data(sii902x.client, 0x3D); + if (dat & 0x1) { + /* cable connection changes */ + if (dat & 0x4) { + sii902x.cable_plugin = 1; + sprintf(event_string, "EVENT=plugin"); + + /* make sure fb is powerdown */ + console_lock(); + fb_blank(sii902x.fbi, FB_BLANK_POWERDOWN); + console_unlock(); + + if (sii902x_read_edid(sii902x.fbi) < 0) + dev_err(&sii902x.client->dev, + "Sii902x: read edid fail\n"); + else { + if (sii902x.fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + fb_destroy_modelist(&sii902x.fbi->modelist); + + for (i = 0; i < sii902x.fbi->monspecs.modedb_len; i++) + fb_add_videomode(&sii902x.fbi->monspecs.modedb[i], + &sii902x.fbi->modelist); + + fb_var_to_videomode(&m, &sii902x.fbi->var); + mode = fb_find_nearest_mode(&m, + &sii902x.fbi->modelist); + + fb_videomode_to_var(&sii902x.fbi->var, mode); + + sii902x.fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + sii902x.fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(sii902x.fbi, &sii902x.fbi->var); + sii902x.fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + } + + console_lock(); + fb_blank(sii902x.fbi, FB_BLANK_UNBLANK); + console_unlock(); + } + } else { + sii902x.cable_plugin = 0; + sprintf(event_string, "EVENT=plugout"); + console_lock(); + fb_blank(sii902x.fbi, FB_BLANK_POWERDOWN); + console_unlock(); + } + kobject_uevent_env(&sii902x.pdev->dev.kobj, KOBJ_CHANGE, envp); + } + i2c_smbus_write_byte_data(sii902x.client, 0x3D, dat); +} + +static irqreturn_t sii902x_detect_handler(int irq, void *data) +{ + if (sii902x.fbi) + schedule_delayed_work(&(sii902x.det_work), msecs_to_jiffies(20)); + return IRQ_HANDLED; +} + +static int sii902x_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + /* assume sii902x on DI0 only */ + if ((IPU_DISP_PORT)) { + if (strcmp(event->info->fix.id, "DISP3 BG - DI1")) + return 0; + } else { + if (strcmp(event->info->fix.id, "DISP3 BG")) + return 0; + } + + switch (val) { + case FB_EVENT_FB_REGISTERED: + if (sii902x.fbi != NULL) + break; + sii902x.fbi = fbi; + break; + case FB_EVENT_MODE_CHANGE: + sii902x_setup(fbi); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) + sii902x_poweron(); + else + sii902x_poweroff(); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = sii902x_fb_event, +}; + +static int __devinit sii902x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i, dat, ret; + struct fsl_mxc_lcd_platform_data *plat = client->dev.platform_data; + struct fb_info edid_fbi; + + if (g_enable_hdmi == false) + return -EPERM; + + sii902x.client = client; + + if (plat->reset) { + sii902x_reset = plat->reset; + sii902x_reset(); + } + + /* Set 902x in hardware TPI mode on and jump out of D3 state */ + if (i2c_smbus_write_byte_data(sii902x.client, 0xc7, 0x00) < 0) { + dev_err(&sii902x.client->dev, + "Sii902x: cound not find device\n"); + return -ENODEV; + } + + /* read device ID */ + for (i = 10; i > 0; i--) { + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1B); + printk(KERN_DEBUG "Sii902x: read id = 0x%02X", dat); + if (dat == 0xb0) { + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1C); + printk(KERN_DEBUG "-0x%02X", dat); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1D); + printk(KERN_DEBUG "-0x%02X", dat); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x30); + printk(KERN_DEBUG "-0x%02X\n", dat); + break; + } + } + if (i == 0) { + dev_err(&sii902x.client->dev, + "Sii902x: cound not find device\n"); + return -ENODEV; + } + + /* try to read edid */ + if (sii902x_read_edid(&edid_fbi) < 0) + dev_warn(&sii902x.client->dev, "Can not read edid\n"); +#if defined(CONFIG_MXC_IPU_V3) && defined(CONFIG_FB_MXC_SYNC_PANEL) + else + mxcfb_register_mode(IPU_DISP_PORT, edid_fbi.monspecs.modedb, + edid_fbi.monspecs.modedb_len, MXC_DISP_DDC_DEV); +#endif + + if (sii902x.client->irq) { + ret = request_irq(sii902x.client->irq, sii902x_detect_handler, + IRQF_TRIGGER_FALLING, + "SII902x_det", &sii902x); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not request det irq %d\n", + sii902x.client->irq); + else { + /*enable cable hot plug irq*/ + i2c_smbus_write_byte_data(sii902x.client, 0x3c, 0x01); + INIT_DELAYED_WORK(&(sii902x.det_work), det_worker); + } + ret = device_create_file(&sii902x.pdev->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for cable state\n"); + ret = device_create_file(&sii902x.pdev->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for edid\n"); + } + + fb_register_client(&nb); + + return 0; +} + +static int __devexit sii902x_remove(struct i2c_client *client) +{ + fb_unregister_client(&nb); + sii902x_poweroff(); + return 0; +} + +static int sii902x_suspend(struct i2c_client *client, pm_message_t message) +{ + /*TODO*/ + return 0; +} + +static int sii902x_resume(struct i2c_client *client) +{ + /*TODO*/ + return 0; +} + +static void sii902x_poweron(void) +{ + /* Turn on DVI or HDMI */ + if (sii902x.edid_cfg.hdmi_cap) + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x01); + else + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x00); + return; +} + +static void sii902x_poweroff(void) +{ + /* disable tmds before changing resolution */ + if (sii902x.edid_cfg.hdmi_cap) + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x11); + else + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x10); + + return; +} + +static const struct i2c_device_id sii902x_id[] = { + { "sii902x", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, sii902x_id); + +static struct i2c_driver sii902x_i2c_driver = { + .driver = { + .name = "sii902x", + }, + .probe = sii902x_probe, + .remove = sii902x_remove, + .suspend = sii902x_suspend, + .resume = sii902x_resume, + .id_table = sii902x_id, +}; + +static int __init sii902x_init(void) +{ + int ret; + + memset(&sii902x, 0, sizeof(sii902x)); + + sii902x.pdev = platform_device_register_simple("sii902x", 0, NULL, 0); + if (IS_ERR(sii902x.pdev)) { + printk(KERN_ERR + "Unable to register Sii902x as a platform device\n"); + ret = PTR_ERR(sii902x.pdev); + goto err; + } + + return i2c_add_driver(&sii902x_i2c_driver); +err: + return ret; +} + +static void __exit sii902x_exit(void) +{ + i2c_del_driver(&sii902x_i2c_driver); + platform_device_unregister(sii902x.pdev); +} + +static int __init enable_hdmi_setup(char *options) +{ + g_enable_hdmi = true; + + return 1; +} +__setup("hdmi", enable_hdmi_setup); + +module_init(sii902x_init); +module_exit(sii902x_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SII902x DVI/HDMI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/tve.c b/drivers/video/mxc/tve.c new file mode 100644 index 000000000000..3470bd445ca8 --- /dev/null +++ b/drivers/video/mxc/tve.c @@ -0,0 +1,1288 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file tve.c + * @brief Driver for i.MX TV encoder + * + * @ingroup Framebuffer + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TVE_ENABLE (1UL) +#define TVE_DAC_FULL_RATE (0UL<<1) +#define TVE_DAC_DIV2_RATE (1UL<<1) +#define TVE_DAC_DIV4_RATE (2UL<<1) +#define TVE_IPU_CLK_ENABLE (1UL<<3) + +#define CD_LM_INT 0x00000001 +#define CD_SM_INT 0x00000002 +#define CD_MON_END_INT 0x00000004 +#define CD_CH_0_LM_ST 0x00000001 +#define CD_CH_0_SM_ST 0x00000010 +#define CD_CH_1_LM_ST 0x00000002 +#define CD_CH_1_SM_ST 0x00000020 +#define CD_CH_2_LM_ST 0x00000004 +#define CD_CH_2_SM_ST 0x00000040 +#define CD_MAN_TRIG 0x00000100 + +#define TVE_STAND_MASK (0x0F<<8) +#define TVE_NTSC_STAND (0UL<<8) +#define TVE_PAL_STAND (3UL<<8) +#define TVE_HD720P60_STAND (4UL<<8) +#define TVE_HD720P50_STAND (5UL<<8) +#define TVE_HD720P30_STAND (6UL<<8) +#define TVE_HD720P25_STAND (7UL<<8) +#define TVE_HD720P24_STAND (8UL<<8) +#define TVE_HD1080I60_STAND (9UL<<8) +#define TVE_HD1080I50_STAND (10UL<<8) +#define TVE_HD1035I60_STAND (11UL<<8) +#define TVE_HD1080P30_STAND (12UL<<8) +#define TVE_HD1080P25_STAND (13UL<<8) +#define TVE_HD1080P24_STAND (14UL<<8) +#define TVE_DAC_SAMPRATE_MASK (0x3<<1) +#define TVEV2_DATA_SRC_MASK (0x3<<4) + +#define TVEV2_DATA_SRC_BUS_1 (0UL<<4) +#define TVEV2_DATA_SRC_BUS_2 (1UL<<4) +#define TVEV2_DATA_SRC_EXT (2UL<<4) + +#define TVEV2_INP_VIDEO_FORM (1UL<<6) +#define TVEV2_P2I_CONV_EN (1UL<<7) + +#define TVEV2_DAC_GAIN_MASK 0x3F +#define TVEV2_DAC_TEST_MODE_MASK 0x7 + +#define TVOUT_FMT_OFF 0 +#define TVOUT_FMT_NTSC 1 +#define TVOUT_FMT_PAL 2 +#define TVOUT_FMT_720P60 3 +#define TVOUT_FMT_720P30 4 +#define TVOUT_FMT_1080I60 5 +#define TVOUT_FMT_1080I50 6 +#define TVOUT_FMT_1080P30 7 +#define TVOUT_FMT_1080P25 8 +#define TVOUT_FMT_1080P24 9 +#define TVOUT_FMT_VGA_SVGA 10 +#define TVOUT_FMT_VGA_XGA 11 +#define TVOUT_FMT_VGA_SXGA 12 +#define TVOUT_FMT_VGA_WSXGA 13 + +#define IPU_DISP_PORT 1 + +static int enabled; /* enable power on or not */ +DEFINE_SPINLOCK(tve_lock); + +static struct fb_info *tve_fbi; +static bool g_enable_tve; +static bool g_enable_vga; + +struct tve_data { + struct platform_device *pdev; + int revision; + int cur_mode; + int output_mode; + int detect; + void *base; + int irq; + int blank; + struct clk *clk; + struct clk *di_clk; + struct regulator *dac_reg; + struct regulator *dig_reg; + struct delayed_work cd_work; +} tve; + +struct tve_reg_mapping { + u32 tve_com_conf_reg; + u32 tve_cd_cont_reg; + u32 tve_int_cont_reg; + u32 tve_stat_reg; + u32 tve_mv_cont_reg; + u32 tve_tvdac_cont_reg; + u32 tve_tst_mode_reg; +}; + +struct tve_reg_fields_mapping { + u32 cd_en; + u32 cd_trig_mode; + u32 cd_lm_int; + u32 cd_sm_int; + u32 cd_mon_end_int; + u32 cd_man_trig; + u32 sync_ch_mask; + u32 tvout_mode_mask; + u32 sync_ch_offset; + u32 tvout_mode_offset; + u32 cd_ch_stat_offset; +}; + +static struct tve_reg_mapping tve_regs_v1 = { + 0, 0x14, 0x28, 0x2C, 0x48, 0x08, 0x30 +}; + +static struct tve_reg_fields_mapping tve_reg_fields_v1 = { + 1, 2, 1, 2, 4, 0x00010000, 0x7000, 0x70, 12, 4, 8 +}; + +static struct tve_reg_mapping tve_regs_v2 = { + 0, 0x34, 0x64, 0x68, 0xDC, 0x28, 0x6c +}; + +static struct tve_reg_fields_mapping tve_reg_fields_v2 = { + 1, 2, 1, 2, 4, 0x01000000, 0x700000, 0x7000, 20, 12, 16 +}; + + +struct tve_reg_mapping *tve_regs; +struct tve_reg_fields_mapping *tve_reg_fields; + +static struct fb_videomode video_modes[] = { + { + /* NTSC TV output */ + "TV-NTSC", 60, 720, 480, 74074, + 122, 15, + 18, 26, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* PAL TV output */ + "TV-PAL", 50, 720, 576, 74074, + 132, 11, + 22, 26, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 720p60 TV output */ + "TV-720P60", 60, 1280, 720, 13468, + 260, 109, + 25, 4, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 720p30 TV output */ + "TV-720P30", 30, 1280, 720, 13468, + 260, 1759, + 25, 4, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080i60 TV output */ + "TV-1080I60", 60, 1920, 1080, 13468, + 192, 87, + 20, 24, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 1080i50 TV output */ + "TV-1080I50", 50, 1920, 1080, 13468, + 192, 527, + 20, 24, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 1080p30 TV output */ + "TV-1080P30", 30, 1920, 1080, 13468, + 192, 87, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080p25 TV output */ + "TV-1080P25", 25, 1920, 1080, 13468, + 192, 527, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080p24 TV output */ + "TV-1080P24", 24, 1920, 1080, 13468, + 192, 637, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; + +static struct fb_videomode video_modes_vga[] = { + { + /* VGA 800x600 40M pixel clk output */ + "VGA-SVGA", 60, 800, 600, 25000, + 215, 28, + 24, 2, + 13, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1024x768 65M pixel clk output */ + "VGA-XGA", 60, 1024, 768, 15384, + 160, 24, + 29, 3, + 136, 6, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1280x1024 108M pixel clk output */ + "VGA-SXGA", 60, 1280, 1024, 9259, + 358, 38, + 38, 2, + 12, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1680x1050 294M pixel clk output */ + "VGA-WSXGA+", 60, 1680, 1050, 6796, + 288, 104, + 33, 2, + 184, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; + +enum tvout_mode { + TV_OFF, + CVBS0, + CVBS2, + CVBS02, + SVIDEO, + SVIDEO_CVBS, + YPBPR, + TVRGB +}; + +static unsigned short tvout_mode_to_channel_map[8] = { + 0, /* TV_OFF */ + 1, /* CVBS0 */ + 4, /* CVBS2 */ + 5, /* CVBS02 */ + 1, /* SVIDEO */ + 5, /* SVIDEO_CVBS */ + 1, /* YPBPR */ + 7 /* TVRGB */ +}; + +static void tve_dump_regs(void) +{ + dev_dbg(&tve.pdev->dev, "tve_com_conf_reg 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_com_conf_reg)); + dev_dbg(&tve.pdev->dev, "tve_cd_cont_reg 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_cd_cont_reg)); + dev_dbg(&tve.pdev->dev, "tve_int_cont_reg 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_int_cont_reg)); + dev_dbg(&tve.pdev->dev, "tve_tst_mode_reg 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_tst_mode_reg)); + dev_dbg(&tve.pdev->dev, "tve_tvdac_cont_reg0 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg)); + dev_dbg(&tve.pdev->dev, "tve_tvdac_cont_reg1 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg + 4)); + dev_dbg(&tve.pdev->dev, "tve_tvdac_cont_reg2 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg + 8)); +} + +static int is_vga_enabled(void) +{ + u32 reg; + + if (tve.revision == 2) { + reg = __raw_readl(tve.base + tve_regs->tve_tst_mode_reg); + if (reg & TVEV2_DAC_TEST_MODE_MASK) + return 1; + else + return 0; + } + return 0; +} + +static inline int is_vga_mode(int mode) +{ + return ((mode == TVOUT_FMT_VGA_SVGA) + || (mode == TVOUT_FMT_VGA_XGA) + || (mode == TVOUT_FMT_VGA_SXGA) + || (mode == TVOUT_FMT_VGA_WSXGA)); +} + +static inline int valid_mode(int mode) +{ + return (is_vga_mode(mode) + || (mode == TVOUT_FMT_NTSC) + || (mode == TVOUT_FMT_PAL) + || (mode == TVOUT_FMT_720P30) + || (mode == TVOUT_FMT_720P60) + || (mode == TVOUT_FMT_1080I50) + || (mode == TVOUT_FMT_1080I60) + || (mode == TVOUT_FMT_1080P24) + || (mode == TVOUT_FMT_1080P25) + || (mode == TVOUT_FMT_1080P30)); +} + +static int get_video_mode(struct fb_info *fbi, int *fmt) +{ + int mode; + + if (fb_mode_is_equal(fbi->mode, &video_modes[0])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_NTSC; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[1])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_PAL; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[2])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_720P60; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[3])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_720P30; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[4])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080I60; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[5])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080I50; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[6])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080P30; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[7])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080P25; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[8])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080P24; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[0])) { + *fmt = IPU_PIX_FMT_GBR24; + mode = TVOUT_FMT_VGA_SVGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[1])) { + *fmt = IPU_PIX_FMT_GBR24; + mode = TVOUT_FMT_VGA_XGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[2])) { + *fmt = IPU_PIX_FMT_GBR24; + mode = TVOUT_FMT_VGA_SXGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[3])) { + *fmt = IPU_PIX_FMT_GBR24; + mode = TVOUT_FMT_VGA_WSXGA; + } else { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_OFF; + } + return mode; +} + +static void tve_disable_vga_mode(void) +{ + if (tve.revision == 2) { + u32 reg; + /* disable test mode */ + reg = __raw_readl(tve.base + tve_regs->tve_tst_mode_reg); + reg = reg & ~TVEV2_DAC_TEST_MODE_MASK; + __raw_writel(reg, tve.base + tve_regs->tve_tst_mode_reg); + } +} + +static void tve_set_tvout_mode(int mode) +{ + u32 conf_reg; + + /* clear sync_ch and tvout_mode fields */ + conf_reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + conf_reg &= ~(tve_reg_fields->sync_ch_mask | + tve_reg_fields->tvout_mode_mask); + + conf_reg = conf_reg & ~TVE_DAC_SAMPRATE_MASK; + if (tve.revision == 2) { + conf_reg = (conf_reg & ~TVEV2_DATA_SRC_MASK) | + TVEV2_DATA_SRC_BUS_1; + conf_reg = conf_reg & ~TVEV2_INP_VIDEO_FORM; + conf_reg = conf_reg & ~TVEV2_P2I_CONV_EN; + } + + conf_reg |= + mode << tve_reg_fields-> + tvout_mode_offset | tvout_mode_to_channel_map[mode] << + tve_reg_fields->sync_ch_offset; + __raw_writel(conf_reg, tve.base + tve_regs->tve_com_conf_reg); +} + +static int _is_tvout_mode_hd_compatible(void) +{ + u32 conf_reg, mode; + + conf_reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + mode = (conf_reg >> tve_reg_fields->tvout_mode_offset) & 7; + if (mode == YPBPR || mode == TVRGB) { + return 1; + } else { + return 0; + } +} + +static int tve_setup_vga(void) +{ + u32 reg; + + if (tve.revision == 2) { + /* set gain */ + reg = __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0; + __raw_writel(reg, tve.base + tve_regs->tve_tvdac_cont_reg); + reg = __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg + 4); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0; + __raw_writel(reg, tve.base + tve_regs->tve_tvdac_cont_reg + 4); + reg = __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg + 8); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0; + __raw_writel(reg, tve.base + tve_regs->tve_tvdac_cont_reg + 8); + + /* set tve_com_conf_reg */ + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_DAC_SAMPRATE_MASK) | TVE_DAC_DIV2_RATE; + reg = (reg & ~TVEV2_DATA_SRC_MASK) | TVEV2_DATA_SRC_BUS_2; + reg = reg | TVEV2_INP_VIDEO_FORM; + reg = reg & ~TVEV2_P2I_CONV_EN; + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; + reg |= TVRGB << tve_reg_fields->tvout_mode_offset | + 1 << tve_reg_fields->sync_ch_offset; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + + /* set test mode */ + reg = __raw_readl(tve.base + tve_regs->tve_tst_mode_reg); + reg = (reg & ~TVEV2_DAC_TEST_MODE_MASK) | 1; + __raw_writel(reg, tve.base + tve_regs->tve_tst_mode_reg); + } + + return 0; +} + +/** + * tve_setup + * initial the CH7024 chipset by setting register + * @param: + * vos: output video format + * @return: + * 0 successful + * otherwise failed + */ +static int tve_setup(int mode) +{ + u32 reg; + struct clk *tve_parent_clk; + unsigned long parent_clock_rate = 216000000, di1_clock_rate = 27000000; + unsigned long tve_clock_rate = 216000000; + unsigned long lock_flags; + + if (tve.cur_mode == mode) + return 0; + + spin_lock_irqsave(&tve_lock, lock_flags); + + switch (mode) { + case TVOUT_FMT_PAL: + case TVOUT_FMT_NTSC: + parent_clock_rate = 216000000; + di1_clock_rate = 27000000; + break; + case TVOUT_FMT_720P60: + case TVOUT_FMT_1080I60: + case TVOUT_FMT_1080I50: + case TVOUT_FMT_720P30: + case TVOUT_FMT_1080P30: + case TVOUT_FMT_1080P25: + case TVOUT_FMT_1080P24: + parent_clock_rate = 297000000; + tve_clock_rate = 297000000; + di1_clock_rate = 74250000; + break; + case TVOUT_FMT_VGA_SVGA: + parent_clock_rate = 160000000; + tve_clock_rate = 80000000; + di1_clock_rate = 40000000; + break; + case TVOUT_FMT_VGA_XGA: + parent_clock_rate = 520000000; + tve_clock_rate = 130000000; + di1_clock_rate = 65000000; + break; + case TVOUT_FMT_VGA_SXGA: + parent_clock_rate = 864000000; + tve_clock_rate = 216000000; + di1_clock_rate = 108000000; + break; + case TVOUT_FMT_VGA_WSXGA: + parent_clock_rate = 588560000; + tve_clock_rate = 294280000; + di1_clock_rate = 147140000; + break; + } + if (enabled) + clk_disable(tve.clk); + + tve_parent_clk = clk_get_parent(tve.clk); + + clk_set_rate(tve_parent_clk, parent_clock_rate); + + tve_clock_rate = clk_round_rate(tve.clk, tve_clock_rate); + clk_set_rate(tve.clk, tve_clock_rate); + + clk_enable(tve.clk); + di1_clock_rate = clk_round_rate(tve.di_clk, di1_clock_rate); + clk_set_rate(tve.di_clk, di1_clock_rate); + + tve.cur_mode = mode; + + /* select output video format */ + if (mode == TVOUT_FMT_PAL) { + tve_disable_vga_mode(); + tve_set_tvout_mode(YPBPR); + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_PAL_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to PAL video\n"); + } else if (mode == TVOUT_FMT_NTSC) { + tve_disable_vga_mode(); + tve_set_tvout_mode(YPBPR); + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_NTSC_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to NTSC video\n"); + } else if (mode == TVOUT_FMT_720P60) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P60_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 720P60 video\n"); + } else if (mode == TVOUT_FMT_720P30) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P30_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 720P30 video\n"); + } else if (mode == TVOUT_FMT_1080I60) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I60_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080I60 video\n"); + } else if (mode == TVOUT_FMT_1080I50) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I50_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080I50 video\n"); + } else if (mode == TVOUT_FMT_1080P30) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080P30 video\n"); + } else if (mode == TVOUT_FMT_1080P25) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P25_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080P25 video\n"); + } else if (mode == TVOUT_FMT_1080P24) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P24_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080P24 video\n"); + } else if (is_vga_mode(mode)) { + /* do not need cable detect */ + tve_setup_vga(); + pr_debug("TVE: change to VGA video\n"); + } else if (mode == TVOUT_FMT_OFF) { + __raw_writel(0x0, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to OFF video\n"); + } else { + pr_debug("TVE: no such video format.\n"); + } + + if (!enabled) + clk_disable(tve.clk); + + spin_unlock_irqrestore(&tve_lock, lock_flags); + return 0; +} + +/** + * tve_enable + * Enable the tve Power to begin TV encoder + */ +static void tve_enable(void) +{ + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&tve_lock, lock_flags); + if (!enabled) { + enabled = 1; + clk_enable(tve.clk); + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + __raw_writel(reg | TVE_IPU_CLK_ENABLE | TVE_ENABLE, + tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE power on.\n"); + } + + if (is_vga_enabled()) { + /* disable interrupt */ + pr_debug("TVE VGA disable cable detect.\n"); + __raw_writel(0xffffffff, tve.base + tve_regs->tve_stat_reg); + __raw_writel(0, tve.base + tve_regs->tve_int_cont_reg); + } else { + /* enable interrupt */ + pr_debug("TVE TVE enable cable detect.\n"); + __raw_writel(0xffffffff, tve.base + tve_regs->tve_stat_reg); + __raw_writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT, + tve.base + tve_regs->tve_int_cont_reg); + } + + spin_unlock_irqrestore(&tve_lock, lock_flags); + + tve_dump_regs(); +} + +/** + * tve_disable + * Disable the tve Power to stop TV encoder + */ +static void tve_disable(void) +{ + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&tve_lock, lock_flags); + if (enabled) { + enabled = 0; + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + __raw_writel(reg & ~TVE_ENABLE & ~TVE_IPU_CLK_ENABLE, + tve.base + tve_regs->tve_com_conf_reg); + clk_disable(tve.clk); + pr_debug("TVE power off.\n"); + } + spin_unlock_irqrestore(&tve_lock, lock_flags); +} + +static int tve_update_detect_status(void) +{ + int old_detect = tve.detect; + u32 stat_lm, stat_sm, stat; + u32 int_ctl; + u32 cd_cont_reg; + u32 timeout = 40; + unsigned long lock_flags; + + spin_lock_irqsave(&tve_lock, lock_flags); + + if (!enabled) { + pr_warning("Warning: update tve status while it disabled!\n"); + tve.detect = 0; + goto done; + } + + int_ctl = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + cd_cont_reg = __raw_readl(tve.base + tve_regs->tve_cd_cont_reg); + + if ((cd_cont_reg & 0x1) == 0) { + pr_warning("Warning: pls enable TVE CD first!\n"); + goto done; + } + + stat = __raw_readl(tve.base + tve_regs->tve_stat_reg); + while (((stat & CD_MON_END_INT) == 0) && (timeout > 0)) { + spin_unlock_irqrestore(&tve_lock, lock_flags); + msleep(2); + spin_lock_irqsave(&tve_lock, lock_flags); + timeout -= 2; + if (!enabled) { + pr_warning("Warning: update tve status while it disabled!\n"); + tve.detect = 0; + goto done; + } else + stat = __raw_readl(tve.base + tve_regs->tve_stat_reg); + } + if (((stat & CD_MON_END_INT) == 0) && (timeout <= 0)) { + pr_warning("Warning: get detect result without CD_MON_END_INT!\n"); + goto done; + } + + stat = stat >> tve_reg_fields->cd_ch_stat_offset; + stat_lm = stat & (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST); + if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST)) && + ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST | CD_CH_2_SM_ST)) == 0) + ) { + tve.detect = 3; + tve.output_mode = YPBPR; + } else if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST)) && + ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST)) == 0)) { + tve.detect = 4; + tve.output_mode = SVIDEO; + } else if (stat_lm == CD_CH_0_LM_ST) { + stat_sm = stat & CD_CH_0_SM_ST; + if (stat_sm != 0) { + /* headset */ + tve.detect = 2; + tve.output_mode = TV_OFF; + } else { + tve.detect = 1; + tve.output_mode = CVBS0; + } + } else if (stat_lm == CD_CH_2_LM_ST) { + stat_sm = stat & CD_CH_2_SM_ST; + if (stat_sm != 0) { + /* headset */ + tve.detect = 2; + tve.output_mode = TV_OFF; + } else { + tve.detect = 1; + tve.output_mode = CVBS2; + } + } else { + /* none */ + tve.detect = 0; + tve.output_mode = TV_OFF; + } + + tve_set_tvout_mode(tve.output_mode); + + /* clear interrupt */ + __raw_writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve.base + tve_regs->tve_stat_reg); + + __raw_writel(int_ctl | CD_SM_INT | CD_LM_INT, + tve.base + tve_regs->tve_int_cont_reg); + + if (old_detect != tve.detect) + sysfs_notify(&tve.pdev->dev.kobj, NULL, "headphone"); + + dev_dbg(&tve.pdev->dev, "detect = %d mode = %d\n", + tve.detect, tve.output_mode); +done: + spin_unlock_irqrestore(&tve_lock, lock_flags); + return tve.detect; +} + +static void cd_work_func(struct work_struct *work) +{ + tve_update_detect_status(); +} +#if 0 +static int tve_man_detect(void) +{ + u32 cd_cont; + u32 int_cont; + + if (!enabled) + return -1; + + int_cont = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + __raw_writel(int_cont & + ~(tve_reg_fields->cd_sm_int | tve_reg_fields->cd_lm_int), + tve.base + tve_regs->tve_int_cont_reg); + + cd_cont = __raw_readl(tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(cd_cont | tve_reg_fields->cd_trig_mode, + tve.base + tve_regs->tve_cd_cont_reg); + + __raw_writel(tve_reg_fields->cd_sm_int | tve_reg_fields-> + cd_lm_int | tve_reg_fields-> + cd_mon_end_int | tve_reg_fields->cd_man_trig, + tve.base + tve_regs->tve_stat_reg); + + while ((__raw_readl(tve.base + tve_regs->tve_stat_reg) + & tve_reg_fields->cd_mon_end_int) == 0) + msleep(5); + + tve_update_detect_status(); + + __raw_writel(cd_cont, tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(int_cont, tve.base + tve_regs->tve_int_cont_reg); + + return tve.detect; +} +#endif + +static irqreturn_t tve_detect_handler(int irq, void *data) +{ + u32 int_ctl = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + + /* disable INT first */ + int_ctl &= ~(CD_SM_INT | CD_LM_INT | CD_MON_END_INT); + __raw_writel(int_ctl, tve.base + tve_regs->tve_int_cont_reg); + + __raw_writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve.base + tve_regs->tve_stat_reg); + + schedule_delayed_work(&tve.cd_work, msecs_to_jiffies(1000)); + + return IRQ_HANDLED; +} + +static inline void tve_set_di_fmt(struct fb_info *fbi, unsigned int fmt) +{ + mm_segment_t old_fs; + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_DIFMT, (unsigned long)&fmt); + set_fs(old_fs); + } +} + +/*! + * FB suspend/resume routing + */ +static int tve_suspend(void) +{ + if (enabled) { + __raw_writel(0, tve.base + tve_regs->tve_int_cont_reg); + __raw_writel(0, tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(0, tve.base + tve_regs->tve_com_conf_reg); + clk_disable(tve.clk); + } + return 0; +} + +static int tve_resume(struct fb_info *fbi) +{ + int mode; + + if (enabled) { + clk_enable(tve.clk); + + /* Setup cable detect */ + if (tve.revision == 1) + __raw_writel(0x01067701, + tve.base + tve_regs->tve_cd_cont_reg); + else + __raw_writel(0x00770601, + tve.base + tve_regs->tve_cd_cont_reg); + + if (valid_mode(tve.cur_mode)) { + mode = tve.cur_mode; + tve_disable(); + tve.cur_mode = TVOUT_FMT_OFF; + tve_setup(mode); + } + tve_enable(); + } + + return 0; +} + +int tve_fb_setup(struct fb_info *fbi) +{ + int mode, fmt; + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + if (!fbi->mode) { + pr_warning("TVE: can not find mode for xres=%d, yres=%d\n", + fbi->var.xres, fbi->var.yres); + tve_disable(); + tve.cur_mode = TVOUT_FMT_OFF; + return 0; + } + + pr_debug("TVE: fb mode change event: xres=%d, yres=%d\n", + fbi->mode->xres, fbi->mode->yres); + + mode = get_video_mode(fbi, &fmt); + if (mode != TVOUT_FMT_OFF) { + tve_set_di_fmt(fbi, fmt); + tve_disable(); + tve_setup(mode); + if (tve.blank == FB_BLANK_UNBLANK) + tve_enable(); + } else { + tve_disable(); + tve_setup(mode); + } + + return 0; +} + +int tve_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + if (strcmp(fbi->fix.id, "DISP3 BG - DI1")) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + pr_debug("TVE: fb registered event\n"); + if (tve_fbi != NULL) + break; + + tve_fbi = fbi; + break; + case FB_EVENT_MODE_CHANGE: + { + if (tve_fbi != fbi) + break; + + tve_fb_setup(fbi); + break; + } + case FB_EVENT_BLANK: + if ((tve_fbi != fbi) || (fbi->mode == NULL)) + return 0; + + pr_debug("TVE: fb blank event\n"); + + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + if (tve.blank != FB_BLANK_UNBLANK) { + int mode, fmt; + mode = get_video_mode(fbi, &fmt); + if (mode != TVOUT_FMT_OFF) { + if (tve.cur_mode != mode) { + tve_disable(); + tve_setup(mode); + } + tve_enable(); + } else + tve_setup(mode); + tve.blank = FB_BLANK_UNBLANK; + } + } else { + tve_disable(); + tve.blank = FB_BLANK_POWERDOWN; + } + break; + case FB_EVENT_SUSPEND: + tve_suspend(); + break; + case FB_EVENT_RESUME: + tve_resume(fbi); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = tve_fb_event, +}; + +static ssize_t show_headphone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int detect; + + if (!enabled) { + strcpy(buf, "tve power off\n"); + return strlen(buf); + } + + detect = tve_update_detect_status(); + + if (detect == 0) + strcpy(buf, "none\n"); + else if (detect == 1) + strcpy(buf, "cvbs\n"); + else if (detect == 2) + strcpy(buf, "headset\n"); + else if (detect == 3) + strcpy(buf, "component\n"); + else + strcpy(buf, "svideo\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static int _tve_get_revision(void) +{ + u32 conf_reg; + u32 rev = 0; + + /* find out TVE rev based on the base addr default value + * can be used at the init/probe ONLY */ + conf_reg = __raw_readl(tve.base); + switch (conf_reg) { + case 0x00842000: + rev = 1; + break; + case 0x00100000: + rev = 2; + break; + } + return rev; +} + +int tve_fb_pre_setup(struct fb_info *fbi) +{ + if (fbi->fbops->fb_ioctl) { + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_BLANK, + (unsigned int)(&tve.blank)); + set_fs(old_fs); + } + return tve_fb_setup(fbi); +} + +static int tve_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + struct fsl_mxc_tve_platform_data *plat_data = pdev->dev.platform_data; + u32 conf_reg; + + if (g_enable_tve == false && g_enable_vga == false) + return -EPERM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) + return -ENOMEM; + + tve.pdev = pdev; + tve.base = ioremap(res->start, res->end - res->start); + + tve.irq = platform_get_irq(pdev, 0); + if (tve.irq < 0) { + ret = tve.irq; + goto err0; + } + + ret = request_irq(tve.irq, tve_detect_handler, 0, pdev->name, pdev); + if (ret < 0) + goto err0; + + ret = device_create_file(&pdev->dev, &dev_attr_headphone); + if (ret < 0) + goto err1; + + tve.dac_reg = regulator_get(&pdev->dev, plat_data->dac_reg); + if (!IS_ERR(tve.dac_reg)) { + regulator_set_voltage(tve.dac_reg, 2750000, 2750000); + regulator_enable(tve.dac_reg); + } + + tve.dig_reg = regulator_get(&pdev->dev, plat_data->dig_reg); + if (!IS_ERR(tve.dig_reg)) { + regulator_set_voltage(tve.dig_reg, 1250000, 1250000); + regulator_enable(tve.dig_reg); + } + + tve.clk = clk_get(&pdev->dev, "tve_clk"); + if (IS_ERR(tve.clk)) { + ret = PTR_ERR(tve.clk); + goto err2; + } + tve.di_clk = clk_get(NULL, "ipu_di1_clk"); + if (IS_ERR(tve.di_clk)) { + ret = PTR_ERR(tve.di_clk); + goto err2; + } + clk_set_rate(tve.clk, 216000000); + clk_set_parent(tve.di_clk, tve.clk); + clk_enable(tve.clk); + + tve.revision = _tve_get_revision(); + if (tve.revision == 1) { + tve_regs = &tve_regs_v1; + tve_reg_fields = &tve_reg_fields_v1; + } else { + tve_regs = &tve_regs_v2; + tve_reg_fields = &tve_reg_fields_v2; + } + + /*[> adjust video mode for mx37 <] + if (cpu_is_mx37()) { + video_modes[0].left_margin = 121; + video_modes[0].right_margin = 16; + video_modes[0].upper_margin = 17; + video_modes[0].lower_margin = 5; + video_modes[1].left_margin = 131; + video_modes[1].right_margin = 12; + video_modes[1].upper_margin = 21; + video_modes[1].lower_margin = 3; + }*/ + + /* TVE is on disp port 1 */ + if (tve.revision == 1) { + if (g_enable_tve) + mxcfb_register_mode(IPU_DISP_PORT, video_modes, + 3, MXC_DISP_SPEC_DEV); + } else { + if (g_enable_tve) + mxcfb_register_mode(IPU_DISP_PORT, video_modes, + ARRAY_SIZE(video_modes), + MXC_DISP_SPEC_DEV); + + if (cpu_is_mx53() && g_enable_vga) + mxcfb_register_mode(IPU_DISP_PORT, video_modes_vga, + ARRAY_SIZE(video_modes_vga), + MXC_DISP_SPEC_DEV); + } + mxcfb_register_presetup(IPU_DISP_PORT, tve_fb_pre_setup); + + /* Setup cable detect, for YPrPb mode, default use channel#-1 for Y */ + INIT_DELAYED_WORK(&tve.cd_work, cd_work_func); + if (tve.revision == 1) + __raw_writel(0x01067701, tve.base + tve_regs->tve_cd_cont_reg); + else + __raw_writel(0x00770601, tve.base + tve_regs->tve_cd_cont_reg); + + conf_reg = 0; + __raw_writel(conf_reg, tve.base + tve_regs->tve_com_conf_reg); + + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 5); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 4); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 3); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 2); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg); + + clk_disable(tve.clk); + + ret = fb_register_client(&nb); + if (ret < 0) + goto err2; + + tve.blank = -1; + + return 0; +err2: + device_remove_file(&pdev->dev, &dev_attr_headphone); +err1: + free_irq(tve.irq, pdev); +err0: + iounmap(tve.base); + return ret; +} + +static int tve_remove(struct platform_device *pdev) +{ + if (enabled) { + clk_disable(tve.clk); + enabled = 0; + } + free_irq(tve.irq, pdev); + device_remove_file(&pdev->dev, &dev_attr_headphone); + fb_unregister_client(&nb); + return 0; +} + +static struct platform_driver tve_driver = { + .driver = { + .name = "mxc_tve", + }, + .probe = tve_probe, + .remove = tve_remove, +}; + +static int __init enable_tve_setup(char *options) +{ + g_enable_tve = true; + + return 1; +} +__setup("tve", enable_tve_setup); + +static int __init enable_vga_setup(char *options) +{ + g_enable_vga = true; + + return 1; +} +__setup("vga", enable_vga_setup); + +static int __init tve_init(void) +{ + return platform_driver_register(&tve_driver); +} + +static void __exit tve_exit(void) +{ + platform_driver_unregister(&tve_driver); +} + +module_init(tve_init); +module_exit(tve_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX TV encoder driver"); +MODULE_LICENSE("GPL");