2016-04-26 16: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
*
* 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/of.h>
# include <linux/of_device.h>
# include <linux/of_graph.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <media/v4l2-of.h>
# include "rcar-vin.h"
/* -----------------------------------------------------------------------------
* Async notifier
*/
# define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier)
2016-08-15 18:06:35 +03:00
static bool rvin_mbus_supported ( struct rvin_graph_entity * entity )
2016-04-26 16:22:19 +03:00
{
2016-08-15 18:06:35 +03:00
struct v4l2_subdev * sd = entity - > subdev ;
2016-04-26 16:22:19 +03:00
struct v4l2_subdev_mbus_code_enum code = {
. which = V4L2_SUBDEV_FORMAT_ACTIVE ,
} ;
code . index = 0 ;
while ( ! v4l2_subdev_call ( sd , pad , enum_mbus_code , NULL , & code ) ) {
code . index + + ;
switch ( code . code ) {
case MEDIA_BUS_FMT_YUYV8_1X16 :
2016-09-02 18:37:06 +03:00
case MEDIA_BUS_FMT_UYVY8_2X8 :
case MEDIA_BUS_FMT_UYVY10_2X10 :
2016-04-26 16:22:19 +03:00
case MEDIA_BUS_FMT_RGB888_1X24 :
2016-08-15 18:06:35 +03:00
entity - > code = code . code ;
2016-04-26 16:22:19 +03:00
return true ;
default :
break ;
}
}
return false ;
}
2016-08-15 18:06:29 +03:00
static int rvin_digital_notify_complete ( struct v4l2_async_notifier * notifier )
2016-04-26 16:22:19 +03:00
{
struct rvin_dev * vin = notifier_to_vin ( notifier ) ;
int ret ;
2016-08-15 18:06:34 +03:00
/* Verify subdevices mbus format */
2016-08-15 18:06:35 +03:00
if ( ! rvin_mbus_supported ( & vin - > digital ) ) {
2016-08-15 18:06:34 +03:00
vin_err ( vin , " Unsupported media bus format for %s \n " ,
vin - > digital . subdev - > name ) ;
return - EINVAL ;
}
vin_dbg ( vin , " Found media bus format for %s: %d \n " ,
2016-08-15 18:06:35 +03:00
vin - > digital . subdev - > name , vin - > digital . code ) ;
2016-08-15 18:06:34 +03:00
2016-04-26 16:22:19 +03:00
ret = v4l2_device_register_subdev_nodes ( & vin - > v4l2_dev ) ;
if ( ret < 0 ) {
vin_err ( vin , " Failed to register subdev nodes \n " ) ;
return ret ;
}
return rvin_v4l2_probe ( vin ) ;
}
2016-08-15 18:06:29 +03:00
static void rvin_digital_notify_unbind ( struct v4l2_async_notifier * notifier ,
struct v4l2_subdev * subdev ,
struct v4l2_async_subdev * asd )
2016-04-26 16:22:19 +03:00
{
struct rvin_dev * vin = notifier_to_vin ( notifier ) ;
2016-08-15 18:06:34 +03:00
if ( vin - > digital . subdev = = subdev ) {
vin_dbg ( vin , " unbind digital subdev %s \n " , subdev - > name ) ;
rvin_v4l2_remove ( vin ) ;
vin - > digital . subdev = NULL ;
return ;
}
vin_err ( vin , " no entity for subdev %s to unbind \n " , subdev - > name ) ;
2016-04-26 16:22:19 +03:00
}
2016-08-15 18:06:29 +03:00
static int rvin_digital_notify_bound ( struct v4l2_async_notifier * notifier ,
struct v4l2_subdev * subdev ,
struct v4l2_async_subdev * asd )
2016-04-26 16:22:19 +03:00
{
struct rvin_dev * vin = notifier_to_vin ( notifier ) ;
2016-08-15 18:06:34 +03:00
v4l2_set_subdev_hostdata ( subdev , vin ) ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
if ( vin - > digital . asd . match . of . node = = subdev - > dev - > of_node ) {
vin_dbg ( vin , " bound digital subdev %s \n " , subdev - > name ) ;
vin - > digital . subdev = subdev ;
return 0 ;
}
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
vin_err ( vin , " no entity for subdev %s to bind \n " , subdev - > name ) ;
return - EINVAL ;
2016-04-26 16:22:19 +03:00
}
2016-08-15 18:06:34 +03:00
static int rvin_digitial_parse_v4l2 ( struct rvin_dev * vin ,
struct device_node * ep ,
struct v4l2_mbus_config * mbus_cfg )
2016-04-26 16:22:19 +03:00
{
2016-08-15 18:06:34 +03:00
struct v4l2_of_endpoint v4l2_ep ;
int ret ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
ret = v4l2_of_parse_endpoint ( ep , & v4l2_ep ) ;
if ( ret ) {
vin_err ( vin , " Could not parse v4l2 endpoint \n " ) ;
return - EINVAL ;
}
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
mbus_cfg - > type = v4l2_ep . bus_type ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
switch ( mbus_cfg - > type ) {
case V4L2_MBUS_PARALLEL :
vin_dbg ( vin , " Found PARALLEL media bus \n " ) ;
mbus_cfg - > flags = v4l2_ep . bus . parallel . flags ;
break ;
case V4L2_MBUS_BT656 :
vin_dbg ( vin , " Found BT656 media bus \n " ) ;
mbus_cfg - > flags = 0 ;
break ;
default :
vin_err ( vin , " Unknown media bus type \n " ) ;
return - EINVAL ;
}
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
return 0 ;
}
static int rvin_digital_graph_parse ( struct rvin_dev * vin )
{
struct device_node * ep , * np ;
int ret ;
vin - > digital . asd . match . of . node = NULL ;
vin - > digital . subdev = NULL ;
/*
* Port 0 id 0 is local digital input , try to get it .
* Not all instances can or will have this , that is OK
*/
ep = of_graph_get_endpoint_by_regs ( vin - > dev - > of_node , 0 , 0 ) ;
if ( ! ep )
return 0 ;
np = of_graph_get_remote_port_parent ( ep ) ;
if ( ! np ) {
vin_err ( vin , " No remote parent for digital input \n " ) ;
of_node_put ( ep ) ;
return - EINVAL ;
2016-04-26 16:22:19 +03:00
}
2016-08-15 18:06:34 +03:00
of_node_put ( np ) ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:35 +03:00
ret = rvin_digitial_parse_v4l2 ( vin , ep , & vin - > digital . mbus_cfg ) ;
2016-04-26 16:22:19 +03:00
of_node_put ( ep ) ;
2016-08-15 18:06:34 +03:00
if ( ret )
return ret ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
vin - > digital . asd . match . of . node = np ;
vin - > digital . asd . match_type = V4L2_ASYNC_MATCH_OF ;
return 0 ;
2016-04-26 16:22:19 +03:00
}
2016-08-15 18:06:34 +03:00
static int rvin_digital_graph_init ( struct rvin_dev * vin )
2016-04-26 16:22:19 +03:00
{
struct v4l2_async_subdev * * subdevs = NULL ;
int ret ;
2016-08-15 18:06:34 +03:00
ret = rvin_digital_graph_parse ( vin ) ;
if ( ret )
return ret ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
if ( ! vin - > digital . asd . match . of . node ) {
vin_dbg ( vin , " No digital subdevice found \n " ) ;
return - ENODEV ;
2016-04-26 16:22:19 +03:00
}
/* Register the subdevices notifier. */
subdevs = devm_kzalloc ( vin - > dev , sizeof ( * subdevs ) , GFP_KERNEL ) ;
2016-08-15 18:06:34 +03:00
if ( subdevs = = NULL )
return - ENOMEM ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:29 +03:00
subdevs [ 0 ] = & vin - > digital . asd ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
vin_dbg ( vin , " Found digital subdevice %s \n " ,
of_node_full_name ( subdevs [ 0 ] - > match . of . node ) ) ;
2016-04-26 16:22:19 +03:00
vin - > notifier . num_subdevs = 1 ;
2016-08-15 18:06:34 +03:00
vin - > notifier . subdevs = subdevs ;
2016-08-15 18:06:29 +03:00
vin - > notifier . bound = rvin_digital_notify_bound ;
vin - > notifier . unbind = rvin_digital_notify_unbind ;
vin - > notifier . complete = rvin_digital_notify_complete ;
2016-04-26 16:22:19 +03:00
ret = v4l2_async_notifier_register ( & vin - > v4l2_dev , & vin - > notifier ) ;
if ( ret < 0 ) {
vin_err ( vin , " Notifier registration failed \n " ) ;
2016-08-15 18:06:34 +03:00
return ret ;
2016-04-26 16:22:19 +03:00
}
2016-08-15 18:06:34 +03:00
return 0 ;
2016-04-26 16:22:19 +03:00
}
/* -----------------------------------------------------------------------------
* Platform Device Driver
*/
static const struct of_device_id rvin_of_id_table [ ] = {
{ . compatible = " renesas,vin-r8a7794 " , . data = ( void * ) RCAR_GEN2 } ,
{ . compatible = " renesas,vin-r8a7793 " , . data = ( void * ) RCAR_GEN2 } ,
{ . compatible = " renesas,vin-r8a7791 " , . data = ( void * ) RCAR_GEN2 } ,
{ . compatible = " renesas,vin-r8a7790 " , . data = ( void * ) RCAR_GEN2 } ,
{ . compatible = " renesas,vin-r8a7779 " , . data = ( void * ) RCAR_H1 } ,
{ . compatible = " renesas,vin-r8a7778 " , . data = ( void * ) RCAR_M1 } ,
2016-07-25 22:19:33 +03:00
{ . compatible = " renesas,rcar-gen2-vin " , . data = ( void * ) RCAR_GEN2 } ,
2016-04-26 16:22:19 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , rvin_of_id_table ) ;
static int rcar_vin_probe ( struct platform_device * pdev )
{
2016-08-15 18:06:34 +03:00
const struct of_device_id * match ;
2016-04-26 16:22:19 +03:00
struct rvin_dev * vin ;
struct resource * mem ;
int irq , ret ;
vin = devm_kzalloc ( & pdev - > dev , sizeof ( * vin ) , GFP_KERNEL ) ;
if ( ! vin )
return - ENOMEM ;
2016-08-15 18:06:34 +03:00
match = of_match_device ( of_match_ptr ( rvin_of_id_table ) , & pdev - > dev ) ;
if ( ! match )
return - ENODEV ;
2016-04-26 16:22:19 +03:00
2016-08-15 18:06:34 +03:00
vin - > dev = & pdev - > dev ;
vin - > chip = ( enum chip_id ) match - > data ;
2016-04-26 16:22:19 +03:00
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( mem = = NULL )
return - EINVAL ;
vin - > base = devm_ioremap_resource ( vin - > dev , mem ) ;
if ( IS_ERR ( vin - > base ) )
return PTR_ERR ( vin - > base ) ;
irq = platform_get_irq ( pdev , 0 ) ;
2016-08-15 18:06:30 +03:00
if ( irq < 0 )
return irq ;
2016-04-26 16:22:19 +03:00
ret = rvin_dma_probe ( vin , irq ) ;
if ( ret )
return ret ;
2016-08-15 18:06:34 +03:00
ret = rvin_digital_graph_init ( vin ) ;
2016-04-26 16:22:19 +03:00
if ( ret < 0 )
goto error ;
pm_suspend_ignore_children ( & pdev - > dev , true ) ;
pm_runtime_enable ( & pdev - > dev ) ;
platform_set_drvdata ( pdev , vin ) ;
return 0 ;
error :
rvin_dma_remove ( vin ) ;
return ret ;
}
static int rcar_vin_remove ( struct platform_device * pdev )
{
struct rvin_dev * vin = platform_get_drvdata ( pdev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
v4l2_async_notifier_unregister ( & vin - > notifier ) ;
rvin_dma_remove ( vin ) ;
return 0 ;
}
static struct platform_driver rcar_vin_driver = {
. driver = {
. name = " rcar-vin " ,
. of_match_table = rvin_of_id_table ,
} ,
. probe = rcar_vin_probe ,
. remove = rcar_vin_remove ,
} ;
module_platform_driver ( rcar_vin_driver ) ;
MODULE_AUTHOR ( " Niklas Söderlund <niklas.soderlund@ragnatech.se> " ) ;
MODULE_DESCRIPTION ( " Renesas R-Car VIN camera host driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;