2010-07-26 11:12:43 -03:00
/*
* Driver for the SH - Mobile MIPI CSI - 2 unit
*
* Copyright ( C ) 2010 , Guennadi Liakhovetski < g . liakhovetski @ gmx . de >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/delay.h>
# include <linux/i2c.h>
# include <linux/io.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <linux/slab.h>
# include <linux/videodev2.h>
2011-07-03 14:03:12 -04:00
# include <linux/module.h>
2010-07-26 11:12:43 -03:00
2011-07-01 11:19:58 -03:00
# include <media/sh_mobile_ceu.h>
2010-07-26 11:12:43 -03:00
# include <media/sh_mobile_csi2.h>
# include <media/soc_camera.h>
2011-07-26 13:13:47 -03:00
# include <media/soc_mediabus.h>
2010-07-26 11:12:43 -03:00
# include <media/v4l2-common.h>
# include <media/v4l2-dev.h>
# include <media/v4l2-device.h>
# include <media/v4l2-mediabus.h>
# include <media/v4l2-subdev.h>
# define SH_CSI2_TREF 0x00
# define SH_CSI2_SRST 0x04
# define SH_CSI2_PHYCNT 0x08
# define SH_CSI2_CHKSUM 0x0C
# define SH_CSI2_VCDT 0x10
struct sh_csi2 {
struct v4l2_subdev subdev ;
struct list_head list ;
unsigned int irq ;
2011-07-26 13:19:01 -03:00
unsigned long mipi_flags ;
2010-07-26 11:12:43 -03:00
void __iomem * base ;
struct platform_device * pdev ;
struct sh_csi2_client_config * client ;
} ;
static int sh_csi2_try_fmt ( struct v4l2_subdev * sd ,
struct v4l2_mbus_framefmt * mf )
{
struct sh_csi2 * priv = container_of ( sd , struct sh_csi2 , subdev ) ;
struct sh_csi2_pdata * pdata = priv - > pdev - > dev . platform_data ;
if ( mf - > width > 8188 )
mf - > width = 8188 ;
else if ( mf - > width & 1 )
mf - > width & = ~ 1 ;
switch ( pdata - > type ) {
case SH_CSI2C :
switch ( mf - > code ) {
case V4L2_MBUS_FMT_UYVY8_2X8 : /* YUV422 */
case V4L2_MBUS_FMT_YUYV8_1_5X8 : /* YUV420 */
2010-09-28 07:01:44 -03:00
case V4L2_MBUS_FMT_Y8_1X8 : /* RAW8 */
2010-07-26 11:12:43 -03:00
case V4L2_MBUS_FMT_SBGGR8_1X8 :
case V4L2_MBUS_FMT_SGRBG8_1X8 :
break ;
default :
/* All MIPI CSI-2 devices must support one of primary formats */
mf - > code = V4L2_MBUS_FMT_YUYV8_2X8 ;
}
break ;
case SH_CSI2I :
switch ( mf - > code ) {
2010-09-28 07:01:44 -03:00
case V4L2_MBUS_FMT_Y8_1X8 : /* RAW8 */
2010-07-26 11:12:43 -03:00
case V4L2_MBUS_FMT_SBGGR8_1X8 :
case V4L2_MBUS_FMT_SGRBG8_1X8 :
case V4L2_MBUS_FMT_SBGGR10_1X10 : /* RAW10 */
case V4L2_MBUS_FMT_SBGGR12_1X12 : /* RAW12 */
break ;
default :
/* All MIPI CSI-2 devices must support one of primary formats */
mf - > code = V4L2_MBUS_FMT_SBGGR8_1X8 ;
}
break ;
}
return 0 ;
}
/*
* We have done our best in try_fmt to try and tell the sensor , which formats
* we support . If now the configuration is unsuitable for us we can only
* error out .
*/
static int sh_csi2_s_fmt ( struct v4l2_subdev * sd ,
struct v4l2_mbus_framefmt * mf )
{
struct sh_csi2 * priv = container_of ( sd , struct sh_csi2 , subdev ) ;
u32 tmp = ( priv - > client - > channel & 3 ) < < 8 ;
dev_dbg ( sd - > v4l2_dev - > dev , " %s(%u) \n " , __func__ , mf - > code ) ;
if ( mf - > width > 8188 | | mf - > width & 1 )
return - EINVAL ;
switch ( mf - > code ) {
case V4L2_MBUS_FMT_UYVY8_2X8 :
tmp | = 0x1e ; /* YUV422 8 bit */
break ;
case V4L2_MBUS_FMT_YUYV8_1_5X8 :
tmp | = 0x18 ; /* YUV420 8 bit */
break ;
case V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE :
tmp | = 0x21 ; /* RGB555 */
break ;
case V4L2_MBUS_FMT_RGB565_2X8_BE :
tmp | = 0x22 ; /* RGB565 */
break ;
2010-09-28 07:01:44 -03:00
case V4L2_MBUS_FMT_Y8_1X8 :
2010-07-26 11:12:43 -03:00
case V4L2_MBUS_FMT_SBGGR8_1X8 :
case V4L2_MBUS_FMT_SGRBG8_1X8 :
tmp | = 0x2a ; /* RAW8 */
break ;
default :
return - EINVAL ;
}
iowrite32 ( tmp , priv - > base + SH_CSI2_VCDT ) ;
return 0 ;
}
2011-07-26 13:19:01 -03:00
static int sh_csi2_g_mbus_config ( struct v4l2_subdev * sd ,
struct v4l2_mbus_config * cfg )
{
cfg - > flags = V4L2_MBUS_PCLK_SAMPLE_RISING |
V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH |
V4L2_MBUS_MASTER | V4L2_MBUS_DATA_ACTIVE_HIGH ;
cfg - > type = V4L2_MBUS_PARALLEL ;
return 0 ;
}
static int sh_csi2_s_mbus_config ( struct v4l2_subdev * sd ,
const struct v4l2_mbus_config * cfg )
{
struct sh_csi2 * priv = container_of ( sd , struct sh_csi2 , subdev ) ;
2011-12-05 16:01:13 -03:00
struct soc_camera_device * icd = v4l2_get_subdev_hostdata ( sd ) ;
2011-07-26 13:19:01 -03:00
struct v4l2_subdev * client_sd = soc_camera_to_subdev ( icd ) ;
struct v4l2_mbus_config client_cfg = { . type = V4L2_MBUS_CSI2 ,
. flags = priv - > mipi_flags } ;
return v4l2_subdev_call ( client_sd , video , s_mbus_config , & client_cfg ) ;
}
2010-07-26 11:12:43 -03:00
static struct v4l2_subdev_video_ops sh_csi2_subdev_video_ops = {
. s_mbus_fmt = sh_csi2_s_fmt ,
. try_mbus_fmt = sh_csi2_try_fmt ,
2011-07-26 13:19:01 -03:00
. g_mbus_config = sh_csi2_g_mbus_config ,
. s_mbus_config = sh_csi2_s_mbus_config ,
2010-07-26 11:12:43 -03:00
} ;
static void sh_csi2_hwinit ( struct sh_csi2 * priv )
{
struct sh_csi2_pdata * pdata = priv - > pdev - > dev . platform_data ;
__u32 tmp = 0x10 ; /* Enable MIPI CSI clock lane */
/* Reflect registers immediately */
iowrite32 ( 0x00000001 , priv - > base + SH_CSI2_TREF ) ;
/* reset CSI2 harware */
iowrite32 ( 0x00000001 , priv - > base + SH_CSI2_SRST ) ;
udelay ( 5 ) ;
iowrite32 ( 0x00000000 , priv - > base + SH_CSI2_SRST ) ;
2011-07-26 13:13:47 -03:00
switch ( pdata - > type ) {
case SH_CSI2C :
if ( priv - > client - > lanes = = 1 )
tmp | = 1 ;
else
/* Default - both lanes */
tmp | = 3 ;
break ;
case SH_CSI2I :
if ( ! priv - > client - > lanes | | priv - > client - > lanes > 4 )
/* Default - all 4 lanes */
tmp | = 0xf ;
else
tmp | = ( 1 < < priv - > client - > lanes ) - 1 ;
}
2010-07-26 11:12:43 -03:00
if ( priv - > client - > phy = = SH_CSI2_PHY_MAIN )
tmp | = 0x8000 ;
iowrite32 ( tmp , priv - > base + SH_CSI2_PHYCNT ) ;
tmp = 0 ;
if ( pdata - > flags & SH_CSI2_ECC )
tmp | = 2 ;
if ( pdata - > flags & SH_CSI2_CRC )
tmp | = 1 ;
iowrite32 ( tmp , priv - > base + SH_CSI2_CHKSUM ) ;
}
2011-07-01 11:19:58 -03:00
static int sh_csi2_client_connect ( struct sh_csi2 * priv )
2010-07-26 11:12:43 -03:00
{
struct sh_csi2_pdata * pdata = priv - > pdev - > dev . platform_data ;
2011-12-05 16:01:13 -03:00
struct soc_camera_device * icd = v4l2_get_subdev_hostdata ( & priv - > subdev ) ;
2011-09-05 08:26:20 -03:00
struct v4l2_subdev * client_sd = soc_camera_to_subdev ( icd ) ;
2011-07-01 11:19:58 -03:00
struct device * dev = v4l2_get_subdevdata ( & priv - > subdev ) ;
2011-07-26 13:13:47 -03:00
struct v4l2_mbus_config cfg ;
unsigned long common_flags , csi2_flags ;
int i , ret ;
2011-07-01 11:19:58 -03:00
2011-09-09 06:40:56 -03:00
if ( priv - > client )
return - EBUSY ;
2010-07-26 11:12:43 -03:00
for ( i = 0 ; i < pdata - > num_clients ; i + + )
if ( & pdata - > clients [ i ] . pdev - > dev = = icd - > pdev )
break ;
2011-07-01 11:19:58 -03:00
dev_dbg ( dev , " %s(%p): found #%d \n " , __func__ , dev , i ) ;
2010-07-26 11:12:43 -03:00
if ( i = = pdata - > num_clients )
2011-07-01 11:19:58 -03:00
return - ENODEV ;
2010-07-26 11:12:43 -03:00
2011-07-26 13:13:47 -03:00
/* Check if we can support this camera */
csi2_flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK | V4L2_MBUS_CSI2_1_LANE ;
switch ( pdata - > type ) {
case SH_CSI2C :
if ( pdata - > clients [ i ] . lanes ! = 1 )
csi2_flags | = V4L2_MBUS_CSI2_2_LANE ;
break ;
case SH_CSI2I :
switch ( pdata - > clients [ i ] . lanes ) {
default :
csi2_flags | = V4L2_MBUS_CSI2_4_LANE ;
case 3 :
csi2_flags | = V4L2_MBUS_CSI2_3_LANE ;
case 2 :
csi2_flags | = V4L2_MBUS_CSI2_2_LANE ;
}
}
cfg . type = V4L2_MBUS_CSI2 ;
2011-09-05 08:26:20 -03:00
ret = v4l2_subdev_call ( client_sd , video , g_mbus_config , & cfg ) ;
2011-07-26 13:13:47 -03:00
if ( ret = = - ENOIOCTLCMD )
common_flags = csi2_flags ;
else if ( ! ret )
common_flags = soc_mbus_config_compatible ( & cfg ,
csi2_flags ) ;
else
common_flags = 0 ;
if ( ! common_flags )
return - EINVAL ;
/* All good: camera MIPI configuration supported */
2011-07-26 13:19:01 -03:00
priv - > mipi_flags = common_flags ;
2011-07-01 11:19:58 -03:00
priv - > client = pdata - > clients + i ;
2010-07-26 11:12:43 -03:00
2011-07-01 11:19:58 -03:00
pm_runtime_get_sync ( dev ) ;
2010-07-26 11:12:43 -03:00
2011-07-01 11:19:58 -03:00
sh_csi2_hwinit ( priv ) ;
2010-07-26 11:12:43 -03:00
2011-07-01 11:19:58 -03:00
return 0 ;
}
2010-07-26 11:12:43 -03:00
2011-07-01 11:19:58 -03:00
static void sh_csi2_client_disconnect ( struct sh_csi2 * priv )
{
2011-09-09 06:40:56 -03:00
if ( ! priv - > client )
return ;
2011-07-01 11:19:58 -03:00
priv - > client = NULL ;
2010-07-26 11:12:43 -03:00
2011-07-01 11:19:58 -03:00
pm_runtime_put ( v4l2_get_subdevdata ( & priv - > subdev ) ) ;
2010-07-26 11:12:43 -03:00
}
2011-07-01 11:19:58 -03:00
static int sh_csi2_s_power ( struct v4l2_subdev * sd , int on )
{
struct sh_csi2 * priv = container_of ( sd , struct sh_csi2 , subdev ) ;
if ( on )
return sh_csi2_client_connect ( priv ) ;
sh_csi2_client_disconnect ( priv ) ;
return 0 ;
}
static struct v4l2_subdev_core_ops sh_csi2_subdev_core_ops = {
. s_power = sh_csi2_s_power ,
} ;
static struct v4l2_subdev_ops sh_csi2_subdev_ops = {
. core = & sh_csi2_subdev_core_ops ,
. video = & sh_csi2_subdev_video_ops ,
} ;
2010-07-26 11:12:43 -03:00
static __devinit int sh_csi2_probe ( struct platform_device * pdev )
{
struct resource * res ;
unsigned int irq ;
int ret ;
struct sh_csi2 * priv ;
/* Platform data specify the PHY, lanes, ECC, CRC */
struct sh_csi2_pdata * pdata = pdev - > dev . platform_data ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
/* Interrupt unused so far */
irq = platform_get_irq ( pdev , 0 ) ;
if ( ! res | | ( int ) irq < = 0 | | ! pdata ) {
dev_err ( & pdev - > dev , " Not enough CSI2 platform resources. \n " ) ;
return - ENODEV ;
}
/* TODO: Add support for CSI2I. Careful: different register layout! */
if ( pdata - > type ! = SH_CSI2C ) {
dev_err ( & pdev - > dev , " Only CSI2C supported ATM. \n " ) ;
return - EINVAL ;
}
priv = kzalloc ( sizeof ( struct sh_csi2 ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > irq = irq ;
if ( ! request_mem_region ( res - > start , resource_size ( res ) , pdev - > name ) ) {
dev_err ( & pdev - > dev , " CSI2 register region already claimed \n " ) ;
ret = - EBUSY ;
goto ereqreg ;
}
priv - > base = ioremap ( res - > start , resource_size ( res ) ) ;
if ( ! priv - > base ) {
ret = - ENXIO ;
dev_err ( & pdev - > dev , " Unable to ioremap CSI2 registers. \n " ) ;
goto eremap ;
}
priv - > pdev = pdev ;
2011-07-01 11:19:58 -03:00
platform_set_drvdata ( pdev , priv ) ;
2010-07-26 11:12:43 -03:00
v4l2_subdev_init ( & priv - > subdev , & sh_csi2_subdev_ops ) ;
v4l2_set_subdevdata ( & priv - > subdev , & pdev - > dev ) ;
2011-07-01 11:19:58 -03:00
snprintf ( priv - > subdev . name , V4L2_SUBDEV_NAME_SIZE , " %s.mipi-csi " ,
dev_name ( pdata - > v4l2_dev - > dev ) ) ;
ret = v4l2_device_register_subdev ( pdata - > v4l2_dev , & priv - > subdev ) ;
dev_dbg ( & pdev - > dev , " %s(%p): ret(register_subdev) = %d \n " , __func__ , priv , ret ) ;
if ( ret < 0 )
goto esdreg ;
2010-07-26 11:12:43 -03:00
pm_runtime_enable ( & pdev - > dev ) ;
dev_dbg ( & pdev - > dev , " CSI2 probed. \n " ) ;
return 0 ;
2011-07-01 11:19:58 -03:00
esdreg :
iounmap ( priv - > base ) ;
2010-07-26 11:12:43 -03:00
eremap :
release_mem_region ( res - > start , resource_size ( res ) ) ;
ereqreg :
kfree ( priv ) ;
return ret ;
}
static __devexit int sh_csi2_remove ( struct platform_device * pdev )
{
struct sh_csi2 * priv = platform_get_drvdata ( pdev ) ;
struct resource * res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2011-07-01 11:19:58 -03:00
v4l2_device_unregister_subdev ( & priv - > subdev ) ;
2010-07-26 11:12:43 -03:00
pm_runtime_disable ( & pdev - > dev ) ;
iounmap ( priv - > base ) ;
release_mem_region ( res - > start , resource_size ( res ) ) ;
platform_set_drvdata ( pdev , NULL ) ;
kfree ( priv ) ;
return 0 ;
}
static struct platform_driver __refdata sh_csi2_pdrv = {
2011-07-01 11:19:58 -03:00
. remove = __devexit_p ( sh_csi2_remove ) ,
. probe = sh_csi2_probe ,
. driver = {
2010-07-26 11:12:43 -03:00
. name = " sh-mobile-csi2 " ,
. owner = THIS_MODULE ,
} ,
} ;
2012-01-10 03:21:49 -03:00
module_platform_driver ( sh_csi2_pdrv ) ;
2010-07-26 11:12:43 -03:00
MODULE_DESCRIPTION ( " SH-Mobile MIPI CSI-2 driver " ) ;
MODULE_AUTHOR ( " Guennadi Liakhovetski <g.liakhovetski@gmx.de> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:sh-mobile-csi2 " ) ;