2010-08-03 09:50:29 -03:00
/*
2012-05-08 15:51:24 -03:00
* Samsung S5P / EXYNOS4 SoC series FIMC ( CAMIF ) driver
2010-08-03 09:50:29 -03:00
*
2012-04-25 06:55:42 -03:00
* Copyright ( C ) 2010 - 2012 Samsung Electronics Co . , Ltd .
* Sylwester Nawrocki < s . nawrocki @ samsung . com >
2010-08-03 09:50:29 -03:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation , either version 2 of the License ,
* or ( at your option ) any later version .
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/bug.h>
# include <linux/interrupt.h>
# include <linux/device.h>
# include <linux/platform_device.h>
2011-09-02 06:25:32 -03:00
# include <linux/pm_runtime.h>
2010-08-03 09:50:29 -03:00
# include <linux/list.h>
2013-03-20 10:44:39 -03:00
# include <linux/mfd/syscon.h>
2010-08-03 09:50:29 -03:00
# include <linux/io.h>
2013-03-26 08:22:21 -03:00
# include <linux/of.h>
# include <linux/of_device.h>
2010-08-03 09:50:29 -03:00
# include <linux/slab.h>
# include <linux/clk.h>
# include <media/v4l2-ioctl.h>
2010-12-01 10:14:59 -03:00
# include <media/videobuf2-core.h>
# include <media/videobuf2-dma-contig.h>
2010-08-03 09:50:29 -03:00
# include "fimc-core.h"
2012-05-02 06:14:49 -03:00
# include "fimc-reg.h"
2013-03-24 16:54:25 +01:00
# include "media-dev.h"
2010-08-03 09:50:29 -03:00
2010-12-27 15:34:43 -03:00
static char * fimc_clocks [ MAX_FIMC_CLOCKS ] = {
2011-06-10 15:36:45 -03:00
" sclk_fimc " , " fimc "
2010-12-27 15:34:43 -03:00
} ;
2010-08-03 09:50:29 -03:00
static struct fimc_fmt fimc_formats [ ] = {
{
2010-12-08 14:05:08 -03:00
. name = " RGB565 " ,
2011-11-04 10:07:06 -03:00
. fourcc = V4L2_PIX_FMT_RGB565 ,
2010-12-08 14:05:08 -03:00
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_RGB565 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
. flags = FMT_FLAGS_M2M ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " BGR666 " ,
. fourcc = V4L2_PIX_FMT_BGR666 ,
. depth = { 32 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_RGB666 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
. flags = FMT_FLAGS_M2M ,
2010-08-03 09:50:29 -03:00
} , {
2014-03-25 17:55:23 -03:00
. name = " BGRA8888, 32 bpp " ,
. fourcc = V4L2_PIX_FMT_BGR32 ,
2010-12-08 14:05:08 -03:00
. depth = { 32 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_RGB888 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
2011-12-01 14:02:24 -03:00
. flags = FMT_FLAGS_M2M | FMT_HAS_ALPHA ,
} , {
. name = " ARGB1555 " ,
. fourcc = V4L2_PIX_FMT_RGB555 ,
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_RGB555 ,
2011-12-01 14:02:24 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
. flags = FMT_FLAGS_M2M_OUT | FMT_HAS_ALPHA ,
} , {
. name = " ARGB4444 " ,
. fourcc = V4L2_PIX_FMT_RGB444 ,
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_RGB444 ,
2011-12-01 14:02:24 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
. flags = FMT_FLAGS_M2M_OUT | FMT_HAS_ALPHA ,
2013-03-20 10:44:39 -03:00
} , {
. name = " YUV 4:4:4 " ,
2014-11-10 14:28:31 -03:00
. mbus_code = MEDIA_BUS_FMT_YUV10_1X30 ,
2013-03-20 10:44:39 -03:00
. flags = FMT_FLAGS_WRITEBACK ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:2 packed, YCbYCr " ,
. fourcc = V4L2_PIX_FMT_YUYV ,
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCBYCR422 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
2014-11-10 14:28:31 -03:00
. mbus_code = MEDIA_BUS_FMT_YUYV8_2X8 ,
2010-12-08 14:05:08 -03:00
. flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM ,
2010-10-07 10:06:16 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:2 packed, CbYCrY " ,
. fourcc = V4L2_PIX_FMT_UYVY ,
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_CBYCRY422 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
2014-11-10 14:28:31 -03:00
. mbus_code = MEDIA_BUS_FMT_UYVY8_2X8 ,
2010-12-08 14:05:08 -03:00
. flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:2 packed, CrYCbY " ,
. fourcc = V4L2_PIX_FMT_VYUY ,
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_CRYCBY422 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
2014-11-10 14:28:31 -03:00
. mbus_code = MEDIA_BUS_FMT_VYUY8_2X8 ,
2010-12-08 14:05:08 -03:00
. flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:2 packed, YCrYCb " ,
. fourcc = V4L2_PIX_FMT_YVYU ,
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCRYCB422 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 1 ,
2014-11-10 14:28:31 -03:00
. mbus_code = MEDIA_BUS_FMT_YVYU8_2X8 ,
2010-12-08 14:05:08 -03:00
. flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:2 planar, Y/Cb/Cr " ,
. fourcc = V4L2_PIX_FMT_YUV422P ,
2014-03-25 17:50:11 -03:00
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCBYCR422 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 3 ,
. flags = FMT_FLAGS_M2M ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:2 planar, Y/CbCr " ,
. fourcc = V4L2_PIX_FMT_NV16 ,
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCBYCR422 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 2 ,
. flags = FMT_FLAGS_M2M ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:2 planar, Y/CrCb " ,
. fourcc = V4L2_PIX_FMT_NV61 ,
. depth = { 16 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCRYCB422 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 2 ,
. flags = FMT_FLAGS_M2M ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:0 planar, YCbCr " ,
. fourcc = V4L2_PIX_FMT_YUV420 ,
. depth = { 12 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCBCR420 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 3 ,
. flags = FMT_FLAGS_M2M ,
2010-08-03 09:50:29 -03:00
} , {
2010-12-08 14:05:08 -03:00
. name = " YUV 4:2:0 planar, Y/CbCr " ,
. fourcc = V4L2_PIX_FMT_NV12 ,
. depth = { 12 } ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCBCR420 ,
2010-12-08 14:05:08 -03:00
. memplanes = 1 ,
. colplanes = 2 ,
. flags = FMT_FLAGS_M2M ,
} , {
2012-05-22 13:50:14 -03:00
. name = " YUV 4:2:0 non-contig. 2p, Y/CbCr " ,
2010-12-08 14:05:08 -03:00
. fourcc = V4L2_PIX_FMT_NV12M ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCBCR420 ,
2010-12-08 14:05:08 -03:00
. depth = { 8 , 4 } ,
. memplanes = 2 ,
. colplanes = 2 ,
. flags = FMT_FLAGS_M2M ,
} , {
2012-05-22 13:50:14 -03:00
. name = " YUV 4:2:0 non-contig. 3p, Y/Cb/Cr " ,
2010-12-08 14:05:08 -03:00
. fourcc = V4L2_PIX_FMT_YUV420M ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCBCR420 ,
2010-12-08 14:05:08 -03:00
. depth = { 8 , 2 , 2 } ,
. memplanes = 3 ,
. colplanes = 3 ,
. flags = FMT_FLAGS_M2M ,
} , {
2012-05-22 13:50:14 -03:00
. name = " YUV 4:2:0 non-contig. 2p, tiled " ,
2010-12-08 14:05:08 -03:00
. fourcc = V4L2_PIX_FMT_NV12MT ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_YCBCR420 ,
2010-12-08 14:05:08 -03:00
. depth = { 8 , 4 } ,
. memplanes = 2 ,
. colplanes = 2 ,
. flags = FMT_FLAGS_M2M ,
2011-08-26 14:57:06 -03:00
} , {
. name = " JPEG encoded data " ,
. fourcc = V4L2_PIX_FMT_JPEG ,
2012-04-26 06:26:29 -03:00
. color = FIMC_FMT_JPEG ,
2011-08-26 14:57:06 -03:00
. depth = { 8 } ,
. memplanes = 1 ,
. colplanes = 1 ,
2014-11-10 14:28:31 -03:00
. mbus_code = MEDIA_BUS_FMT_JPEG_1X8 ,
2012-09-24 11:08:45 -03:00
. flags = FMT_FLAGS_CAM | FMT_FLAGS_COMPRESSED ,
} , {
. name = " S5C73MX interleaved UYVY/JPEG " ,
. fourcc = V4L2_PIX_FMT_S5C_UYVY_JPG ,
. color = FIMC_FMT_YUYV_JPEG ,
. depth = { 8 } ,
. memplanes = 2 ,
. colplanes = 1 ,
. mdataplanes = 0x2 , /* plane 1 holds frame meta data */
2014-11-10 14:28:31 -03:00
. mbus_code = MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8 ,
2012-09-24 11:08:45 -03:00
. flags = FMT_FLAGS_CAM | FMT_FLAGS_COMPRESSED ,
2010-10-07 10:06:16 -03:00
} ,
2010-10-08 05:01:14 -03:00
} ;
2010-08-03 09:50:29 -03:00
2012-04-27 09:33:23 -03:00
struct fimc_fmt * fimc_get_format ( unsigned int index )
2011-12-01 14:02:24 -03:00
{
2012-05-08 15:51:24 -03:00
if ( index > = ARRAY_SIZE ( fimc_formats ) )
return NULL ;
return & fimc_formats [ index ] ;
2011-12-01 14:02:24 -03:00
}
2011-08-26 14:57:06 -03:00
int fimc_check_scaler_ratio ( struct fimc_ctx * ctx , int sw , int sh ,
int dw , int dh , int rotation )
2010-08-03 09:50:29 -03:00
{
2011-08-26 14:57:06 -03:00
if ( rotation = = 90 | | rotation = = 270 )
swap ( dw , dh ) ;
2010-08-03 09:50:29 -03:00
2011-08-26 14:57:06 -03:00
if ( ! ctx - > scaler . enabled )
return ( sw = = dw & & sh = = dh ) ? 0 : - EINVAL ;
2010-08-03 09:50:29 -03:00
2011-08-26 14:57:06 -03:00
if ( ( sw > = SCALER_MAX_HRATIO * dw ) | | ( sh > = SCALER_MAX_VRATIO * dh ) )
2010-12-28 22:12:43 -03:00
return - EINVAL ;
2010-08-03 09:50:29 -03:00
return 0 ;
}
static int fimc_get_scaler_factor ( u32 src , u32 tar , u32 * ratio , u32 * shift )
{
2010-10-08 05:01:14 -03:00
u32 sh = 6 ;
if ( src > = 64 * tar )
2010-08-03 09:50:29 -03:00
return - EINVAL ;
2010-10-08 05:01:14 -03:00
while ( sh - - ) {
u32 tmp = 1 < < sh ;
if ( src > = tar * tmp ) {
* shift = sh , * ratio = tmp ;
return 0 ;
}
2010-08-03 09:50:29 -03:00
}
2010-10-08 05:01:14 -03:00
* shift = 0 , * ratio = 1 ;
2010-08-03 09:50:29 -03:00
return 0 ;
}
2010-10-07 10:06:16 -03:00
int fimc_set_scaler_info ( struct fimc_ctx * ctx )
2010-08-03 09:50:29 -03:00
{
2012-08-02 10:27:46 -03:00
const struct fimc_variant * variant = ctx - > fimc_dev - > variant ;
2011-06-10 15:36:48 -03:00
struct device * dev = & ctx - > fimc_dev - > pdev - > dev ;
2010-08-03 09:50:29 -03:00
struct fimc_scaler * sc = & ctx - > scaler ;
struct fimc_frame * s_frame = & ctx - > s_frame ;
struct fimc_frame * d_frame = & ctx - > d_frame ;
int tx , ty , sx , sy ;
int ret ;
2010-10-08 05:01:22 -03:00
if ( ctx - > rotation = = 90 | | ctx - > rotation = = 270 ) {
ty = d_frame - > width ;
tx = d_frame - > height ;
} else {
tx = d_frame - > width ;
ty = d_frame - > height ;
}
2010-08-03 09:50:29 -03:00
if ( tx < = 0 | | ty < = 0 ) {
2012-12-07 16:40:08 -03:00
dev_err ( dev , " Invalid target size: %dx%d \n " , tx , ty ) ;
2010-08-03 09:50:29 -03:00
return - EINVAL ;
}
sx = s_frame - > width ;
sy = s_frame - > height ;
if ( sx < = 0 | | sy < = 0 ) {
2012-12-07 16:40:08 -03:00
dev_err ( dev , " Invalid source size: %dx%d \n " , sx , sy ) ;
2010-08-03 09:50:29 -03:00
return - EINVAL ;
}
sc - > real_width = sx ;
sc - > real_height = sy ;
ret = fimc_get_scaler_factor ( sx , tx , & sc - > pre_hratio , & sc - > hfactor ) ;
if ( ret )
return ret ;
ret = fimc_get_scaler_factor ( sy , ty , & sc - > pre_vratio , & sc - > vfactor ) ;
if ( ret )
return ret ;
sc - > pre_dst_width = sx / sc - > pre_hratio ;
sc - > pre_dst_height = sy / sc - > pre_vratio ;
2010-12-28 11:27:13 -03:00
if ( variant - > has_mainscaler_ext ) {
sc - > main_hratio = ( sx < < 14 ) / ( tx < < sc - > hfactor ) ;
sc - > main_vratio = ( sy < < 14 ) / ( ty < < sc - > vfactor ) ;
} else {
sc - > main_hratio = ( sx < < 8 ) / ( tx < < sc - > hfactor ) ;
sc - > main_vratio = ( sy < < 8 ) / ( ty < < sc - > vfactor ) ;
}
2010-08-03 09:50:29 -03:00
sc - > scaleup_h = ( tx > = sx ) ? 1 : 0 ;
sc - > scaleup_v = ( ty > = sy ) ? 1 : 0 ;
/* check to see if input and output size/format differ */
if ( s_frame - > fmt - > color = = d_frame - > fmt - > color
& & s_frame - > width = = d_frame - > width
& & s_frame - > height = = d_frame - > height )
sc - > copy_mode = 1 ;
else
sc - > copy_mode = 0 ;
return 0 ;
}
2011-09-02 06:25:32 -03:00
static irqreturn_t fimc_irq_handler ( int irq , void * priv )
2010-08-03 09:50:29 -03:00
{
2010-10-08 05:01:14 -03:00
struct fimc_dev * fimc = priv ;
2011-02-23 08:24:33 -03:00
struct fimc_ctx * ctx ;
2010-08-03 09:50:29 -03:00
fimc_hw_clear_irq ( fimc ) ;
2011-09-02 06:25:32 -03:00
spin_lock ( & fimc - > slock ) ;
2011-02-23 08:24:33 -03:00
if ( test_and_clear_bit ( ST_M2M_PEND , & fimc - > state ) ) {
2011-09-02 06:25:32 -03:00
if ( test_and_clear_bit ( ST_M2M_SUSPENDING , & fimc - > state ) ) {
set_bit ( ST_M2M_SUSPENDED , & fimc - > state ) ;
wake_up ( & fimc - > irq_queue ) ;
goto out ;
}
2011-02-23 08:24:33 -03:00
ctx = v4l2_m2m_get_curr_priv ( fimc - > m2m . m2m_dev ) ;
if ( ctx ! = NULL ) {
2011-09-02 06:25:32 -03:00
spin_unlock ( & fimc - > slock ) ;
2011-02-23 08:24:33 -03:00
fimc_m2m_job_finish ( ctx , VB2_BUF_STATE_DONE ) ;
2010-08-03 09:50:29 -03:00
2011-02-23 08:24:33 -03:00
if ( ctx - > state & FIMC_CTX_SHUT ) {
ctx - > state & = ~ FIMC_CTX_SHUT ;
wake_up ( & fimc - > irq_queue ) ;
}
2012-03-19 13:11:40 -03:00
return IRQ_HANDLED ;
2010-08-03 09:50:29 -03:00
}
2011-08-26 14:57:06 -03:00
} else if ( test_bit ( ST_CAPT_PEND , & fimc - > state ) ) {
2012-05-08 15:51:24 -03:00
int last_buf = test_bit ( ST_CAPT_JPEG , & fimc - > state ) & &
fimc - > vid_cap . reqbufs_count = = 1 ;
fimc_capture_irq_handler ( fimc , ! last_buf ) ;
2010-08-03 09:50:29 -03:00
}
2011-09-02 06:25:32 -03:00
out :
2010-08-03 09:50:29 -03:00
spin_unlock ( & fimc - > slock ) ;
return IRQ_HANDLED ;
}
2010-12-08 14:05:08 -03:00
/* The color format (colplanes, memplanes) must be already configured. */
2010-12-01 10:14:59 -03:00
int fimc_prepare_addr ( struct fimc_ctx * ctx , struct vb2_buffer * vb ,
2010-10-08 05:01:14 -03:00
struct fimc_frame * frame , struct fimc_addr * paddr )
2010-08-03 09:50:29 -03:00
{
int ret = 0 ;
2010-10-08 05:01:14 -03:00
u32 pix_size ;
2010-08-03 09:50:29 -03:00
2010-12-01 10:14:59 -03:00
if ( vb = = NULL | | frame = = NULL )
2010-08-03 09:50:29 -03:00
return - EINVAL ;
pix_size = frame - > width * frame - > height ;
2010-12-08 14:05:08 -03:00
dbg ( " memplanes= %d, colplanes= %d, pix_size= %d " ,
frame - > fmt - > memplanes , frame - > fmt - > colplanes , pix_size ) ;
2011-08-29 03:20:56 -03:00
paddr - > y = vb2_dma_contig_plane_dma_addr ( vb , 0 ) ;
2010-08-03 09:50:29 -03:00
2010-12-08 14:05:08 -03:00
if ( frame - > fmt - > memplanes = = 1 ) {
switch ( frame - > fmt - > colplanes ) {
2010-08-03 09:50:29 -03:00
case 1 :
paddr - > cb = 0 ;
paddr - > cr = 0 ;
break ;
case 2 :
/* decompose Y into Y/Cb */
paddr - > cb = ( u32 ) ( paddr - > y + pix_size ) ;
paddr - > cr = 0 ;
break ;
case 3 :
paddr - > cb = ( u32 ) ( paddr - > y + pix_size ) ;
/* decompose Y into Y/Cb/Cr */
2012-04-26 06:26:29 -03:00
if ( FIMC_FMT_YCBCR420 = = frame - > fmt - > color )
2010-08-03 09:50:29 -03:00
paddr - > cr = ( u32 ) ( paddr - > cb
+ ( pix_size > > 2 ) ) ;
else /* 422 */
paddr - > cr = ( u32 ) ( paddr - > cb
+ ( pix_size > > 1 ) ) ;
break ;
default :
return - EINVAL ;
}
2012-09-24 11:08:45 -03:00
} else if ( ! frame - > fmt - > mdataplanes ) {
2010-12-08 14:05:08 -03:00
if ( frame - > fmt - > memplanes > = 2 )
2011-08-29 03:20:56 -03:00
paddr - > cb = vb2_dma_contig_plane_dma_addr ( vb , 1 ) ;
2010-12-08 14:05:08 -03:00
if ( frame - > fmt - > memplanes = = 3 )
2011-08-29 03:20:56 -03:00
paddr - > cr = vb2_dma_contig_plane_dma_addr ( vb , 2 ) ;
2010-08-03 09:50:29 -03:00
}
2010-10-08 05:01:14 -03:00
dbg ( " PHYS_ADDR: y= 0x%X cb= 0x%X cr= 0x%X ret= %d " ,
paddr - > y , paddr - > cb , paddr - > cr , ret ) ;
2010-08-03 09:50:29 -03:00
return ret ;
}
/* Set order for 1 and 2 plane YCBCR 4:2:2 formats. */
2011-06-10 15:36:53 -03:00
void fimc_set_yuv_order ( struct fimc_ctx * ctx )
2010-08-03 09:50:29 -03:00
{
/* The one only mode supported in SoC. */
2012-05-02 06:14:49 -03:00
ctx - > in_order_2p = FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB ;
ctx - > out_order_2p = FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB ;
2010-08-03 09:50:29 -03:00
/* Set order for 1 plane input formats. */
switch ( ctx - > s_frame . fmt - > color ) {
2012-04-26 06:26:29 -03:00
case FIMC_FMT_YCRYCB422 :
2013-03-21 14:22:34 -03:00
ctx - > in_order_1p = FIMC_REG_MSCTRL_ORDER422_YCRYCB ;
2010-08-03 09:50:29 -03:00
break ;
2012-04-26 06:26:29 -03:00
case FIMC_FMT_CBYCRY422 :
2013-03-21 14:22:34 -03:00
ctx - > in_order_1p = FIMC_REG_MSCTRL_ORDER422_CBYCRY ;
2010-08-03 09:50:29 -03:00
break ;
2012-04-26 06:26:29 -03:00
case FIMC_FMT_CRYCBY422 :
2013-03-21 14:22:34 -03:00
ctx - > in_order_1p = FIMC_REG_MSCTRL_ORDER422_CRYCBY ;
2010-08-03 09:50:29 -03:00
break ;
2012-04-26 06:26:29 -03:00
case FIMC_FMT_YCBYCR422 :
2010-08-03 09:50:29 -03:00
default :
2013-03-21 14:22:34 -03:00
ctx - > in_order_1p = FIMC_REG_MSCTRL_ORDER422_YCBYCR ;
2010-08-03 09:50:29 -03:00
break ;
}
dbg ( " ctx->in_order_1p= %d " , ctx - > in_order_1p ) ;
switch ( ctx - > d_frame . fmt - > color ) {
2012-04-26 06:26:29 -03:00
case FIMC_FMT_YCRYCB422 :
2013-03-21 14:22:34 -03:00
ctx - > out_order_1p = FIMC_REG_CIOCTRL_ORDER422_YCRYCB ;
2010-08-03 09:50:29 -03:00
break ;
2012-04-26 06:26:29 -03:00
case FIMC_FMT_CBYCRY422 :
2013-03-21 14:22:34 -03:00
ctx - > out_order_1p = FIMC_REG_CIOCTRL_ORDER422_CBYCRY ;
2010-08-03 09:50:29 -03:00
break ;
2012-04-26 06:26:29 -03:00
case FIMC_FMT_CRYCBY422 :
2013-03-21 14:22:34 -03:00
ctx - > out_order_1p = FIMC_REG_CIOCTRL_ORDER422_CRYCBY ;
2010-08-03 09:50:29 -03:00
break ;
2012-04-26 06:26:29 -03:00
case FIMC_FMT_YCBYCR422 :
2010-08-03 09:50:29 -03:00
default :
2013-03-21 14:22:34 -03:00
ctx - > out_order_1p = FIMC_REG_CIOCTRL_ORDER422_YCBYCR ;
2010-08-03 09:50:29 -03:00
break ;
}
dbg ( " ctx->out_order_1p= %d " , ctx - > out_order_1p ) ;
}
2011-06-10 15:36:53 -03:00
void fimc_prepare_dma_offset ( struct fimc_ctx * ctx , struct fimc_frame * f )
2010-09-06 03:53:44 -03:00
{
2013-03-26 08:22:21 -03:00
bool pix_hoff = ctx - > fimc_dev - > drv_data - > dma_pix_hoff ;
2010-12-08 14:05:08 -03:00
u32 i , depth = 0 ;
2014-03-25 17:52:31 -03:00
for ( i = 0 ; i < f - > fmt - > memplanes ; i + + )
2010-12-08 14:05:08 -03:00
depth + = f - > fmt - > depth [ i ] ;
2010-09-06 03:53:44 -03:00
f - > dma_offset . y_h = f - > offs_h ;
2013-03-26 08:22:21 -03:00
if ( ! pix_hoff )
2010-12-08 14:05:08 -03:00
f - > dma_offset . y_h * = ( depth > > 3 ) ;
2010-09-06 03:53:44 -03:00
f - > dma_offset . y_v = f - > offs_v ;
f - > dma_offset . cb_h = f - > offs_h ;
f - > dma_offset . cb_v = f - > offs_v ;
f - > dma_offset . cr_h = f - > offs_h ;
f - > dma_offset . cr_v = f - > offs_v ;
2013-03-26 08:22:21 -03:00
if ( ! pix_hoff ) {
2010-12-08 14:05:08 -03:00
if ( f - > fmt - > colplanes = = 3 ) {
2010-09-06 03:53:44 -03:00
f - > dma_offset . cb_h > > = 1 ;
f - > dma_offset . cr_h > > = 1 ;
}
2012-04-26 06:26:29 -03:00
if ( f - > fmt - > color = = FIMC_FMT_YCBCR420 ) {
2010-09-06 03:53:44 -03:00
f - > dma_offset . cb_v > > = 1 ;
f - > dma_offset . cr_v > > = 1 ;
}
}
dbg ( " in_offset: color= %d, y_h= %d, y_v= %d " ,
f - > fmt - > color , f - > dma_offset . y_h , f - > dma_offset . y_v ) ;
}
2012-05-26 11:11:54 -03:00
static int fimc_set_color_effect ( struct fimc_ctx * ctx , enum v4l2_colorfx colorfx )
2012-04-02 06:41:22 -03:00
{
struct fimc_effect * effect = & ctx - > effect ;
switch ( colorfx ) {
case V4L2_COLORFX_NONE :
effect - > type = FIMC_REG_CIIMGEFF_FIN_BYPASS ;
break ;
case V4L2_COLORFX_BW :
effect - > type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY ;
effect - > pat_cb = 128 ;
effect - > pat_cr = 128 ;
break ;
case V4L2_COLORFX_SEPIA :
effect - > type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY ;
effect - > pat_cb = 115 ;
effect - > pat_cr = 145 ;
break ;
case V4L2_COLORFX_NEGATIVE :
effect - > type = FIMC_REG_CIIMGEFF_FIN_NEGATIVE ;
break ;
case V4L2_COLORFX_EMBOSS :
effect - > type = FIMC_REG_CIIMGEFF_FIN_EMBOSSING ;
break ;
case V4L2_COLORFX_ART_FREEZE :
effect - > type = FIMC_REG_CIIMGEFF_FIN_ARTFREEZE ;
break ;
case V4L2_COLORFX_SILHOUETTE :
effect - > type = FIMC_REG_CIIMGEFF_FIN_SILHOUETTE ;
break ;
case V4L2_COLORFX_SET_CBCR :
effect - > type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY ;
effect - > pat_cb = ctx - > ctrls . colorfx_cbcr - > val > > 8 ;
effect - > pat_cr = ctx - > ctrls . colorfx_cbcr - > val & 0xff ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
2011-08-24 19:25:10 -03:00
/*
* V4L2 controls handling
*/
# define ctrl_to_ctx(__ctrl) \
2012-04-02 06:41:22 -03:00
container_of ( ( __ctrl ) - > handler , struct fimc_ctx , ctrls . handler )
2011-08-24 19:25:10 -03:00
2011-12-01 14:02:24 -03:00
static int __fimc_s_ctrl ( struct fimc_ctx * ctx , struct v4l2_ctrl * ctrl )
2011-08-24 19:25:10 -03:00
{
struct fimc_dev * fimc = ctx - > fimc_dev ;
2012-08-02 10:27:46 -03:00
const struct fimc_variant * variant = fimc - > variant ;
2011-08-24 19:25:10 -03:00
int ret = 0 ;
if ( ctrl - > flags & V4L2_CTRL_FLAG_INACTIVE )
return 0 ;
switch ( ctrl - > id ) {
case V4L2_CID_HFLIP :
ctx - > hflip = ctrl - > val ;
break ;
case V4L2_CID_VFLIP :
ctx - > vflip = ctrl - > val ;
break ;
case V4L2_CID_ROTATE :
2013-01-30 09:54:06 -03:00
if ( fimc_capture_pending ( fimc ) ) {
2011-08-26 14:57:06 -03:00
ret = fimc_check_scaler_ratio ( ctx , ctx - > s_frame . width ,
2011-08-24 19:25:10 -03:00
ctx - > s_frame . height , ctx - > d_frame . width ,
ctx - > d_frame . height , ctrl - > val ) ;
2011-12-01 14:02:24 -03:00
if ( ret )
return - EINVAL ;
2011-08-24 19:25:10 -03:00
}
if ( ( ctrl - > val = = 90 | | ctrl - > val = = 270 ) & &
! variant - > has_out_rot )
return - EINVAL ;
2011-12-01 14:02:24 -03:00
2011-08-24 19:25:10 -03:00
ctx - > rotation = ctrl - > val ;
break ;
2011-12-01 14:02:24 -03:00
case V4L2_CID_ALPHA_COMPONENT :
ctx - > d_frame . alpha = ctrl - > val ;
break ;
2012-04-02 06:41:22 -03:00
case V4L2_CID_COLORFX :
ret = fimc_set_color_effect ( ctx , ctrl - > val ) ;
if ( ret )
return ret ;
break ;
2011-08-24 19:25:10 -03:00
}
2012-04-02 06:41:22 -03:00
2011-08-24 19:25:10 -03:00
ctx - > state | = FIMC_PARAMS ;
set_bit ( ST_CAPT_APPLY_CFG , & fimc - > state ) ;
return 0 ;
}
2011-12-01 14:02:24 -03:00
static int fimc_s_ctrl ( struct v4l2_ctrl * ctrl )
{
struct fimc_ctx * ctx = ctrl_to_ctx ( ctrl ) ;
unsigned long flags ;
int ret ;
2012-03-19 13:11:40 -03:00
spin_lock_irqsave ( & ctx - > fimc_dev - > slock , flags ) ;
2011-12-01 14:02:24 -03:00
ret = __fimc_s_ctrl ( ctx , ctrl ) ;
2012-03-19 13:11:40 -03:00
spin_unlock_irqrestore ( & ctx - > fimc_dev - > slock , flags ) ;
2011-12-01 14:02:24 -03:00
return ret ;
}
2011-08-24 19:25:10 -03:00
static const struct v4l2_ctrl_ops fimc_ctrl_ops = {
. s_ctrl = fimc_s_ctrl ,
} ;
int fimc_ctrls_create ( struct fimc_ctx * ctx )
{
2011-12-01 14:02:24 -03:00
unsigned int max_alpha = fimc_get_alpha_mask ( ctx - > d_frame . fmt ) ;
2012-04-02 06:41:22 -03:00
struct fimc_ctrls * ctrls = & ctx - > ctrls ;
struct v4l2_ctrl_handler * handler = & ctrls - > handler ;
2011-12-01 14:02:24 -03:00
2012-04-02 06:41:22 -03:00
if ( ctx - > ctrls . ready )
2011-08-24 19:25:10 -03:00
return 0 ;
2012-04-02 06:41:22 -03:00
v4l2_ctrl_handler_init ( handler , 6 ) ;
ctrls - > rotate = v4l2_ctrl_new_std ( handler , & fimc_ctrl_ops ,
2012-01-10 05:46:57 -03:00
V4L2_CID_ROTATE , 0 , 270 , 90 , 0 ) ;
2012-04-02 06:41:22 -03:00
ctrls - > hflip = v4l2_ctrl_new_std ( handler , & fimc_ctrl_ops ,
2012-01-10 05:46:57 -03:00
V4L2_CID_HFLIP , 0 , 1 , 1 , 0 ) ;
2012-04-02 06:41:22 -03:00
ctrls - > vflip = v4l2_ctrl_new_std ( handler , & fimc_ctrl_ops ,
2012-01-10 05:46:57 -03:00
V4L2_CID_VFLIP , 0 , 1 , 1 , 0 ) ;
2012-04-02 06:41:22 -03:00
2013-03-26 08:22:21 -03:00
if ( ctx - > fimc_dev - > drv_data - > alpha_color )
2012-04-02 06:41:22 -03:00
ctrls - > alpha = v4l2_ctrl_new_std ( handler , & fimc_ctrl_ops ,
V4L2_CID_ALPHA_COMPONENT ,
0 , max_alpha , 1 , 0 ) ;
2011-12-01 14:02:24 -03:00
else
2012-04-02 06:41:22 -03:00
ctrls - > alpha = NULL ;
ctrls - > colorfx = v4l2_ctrl_new_std_menu ( handler , & fimc_ctrl_ops ,
V4L2_CID_COLORFX , V4L2_COLORFX_SET_CBCR ,
~ 0x983f , V4L2_COLORFX_NONE ) ;
ctrls - > colorfx_cbcr = v4l2_ctrl_new_std ( handler , & fimc_ctrl_ops ,
V4L2_CID_COLORFX_CBCR , 0 , 0xffff , 1 , 0 ) ;
2011-12-01 14:02:24 -03:00
2012-04-02 06:41:22 -03:00
ctx - > effect . type = FIMC_REG_CIIMGEFF_FIN_BYPASS ;
2011-08-24 19:25:10 -03:00
2012-04-02 06:41:22 -03:00
if ( ! handler - > error ) {
2012-06-15 13:40:32 -03:00
v4l2_ctrl_cluster ( 2 , & ctrls - > colorfx ) ;
2012-04-02 06:41:22 -03:00
ctrls - > ready = true ;
}
return handler - > error ;
2011-08-24 19:25:10 -03:00
}
void fimc_ctrls_delete ( struct fimc_ctx * ctx )
{
2012-04-02 06:41:22 -03:00
struct fimc_ctrls * ctrls = & ctx - > ctrls ;
if ( ctrls - > ready ) {
v4l2_ctrl_handler_free ( & ctrls - > handler ) ;
ctrls - > ready = false ;
ctrls - > alpha = NULL ;
2011-08-24 19:25:10 -03:00
}
}
void fimc_ctrls_activate ( struct fimc_ctx * ctx , bool active )
{
2011-12-01 14:02:24 -03:00
unsigned int has_alpha = ctx - > d_frame . fmt - > flags & FMT_HAS_ALPHA ;
2012-04-02 06:41:22 -03:00
struct fimc_ctrls * ctrls = & ctx - > ctrls ;
2011-12-01 14:02:24 -03:00
2012-04-02 06:41:22 -03:00
if ( ! ctrls - > ready )
2011-08-24 19:25:10 -03:00
return ;
2012-05-25 07:04:01 -03:00
mutex_lock ( ctrls - > handler . lock ) ;
2012-04-02 06:41:22 -03:00
v4l2_ctrl_activate ( ctrls - > rotate , active ) ;
v4l2_ctrl_activate ( ctrls - > hflip , active ) ;
v4l2_ctrl_activate ( ctrls - > vflip , active ) ;
v4l2_ctrl_activate ( ctrls - > colorfx , active ) ;
if ( ctrls - > alpha )
v4l2_ctrl_activate ( ctrls - > alpha , active & & has_alpha ) ;
2011-08-24 19:25:10 -03:00
if ( active ) {
2012-04-02 06:41:22 -03:00
fimc_set_color_effect ( ctx , ctrls - > colorfx - > cur . val ) ;
ctx - > rotation = ctrls - > rotate - > val ;
ctx - > hflip = ctrls - > hflip - > val ;
ctx - > vflip = ctrls - > vflip - > val ;
2011-08-24 19:25:10 -03:00
} else {
2012-04-02 06:41:22 -03:00
ctx - > effect . type = FIMC_REG_CIIMGEFF_FIN_BYPASS ;
2011-08-24 19:25:10 -03:00
ctx - > rotation = 0 ;
ctx - > hflip = 0 ;
ctx - > vflip = 0 ;
}
2012-05-25 07:04:01 -03:00
mutex_unlock ( ctrls - > handler . lock ) ;
2011-08-24 19:25:10 -03:00
}
2011-12-01 14:02:24 -03:00
/* Update maximum value of the alpha color control */
void fimc_alpha_ctrl_update ( struct fimc_ctx * ctx )
{
struct fimc_dev * fimc = ctx - > fimc_dev ;
2012-04-02 06:41:22 -03:00
struct v4l2_ctrl * ctrl = ctx - > ctrls . alpha ;
2011-12-01 14:02:24 -03:00
2013-03-26 08:22:21 -03:00
if ( ctrl = = NULL | | ! fimc - > drv_data - > alpha_color )
2011-12-01 14:02:24 -03:00
return ;
v4l2_ctrl_lock ( ctrl ) ;
ctrl - > maximum = fimc_get_alpha_mask ( ctx - > d_frame . fmt ) ;
if ( ctrl - > cur . val > ctrl - > maximum )
ctrl - > cur . val = ctrl - > maximum ;
v4l2_ctrl_unlock ( ctrl ) ;
}
2013-01-11 06:36:19 -03:00
void __fimc_get_format ( struct fimc_frame * frame , struct v4l2_format * f )
2010-08-03 09:50:29 -03:00
{
2011-06-10 15:36:50 -03:00
struct v4l2_pix_format_mplane * pixm = & f - > fmt . pix_mp ;
2011-01-15 01:17:42 -03:00
int i ;
2010-08-03 09:50:29 -03:00
2011-06-10 15:36:50 -03:00
pixm - > width = frame - > o_width ;
pixm - > height = frame - > o_height ;
pixm - > field = V4L2_FIELD_NONE ;
pixm - > pixelformat = frame - > fmt - > fourcc ;
pixm - > colorspace = V4L2_COLORSPACE_JPEG ;
pixm - > num_planes = frame - > fmt - > memplanes ;
2011-01-15 01:17:42 -03:00
for ( i = 0 ; i < pixm - > num_planes ; + + i ) {
2013-01-11 06:36:19 -03:00
pixm - > plane_fmt [ i ] . bytesperline = frame - > bytesperline [ i ] ;
pixm - > plane_fmt [ i ] . sizeimage = frame - > payload [ i ] ;
2011-01-15 01:17:42 -03:00
}
2010-08-03 09:50:29 -03:00
}
2011-08-26 14:51:00 -03:00
/**
* fimc_adjust_mplane_format - adjust bytesperline / sizeimage for each plane
* @ fmt : fimc pixel format description ( input )
* @ width : requested pixel width
* @ height : requested pixel height
* @ pix : multi - plane format to adjust
*/
void fimc_adjust_mplane_format ( struct fimc_fmt * fmt , u32 width , u32 height ,
struct v4l2_pix_format_mplane * pix )
{
u32 bytesperline = 0 ;
int i ;
pix - > colorspace = V4L2_COLORSPACE_JPEG ;
pix - > field = V4L2_FIELD_NONE ;
pix - > num_planes = fmt - > memplanes ;
2011-12-01 14:02:24 -03:00
pix - > pixelformat = fmt - > fourcc ;
2011-08-26 14:51:00 -03:00
pix - > height = height ;
pix - > width = width ;
for ( i = 0 ; i < pix - > num_planes ; + + i ) {
2012-05-16 15:00:26 -03:00
struct v4l2_plane_pix_format * plane_fmt = & pix - > plane_fmt [ i ] ;
u32 bpl = plane_fmt - > bytesperline ;
2011-08-26 14:51:00 -03:00
if ( fmt - > colplanes > 1 & & ( bpl = = 0 | | bpl < pix - > width ) )
bpl = pix - > width ; /* Planar */
if ( fmt - > colplanes = = 1 & & /* Packed */
( bpl = = 0 | | ( ( bpl * 8 ) / fmt - > depth [ i ] ) < pix - > width ) )
bpl = ( pix - > width * fmt - > depth [ 0 ] ) / 8 ;
2013-01-11 06:36:19 -03:00
/*
* Currently bytesperline for each plane is same , except
* V4L2_PIX_FMT_YUV420M format . This calculation may need
* to be changed when other multi - planar formats are added
* to the fimc_formats [ ] array .
*/
if ( i = = 0 )
2011-08-26 14:51:00 -03:00
bytesperline = bpl ;
2013-01-11 06:36:19 -03:00
else if ( i = = 1 & & fmt - > memplanes = = 3 )
bytesperline / = 2 ;
2011-08-26 14:51:00 -03:00
2012-05-16 15:00:26 -03:00
plane_fmt - > bytesperline = bytesperline ;
plane_fmt - > sizeimage = max ( ( pix - > width * pix - > height *
fmt - > depth [ i ] ) / 8 , plane_fmt - > sizeimage ) ;
2011-08-26 14:51:00 -03:00
}
}
2011-06-13 11:09:40 -03:00
/**
* fimc_find_format - lookup fimc color format by fourcc or media bus format
* @ pixelformat : fourcc to match , ignored if null
* @ mbus_code : media bus code to match , ignored if null
* @ mask : the color flags to match
* @ index : offset in the fimc_formats array , ignored if negative
*/
2012-04-22 17:07:09 -03:00
struct fimc_fmt * fimc_find_format ( const u32 * pixelformat , const u32 * mbus_code ,
2011-06-13 11:09:40 -03:00
unsigned int mask , int index )
2010-08-03 09:50:29 -03:00
{
2011-06-13 11:09:40 -03:00
struct fimc_fmt * fmt , * def_fmt = NULL ;
2010-08-03 09:50:29 -03:00
unsigned int i ;
2011-06-13 11:09:40 -03:00
int id = 0 ;
2010-08-03 09:50:29 -03:00
2012-04-22 17:07:09 -03:00
if ( index > = ( int ) ARRAY_SIZE ( fimc_formats ) )
2011-06-13 11:09:40 -03:00
return NULL ;
2010-10-07 10:06:16 -03:00
for ( i = 0 ; i < ARRAY_SIZE ( fimc_formats ) ; + + i ) {
fmt = & fimc_formats [ i ] ;
2011-06-13 11:09:40 -03:00
if ( ! ( fmt - > flags & mask ) )
continue ;
if ( pixelformat & & fmt - > fourcc = = * pixelformat )
return fmt ;
if ( mbus_code & & fmt - > mbus_code = = * mbus_code )
return fmt ;
if ( index = = id )
def_fmt = fmt ;
id + + ;
2010-10-07 10:06:16 -03:00
}
2011-06-13 11:09:40 -03:00
return def_fmt ;
2010-10-07 10:06:16 -03:00
}
2011-09-02 06:25:32 -03:00
static void fimc_clk_put ( struct fimc_dev * fimc )
2010-08-03 09:50:29 -03:00
{
int i ;
2012-03-17 18:31:33 -03:00
for ( i = 0 ; i < MAX_FIMC_CLOCKS ; i + + ) {
2013-01-29 06:42:28 -03:00
if ( IS_ERR ( fimc - > clock [ i ] ) )
2012-01-30 11:39:30 -03:00
continue ;
clk_unprepare ( fimc - > clock [ i ] ) ;
clk_put ( fimc - > clock [ i ] ) ;
2013-01-29 06:42:28 -03:00
fimc - > clock [ i ] = ERR_PTR ( - EINVAL ) ;
2010-08-03 09:50:29 -03:00
}
}
static int fimc_clk_get ( struct fimc_dev * fimc )
{
2012-01-30 11:39:30 -03:00
int i , ret ;
2013-01-29 06:42:28 -03:00
for ( i = 0 ; i < MAX_FIMC_CLOCKS ; i + + )
fimc - > clock [ i ] = ERR_PTR ( - EINVAL ) ;
2012-03-17 18:31:33 -03:00
for ( i = 0 ; i < MAX_FIMC_CLOCKS ; i + + ) {
2010-12-27 15:34:43 -03:00
fimc - > clock [ i ] = clk_get ( & fimc - > pdev - > dev , fimc_clocks [ i ] ) ;
2013-01-29 06:42:28 -03:00
if ( IS_ERR ( fimc - > clock [ i ] ) ) {
ret = PTR_ERR ( fimc - > clock [ i ] ) ;
2012-01-30 11:39:30 -03:00
goto err ;
2013-01-29 06:42:28 -03:00
}
2012-01-30 11:39:30 -03:00
ret = clk_prepare ( fimc - > clock [ i ] ) ;
if ( ret < 0 ) {
clk_put ( fimc - > clock [ i ] ) ;
2013-01-29 06:42:28 -03:00
fimc - > clock [ i ] = ERR_PTR ( - EINVAL ) ;
2012-01-30 11:39:30 -03:00
goto err ;
}
2010-08-03 09:50:29 -03:00
}
2011-09-02 06:25:32 -03:00
return 0 ;
2012-01-30 11:39:30 -03:00
err :
fimc_clk_put ( fimc ) ;
dev_err ( & fimc - > pdev - > dev , " failed to get clock: %s \n " ,
fimc_clocks [ i ] ) ;
return - ENXIO ;
2011-09-02 06:25:32 -03:00
}
2014-12-04 01:10:10 +01:00
# ifdef CONFIG_PM
2011-09-02 06:25:32 -03:00
static int fimc_m2m_suspend ( struct fimc_dev * fimc )
{
unsigned long flags ;
int timeout ;
spin_lock_irqsave ( & fimc - > slock , flags ) ;
if ( ! fimc_m2m_pending ( fimc ) ) {
spin_unlock_irqrestore ( & fimc - > slock , flags ) ;
return 0 ;
}
clear_bit ( ST_M2M_SUSPENDED , & fimc - > state ) ;
set_bit ( ST_M2M_SUSPENDING , & fimc - > state ) ;
spin_unlock_irqrestore ( & fimc - > slock , flags ) ;
timeout = wait_event_timeout ( fimc - > irq_queue ,
test_bit ( ST_M2M_SUSPENDED , & fimc - > state ) ,
FIMC_SHUTDOWN_TIMEOUT ) ;
clear_bit ( ST_M2M_SUSPENDING , & fimc - > state ) ;
return timeout = = 0 ? - EAGAIN : 0 ;
}
static int fimc_m2m_resume ( struct fimc_dev * fimc )
{
2013-02-06 01:47:10 -03:00
struct fimc_ctx * ctx ;
2011-09-02 06:25:32 -03:00
unsigned long flags ;
spin_lock_irqsave ( & fimc - > slock , flags ) ;
/* Clear for full H/W setup in first run after resume */
2013-02-06 01:47:10 -03:00
ctx = fimc - > m2m . ctx ;
2011-09-02 06:25:32 -03:00
fimc - > m2m . ctx = NULL ;
spin_unlock_irqrestore ( & fimc - > slock , flags ) ;
if ( test_and_clear_bit ( ST_M2M_SUSPENDED , & fimc - > state ) )
2013-02-06 01:47:10 -03:00
fimc_m2m_job_finish ( ctx , VB2_BUF_STATE_ERROR ) ;
2010-08-03 09:50:29 -03:00
return 0 ;
}
2014-12-04 01:10:10 +01:00
# endif /* CONFIG_PM */
2010-08-03 09:50:29 -03:00
2013-03-26 08:22:21 -03:00
static const struct of_device_id fimc_of_match [ ] ;
static int fimc_parse_dt ( struct fimc_dev * fimc , u32 * clk_freq )
{
struct device * dev = & fimc - > pdev - > dev ;
struct device_node * node = dev - > of_node ;
const struct of_device_id * of_id ;
struct fimc_variant * v ;
struct fimc_pix_limit * lim ;
u32 args [ FIMC_PIX_LIMITS_MAX ] ;
int ret ;
if ( of_property_read_bool ( node , " samsung,lcd-wb " ) )
return - ENODEV ;
v = devm_kzalloc ( dev , sizeof ( * v ) + sizeof ( * lim ) , GFP_KERNEL ) ;
if ( ! v )
return - ENOMEM ;
of_id = of_match_node ( fimc_of_match , node ) ;
if ( ! of_id )
return - EINVAL ;
fimc - > drv_data = of_id - > data ;
ret = of_property_read_u32_array ( node , " samsung,pix-limits " ,
args , FIMC_PIX_LIMITS_MAX ) ;
if ( ret < 0 )
return ret ;
lim = ( struct fimc_pix_limit * ) & v [ 1 ] ;
lim - > scaler_en_w = args [ 0 ] ;
lim - > scaler_dis_w = args [ 1 ] ;
lim - > out_rot_en_w = args [ 2 ] ;
lim - > out_rot_dis_w = args [ 3 ] ;
v - > pix_limit = lim ;
ret = of_property_read_u32_array ( node , " samsung,min-pix-sizes " ,
args , 2 ) ;
v - > min_inp_pixsize = ret ? FIMC_DEF_MIN_SIZE : args [ 0 ] ;
v - > min_out_pixsize = ret ? FIMC_DEF_MIN_SIZE : args [ 1 ] ;
ret = of_property_read_u32_array ( node , " samsung,min-pix-alignment " ,
args , 2 ) ;
v - > min_vsize_align = ret ? FIMC_DEF_HEIGHT_ALIGN : args [ 0 ] ;
v - > hor_offs_align = ret ? FIMC_DEF_HOR_OFFS_ALIGN : args [ 1 ] ;
ret = of_property_read_u32 ( node , " samsung,rotators " , & args [ 1 ] ) ;
v - > has_inp_rot = ret ? 1 : args [ 1 ] & 0x01 ;
v - > has_out_rot = ret ? 1 : args [ 1 ] & 0x10 ;
v - > has_mainscaler_ext = of_property_read_bool ( node ,
" samsung,mainscaler-ext " ) ;
v - > has_isp_wb = of_property_read_bool ( node , " samsung,isp-wb " ) ;
v - > has_cam_if = of_property_read_bool ( node , " samsung,cam-if " ) ;
of_property_read_u32 ( node , " clock-frequency " , clk_freq ) ;
fimc - > id = of_alias_get_id ( node , " fimc " ) ;
fimc - > variant = v ;
return 0 ;
}
2010-08-03 09:50:29 -03:00
static int fimc_probe ( struct platform_device * pdev )
{
2013-03-26 08:22:21 -03:00
struct device * dev = & pdev - > dev ;
u32 lclk_freq = 0 ;
2010-08-03 09:50:29 -03:00
struct fimc_dev * fimc ;
struct resource * res ;
int ret = 0 ;
2013-03-26 08:22:21 -03:00
fimc = devm_kzalloc ( dev , sizeof ( * fimc ) , GFP_KERNEL ) ;
2010-08-03 09:50:29 -03:00
if ( ! fimc )
return - ENOMEM ;
fimc - > pdev = pdev ;
2013-03-26 08:22:21 -03:00
if ( dev - > of_node ) {
ret = fimc_parse_dt ( fimc , & lclk_freq ) ;
if ( ret < 0 )
return ret ;
} else {
fimc - > drv_data = fimc_get_drvdata ( pdev ) ;
fimc - > id = pdev - > id ;
}
if ( ! fimc - > drv_data | | fimc - > id > = fimc - > drv_data - > num_entities | |
fimc - > id < 0 ) {
2013-04-16 03:02:19 -03:00
dev_err ( dev , " Invalid driver data or device id (%d) \n " ,
fimc - > id ) ;
2013-03-26 08:22:21 -03:00
return - EINVAL ;
}
if ( ! dev - > of_node )
fimc - > variant = fimc - > drv_data - > variant [ fimc - > id ] ;
2011-09-02 06:25:32 -03:00
2010-10-07 10:06:16 -03:00
init_waitqueue_head ( & fimc - > irq_queue ) ;
2010-08-03 09:50:29 -03:00
spin_lock_init ( & fimc - > slock ) ;
mutex_init ( & fimc - > lock ) ;
2013-03-31 20:31:02 -03:00
fimc - > sysreg = fimc_get_sysreg_regmap ( dev - > of_node ) ;
2013-03-20 10:44:39 -03:00
if ( IS_ERR ( fimc - > sysreg ) )
return PTR_ERR ( fimc - > sysreg ) ;
2010-08-03 09:50:29 -03:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-03-26 08:22:21 -03:00
fimc - > regs = devm_ioremap_resource ( dev , res ) ;
2013-01-21 06:09:07 -03:00
if ( IS_ERR ( fimc - > regs ) )
return PTR_ERR ( fimc - > regs ) ;
2010-08-03 09:50:29 -03:00
res = platform_get_resource ( pdev , IORESOURCE_IRQ , 0 ) ;
2012-01-30 11:37:59 -03:00
if ( res = = NULL ) {
2013-03-26 08:22:21 -03:00
dev_err ( dev , " Failed to get IRQ resource \n " ) ;
2012-01-30 11:37:59 -03:00
return - ENXIO ;
2010-08-03 09:50:29 -03:00
}
2011-09-02 06:25:32 -03:00
ret = fimc_clk_get ( fimc ) ;
if ( ret )
2012-01-30 11:37:59 -03:00
return ret ;
2013-01-29 06:42:28 -03:00
2013-03-26 08:22:21 -03:00
if ( lclk_freq = = 0 )
lclk_freq = fimc - > drv_data - > lclk_frequency ;
ret = clk_set_rate ( fimc - > clock [ CLK_BUS ] , lclk_freq ) ;
2013-01-29 06:42:28 -03:00
if ( ret < 0 )
return ret ;
ret = clk_enable ( fimc - > clock [ CLK_BUS ] ) ;
if ( ret < 0 )
return ret ;
2011-09-02 06:25:32 -03:00
2013-03-26 08:22:21 -03:00
ret = devm_request_irq ( dev , res - > start , fimc_irq_handler ,
0 , dev_name ( dev ) , fimc ) ;
2013-10-19 18:08:03 -03:00
if ( ret < 0 ) {
2013-03-26 08:22:21 -03:00
dev_err ( dev , " failed to install irq (%d) \n " , ret ) ;
2013-10-19 18:08:03 -03:00
goto err_sclk ;
2010-08-03 09:50:29 -03:00
}
2012-04-20 18:57:25 -03:00
ret = fimc_initialize_capture_subdev ( fimc ) ;
2013-10-19 18:08:03 -03:00
if ( ret < 0 )
goto err_sclk ;
2012-04-20 18:57:25 -03:00
platform_set_drvdata ( pdev , fimc ) ;
2013-03-26 08:22:21 -03:00
pm_runtime_enable ( dev ) ;
2013-10-19 18:08:03 -03:00
if ( ! pm_runtime_enabled ( dev ) ) {
ret = clk_enable ( fimc - > clock [ CLK_GATE ] ) ;
if ( ret < 0 )
goto err_sd ;
}
2010-12-01 10:14:59 -03:00
/* Initialize contiguous memory allocator */
2013-03-26 08:22:21 -03:00
fimc - > alloc_ctx = vb2_dma_contig_init_ctx ( dev ) ;
2010-12-01 10:14:59 -03:00
if ( IS_ERR ( fimc - > alloc_ctx ) ) {
ret = PTR_ERR ( fimc - > alloc_ctx ) ;
2013-10-19 18:08:03 -03:00
goto err_gclk ;
2010-12-01 10:14:59 -03:00
}
2013-03-26 08:22:21 -03:00
dev_dbg ( dev , " FIMC.%d registered successfully \n " , fimc - > id ) ;
2010-08-03 09:50:29 -03:00
return 0 ;
2013-10-19 18:08:03 -03:00
err_gclk :
2014-01-07 19:07:52 -03:00
if ( ! pm_runtime_enabled ( dev ) )
clk_disable ( fimc - > clock [ CLK_GATE ] ) ;
2012-04-20 18:57:25 -03:00
err_sd :
fimc_unregister_capture_subdev ( fimc ) ;
2013-10-19 18:08:03 -03:00
err_sclk :
2013-01-29 06:42:28 -03:00
clk_disable ( fimc - > clock [ CLK_BUS ] ) ;
2011-09-02 06:25:32 -03:00
fimc_clk_put ( fimc ) ;
2010-08-03 09:50:29 -03:00
return ret ;
}
2014-12-04 01:10:10 +01:00
# ifdef CONFIG_PM
2011-09-02 06:25:32 -03:00
static int fimc_runtime_resume ( struct device * dev )
2010-08-03 09:50:29 -03:00
{
2011-09-02 06:25:32 -03:00
struct fimc_dev * fimc = dev_get_drvdata ( dev ) ;
2010-08-03 09:50:29 -03:00
2011-09-02 06:25:32 -03:00
dbg ( " fimc%d: state: 0x%lx " , fimc - > id , fimc - > state ) ;
2013-10-20 21:34:01 -03:00
/* Enable clocks and perform basic initialization */
2011-09-02 06:25:32 -03:00
clk_enable ( fimc - > clock [ CLK_GATE ] ) ;
2010-08-03 09:50:29 -03:00
fimc_hw_reset ( fimc ) ;
2011-09-02 06:25:32 -03:00
/* Resume the capture or mem-to-mem device */
if ( fimc_capture_busy ( fimc ) )
return fimc_capture_resume ( fimc ) ;
2011-11-17 06:23:21 -03:00
return fimc_m2m_resume ( fimc ) ;
2011-09-02 06:25:32 -03:00
}
static int fimc_runtime_suspend ( struct device * dev )
{
struct fimc_dev * fimc = dev_get_drvdata ( dev ) ;
int ret = 0 ;
if ( fimc_capture_busy ( fimc ) )
ret = fimc_capture_suspend ( fimc ) ;
else
ret = fimc_m2m_suspend ( fimc ) ;
if ( ! ret )
clk_disable ( fimc - > clock [ CLK_GATE ] ) ;
dbg ( " fimc%d: state: 0x%lx " , fimc - > id , fimc - > state ) ;
return ret ;
}
2014-01-07 19:08:48 -03:00
# endif
2011-09-02 06:25:32 -03:00
# ifdef CONFIG_PM_SLEEP
static int fimc_resume ( struct device * dev )
{
struct fimc_dev * fimc = dev_get_drvdata ( dev ) ;
unsigned long flags ;
dbg ( " fimc%d: state: 0x%lx " , fimc - > id , fimc - > state ) ;
/* Do not resume if the device was idle before system suspend */
spin_lock_irqsave ( & fimc - > slock , flags ) ;
if ( ! test_and_clear_bit ( ST_LPM , & fimc - > state ) | |
( ! fimc_m2m_active ( fimc ) & & ! fimc_capture_busy ( fimc ) ) ) {
spin_unlock_irqrestore ( & fimc - > slock , flags ) ;
return 0 ;
}
fimc_hw_reset ( fimc ) ;
spin_unlock_irqrestore ( & fimc - > slock , flags ) ;
if ( fimc_capture_busy ( fimc ) )
return fimc_capture_resume ( fimc ) ;
return fimc_m2m_resume ( fimc ) ;
}
static int fimc_suspend ( struct device * dev )
{
struct fimc_dev * fimc = dev_get_drvdata ( dev ) ;
dbg ( " fimc%d: state: 0x%lx " , fimc - > id , fimc - > state ) ;
if ( test_and_set_bit ( ST_LPM , & fimc - > state ) )
return 0 ;
if ( fimc_capture_busy ( fimc ) )
return fimc_capture_suspend ( fimc ) ;
return fimc_m2m_suspend ( fimc ) ;
}
# endif /* CONFIG_PM_SLEEP */
2012-12-21 13:17:53 -08:00
static int fimc_remove ( struct platform_device * pdev )
2011-09-02 06:25:32 -03:00
{
struct fimc_dev * fimc = platform_get_drvdata ( pdev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
2013-07-19 07:39:53 -03:00
if ( ! pm_runtime_status_suspended ( & pdev - > dev ) )
clk_disable ( fimc - > clock [ CLK_GATE ] ) ;
2011-09-02 06:25:32 -03:00
pm_runtime_set_suspended ( & pdev - > dev ) ;
2010-08-03 09:50:29 -03:00
2012-04-20 18:57:25 -03:00
fimc_unregister_capture_subdev ( fimc ) ;
2010-12-01 10:14:59 -03:00
vb2_dma_contig_cleanup_ctx ( fimc - > alloc_ctx ) ;
2011-09-02 06:25:32 -03:00
clk_disable ( fimc - > clock [ CLK_BUS ] ) ;
fimc_clk_put ( fimc ) ;
2010-10-08 05:01:14 -03:00
2011-09-02 06:25:32 -03:00
dev_info ( & pdev - > dev , " driver unloaded \n " ) ;
2010-08-03 09:50:29 -03:00
return 0 ;
}
2010-10-11 13:19:27 -03:00
/* Image pixel limits, similar across several FIMC HW revisions. */
2012-08-02 10:27:46 -03:00
static const struct fimc_pix_limit s5p_pix_limit [ 4 ] = {
2010-10-11 13:19:27 -03:00
[ 0 ] = {
. scaler_en_w = 3264 ,
. scaler_dis_w = 8192 ,
. out_rot_en_w = 1920 ,
. out_rot_dis_w = 4224 ,
} ,
[ 1 ] = {
. scaler_en_w = 4224 ,
. scaler_dis_w = 8192 ,
. out_rot_en_w = 1920 ,
. out_rot_dis_w = 4224 ,
} ,
[ 2 ] = {
. scaler_en_w = 1920 ,
. scaler_dis_w = 8192 ,
. out_rot_en_w = 1280 ,
. out_rot_dis_w = 1920 ,
} ,
} ;
2012-08-02 10:27:46 -03:00
static const struct fimc_variant fimc0_variant_s5p = {
2010-10-11 13:19:27 -03:00
. has_inp_rot = 1 ,
. has_out_rot = 1 ,
2011-09-01 06:01:08 -03:00
. has_cam_if = 1 ,
2010-08-03 09:50:29 -03:00
. min_inp_pixsize = 16 ,
. min_out_pixsize = 16 ,
2010-10-11 13:19:27 -03:00
. hor_offs_align = 8 ,
2011-05-27 13:12:23 -03:00
. min_vsize_align = 16 ,
2010-10-11 13:19:27 -03:00
. pix_limit = & s5p_pix_limit [ 0 ] ,
2010-08-03 09:50:29 -03:00
} ;
2012-08-02 10:27:46 -03:00
static const struct fimc_variant fimc2_variant_s5p = {
2011-09-01 06:01:08 -03:00
. has_cam_if = 1 ,
2010-08-03 09:50:29 -03:00
. min_inp_pixsize = 16 ,
. min_out_pixsize = 16 ,
2010-10-11 13:19:27 -03:00
. hor_offs_align = 8 ,
2011-05-27 13:12:23 -03:00
. min_vsize_align = 16 ,
2012-04-27 09:33:23 -03:00
. pix_limit = & s5p_pix_limit [ 1 ] ,
2010-08-03 09:50:29 -03:00
} ;
2012-08-02 10:27:46 -03:00
static const struct fimc_variant fimc0_variant_s5pv210 = {
2010-10-11 13:19:27 -03:00
. has_inp_rot = 1 ,
. has_out_rot = 1 ,
2011-09-01 06:01:08 -03:00
. has_cam_if = 1 ,
2010-08-03 09:50:29 -03:00
. min_inp_pixsize = 16 ,
2010-10-08 05:01:14 -03:00
. min_out_pixsize = 16 ,
2010-10-11 13:19:27 -03:00
. hor_offs_align = 8 ,
2011-05-27 13:12:23 -03:00
. min_vsize_align = 16 ,
2010-10-11 13:19:27 -03:00
. pix_limit = & s5p_pix_limit [ 1 ] ,
} ;
2010-08-03 09:50:29 -03:00
2012-08-02 10:27:46 -03:00
static const struct fimc_variant fimc1_variant_s5pv210 = {
2010-10-11 13:19:27 -03:00
. has_inp_rot = 1 ,
. has_out_rot = 1 ,
2011-09-01 06:01:08 -03:00
. has_cam_if = 1 ,
2010-12-28 11:27:13 -03:00
. has_mainscaler_ext = 1 ,
2010-10-11 13:19:27 -03:00
. min_inp_pixsize = 16 ,
. min_out_pixsize = 16 ,
. hor_offs_align = 1 ,
2011-05-27 13:12:23 -03:00
. min_vsize_align = 1 ,
2010-10-11 13:19:27 -03:00
. pix_limit = & s5p_pix_limit [ 2 ] ,
2010-08-03 09:50:29 -03:00
} ;
2012-08-02 10:27:46 -03:00
static const struct fimc_variant fimc2_variant_s5pv210 = {
2011-09-01 06:01:08 -03:00
. has_cam_if = 1 ,
2010-08-03 09:50:29 -03:00
. min_inp_pixsize = 16 ,
2010-10-08 05:01:14 -03:00
. min_out_pixsize = 16 ,
2010-10-11 13:19:27 -03:00
. hor_offs_align = 8 ,
2011-05-27 13:12:23 -03:00
. min_vsize_align = 16 ,
2010-10-11 13:19:27 -03:00
. pix_limit = & s5p_pix_limit [ 2 ] ,
} ;
2010-08-03 09:50:29 -03:00
2010-10-11 13:19:27 -03:00
/* S5PC100 */
2012-08-02 10:27:46 -03:00
static const struct fimc_drvdata fimc_drvdata_s5p = {
2010-08-03 09:50:29 -03:00
. variant = {
2010-10-11 13:19:27 -03:00
[ 0 ] = & fimc0_variant_s5p ,
[ 1 ] = & fimc0_variant_s5p ,
2010-08-03 09:50:29 -03:00
[ 2 ] = & fimc2_variant_s5p ,
} ,
2013-03-26 08:22:21 -03:00
. num_entities = 3 ,
2010-10-11 13:19:27 -03:00
. lclk_frequency = 133000000UL ,
2013-03-26 08:22:21 -03:00
. out_buf_count = 4 ,
2010-08-03 09:50:29 -03:00
} ;
2010-10-11 13:19:27 -03:00
/* S5PV210, S5PC110 */
2012-08-02 10:27:46 -03:00
static const struct fimc_drvdata fimc_drvdata_s5pv210 = {
2010-08-03 09:50:29 -03:00
. variant = {
2010-10-11 13:19:27 -03:00
[ 0 ] = & fimc0_variant_s5pv210 ,
[ 1 ] = & fimc1_variant_s5pv210 ,
2010-08-03 09:50:29 -03:00
[ 2 ] = & fimc2_variant_s5pv210 ,
} ,
2013-03-26 08:22:21 -03:00
. num_entities = 3 ,
. lclk_frequency = 166000000UL ,
. out_buf_count = 4 ,
. dma_pix_hoff = 1 ,
2010-10-11 13:19:27 -03:00
} ;
2012-04-27 09:33:23 -03:00
/* EXYNOS4210, S5PV310, S5PC210 */
2012-08-02 10:27:46 -03:00
static const struct fimc_drvdata fimc_drvdata_exynos4210 = {
2013-03-26 08:22:21 -03:00
. num_entities = 4 ,
2012-08-02 10:27:46 -03:00
. lclk_frequency = 166000000UL ,
2013-03-26 08:22:21 -03:00
. dma_pix_hoff = 1 ,
. cistatus2 = 1 ,
. alpha_color = 1 ,
. out_buf_count = 32 ,
2012-08-02 10:27:46 -03:00
} ;
/* EXYNOS4212, EXYNOS4412 */
static const struct fimc_drvdata fimc_drvdata_exynos4x12 = {
2013-03-26 08:22:21 -03:00
. num_entities = 4 ,
. lclk_frequency = 166000000UL ,
. dma_pix_hoff = 1 ,
. cistatus2 = 1 ,
. alpha_color = 1 ,
. out_buf_count = 32 ,
2010-08-03 09:50:29 -03:00
} ;
2012-08-02 10:27:46 -03:00
static const struct platform_device_id fimc_driver_ids [ ] = {
2010-08-03 09:50:29 -03:00
{
. name = " s5p-fimc " ,
. driver_data = ( unsigned long ) & fimc_drvdata_s5p ,
} , {
. name = " s5pv210-fimc " ,
. driver_data = ( unsigned long ) & fimc_drvdata_s5pv210 ,
2010-10-11 13:19:27 -03:00
} , {
2011-04-08 09:08:52 -03:00
. name = " exynos4-fimc " ,
2012-08-02 10:27:46 -03:00
. driver_data = ( unsigned long ) & fimc_drvdata_exynos4210 ,
} , {
. name = " exynos4x12-fimc " ,
. driver_data = ( unsigned long ) & fimc_drvdata_exynos4x12 ,
2010-08-03 09:50:29 -03:00
} ,
2013-03-26 08:22:21 -03:00
{ } ,
2010-08-03 09:50:29 -03:00
} ;
2013-03-26 08:22:21 -03:00
static const struct of_device_id fimc_of_match [ ] = {
{
. compatible = " samsung,s5pv210-fimc " ,
. data = & fimc_drvdata_s5pv210 ,
} , {
. compatible = " samsung,exynos4210-fimc " ,
. data = & fimc_drvdata_exynos4210 ,
} , {
. compatible = " samsung,exynos4212-fimc " ,
. data = & fimc_drvdata_exynos4x12 ,
} ,
{ /* sentinel */ } ,
} ;
2011-09-02 06:25:32 -03:00
static const struct dev_pm_ops fimc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( fimc_suspend , fimc_resume )
SET_RUNTIME_PM_OPS ( fimc_runtime_suspend , fimc_runtime_resume , NULL )
} ;
2010-08-03 09:50:29 -03:00
static struct platform_driver fimc_driver = {
. probe = fimc_probe ,
2012-12-21 13:17:53 -08:00
. remove = fimc_remove ,
2010-08-03 09:50:29 -03:00
. id_table = fimc_driver_ids ,
. driver = {
2013-03-26 08:22:21 -03:00
. of_match_table = fimc_of_match ,
2013-04-23 11:36:04 -03:00
. name = FIMC_DRIVER_NAME ,
2013-03-26 08:22:21 -03:00
. pm = & fimc_pm_ops ,
2010-08-03 09:50:29 -03:00
}
} ;
2011-09-01 06:01:08 -03:00
int __init fimc_register_driver ( void )
2010-08-03 09:50:29 -03:00
{
2012-03-21 09:58:09 -03:00
return platform_driver_register ( & fimc_driver ) ;
2010-08-03 09:50:29 -03:00
}
2011-09-01 06:01:08 -03:00
void __exit fimc_unregister_driver ( void )
2010-08-03 09:50:29 -03:00
{
platform_driver_unregister ( & fimc_driver ) ;
}