2017-06-19 14:00:20 -03:00
/*
* vimc - scaler . c Virtual Media Controller Driver
*
* Copyright ( C ) 2015 - 2017 Helen Koike < helen . fornazier @ gmail . com >
*
* 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 .
*
*/
# include <linux/component.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/vmalloc.h>
# include <linux/v4l2-mediabus.h>
# include <media/v4l2-subdev.h>
# include "vimc-common.h"
# define VIMC_SCA_DRV_NAME "vimc-scaler"
static unsigned int sca_mult = 3 ;
module_param ( sca_mult , uint , 0000 ) ;
MODULE_PARM_DESC ( sca_mult , " the image size multiplier " ) ;
# define IS_SINK(pad) (!pad)
# define IS_SRC(pad) (pad)
# define MAX_ZOOM 8
struct vimc_sca_device {
struct vimc_ent_device ved ;
struct v4l2_subdev sd ;
struct device * dev ;
/* NOTE: the source fmt is the same as the sink
* with the width and hight multiplied by mult
*/
struct v4l2_mbus_framefmt sink_fmt ;
/* Values calculated when the stream starts */
u8 * src_frame ;
unsigned int src_line_size ;
unsigned int bpp ;
} ;
static const struct v4l2_mbus_framefmt sink_fmt_default = {
. width = 640 ,
. height = 480 ,
. code = MEDIA_BUS_FMT_RGB888_1X24 ,
. field = V4L2_FIELD_NONE ,
. colorspace = V4L2_COLORSPACE_DEFAULT ,
} ;
static int vimc_sca_init_cfg ( struct v4l2_subdev * sd ,
struct v4l2_subdev_pad_config * cfg )
{
struct v4l2_mbus_framefmt * mf ;
unsigned int i ;
mf = v4l2_subdev_get_try_format ( sd , cfg , 0 ) ;
* mf = sink_fmt_default ;
for ( i = 1 ; i < sd - > entity . num_pads ; i + + ) {
mf = v4l2_subdev_get_try_format ( sd , cfg , i ) ;
* mf = sink_fmt_default ;
mf - > width = mf - > width * sca_mult ;
mf - > height = mf - > height * sca_mult ;
}
return 0 ;
}
static int vimc_sca_enum_mbus_code ( struct v4l2_subdev * sd ,
struct v4l2_subdev_pad_config * cfg ,
struct v4l2_subdev_mbus_code_enum * code )
{
const struct vimc_pix_map * vpix = vimc_pix_map_by_index ( code - > index ) ;
/* We don't support bayer format */
if ( ! vpix | | vpix - > bayer )
return - EINVAL ;
code - > code = vpix - > code ;
return 0 ;
}
static int vimc_sca_enum_frame_size ( struct v4l2_subdev * sd ,
struct v4l2_subdev_pad_config * cfg ,
struct v4l2_subdev_frame_size_enum * fse )
{
const struct vimc_pix_map * vpix ;
if ( fse - > index )
return - EINVAL ;
/* Only accept code in the pix map table in non bayer format */
vpix = vimc_pix_map_by_code ( fse - > code ) ;
if ( ! vpix | | vpix - > bayer )
return - EINVAL ;
fse - > min_width = VIMC_FRAME_MIN_WIDTH ;
fse - > min_height = VIMC_FRAME_MIN_HEIGHT ;
if ( IS_SINK ( fse - > pad ) ) {
fse - > max_width = VIMC_FRAME_MAX_WIDTH ;
fse - > max_height = VIMC_FRAME_MAX_HEIGHT ;
} else {
fse - > max_width = VIMC_FRAME_MAX_WIDTH * MAX_ZOOM ;
fse - > max_height = VIMC_FRAME_MAX_HEIGHT * MAX_ZOOM ;
}
return 0 ;
}
static int vimc_sca_get_fmt ( struct v4l2_subdev * sd ,
struct v4l2_subdev_pad_config * cfg ,
struct v4l2_subdev_format * format )
{
struct vimc_sca_device * vsca = v4l2_get_subdevdata ( sd ) ;
/* Get the current sink format */
format - > format = ( format - > which = = V4L2_SUBDEV_FORMAT_TRY ) ?
* v4l2_subdev_get_try_format ( sd , cfg , 0 ) :
vsca - > sink_fmt ;
/* Scale the frame size for the source pad */
if ( IS_SRC ( format - > pad ) ) {
format - > format . width = vsca - > sink_fmt . width * sca_mult ;
format - > format . height = vsca - > sink_fmt . height * sca_mult ;
}
return 0 ;
}
static void vimc_sca_adjust_sink_fmt ( struct v4l2_mbus_framefmt * fmt )
{
const struct vimc_pix_map * vpix ;
/* Only accept code in the pix map table in non bayer format */
vpix = vimc_pix_map_by_code ( fmt - > code ) ;
if ( ! vpix | | vpix - > bayer )
fmt - > code = sink_fmt_default . code ;
fmt - > width = clamp_t ( u32 , fmt - > width , VIMC_FRAME_MIN_WIDTH ,
VIMC_FRAME_MAX_WIDTH ) & ~ 1 ;
fmt - > height = clamp_t ( u32 , fmt - > height , VIMC_FRAME_MIN_HEIGHT ,
VIMC_FRAME_MAX_HEIGHT ) & ~ 1 ;
if ( fmt - > field = = V4L2_FIELD_ANY )
fmt - > field = sink_fmt_default . field ;
vimc_colorimetry_clamp ( fmt ) ;
}
static int vimc_sca_set_fmt ( struct v4l2_subdev * sd ,
struct v4l2_subdev_pad_config * cfg ,
struct v4l2_subdev_format * fmt )
{
struct vimc_sca_device * vsca = v4l2_get_subdevdata ( sd ) ;
struct v4l2_mbus_framefmt * sink_fmt ;
if ( fmt - > which = = V4L2_SUBDEV_FORMAT_ACTIVE ) {
/* Do not change the format while stream is on */
if ( vsca - > src_frame )
return - EBUSY ;
sink_fmt = & vsca - > sink_fmt ;
} else {
sink_fmt = v4l2_subdev_get_try_format ( sd , cfg , 0 ) ;
}
/*
* Do not change the format of the source pad ,
* it is propagated from the sink
*/
if ( IS_SRC ( fmt - > pad ) ) {
fmt - > format = * sink_fmt ;
fmt - > format . width = sink_fmt - > width * sca_mult ;
fmt - > format . height = sink_fmt - > height * sca_mult ;
} else {
/* Set the new format in the sink pad */
vimc_sca_adjust_sink_fmt ( & fmt - > format ) ;
dev_dbg ( vsca - > dev , " %s: sink format update: "
" old:%dx%d (0x%x, %d, %d, %d, %d) "
" new:%dx%d (0x%x, %d, %d, %d, %d) \n " , vsca - > sd . name ,
/* old */
sink_fmt - > width , sink_fmt - > height , sink_fmt - > code ,
sink_fmt - > colorspace , sink_fmt - > quantization ,
sink_fmt - > xfer_func , sink_fmt - > ycbcr_enc ,
/* new */
fmt - > format . width , fmt - > format . height , fmt - > format . code ,
fmt - > format . colorspace , fmt - > format . quantization ,
fmt - > format . xfer_func , fmt - > format . ycbcr_enc ) ;
* sink_fmt = fmt - > format ;
}
return 0 ;
}
static const struct v4l2_subdev_pad_ops vimc_sca_pad_ops = {
. init_cfg = vimc_sca_init_cfg ,
. enum_mbus_code = vimc_sca_enum_mbus_code ,
. enum_frame_size = vimc_sca_enum_frame_size ,
. get_fmt = vimc_sca_get_fmt ,
. set_fmt = vimc_sca_set_fmt ,
} ;
static int vimc_sca_s_stream ( struct v4l2_subdev * sd , int enable )
{
struct vimc_sca_device * vsca = v4l2_get_subdevdata ( sd ) ;
int ret ;
if ( enable ) {
const struct vimc_pix_map * vpix ;
unsigned int frame_size ;
if ( vsca - > src_frame )
return 0 ;
/* Save the bytes per pixel of the sink */
vpix = vimc_pix_map_by_code ( vsca - > sink_fmt . code ) ;
vsca - > bpp = vpix - > bpp ;
/* Calculate the width in bytes of the src frame */
vsca - > src_line_size = vsca - > sink_fmt . width *
sca_mult * vsca - > bpp ;
/* Calculate the frame size of the source pad */
frame_size = vsca - > src_line_size * vsca - > sink_fmt . height *
sca_mult ;
/* Allocate the frame buffer. Use vmalloc to be able to
* allocate a large amount of memory
*/
vsca - > src_frame = vmalloc ( frame_size ) ;
if ( ! vsca - > src_frame )
return - ENOMEM ;
/* Turn the stream on in the subdevices directly connected */
ret = vimc_pipeline_s_stream ( & vsca - > sd . entity , 1 ) ;
if ( ret ) {
vfree ( vsca - > src_frame ) ;
vsca - > src_frame = NULL ;
return ret ;
}
} else {
if ( ! vsca - > src_frame )
return 0 ;
/* Disable streaming from the pipe */
ret = vimc_pipeline_s_stream ( & vsca - > sd . entity , 0 ) ;
if ( ret )
return ret ;
vfree ( vsca - > src_frame ) ;
vsca - > src_frame = NULL ;
}
return 0 ;
}
2017-08-08 06:58:28 -04:00
static const struct v4l2_subdev_video_ops vimc_sca_video_ops = {
2017-06-19 14:00:20 -03:00
. s_stream = vimc_sca_s_stream ,
} ;
static const struct v4l2_subdev_ops vimc_sca_ops = {
. pad = & vimc_sca_pad_ops ,
. video = & vimc_sca_video_ops ,
} ;
static void vimc_sca_fill_pix ( u8 * const ptr ,
const u8 * const pixel ,
const unsigned int bpp )
{
unsigned int i ;
/* copy the pixel to the pointer */
for ( i = 0 ; i < bpp ; i + + )
ptr [ i ] = pixel [ i ] ;
}
static void vimc_sca_scale_pix ( const struct vimc_sca_device * const vsca ,
const unsigned int lin , const unsigned int col ,
const u8 * const sink_frame )
{
unsigned int i , j , index ;
const u8 * pixel ;
/* Point to the pixel value in position (lin, col) in the sink frame */
index = VIMC_FRAME_INDEX ( lin , col ,
vsca - > sink_fmt . width ,
vsca - > bpp ) ;
pixel = & sink_frame [ index ] ;
dev_dbg ( vsca - > dev ,
" sca: %s: --- scale_pix sink pos %dx%d, index %d --- \n " ,
vsca - > sd . name , lin , col , index ) ;
/* point to the place we are going to put the first pixel
* in the scaled src frame
*/
index = VIMC_FRAME_INDEX ( lin * sca_mult , col * sca_mult ,
vsca - > sink_fmt . width * sca_mult , vsca - > bpp ) ;
dev_dbg ( vsca - > dev , " sca: %s: scale_pix src pos %dx%d, index %d \n " ,
vsca - > sd . name , lin * sca_mult , col * sca_mult , index ) ;
/* Repeat this pixel mult times */
for ( i = 0 ; i < sca_mult ; i + + ) {
/* Iterate through each beginning of a
* pixel repetition in a line
*/
for ( j = 0 ; j < sca_mult * vsca - > bpp ; j + = vsca - > bpp ) {
dev_dbg ( vsca - > dev ,
" sca: %s: sca: scale_pix src pos %d \n " ,
vsca - > sd . name , index + j ) ;
/* copy the pixel to the position index + j */
vimc_sca_fill_pix ( & vsca - > src_frame [ index + j ] ,
pixel , vsca - > bpp ) ;
}
/* move the index to the next line */
index + = vsca - > src_line_size ;
}
}
static void vimc_sca_fill_src_frame ( const struct vimc_sca_device * const vsca ,
const u8 * const sink_frame )
{
unsigned int i , j ;
/* Scale each pixel from the original sink frame */
/* TODO: implement scale down, only scale up is supported for now */
for ( i = 0 ; i < vsca - > sink_fmt . height ; i + + )
for ( j = 0 ; j < vsca - > sink_fmt . width ; j + + )
vimc_sca_scale_pix ( vsca , i , j , sink_frame ) ;
}
static void vimc_sca_process_frame ( struct vimc_ent_device * ved ,
struct media_pad * sink ,
const void * sink_frame )
{
struct vimc_sca_device * vsca = container_of ( ved , struct vimc_sca_device ,
ved ) ;
unsigned int i ;
/* If the stream in this node is not active, just return */
if ( ! vsca - > src_frame )
return ;
vimc_sca_fill_src_frame ( vsca , sink_frame ) ;
/* Propagate the frame through all source pads */
for ( i = 1 ; i < vsca - > sd . entity . num_pads ; i + + ) {
struct media_pad * pad = & vsca - > sd . entity . pads [ i ] ;
vimc_propagate_frame ( pad , vsca - > src_frame ) ;
}
} ;
static void vimc_sca_comp_unbind ( struct device * comp , struct device * master ,
void * master_data )
{
struct vimc_ent_device * ved = dev_get_drvdata ( comp ) ;
struct vimc_sca_device * vsca = container_of ( ved , struct vimc_sca_device ,
ved ) ;
vimc_ent_sd_unregister ( ved , & vsca - > sd ) ;
kfree ( vsca ) ;
}
static int vimc_sca_comp_bind ( struct device * comp , struct device * master ,
void * master_data )
{
struct v4l2_device * v4l2_dev = master_data ;
struct vimc_platform_data * pdata = comp - > platform_data ;
struct vimc_sca_device * vsca ;
int ret ;
/* Allocate the vsca struct */
vsca = kzalloc ( sizeof ( * vsca ) , GFP_KERNEL ) ;
if ( ! vsca )
return - ENOMEM ;
/* Initialize ved and sd */
ret = vimc_ent_sd_register ( & vsca - > ved , & vsca - > sd , v4l2_dev ,
pdata - > entity_name ,
MEDIA_ENT_F_ATV_DECODER , 2 ,
( const unsigned long [ 2 ] ) { MEDIA_PAD_FL_SINK ,
MEDIA_PAD_FL_SOURCE } ,
& vimc_sca_ops ) ;
if ( ret ) {
kfree ( vsca ) ;
return ret ;
}
vsca - > ved . process_frame = vimc_sca_process_frame ;
dev_set_drvdata ( comp , & vsca - > ved ) ;
vsca - > dev = comp ;
/* Initialize the frame format */
vsca - > sink_fmt = sink_fmt_default ;
return 0 ;
}
static const struct component_ops vimc_sca_comp_ops = {
. bind = vimc_sca_comp_bind ,
. unbind = vimc_sca_comp_unbind ,
} ;
static int vimc_sca_probe ( struct platform_device * pdev )
{
return component_add ( & pdev - > dev , & vimc_sca_comp_ops ) ;
}
static int vimc_sca_remove ( struct platform_device * pdev )
{
component_del ( & pdev - > dev , & vimc_sca_comp_ops ) ;
return 0 ;
}
2017-07-14 05:58:39 -03:00
static const struct platform_device_id vimc_sca_driver_ids [ ] = {
{
. name = VIMC_SCA_DRV_NAME ,
} ,
{ }
} ;
2017-06-19 14:00:20 -03:00
static struct platform_driver vimc_sca_pdrv = {
. probe = vimc_sca_probe ,
. remove = vimc_sca_remove ,
2017-07-14 05:58:39 -03:00
. id_table = vimc_sca_driver_ids ,
2017-06-19 14:00:20 -03:00
. driver = {
. name = VIMC_SCA_DRV_NAME ,
} ,
} ;
module_platform_driver ( vimc_sca_pdrv ) ;
MODULE_DEVICE_TABLE ( platform , vimc_sca_driver_ids ) ;
MODULE_DESCRIPTION ( " Virtual Media Controller Driver (VIMC) Scaler " ) ;
MODULE_AUTHOR ( " Helen Mae Koike Fornazier <helen.fornazier@gmail.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;