2018-07-25 22:35:06 -04:00
// SPDX-License-Identifier: GPL-2.0+
2016-04-26 10:22:19 -03:00
/*
* Driver for Renesas R - Car VIN
*
* Copyright ( C ) 2016 Renesas Electronics Corp .
* Copyright ( C ) 2011 - 2013 Renesas Solutions Corp .
* Copyright ( C ) 2013 Cogent Embedded , Inc . , < source @ cogentembedded . com >
* Copyright ( C ) 2008 Magnus Damm
*
* Based on the soc - camera rcar_vin driver
*/
# include <linux/pm_runtime.h>
# include <media/v4l2-event.h>
# include <media/v4l2-ioctl.h>
2018-04-14 07:57:15 -04:00
# include <media/v4l2-mc.h>
2016-04-26 10:22:19 -03:00
# include <media/v4l2-rect.h>
# include "rcar-vin.h"
# define RVIN_DEFAULT_FORMAT V4L2_PIX_FMT_YUYV
2018-04-14 07:57:15 -04:00
# define RVIN_DEFAULT_WIDTH 800
# define RVIN_DEFAULT_HEIGHT 600
2018-04-14 07:57:04 -04:00
# define RVIN_DEFAULT_FIELD V4L2_FIELD_NONE
2018-04-14 07:57:15 -04:00
# define RVIN_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB
2016-04-26 10:22:19 -03:00
/* -----------------------------------------------------------------------------
* Format Conversions
*/
static const struct rvin_video_format rvin_formats [ ] = {
{
. fourcc = V4L2_PIX_FMT_NV16 ,
. bpp = 1 ,
} ,
{
. fourcc = V4L2_PIX_FMT_YUYV ,
. bpp = 2 ,
} ,
{
. fourcc = V4L2_PIX_FMT_UYVY ,
. bpp = 2 ,
} ,
{
. fourcc = V4L2_PIX_FMT_RGB565 ,
. bpp = 2 ,
} ,
{
. fourcc = V4L2_PIX_FMT_XRGB555 ,
. bpp = 2 ,
} ,
{
. fourcc = V4L2_PIX_FMT_XBGR32 ,
. bpp = 4 ,
} ,
} ;
const struct rvin_video_format * rvin_format_from_pixel ( u32 pixelformat )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( rvin_formats ) ; i + + )
if ( rvin_formats [ i ] . fourcc = = pixelformat )
return rvin_formats + i ;
return NULL ;
}
static u32 rvin_format_bytesperline ( struct v4l2_pix_format * pix )
{
const struct rvin_video_format * fmt ;
fmt = rvin_format_from_pixel ( pix - > pixelformat ) ;
if ( WARN_ON ( ! fmt ) )
return - EINVAL ;
return pix - > width * fmt - > bpp ;
}
static u32 rvin_format_sizeimage ( struct v4l2_pix_format * pix )
{
if ( pix - > pixelformat = = V4L2_PIX_FMT_NV16 )
return pix - > bytesperline * pix - > height * 2 ;
return pix - > bytesperline * pix - > height ;
}
2018-04-14 07:57:09 -04:00
static void rvin_format_align ( struct rvin_dev * vin , struct v4l2_pix_format * pix )
2018-04-14 07:57:08 -04:00
{
u32 walign ;
if ( ! rvin_format_from_pixel ( pix - > pixelformat ) | |
( vin - > info - > model = = RCAR_M1 & &
pix - > pixelformat = = V4L2_PIX_FMT_XBGR32 ) )
pix - > pixelformat = RVIN_DEFAULT_FORMAT ;
switch ( pix - > field ) {
case V4L2_FIELD_TOP :
case V4L2_FIELD_BOTTOM :
case V4L2_FIELD_NONE :
case V4L2_FIELD_INTERLACED_TB :
case V4L2_FIELD_INTERLACED_BT :
case V4L2_FIELD_INTERLACED :
break ;
case V4L2_FIELD_ALTERNATE :
/*
* Driver does not ( yet ) support outputting ALTERNATE to a
* userspace . It does support outputting INTERLACED so use
* the VIN hardware to combine the two fields .
*/
pix - > field = V4L2_FIELD_INTERLACED ;
pix - > height * = 2 ;
break ;
default :
pix - > field = RVIN_DEFAULT_FIELD ;
break ;
}
/* HW limit width to a multiple of 32 (2^5) for NV16 else 2 (2^1) */
walign = vin - > format . pixelformat = = V4L2_PIX_FMT_NV16 ? 5 : 1 ;
/* Limit to VIN capabilities */
v4l_bound_align_image ( & pix - > width , 2 , vin - > info - > max_width , walign ,
& pix - > height , 4 , vin - > info - > max_height , 2 , 0 ) ;
pix - > bytesperline = rvin_format_bytesperline ( pix ) ;
pix - > sizeimage = rvin_format_sizeimage ( pix ) ;
vin_dbg ( vin , " Format %ux%u bpl: %u size: %u \n " ,
pix - > width , pix - > height , pix - > bytesperline , pix - > sizeimage ) ;
}
2016-04-26 10:22:19 -03:00
/* -----------------------------------------------------------------------------
* V4L2
*/
2016-09-02 13:44:59 -03:00
static int rvin_reset_format ( struct rvin_dev * vin )
{
struct v4l2_subdev_format fmt = {
. which = V4L2_SUBDEV_FORMAT_ACTIVE ,
2018-06-12 05:43:23 -04:00
. pad = vin - > parallel - > source_pad ,
2016-09-02 13:44:59 -03:00
} ;
int ret ;
ret = v4l2_subdev_call ( vin_to_source ( vin ) , pad , get_fmt , NULL , & fmt ) ;
if ( ret )
return ret ;
2018-04-14 07:57:09 -04:00
v4l2_fill_pix_format ( & vin - > format , & fmt . format ) ;
rvin_format_align ( vin , & vin - > format ) ;
2016-09-02 13:44:59 -03:00
2018-04-14 07:57:09 -04:00
vin - > source . top = 0 ;
vin - > source . left = 0 ;
vin - > source . width = vin - > format . width ;
vin - > source . height = vin - > format . height ;
2016-09-02 13:44:59 -03:00
2018-04-14 07:57:09 -04:00
vin - > crop = vin - > source ;
vin - > compose = vin - > source ;
2017-05-23 21:15:24 -03:00
2016-09-02 13:44:59 -03:00
return 0 ;
}
2018-04-14 07:57:09 -04:00
static int rvin_try_format ( struct rvin_dev * vin , u32 which ,
struct v4l2_pix_format * pix ,
struct v4l2_rect * crop , struct v4l2_rect * compose )
2016-04-26 10:22:19 -03:00
{
2018-04-14 07:57:09 -04:00
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
2016-05-11 11:02:51 -03:00
struct v4l2_subdev_pad_config * pad_cfg ;
2016-04-26 10:22:19 -03:00
struct v4l2_subdev_format format = {
. which = which ,
2018-06-12 05:43:23 -04:00
. pad = vin - > parallel - > source_pad ,
2016-04-26 10:22:19 -03:00
} ;
2016-09-02 13:44:58 -03:00
enum v4l2_field field ;
2018-04-14 07:57:03 -04:00
u32 width , height ;
2016-04-26 10:22:19 -03:00
int ret ;
2016-05-11 11:02:51 -03:00
pad_cfg = v4l2_subdev_alloc_pad_config ( sd ) ;
if ( pad_cfg = = NULL )
return - ENOMEM ;
2018-04-14 07:57:09 -04:00
if ( ! rvin_format_from_pixel ( pix - > pixelformat ) | |
( vin - > info - > model = = RCAR_M1 & &
pix - > pixelformat = = V4L2_PIX_FMT_XBGR32 ) )
pix - > pixelformat = RVIN_DEFAULT_FORMAT ;
2018-04-14 07:57:11 -04:00
v4l2_fill_mbus_format ( & format . format , pix , vin - > mbus_code ) ;
2016-05-11 11:02:51 -03:00
2018-04-14 07:57:03 -04:00
/* Allow the video device to override field and to scale */
2016-09-02 13:44:58 -03:00
field = pix - > field ;
2018-04-14 07:57:03 -04:00
width = pix - > width ;
height = pix - > height ;
2016-09-02 13:44:58 -03:00
2016-08-15 12:06:31 -03:00
ret = v4l2_subdev_call ( sd , pad , set_fmt , pad_cfg , & format ) ;
if ( ret < 0 & & ret ! = - ENOIOCTLCMD )
goto done ;
2016-04-26 10:22:19 -03:00
v4l2_fill_pix_format ( pix , & format . format ) ;
2018-04-14 07:57:09 -04:00
if ( crop ) {
crop - > top = 0 ;
crop - > left = 0 ;
crop - > width = pix - > width ;
crop - > height = pix - > height ;
/*
* If source is ALTERNATE the driver will use the VIN hardware
* to INTERLACE it . The crop height then needs to be doubled .
*/
if ( pix - > field = = V4L2_FIELD_ALTERNATE )
crop - > height * = 2 ;
}
2016-04-26 10:22:19 -03:00
2018-04-14 07:57:04 -04:00
if ( field ! = V4L2_FIELD_ANY )
pix - > field = field ;
2018-04-14 07:57:03 -04:00
pix - > width = width ;
pix - > height = height ;
2018-04-14 07:57:09 -04:00
rvin_format_align ( vin , pix ) ;
2016-04-26 10:22:19 -03:00
2018-04-14 07:57:09 -04:00
if ( compose ) {
compose - > top = 0 ;
compose - > left = 0 ;
compose - > width = pix - > width ;
compose - > height = pix - > height ;
}
2016-08-15 12:06:31 -03:00
done :
2016-05-11 11:02:51 -03:00
v4l2_subdev_free_pad_config ( pad_cfg ) ;
2016-04-26 10:22:19 -03:00
2018-04-14 07:57:09 -04:00
return 0 ;
2016-04-26 10:22:19 -03:00
}
static int rvin_querycap ( struct file * file , void * priv ,
struct v4l2_capability * cap )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
strlcpy ( cap - > driver , KBUILD_MODNAME , sizeof ( cap - > driver ) ) ;
strlcpy ( cap - > card , " R_Car_VIN " , sizeof ( cap - > card ) ) ;
snprintf ( cap - > bus_info , sizeof ( cap - > bus_info ) , " platform:%s " ,
dev_name ( vin - > dev ) ) ;
return 0 ;
}
static int rvin_try_fmt_vid_cap ( struct file * file , void * priv ,
struct v4l2_format * f )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
2018-04-14 07:57:09 -04:00
return rvin_try_format ( vin , V4L2_SUBDEV_FORMAT_TRY , & f - > fmt . pix , NULL ,
NULL ) ;
2016-04-26 10:22:19 -03:00
}
static int rvin_s_fmt_vid_cap ( struct file * file , void * priv ,
struct v4l2_format * f )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
2018-04-14 07:57:09 -04:00
struct v4l2_rect crop , compose ;
2016-04-26 10:22:19 -03:00
int ret ;
if ( vb2_is_busy ( & vin - > queue ) )
return - EBUSY ;
2018-04-14 07:57:09 -04:00
ret = rvin_try_format ( vin , V4L2_SUBDEV_FORMAT_ACTIVE , & f - > fmt . pix ,
& crop , & compose ) ;
2016-04-26 10:22:19 -03:00
if ( ret )
return ret ;
vin - > format = f - > fmt . pix ;
2018-04-14 07:57:09 -04:00
vin - > crop = crop ;
vin - > compose = compose ;
vin - > source = crop ;
2016-09-02 13:44:59 -03:00
2016-04-26 10:22:19 -03:00
return 0 ;
}
static int rvin_g_fmt_vid_cap ( struct file * file , void * priv ,
struct v4l2_format * f )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
f - > fmt . pix = vin - > format ;
return 0 ;
}
static int rvin_enum_fmt_vid_cap ( struct file * file , void * priv ,
struct v4l2_fmtdesc * f )
{
if ( f - > index > = ARRAY_SIZE ( rvin_formats ) )
return - EINVAL ;
f - > pixelformat = rvin_formats [ f - > index ] . fourcc ;
return 0 ;
}
static int rvin_g_selection ( struct file * file , void * fh ,
struct v4l2_selection * s )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
if ( s - > type ! = V4L2_BUF_TYPE_VIDEO_CAPTURE )
return - EINVAL ;
switch ( s - > target ) {
case V4L2_SEL_TGT_CROP_BOUNDS :
case V4L2_SEL_TGT_CROP_DEFAULT :
s - > r . left = s - > r . top = 0 ;
s - > r . width = vin - > source . width ;
s - > r . height = vin - > source . height ;
break ;
case V4L2_SEL_TGT_CROP :
s - > r = vin - > crop ;
break ;
case V4L2_SEL_TGT_COMPOSE_BOUNDS :
case V4L2_SEL_TGT_COMPOSE_DEFAULT :
s - > r . left = s - > r . top = 0 ;
s - > r . width = vin - > format . width ;
s - > r . height = vin - > format . height ;
break ;
case V4L2_SEL_TGT_COMPOSE :
s - > r = vin - > compose ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static int rvin_s_selection ( struct file * file , void * fh ,
struct v4l2_selection * s )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
const struct rvin_video_format * fmt ;
struct v4l2_rect r = s - > r ;
struct v4l2_rect max_rect ;
struct v4l2_rect min_rect = {
. width = 6 ,
. height = 2 ,
} ;
if ( s - > type ! = V4L2_BUF_TYPE_VIDEO_CAPTURE )
return - EINVAL ;
v4l2_rect_set_min_size ( & r , & min_rect ) ;
switch ( s - > target ) {
case V4L2_SEL_TGT_CROP :
/* Can't crop outside of source input */
max_rect . top = max_rect . left = 0 ;
max_rect . width = vin - > source . width ;
max_rect . height = vin - > source . height ;
v4l2_rect_map_inside ( & r , & max_rect ) ;
2018-04-12 22:18:13 -04:00
v4l_bound_align_image ( & r . width , 6 , vin - > source . width , 0 ,
& r . height , 2 , vin - > source . height , 0 , 0 ) ;
2016-04-26 10:22:19 -03:00
r . top = clamp_t ( s32 , r . top , 0 , vin - > source . height - r . height ) ;
r . left = clamp_t ( s32 , r . left , 0 , vin - > source . width - r . width ) ;
vin - > crop = s - > r = r ;
vin_dbg ( vin , " Cropped %dx%d@%d:%d of %dx%d \n " ,
2016-08-15 12:06:26 -03:00
r . width , r . height , r . left , r . top ,
vin - > source . width , vin - > source . height ) ;
2016-04-26 10:22:19 -03:00
break ;
case V4L2_SEL_TGT_COMPOSE :
/* Make sure compose rect fits inside output format */
max_rect . top = max_rect . left = 0 ;
max_rect . width = vin - > format . width ;
max_rect . height = vin - > format . height ;
v4l2_rect_map_inside ( & r , & max_rect ) ;
/*
* Composing is done by adding a offset to the buffer address ,
* the HW wants this address to be aligned to HW_BUFFER_MASK .
* Make sure the top and left values meets this requirement .
*/
while ( ( r . top * vin - > format . bytesperline ) & HW_BUFFER_MASK )
r . top - - ;
fmt = rvin_format_from_pixel ( vin - > format . pixelformat ) ;
while ( ( r . left * fmt - > bpp ) & HW_BUFFER_MASK )
r . left - - ;
vin - > compose = s - > r = r ;
vin_dbg ( vin , " Compose %dx%d@%d:%d in %dx%d \n " ,
2016-08-15 12:06:26 -03:00
r . width , r . height , r . left , r . top ,
vin - > format . width , vin - > format . height ) ;
2016-04-26 10:22:19 -03:00
break ;
default :
return - EINVAL ;
}
/* HW supports modifying configuration while running */
rvin_crop_scale_comp ( vin ) ;
return 0 ;
}
static int rvin_cropcap ( struct file * file , void * priv ,
struct v4l2_cropcap * crop )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
if ( crop - > type ! = V4L2_BUF_TYPE_VIDEO_CAPTURE )
return - EINVAL ;
2016-07-04 05:08:01 -03:00
return v4l2_subdev_call ( sd , video , g_pixelaspect , & crop - > pixelaspect ) ;
2016-04-26 10:22:19 -03:00
}
static int rvin_enum_input ( struct file * file , void * priv ,
struct v4l2_input * i )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
int ret ;
if ( i - > index ! = 0 )
return - EINVAL ;
ret = v4l2_subdev_call ( sd , video , g_input_status , & i - > status ) ;
if ( ret < 0 & & ret ! = - ENOIOCTLCMD & & ret ! = - ENODEV )
return ret ;
i - > type = V4L2_INPUT_TYPE_CAMERA ;
2016-05-11 11:02:53 -03:00
2017-05-23 21:15:27 -03:00
if ( v4l2_subdev_has_op ( sd , pad , dv_timings_cap ) ) {
2016-05-11 11:02:53 -03:00
i - > capabilities = V4L2_IN_CAP_DV_TIMINGS ;
2017-05-23 21:15:27 -03:00
i - > std = 0 ;
} else {
i - > capabilities = V4L2_IN_CAP_STD ;
i - > std = vin - > vdev . tvnorms ;
}
2016-05-11 11:02:53 -03:00
2016-04-26 10:22:19 -03:00
strlcpy ( i - > name , " Camera " , sizeof ( i - > name ) ) ;
return 0 ;
}
static int rvin_g_input ( struct file * file , void * priv , unsigned int * i )
{
* i = 0 ;
return 0 ;
}
static int rvin_s_input ( struct file * file , void * priv , unsigned int i )
{
if ( i > 0 )
return - EINVAL ;
return 0 ;
}
static int rvin_querystd ( struct file * file , void * priv , v4l2_std_id * a )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
return v4l2_subdev_call ( sd , video , querystd , a ) ;
}
static int rvin_s_std ( struct file * file , void * priv , v4l2_std_id a )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
2016-09-02 13:44:59 -03:00
int ret ;
2016-04-26 10:22:19 -03:00
2016-09-02 13:44:59 -03:00
ret = v4l2_subdev_call ( vin_to_source ( vin ) , video , s_std , a ) ;
2016-04-26 10:22:19 -03:00
if ( ret < 0 )
return ret ;
2018-04-14 07:57:10 -04:00
vin - > std = a ;
2016-04-26 10:22:19 -03:00
/* Changing the standard will change the width/height */
2016-09-02 13:44:59 -03:00
return rvin_reset_format ( vin ) ;
2016-04-26 10:22:19 -03:00
}
static int rvin_g_std ( struct file * file , void * priv , v4l2_std_id * a )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
2018-04-14 07:57:10 -04:00
if ( v4l2_subdev_has_op ( vin_to_source ( vin ) , pad , dv_timings_cap ) )
return - ENOIOCTLCMD ;
* a = vin - > std ;
return 0 ;
2016-04-26 10:22:19 -03:00
}
static int rvin_subscribe_event ( struct v4l2_fh * fh ,
const struct v4l2_event_subscription * sub )
{
switch ( sub - > type ) {
case V4L2_EVENT_SOURCE_CHANGE :
return v4l2_event_subscribe ( fh , sub , 4 , NULL ) ;
}
return v4l2_ctrl_subscribe_event ( fh , sub ) ;
}
2016-05-11 11:02:53 -03:00
static int rvin_enum_dv_timings ( struct file * file , void * priv_fh ,
2016-08-15 12:06:26 -03:00
struct v4l2_enum_dv_timings * timings )
2016-05-11 11:02:53 -03:00
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
2017-05-23 21:15:26 -03:00
int ret ;
if ( timings - > pad )
return - EINVAL ;
2016-05-11 11:02:53 -03:00
2018-06-12 05:43:23 -04:00
timings - > pad = vin - > parallel - > sink_pad ;
2016-05-11 11:02:53 -03:00
ret = v4l2_subdev_call ( sd , pad , enum_dv_timings , timings ) ;
2017-05-23 21:15:26 -03:00
timings - > pad = 0 ;
2016-05-11 11:02:53 -03:00
return ret ;
}
static int rvin_s_dv_timings ( struct file * file , void * priv_fh ,
2016-08-15 12:06:26 -03:00
struct v4l2_dv_timings * timings )
2016-05-11 11:02:53 -03:00
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
2016-08-15 12:06:27 -03:00
int ret ;
ret = v4l2_subdev_call ( sd , video , s_dv_timings , timings ) ;
if ( ret )
return ret ;
2017-05-23 21:15:25 -03:00
/* Changing the timings will change the width/height */
return rvin_reset_format ( vin ) ;
2016-05-11 11:02:53 -03:00
}
static int rvin_g_dv_timings ( struct file * file , void * priv_fh ,
2016-08-15 12:06:26 -03:00
struct v4l2_dv_timings * timings )
2016-05-11 11:02:53 -03:00
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
2016-08-15 12:06:26 -03:00
return v4l2_subdev_call ( sd , video , g_dv_timings , timings ) ;
2016-05-11 11:02:53 -03:00
}
static int rvin_query_dv_timings ( struct file * file , void * priv_fh ,
2016-08-15 12:06:26 -03:00
struct v4l2_dv_timings * timings )
2016-05-11 11:02:53 -03:00
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
2016-08-15 12:06:26 -03:00
return v4l2_subdev_call ( sd , video , query_dv_timings , timings ) ;
2016-05-11 11:02:53 -03:00
}
static int rvin_dv_timings_cap ( struct file * file , void * priv_fh ,
2016-08-15 12:06:26 -03:00
struct v4l2_dv_timings_cap * cap )
2016-05-11 11:02:53 -03:00
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
2017-05-23 21:15:26 -03:00
int ret ;
if ( cap - > pad )
return - EINVAL ;
2016-05-11 11:02:53 -03:00
2018-06-12 05:43:23 -04:00
cap - > pad = vin - > parallel - > sink_pad ;
2016-05-11 11:02:53 -03:00
ret = v4l2_subdev_call ( sd , pad , dv_timings_cap , cap ) ;
2017-05-23 21:15:26 -03:00
cap - > pad = 0 ;
2016-05-11 11:02:53 -03:00
return ret ;
}
2016-09-15 14:33:23 -03:00
static int rvin_g_edid ( struct file * file , void * fh , struct v4l2_edid * edid )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
2017-05-23 21:15:26 -03:00
int ret ;
2016-09-15 14:33:23 -03:00
if ( edid - > pad )
return - EINVAL ;
2018-06-12 05:43:23 -04:00
edid - > pad = vin - > parallel - > sink_pad ;
2016-09-15 14:33:23 -03:00
ret = v4l2_subdev_call ( sd , pad , get_edid , edid ) ;
2017-05-23 21:15:26 -03:00
edid - > pad = 0 ;
2016-09-15 14:33:23 -03:00
return ret ;
}
static int rvin_s_edid ( struct file * file , void * fh , struct v4l2_edid * edid )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
2017-05-23 21:15:26 -03:00
int ret ;
2016-09-15 14:33:23 -03:00
if ( edid - > pad )
return - EINVAL ;
2018-06-12 05:43:23 -04:00
edid - > pad = vin - > parallel - > sink_pad ;
2016-09-15 14:33:23 -03:00
ret = v4l2_subdev_call ( sd , pad , set_edid , edid ) ;
2017-05-23 21:15:26 -03:00
edid - > pad = 0 ;
2016-09-15 14:33:23 -03:00
return ret ;
}
2016-04-26 10:22:19 -03:00
static const struct v4l2_ioctl_ops rvin_ioctl_ops = {
. vidioc_querycap = rvin_querycap ,
. vidioc_try_fmt_vid_cap = rvin_try_fmt_vid_cap ,
. vidioc_g_fmt_vid_cap = rvin_g_fmt_vid_cap ,
. vidioc_s_fmt_vid_cap = rvin_s_fmt_vid_cap ,
. vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap ,
. vidioc_g_selection = rvin_g_selection ,
. vidioc_s_selection = rvin_s_selection ,
. vidioc_cropcap = rvin_cropcap ,
. vidioc_enum_input = rvin_enum_input ,
. vidioc_g_input = rvin_g_input ,
. vidioc_s_input = rvin_s_input ,
2016-05-11 11:02:53 -03:00
. vidioc_dv_timings_cap = rvin_dv_timings_cap ,
. vidioc_enum_dv_timings = rvin_enum_dv_timings ,
. vidioc_g_dv_timings = rvin_g_dv_timings ,
. vidioc_s_dv_timings = rvin_s_dv_timings ,
. vidioc_query_dv_timings = rvin_query_dv_timings ,
2016-09-15 14:33:23 -03:00
. vidioc_g_edid = rvin_g_edid ,
. vidioc_s_edid = rvin_s_edid ,
2016-04-26 10:22:19 -03:00
. vidioc_querystd = rvin_querystd ,
. vidioc_g_std = rvin_g_std ,
. vidioc_s_std = rvin_s_std ,
. vidioc_reqbufs = vb2_ioctl_reqbufs ,
. vidioc_create_bufs = vb2_ioctl_create_bufs ,
. vidioc_querybuf = vb2_ioctl_querybuf ,
. vidioc_qbuf = vb2_ioctl_qbuf ,
. vidioc_dqbuf = vb2_ioctl_dqbuf ,
. vidioc_expbuf = vb2_ioctl_expbuf ,
. vidioc_prepare_buf = vb2_ioctl_prepare_buf ,
. vidioc_streamon = vb2_ioctl_streamon ,
. vidioc_streamoff = vb2_ioctl_streamoff ,
. vidioc_log_status = v4l2_ctrl_log_status ,
. vidioc_subscribe_event = rvin_subscribe_event ,
. vidioc_unsubscribe_event = v4l2_event_unsubscribe ,
} ;
2018-04-14 07:57:15 -04:00
/* -----------------------------------------------------------------------------
* V4L2 Media Controller
*/
2018-04-14 07:57:16 -04:00
static void rvin_mc_try_format ( struct rvin_dev * vin ,
struct v4l2_pix_format * pix )
{
/*
* The V4L2 specification clearly documents the colorspace fields
* as being set by drivers for capture devices . Using the values
* supplied by userspace thus wouldn ' t comply with the API . Until
* the API is updated force fixed vaules .
*/
pix - > colorspace = RVIN_DEFAULT_COLORSPACE ;
pix - > xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT ( pix - > colorspace ) ;
pix - > ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT ( pix - > colorspace ) ;
pix - > quantization = V4L2_MAP_QUANTIZATION_DEFAULT ( true , pix - > colorspace ,
pix - > ycbcr_enc ) ;
rvin_format_align ( vin , pix ) ;
}
2018-04-14 07:57:15 -04:00
static int rvin_mc_try_fmt_vid_cap ( struct file * file , void * priv ,
struct v4l2_format * f )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
2018-04-14 07:57:16 -04:00
rvin_mc_try_format ( vin , & f - > fmt . pix ) ;
2018-04-14 07:57:15 -04:00
return 0 ;
}
static int rvin_mc_s_fmt_vid_cap ( struct file * file , void * priv ,
struct v4l2_format * f )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
if ( vb2_is_busy ( & vin - > queue ) )
return - EBUSY ;
2018-04-14 07:57:16 -04:00
rvin_mc_try_format ( vin , & f - > fmt . pix ) ;
2018-04-14 07:57:15 -04:00
vin - > format = f - > fmt . pix ;
2018-05-11 10:41:26 -04:00
vin - > crop . top = 0 ;
vin - > crop . left = 0 ;
vin - > crop . width = vin - > format . width ;
vin - > crop . height = vin - > format . height ;
vin - > compose = vin - > crop ;
2018-04-14 07:57:15 -04:00
return 0 ;
}
static int rvin_mc_enum_input ( struct file * file , void * priv ,
struct v4l2_input * i )
{
if ( i - > index ! = 0 )
return - EINVAL ;
i - > type = V4L2_INPUT_TYPE_CAMERA ;
strlcpy ( i - > name , " Camera " , sizeof ( i - > name ) ) ;
return 0 ;
}
static const struct v4l2_ioctl_ops rvin_mc_ioctl_ops = {
. vidioc_querycap = rvin_querycap ,
. vidioc_try_fmt_vid_cap = rvin_mc_try_fmt_vid_cap ,
. vidioc_g_fmt_vid_cap = rvin_g_fmt_vid_cap ,
. vidioc_s_fmt_vid_cap = rvin_mc_s_fmt_vid_cap ,
. vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap ,
. vidioc_enum_input = rvin_mc_enum_input ,
. vidioc_g_input = rvin_g_input ,
. vidioc_s_input = rvin_s_input ,
. vidioc_reqbufs = vb2_ioctl_reqbufs ,
. vidioc_create_bufs = vb2_ioctl_create_bufs ,
. vidioc_querybuf = vb2_ioctl_querybuf ,
. vidioc_qbuf = vb2_ioctl_qbuf ,
. vidioc_dqbuf = vb2_ioctl_dqbuf ,
. vidioc_expbuf = vb2_ioctl_expbuf ,
. vidioc_prepare_buf = vb2_ioctl_prepare_buf ,
. vidioc_streamon = vb2_ioctl_streamon ,
. vidioc_streamoff = vb2_ioctl_streamoff ,
. vidioc_log_status = v4l2_ctrl_log_status ,
. vidioc_subscribe_event = rvin_subscribe_event ,
. vidioc_unsubscribe_event = v4l2_event_unsubscribe ,
} ;
2016-04-26 10:22:19 -03:00
/* -----------------------------------------------------------------------------
* File Operations
*/
static int rvin_power_on ( struct rvin_dev * vin )
{
int ret ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
pm_runtime_get_sync ( vin - > v4l2_dev . dev ) ;
ret = v4l2_subdev_call ( sd , core , s_power , 1 ) ;
if ( ret < 0 & & ret ! = - ENOIOCTLCMD & & ret ! = - ENODEV )
return ret ;
return 0 ;
}
static int rvin_power_off ( struct rvin_dev * vin )
{
int ret ;
struct v4l2_subdev * sd = vin_to_source ( vin ) ;
ret = v4l2_subdev_call ( sd , core , s_power , 0 ) ;
pm_runtime_put ( vin - > v4l2_dev . dev ) ;
if ( ret < 0 & & ret ! = - ENOIOCTLCMD & & ret ! = - ENODEV )
return ret ;
return 0 ;
}
static int rvin_initialize_device ( struct file * file )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
int ret ;
struct v4l2_format f = {
. type = V4L2_BUF_TYPE_VIDEO_CAPTURE ,
. fmt . pix = {
. width = vin - > format . width ,
. height = vin - > format . height ,
. field = vin - > format . field ,
. colorspace = vin - > format . colorspace ,
. pixelformat = vin - > format . pixelformat ,
} ,
} ;
ret = rvin_power_on ( vin ) ;
if ( ret < 0 )
return ret ;
pm_runtime_enable ( & vin - > vdev . dev ) ;
ret = pm_runtime_resume ( & vin - > vdev . dev ) ;
if ( ret < 0 & & ret ! = - ENOSYS )
goto eresume ;
/*
* Try to configure with default parameters . Notice : this is the
* very first open , so , we cannot race against other calls ,
* apart from someone else calling open ( ) simultaneously , but
* . host_lock is protecting us against it .
*/
ret = rvin_s_fmt_vid_cap ( file , NULL , & f ) ;
if ( ret < 0 )
goto esfmt ;
v4l2_ctrl_handler_setup ( & vin - > ctrl_handler ) ;
return 0 ;
esfmt :
pm_runtime_disable ( & vin - > vdev . dev ) ;
eresume :
rvin_power_off ( vin ) ;
return ret ;
}
static int rvin_open ( struct file * file )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
int ret ;
mutex_lock ( & vin - > lock ) ;
file - > private_data = vin ;
ret = v4l2_fh_open ( file ) ;
if ( ret )
goto unlock ;
if ( ! v4l2_fh_is_singular_file ( file ) )
goto unlock ;
if ( rvin_initialize_device ( file ) ) {
v4l2_fh_release ( file ) ;
ret = - ENODEV ;
}
unlock :
mutex_unlock ( & vin - > lock ) ;
return ret ;
}
static int rvin_release ( struct file * file )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
bool fh_singular ;
int ret ;
mutex_lock ( & vin - > lock ) ;
/* Save the singular status before we call the clean-up helper */
fh_singular = v4l2_fh_is_singular_file ( file ) ;
/* the release helper will cleanup any on-going streaming */
ret = _vb2_fop_release ( file , NULL ) ;
/*
* If this was the last open file .
* Then de - initialize hw module .
*/
if ( fh_singular ) {
pm_runtime_suspend ( & vin - > vdev . dev ) ;
pm_runtime_disable ( & vin - > vdev . dev ) ;
rvin_power_off ( vin ) ;
}
mutex_unlock ( & vin - > lock ) ;
return ret ;
}
static const struct v4l2_file_operations rvin_fops = {
. owner = THIS_MODULE ,
. unlocked_ioctl = video_ioctl2 ,
. open = rvin_open ,
. release = rvin_release ,
. poll = vb2_fop_poll ,
. mmap = vb2_fop_mmap ,
. read = vb2_fop_read ,
} ;
2018-04-14 07:57:15 -04:00
/* -----------------------------------------------------------------------------
* Media controller file operations
*/
static int rvin_mc_open ( struct file * file )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
int ret ;
ret = mutex_lock_interruptible ( & vin - > lock ) ;
if ( ret )
return ret ;
ret = pm_runtime_get_sync ( vin - > dev ) ;
if ( ret < 0 )
goto err_unlock ;
ret = v4l2_pipeline_pm_use ( & vin - > vdev . entity , 1 ) ;
if ( ret < 0 )
goto err_pm ;
file - > private_data = vin ;
ret = v4l2_fh_open ( file ) ;
if ( ret )
goto err_v4l2pm ;
mutex_unlock ( & vin - > lock ) ;
return 0 ;
err_v4l2pm :
v4l2_pipeline_pm_use ( & vin - > vdev . entity , 0 ) ;
err_pm :
pm_runtime_put ( vin - > dev ) ;
err_unlock :
mutex_unlock ( & vin - > lock ) ;
return ret ;
}
static int rvin_mc_release ( struct file * file )
{
struct rvin_dev * vin = video_drvdata ( file ) ;
int ret ;
mutex_lock ( & vin - > lock ) ;
/* the release helper will cleanup any on-going streaming. */
ret = _vb2_fop_release ( file , NULL ) ;
v4l2_pipeline_pm_use ( & vin - > vdev . entity , 0 ) ;
pm_runtime_put ( vin - > dev ) ;
mutex_unlock ( & vin - > lock ) ;
return ret ;
}
static const struct v4l2_file_operations rvin_mc_fops = {
. owner = THIS_MODULE ,
. unlocked_ioctl = video_ioctl2 ,
. open = rvin_mc_open ,
. release = rvin_mc_release ,
. poll = vb2_fop_poll ,
. mmap = vb2_fop_mmap ,
. read = vb2_fop_read ,
} ;
2018-04-14 07:56:57 -04:00
void rvin_v4l2_unregister ( struct rvin_dev * vin )
2016-04-26 10:22:19 -03:00
{
2018-04-14 07:56:58 -04:00
if ( ! video_is_registered ( & vin - > vdev ) )
return ;
2016-04-26 10:22:19 -03:00
v4l2_info ( & vin - > v4l2_dev , " Removing %s \n " ,
video_device_node_name ( & vin - > vdev ) ) ;
/* Checks internaly if vdev have been init or not */
video_unregister_device ( & vin - > vdev ) ;
}
static void rvin_notify ( struct v4l2_subdev * sd ,
unsigned int notification , void * arg )
{
struct rvin_dev * vin =
container_of ( sd - > v4l2_dev , struct rvin_dev , v4l2_dev ) ;
switch ( notification ) {
case V4L2_DEVICE_NOTIFY_EVENT :
v4l2_event_queue ( & vin - > vdev , arg ) ;
break ;
default :
break ;
}
}
2018-04-14 07:56:57 -04:00
int rvin_v4l2_register ( struct rvin_dev * vin )
2016-04-26 10:22:19 -03:00
{
struct video_device * vdev = & vin - > vdev ;
2017-05-23 21:15:29 -03:00
int ret ;
2016-04-26 10:22:19 -03:00
vin - > v4l2_dev . notify = rvin_notify ;
/* video node */
vdev - > v4l2_dev = & vin - > v4l2_dev ;
vdev - > queue = & vin - > queue ;
2018-04-14 07:57:19 -04:00
snprintf ( vdev - > name , sizeof ( vdev - > name ) , " VIN%u output " , vin - > id ) ;
2016-04-26 10:22:19 -03:00
vdev - > release = video_device_release_empty ;
vdev - > lock = & vin - > lock ;
vdev - > device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE ;
2018-04-14 07:57:15 -04:00
/* Set a default format */
2016-04-26 10:22:19 -03:00
vin - > format . pixelformat = RVIN_DEFAULT_FORMAT ;
2018-04-14 07:57:15 -04:00
vin - > format . width = RVIN_DEFAULT_WIDTH ;
vin - > format . height = RVIN_DEFAULT_HEIGHT ;
vin - > format . field = RVIN_DEFAULT_FIELD ;
vin - > format . colorspace = RVIN_DEFAULT_COLORSPACE ;
if ( vin - > info - > use_mc ) {
vdev - > fops = & rvin_mc_fops ;
vdev - > ioctl_ops = & rvin_mc_ioctl_ops ;
} else {
vdev - > fops = & rvin_fops ;
vdev - > ioctl_ops = & rvin_ioctl_ops ;
rvin_reset_format ( vin ) ;
}
rvin_format_align ( vin , & vin - > format ) ;
2016-04-26 10:22:19 -03:00
ret = video_register_device ( & vin - > vdev , VFL_TYPE_GRABBER , - 1 ) ;
if ( ret ) {
vin_err ( vin , " Failed to register video device \n " ) ;
return ret ;
}
video_set_drvdata ( & vin - > vdev , vin ) ;
v4l2_info ( & vin - > v4l2_dev , " Device registered as %s \n " ,
video_device_node_name ( & vin - > vdev ) ) ;
return ret ;
}