2011-04-04 09:44:19 -03:00
/*
* Samsung S5P / EXYNOS4 SoC series MIPI - CSI receiver driver
*
2012-02-14 13:23:46 -03:00
* Copyright ( C ) 2011 - 2012 Samsung Electronics Co . , Ltd .
2012-09-17 06:03:50 -03:00
* Sylwester Nawrocki < s . nawrocki @ samsung . com >
2011-04-04 09:44:19 -03:00
*
* 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/clk.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/errno.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/kernel.h>
# include <linux/memory.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <linux/regulator/consumer.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/videodev2.h>
# include <media/v4l2-subdev.h>
2012-08-24 15:22:12 +02:00
# include <linux/platform_data/mipi-csis.h>
2011-04-04 09:44:19 -03:00
# include "mipi-csis.h"
static int debug ;
module_param ( debug , int , 0644 ) ;
2012-09-05 10:10:37 -03:00
MODULE_PARM_DESC ( debug , " Debug level (0-2) " ) ;
2011-04-04 09:44:19 -03:00
/* Register map definition */
/* CSIS global control */
# define S5PCSIS_CTRL 0x00
# define S5PCSIS_CTRL_DPDN_DEFAULT (0 << 31)
# define S5PCSIS_CTRL_DPDN_SWAP (1 << 31)
# define S5PCSIS_CTRL_ALIGN_32BIT (1 << 20)
# define S5PCSIS_CTRL_UPDATE_SHADOW (1 << 16)
# define S5PCSIS_CTRL_WCLK_EXTCLK (1 << 8)
# define S5PCSIS_CTRL_RESET (1 << 4)
# define S5PCSIS_CTRL_ENABLE (1 << 0)
/* D-PHY control */
# define S5PCSIS_DPHYCTRL 0x04
# define S5PCSIS_DPHYCTRL_HSS_MASK (0x1f << 27)
# define S5PCSIS_DPHYCTRL_ENABLE (0x1f << 0)
# define S5PCSIS_CONFIG 0x08
# define S5PCSIS_CFG_FMT_YCBCR422_8BIT (0x1e << 2)
# define S5PCSIS_CFG_FMT_RAW8 (0x2a << 2)
# define S5PCSIS_CFG_FMT_RAW10 (0x2b << 2)
# define S5PCSIS_CFG_FMT_RAW12 (0x2c << 2)
/* User defined formats, x = 1...4 */
# define S5PCSIS_CFG_FMT_USER(x) ((0x30 + x - 1) << 2)
# define S5PCSIS_CFG_FMT_MASK (0x3f << 2)
# define S5PCSIS_CFG_NR_LANE_MASK 3
2012-09-05 10:10:37 -03:00
/* Interrupt mask */
2011-04-04 09:44:19 -03:00
# define S5PCSIS_INTMSK 0x10
2012-09-05 10:10:37 -03:00
# define S5PCSIS_INTMSK_EN_ALL 0xf000103f
# define S5PCSIS_INTMSK_EVEN_BEFORE (1 << 31)
# define S5PCSIS_INTMSK_EVEN_AFTER (1 << 30)
# define S5PCSIS_INTMSK_ODD_BEFORE (1 << 29)
# define S5PCSIS_INTMSK_ODD_AFTER (1 << 28)
# define S5PCSIS_INTMSK_ERR_SOT_HS (1 << 12)
# define S5PCSIS_INTMSK_ERR_LOST_FS (1 << 5)
# define S5PCSIS_INTMSK_ERR_LOST_FE (1 << 4)
# define S5PCSIS_INTMSK_ERR_OVER (1 << 3)
# define S5PCSIS_INTMSK_ERR_ECC (1 << 2)
# define S5PCSIS_INTMSK_ERR_CRC (1 << 1)
# define S5PCSIS_INTMSK_ERR_UNKNOWN (1 << 0)
/* Interrupt source */
2011-04-04 09:44:19 -03:00
# define S5PCSIS_INTSRC 0x14
2012-09-05 10:10:37 -03:00
# define S5PCSIS_INTSRC_EVEN_BEFORE (1 << 31)
# define S5PCSIS_INTSRC_EVEN_AFTER (1 << 30)
# define S5PCSIS_INTSRC_EVEN (0x3 << 30)
# define S5PCSIS_INTSRC_ODD_BEFORE (1 << 29)
# define S5PCSIS_INTSRC_ODD_AFTER (1 << 28)
# define S5PCSIS_INTSRC_ODD (0x3 << 28)
# define S5PCSIS_INTSRC_NON_IMAGE_DATA (0xff << 28)
# define S5PCSIS_INTSRC_ERR_SOT_HS (0xf << 12)
# define S5PCSIS_INTSRC_ERR_LOST_FS (1 << 5)
# define S5PCSIS_INTSRC_ERR_LOST_FE (1 << 4)
# define S5PCSIS_INTSRC_ERR_OVER (1 << 3)
# define S5PCSIS_INTSRC_ERR_ECC (1 << 2)
# define S5PCSIS_INTSRC_ERR_CRC (1 << 1)
# define S5PCSIS_INTSRC_ERR_UNKNOWN (1 << 0)
# define S5PCSIS_INTSRC_ERRORS 0xf03f
2011-04-04 09:44:19 -03:00
/* Pixel resolution */
# define S5PCSIS_RESOL 0x2c
# define CSIS_MAX_PIX_WIDTH 0xffff
# define CSIS_MAX_PIX_HEIGHT 0xffff
2012-09-21 15:18:41 -03:00
/* Non-image packet data buffers */
# define S5PCSIS_PKTDATA_ODD 0x2000
# define S5PCSIS_PKTDATA_EVEN 0x3000
# define S5PCSIS_PKTDATA_SIZE SZ_4K
2011-04-04 09:44:19 -03:00
enum {
CSIS_CLK_MUX ,
CSIS_CLK_GATE ,
} ;
static char * csi_clock_name [ ] = {
[ CSIS_CLK_MUX ] = " sclk_csis " ,
[ CSIS_CLK_GATE ] = " csis " ,
} ;
# define NUM_CSIS_CLOCKS ARRAY_SIZE(csi_clock_name)
2011-06-29 13:08:49 -03:00
static const char * const csis_supply_name [ ] = {
2012-09-17 06:03:50 -03:00
" vddcore " , /* CSIS Core (1.0V, 1.1V or 1.2V) suppply */
" vddio " , /* CSIS I/O and PLL (1.8V) supply */
2011-06-29 13:08:49 -03:00
} ;
# define CSIS_NUM_SUPPLIES ARRAY_SIZE(csis_supply_name)
2011-04-04 09:44:19 -03:00
enum {
ST_POWERED = 1 ,
ST_STREAMING = 2 ,
ST_SUSPENDED = 4 ,
} ;
2012-09-05 10:10:37 -03:00
struct s5pcsis_event {
u32 mask ;
const char * const name ;
unsigned int counter ;
} ;
static const struct s5pcsis_event s5pcsis_events [ ] = {
/* Errors */
{ S5PCSIS_INTSRC_ERR_SOT_HS , " SOT Error " } ,
{ S5PCSIS_INTSRC_ERR_LOST_FS , " Lost Frame Start Error " } ,
{ S5PCSIS_INTSRC_ERR_LOST_FE , " Lost Frame End Error " } ,
{ S5PCSIS_INTSRC_ERR_OVER , " FIFO Overflow Error " } ,
{ S5PCSIS_INTSRC_ERR_ECC , " ECC Error " } ,
{ S5PCSIS_INTSRC_ERR_CRC , " CRC Error " } ,
{ S5PCSIS_INTSRC_ERR_UNKNOWN , " Unknown Error " } ,
/* Non-image data receive events */
{ S5PCSIS_INTSRC_EVEN_BEFORE , " Non-image data before even frame " } ,
{ S5PCSIS_INTSRC_EVEN_AFTER , " Non-image data after even frame " } ,
{ S5PCSIS_INTSRC_ODD_BEFORE , " Non-image data before odd frame " } ,
{ S5PCSIS_INTSRC_ODD_AFTER , " Non-image data after odd frame " } ,
} ;
# define S5PCSIS_NUM_EVENTS ARRAY_SIZE(s5pcsis_events)
2012-09-21 15:18:41 -03:00
struct csis_pktbuf {
u32 * data ;
unsigned int len ;
} ;
2011-04-04 09:44:19 -03:00
/**
* struct csis_state - the driver ' s internal state data structure
* @ lock : mutex serializing the subdev and power management operations ,
* protecting @ format and @ flags members
* @ pads : CSIS pads array
* @ sd : v4l2_subdev associated with CSIS device instance
2012-09-17 06:03:10 -03:00
* @ index : the hardware instance index
2011-04-04 09:44:19 -03:00
* @ pdev : CSIS platform device
* @ regs : mmaped I / O registers memory
2012-09-05 10:10:37 -03:00
* @ supplies : CSIS regulator supplies
2011-04-04 09:44:19 -03:00
* @ clock : CSIS clocks
* @ irq : requested s5p - mipi - csis irq number
* @ flags : the state variable for power and streaming control
* @ csis_fmt : current CSIS pixel format
* @ format : common media bus format for the source and sink pad
2012-09-05 10:10:37 -03:00
* @ slock : spinlock protecting structure members below
2012-09-21 15:18:41 -03:00
* @ pkt_buf : the frame embedded ( non - image ) data buffer
2012-09-05 10:10:37 -03:00
* @ events : MIPI - CSIS event ( error ) counters
2011-04-04 09:44:19 -03:00
*/
struct csis_state {
struct mutex lock ;
struct media_pad pads [ CSIS_PADS_NUM ] ;
struct v4l2_subdev sd ;
2012-09-17 06:03:10 -03:00
u8 index ;
2011-04-04 09:44:19 -03:00
struct platform_device * pdev ;
void __iomem * regs ;
2011-06-29 13:08:49 -03:00
struct regulator_bulk_data supplies [ CSIS_NUM_SUPPLIES ] ;
2011-04-04 09:44:19 -03:00
struct clk * clock [ NUM_CSIS_CLOCKS ] ;
int irq ;
u32 flags ;
const struct csis_pix_format * csis_fmt ;
struct v4l2_mbus_framefmt format ;
2012-09-05 10:10:37 -03:00
struct spinlock slock ;
2012-09-21 15:18:41 -03:00
struct csis_pktbuf pkt_buf ;
2012-09-05 10:10:37 -03:00
struct s5pcsis_event events [ S5PCSIS_NUM_EVENTS ] ;
2011-04-04 09:44:19 -03:00
} ;
/**
* struct csis_pix_format - CSIS pixel format description
* @ pix_width_alignment : horizontal pixel alignment , width will be
* multiple of 2 ^ pix_width_alignment
* @ code : corresponding media bus code
* @ fmt_reg : S5PCSIS_CONFIG register value
2012-03-21 06:21:30 -03:00
* @ data_alignment : MIPI - CSI data alignment in bits
2011-04-04 09:44:19 -03:00
*/
struct csis_pix_format {
unsigned int pix_width_alignment ;
enum v4l2_mbus_pixelcode code ;
u32 fmt_reg ;
2012-03-21 06:21:30 -03:00
u8 data_alignment ;
2011-04-04 09:44:19 -03:00
} ;
static const struct csis_pix_format s5pcsis_formats [ ] = {
{
. code = V4L2_MBUS_FMT_VYUY8_2X8 ,
. fmt_reg = S5PCSIS_CFG_FMT_YCBCR422_8BIT ,
2012-03-21 06:21:30 -03:00
. data_alignment = 32 ,
2011-04-04 09:44:19 -03:00
} , {
. code = V4L2_MBUS_FMT_JPEG_1X8 ,
. fmt_reg = S5PCSIS_CFG_FMT_USER ( 1 ) ,
2012-03-21 06:21:30 -03:00
. data_alignment = 32 ,
2012-09-24 11:08:45 -03:00
} , {
. code = V4L2_MBUS_FMT_S5C_UYVY_JPEG_1X8 ,
. fmt_reg = S5PCSIS_CFG_FMT_USER ( 1 ) ,
. data_alignment = 32 ,
}
2011-04-04 09:44:19 -03:00
} ;
# define s5pcsis_write(__csis, __r, __v) writel(__v, __csis->regs + __r)
# define s5pcsis_read(__csis, __r) readl(__csis->regs + __r)
static struct csis_state * sd_to_csis_state ( struct v4l2_subdev * sdev )
{
return container_of ( sdev , struct csis_state , sd ) ;
}
static const struct csis_pix_format * find_csis_format (
struct v4l2_mbus_framefmt * mf )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( s5pcsis_formats ) ; i + + )
if ( mf - > code = = s5pcsis_formats [ i ] . code )
return & s5pcsis_formats [ i ] ;
return NULL ;
}
static void s5pcsis_enable_interrupts ( struct csis_state * state , bool on )
{
u32 val = s5pcsis_read ( state , S5PCSIS_INTMSK ) ;
val = on ? val | S5PCSIS_INTMSK_EN_ALL :
val & ~ S5PCSIS_INTMSK_EN_ALL ;
s5pcsis_write ( state , S5PCSIS_INTMSK , val ) ;
}
static void s5pcsis_reset ( struct csis_state * state )
{
u32 val = s5pcsis_read ( state , S5PCSIS_CTRL ) ;
s5pcsis_write ( state , S5PCSIS_CTRL , val | S5PCSIS_CTRL_RESET ) ;
udelay ( 10 ) ;
}
static void s5pcsis_system_enable ( struct csis_state * state , int on )
{
u32 val ;
val = s5pcsis_read ( state , S5PCSIS_CTRL ) ;
if ( on )
val | = S5PCSIS_CTRL_ENABLE ;
else
val & = ~ S5PCSIS_CTRL_ENABLE ;
s5pcsis_write ( state , S5PCSIS_CTRL , val ) ;
val = s5pcsis_read ( state , S5PCSIS_DPHYCTRL ) ;
if ( on )
val | = S5PCSIS_DPHYCTRL_ENABLE ;
else
val & = ~ S5PCSIS_DPHYCTRL_ENABLE ;
s5pcsis_write ( state , S5PCSIS_DPHYCTRL , val ) ;
}
/* Called with the state.lock mutex held */
static void __s5pcsis_set_format ( struct csis_state * state )
{
struct v4l2_mbus_framefmt * mf = & state - > format ;
u32 val ;
2012-09-24 11:08:45 -03:00
v4l2_dbg ( 1 , debug , & state - > sd , " fmt: %#x, %d x %d \n " ,
2011-04-04 09:44:19 -03:00
mf - > code , mf - > width , mf - > height ) ;
/* Color format */
val = s5pcsis_read ( state , S5PCSIS_CONFIG ) ;
val = ( val & ~ S5PCSIS_CFG_FMT_MASK ) | state - > csis_fmt - > fmt_reg ;
s5pcsis_write ( state , S5PCSIS_CONFIG , val ) ;
/* Pixel resolution */
val = ( mf - > width < < 16 ) | mf - > height ;
s5pcsis_write ( state , S5PCSIS_RESOL , val ) ;
}
static void s5pcsis_set_hsync_settle ( struct csis_state * state , int settle )
{
u32 val = s5pcsis_read ( state , S5PCSIS_DPHYCTRL ) ;
val = ( val & ~ S5PCSIS_DPHYCTRL_HSS_MASK ) | ( settle < < 27 ) ;
s5pcsis_write ( state , S5PCSIS_DPHYCTRL , val ) ;
}
static void s5pcsis_set_params ( struct csis_state * state )
{
struct s5p_platform_mipi_csis * pdata = state - > pdev - > dev . platform_data ;
u32 val ;
val = s5pcsis_read ( state , S5PCSIS_CONFIG ) ;
val = ( val & ~ S5PCSIS_CFG_NR_LANE_MASK ) | ( pdata - > lanes - 1 ) ;
s5pcsis_write ( state , S5PCSIS_CONFIG , val ) ;
__s5pcsis_set_format ( state ) ;
s5pcsis_set_hsync_settle ( state , pdata - > hs_settle ) ;
val = s5pcsis_read ( state , S5PCSIS_CTRL ) ;
2012-03-21 06:21:30 -03:00
if ( state - > csis_fmt - > data_alignment = = 32 )
2011-04-04 09:44:19 -03:00
val | = S5PCSIS_CTRL_ALIGN_32BIT ;
else /* 24-bits */
val & = ~ S5PCSIS_CTRL_ALIGN_32BIT ;
2012-09-17 06:03:38 -03:00
2011-04-04 09:44:19 -03:00
val & = ~ S5PCSIS_CTRL_WCLK_EXTCLK ;
2012-09-17 06:03:38 -03:00
if ( pdata - > wclk_source )
val | = S5PCSIS_CTRL_WCLK_EXTCLK ;
2011-04-04 09:44:19 -03:00
s5pcsis_write ( state , S5PCSIS_CTRL , val ) ;
/* Update the shadow register. */
val = s5pcsis_read ( state , S5PCSIS_CTRL ) ;
s5pcsis_write ( state , S5PCSIS_CTRL , val | S5PCSIS_CTRL_UPDATE_SHADOW ) ;
}
static void s5pcsis_clk_put ( struct csis_state * state )
{
int i ;
2012-01-30 11:39:30 -03:00
for ( i = 0 ; i < NUM_CSIS_CLOCKS ; i + + ) {
if ( IS_ERR_OR_NULL ( state - > clock [ i ] ) )
continue ;
clk_unprepare ( state - > clock [ i ] ) ;
clk_put ( state - > clock [ i ] ) ;
state - > clock [ i ] = NULL ;
}
2011-04-04 09:44:19 -03:00
}
static int s5pcsis_clk_get ( struct csis_state * state )
{
struct device * dev = & state - > pdev - > dev ;
2012-01-30 11:39:30 -03:00
int i , ret ;
2011-04-04 09:44:19 -03:00
for ( i = 0 ; i < NUM_CSIS_CLOCKS ; i + + ) {
state - > clock [ i ] = clk_get ( dev , csi_clock_name [ i ] ) ;
2012-01-30 11:39:30 -03:00
if ( IS_ERR ( state - > clock [ i ] ) )
goto err ;
ret = clk_prepare ( state - > clock [ i ] ) ;
if ( ret < 0 ) {
clk_put ( state - > clock [ i ] ) ;
state - > clock [ i ] = NULL ;
goto err ;
2011-04-04 09:44:19 -03:00
}
}
return 0 ;
2012-01-30 11:39:30 -03:00
err :
s5pcsis_clk_put ( state ) ;
dev_err ( dev , " failed to get clock: %s \n " , csi_clock_name [ i ] ) ;
return - ENXIO ;
2011-04-04 09:44:19 -03:00
}
static void s5pcsis_start_stream ( struct csis_state * state )
{
s5pcsis_reset ( state ) ;
s5pcsis_set_params ( state ) ;
s5pcsis_system_enable ( state , true ) ;
s5pcsis_enable_interrupts ( state , true ) ;
}
static void s5pcsis_stop_stream ( struct csis_state * state )
{
s5pcsis_enable_interrupts ( state , false ) ;
s5pcsis_system_enable ( state , false ) ;
}
2012-09-05 10:10:37 -03:00
static void s5pcsis_clear_counters ( struct csis_state * state )
{
unsigned long flags ;
int i ;
spin_lock_irqsave ( & state - > slock , flags ) ;
for ( i = 0 ; i < S5PCSIS_NUM_EVENTS ; i + + )
state - > events [ i ] . counter = 0 ;
spin_unlock_irqrestore ( & state - > slock , flags ) ;
}
static void s5pcsis_log_counters ( struct csis_state * state , bool non_errors )
{
int i = non_errors ? S5PCSIS_NUM_EVENTS : S5PCSIS_NUM_EVENTS - 4 ;
unsigned long flags ;
spin_lock_irqsave ( & state - > slock , flags ) ;
for ( i - - ; i > = 0 ; i - - )
if ( state - > events [ i ] . counter > = 0 )
v4l2_info ( & state - > sd , " %s events: %d \n " ,
state - > events [ i ] . name ,
state - > events [ i ] . counter ) ;
spin_unlock_irqrestore ( & state - > slock , flags ) ;
}
/*
* V4L2 subdev operations
*/
static int s5pcsis_s_power ( struct v4l2_subdev * sd , int on )
{
struct csis_state * state = sd_to_csis_state ( sd ) ;
struct device * dev = & state - > pdev - > dev ;
if ( on )
return pm_runtime_get_sync ( dev ) ;
return pm_runtime_put_sync ( dev ) ;
}
2011-04-04 09:44:19 -03:00
static int s5pcsis_s_stream ( struct v4l2_subdev * sd , int enable )
{
struct csis_state * state = sd_to_csis_state ( sd ) ;
int ret = 0 ;
v4l2_dbg ( 1 , debug , sd , " %s: %d, state: 0x%x \n " ,
__func__ , enable , state - > flags ) ;
if ( enable ) {
2012-09-05 10:10:37 -03:00
s5pcsis_clear_counters ( state ) ;
2011-04-04 09:44:19 -03:00
ret = pm_runtime_get_sync ( & state - > pdev - > dev ) ;
if ( ret & & ret ! = 1 )
return ret ;
}
2012-09-05 10:10:37 -03:00
2011-04-04 09:44:19 -03:00
mutex_lock ( & state - > lock ) ;
if ( enable ) {
if ( state - > flags & ST_SUSPENDED ) {
ret = - EBUSY ;
goto unlock ;
}
s5pcsis_start_stream ( state ) ;
state - > flags | = ST_STREAMING ;
} else {
s5pcsis_stop_stream ( state ) ;
state - > flags & = ~ ST_STREAMING ;
2012-09-05 10:10:37 -03:00
if ( debug > 0 )
s5pcsis_log_counters ( state , true ) ;
2011-04-04 09:44:19 -03:00
}
unlock :
mutex_unlock ( & state - > lock ) ;
if ( ! enable )
pm_runtime_put ( & state - > pdev - > dev ) ;
return ret = = 1 ? 0 : ret ;
}
static int s5pcsis_enum_mbus_code ( struct v4l2_subdev * sd ,
struct v4l2_subdev_fh * fh ,
struct v4l2_subdev_mbus_code_enum * code )
{
if ( code - > index > = ARRAY_SIZE ( s5pcsis_formats ) )
return - EINVAL ;
code - > code = s5pcsis_formats [ code - > index ] . code ;
return 0 ;
}
static struct csis_pix_format const * s5pcsis_try_format (
struct v4l2_mbus_framefmt * mf )
{
struct csis_pix_format const * csis_fmt ;
csis_fmt = find_csis_format ( mf ) ;
if ( csis_fmt = = NULL )
csis_fmt = & s5pcsis_formats [ 0 ] ;
mf - > code = csis_fmt - > code ;
v4l_bound_align_image ( & mf - > width , 1 , CSIS_MAX_PIX_WIDTH ,
csis_fmt - > pix_width_alignment ,
& mf - > height , 1 , CSIS_MAX_PIX_HEIGHT , 1 ,
0 ) ;
return csis_fmt ;
}
static struct v4l2_mbus_framefmt * __s5pcsis_get_format (
struct csis_state * state , struct v4l2_subdev_fh * fh ,
u32 pad , enum v4l2_subdev_format_whence which )
{
if ( which = = V4L2_SUBDEV_FORMAT_TRY )
return fh ? v4l2_subdev_get_try_format ( fh , pad ) : NULL ;
return & state - > format ;
}
static int s5pcsis_set_fmt ( struct v4l2_subdev * sd , struct v4l2_subdev_fh * fh ,
struct v4l2_subdev_format * fmt )
{
struct csis_state * state = sd_to_csis_state ( sd ) ;
struct csis_pix_format const * csis_fmt ;
struct v4l2_mbus_framefmt * mf ;
if ( fmt - > pad ! = CSIS_PAD_SOURCE & & fmt - > pad ! = CSIS_PAD_SINK )
return - EINVAL ;
mf = __s5pcsis_get_format ( state , fh , fmt - > pad , fmt - > which ) ;
if ( fmt - > pad = = CSIS_PAD_SOURCE ) {
if ( mf ) {
mutex_lock ( & state - > lock ) ;
fmt - > format = * mf ;
mutex_unlock ( & state - > lock ) ;
}
return 0 ;
}
csis_fmt = s5pcsis_try_format ( & fmt - > format ) ;
if ( mf ) {
mutex_lock ( & state - > lock ) ;
* mf = fmt - > format ;
if ( fmt - > which = = V4L2_SUBDEV_FORMAT_ACTIVE )
state - > csis_fmt = csis_fmt ;
mutex_unlock ( & state - > lock ) ;
}
return 0 ;
}
static int s5pcsis_get_fmt ( struct v4l2_subdev * sd , struct v4l2_subdev_fh * fh ,
struct v4l2_subdev_format * fmt )
{
struct csis_state * state = sd_to_csis_state ( sd ) ;
struct v4l2_mbus_framefmt * mf ;
if ( fmt - > pad ! = CSIS_PAD_SOURCE & & fmt - > pad ! = CSIS_PAD_SINK )
return - EINVAL ;
mf = __s5pcsis_get_format ( state , fh , fmt - > pad , fmt - > which ) ;
if ( ! mf )
return - EINVAL ;
mutex_lock ( & state - > lock ) ;
fmt - > format = * mf ;
mutex_unlock ( & state - > lock ) ;
return 0 ;
}
2012-09-21 15:18:41 -03:00
static int s5pcsis_s_rx_buffer ( struct v4l2_subdev * sd , void * buf ,
unsigned int * size )
{
struct csis_state * state = sd_to_csis_state ( sd ) ;
unsigned long flags ;
* size = min_t ( unsigned int , * size , S5PCSIS_PKTDATA_SIZE ) ;
spin_lock_irqsave ( & state - > slock , flags ) ;
state - > pkt_buf . data = buf ;
state - > pkt_buf . len = * size ;
spin_unlock_irqrestore ( & state - > slock , flags ) ;
return 0 ;
}
2012-09-05 10:10:37 -03:00
static int s5pcsis_log_status ( struct v4l2_subdev * sd )
{
struct csis_state * state = sd_to_csis_state ( sd ) ;
s5pcsis_log_counters ( state , true ) ;
return 0 ;
}
2011-11-15 15:34:06 -03:00
static int s5pcsis_open ( struct v4l2_subdev * sd , struct v4l2_subdev_fh * fh )
{
struct v4l2_mbus_framefmt * format = v4l2_subdev_get_try_format ( fh , 0 ) ;
format - > colorspace = V4L2_COLORSPACE_JPEG ;
format - > code = s5pcsis_formats [ 0 ] . code ;
format - > width = S5PCSIS_DEF_PIX_WIDTH ;
format - > height = S5PCSIS_DEF_PIX_HEIGHT ;
format - > field = V4L2_FIELD_NONE ;
return 0 ;
}
static const struct v4l2_subdev_internal_ops s5pcsis_sd_internal_ops = {
. open = s5pcsis_open ,
} ;
2011-04-04 09:44:19 -03:00
static struct v4l2_subdev_core_ops s5pcsis_core_ops = {
. s_power = s5pcsis_s_power ,
2012-09-05 10:10:37 -03:00
. log_status = s5pcsis_log_status ,
2011-04-04 09:44:19 -03:00
} ;
static struct v4l2_subdev_pad_ops s5pcsis_pad_ops = {
. enum_mbus_code = s5pcsis_enum_mbus_code ,
. get_fmt = s5pcsis_get_fmt ,
. set_fmt = s5pcsis_set_fmt ,
} ;
static struct v4l2_subdev_video_ops s5pcsis_video_ops = {
2012-09-21 15:18:41 -03:00
. s_rx_buffer = s5pcsis_s_rx_buffer ,
2011-04-04 09:44:19 -03:00
. s_stream = s5pcsis_s_stream ,
} ;
static struct v4l2_subdev_ops s5pcsis_subdev_ops = {
. core = & s5pcsis_core_ops ,
. pad = & s5pcsis_pad_ops ,
. video = & s5pcsis_video_ops ,
} ;
static irqreturn_t s5pcsis_irq_handler ( int irq , void * dev_id )
{
struct csis_state * state = dev_id ;
2012-09-21 15:18:41 -03:00
struct csis_pktbuf * pktbuf = & state - > pkt_buf ;
2012-09-05 10:10:37 -03:00
unsigned long flags ;
u32 status ;
status = s5pcsis_read ( state , S5PCSIS_INTSRC ) ;
spin_lock_irqsave ( & state - > slock , flags ) ;
2012-09-21 15:18:41 -03:00
if ( ( status & S5PCSIS_INTSRC_NON_IMAGE_DATA ) & & pktbuf - > data ) {
u32 offset ;
if ( status & S5PCSIS_INTSRC_EVEN )
offset = S5PCSIS_PKTDATA_EVEN ;
else
offset = S5PCSIS_PKTDATA_ODD ;
memcpy ( pktbuf - > data , state - > regs + offset , pktbuf - > len ) ;
pktbuf - > data = NULL ;
rmb ( ) ;
}
2012-09-05 10:10:37 -03:00
/* Update the event/error counters */
if ( ( status & S5PCSIS_INTSRC_ERRORS ) | | debug ) {
int i ;
for ( i = 0 ; i < S5PCSIS_NUM_EVENTS ; i + + ) {
if ( ! ( status & state - > events [ i ] . mask ) )
continue ;
state - > events [ i ] . counter + + ;
v4l2_dbg ( 2 , debug , & state - > sd , " %s: %d \n " ,
state - > events [ i ] . name ,
state - > events [ i ] . counter ) ;
}
v4l2_dbg ( 2 , debug , & state - > sd , " status: %08x \n " , status ) ;
}
spin_unlock_irqrestore ( & state - > slock , flags ) ;
2011-04-04 09:44:19 -03:00
2012-09-05 10:10:37 -03:00
s5pcsis_write ( state , S5PCSIS_INTSRC , status ) ;
2011-04-04 09:44:19 -03:00
return IRQ_HANDLED ;
}
static int __devinit s5pcsis_probe ( struct platform_device * pdev )
{
struct s5p_platform_mipi_csis * pdata ;
struct resource * mem_res ;
struct csis_state * state ;
int ret = - ENOMEM ;
2011-06-29 13:08:49 -03:00
int i ;
2011-04-04 09:44:19 -03:00
2012-02-14 13:23:46 -03:00
state = devm_kzalloc ( & pdev - > dev , sizeof ( * state ) , GFP_KERNEL ) ;
2011-04-04 09:44:19 -03:00
if ( ! state )
return - ENOMEM ;
mutex_init ( & state - > lock ) ;
2012-09-05 10:10:37 -03:00
spin_lock_init ( & state - > slock ) ;
2011-04-04 09:44:19 -03:00
state - > pdev = pdev ;
2012-09-17 06:03:10 -03:00
state - > index = max ( 0 , pdev - > id ) ;
2011-04-04 09:44:19 -03:00
pdata = pdev - > dev . platform_data ;
2012-09-17 06:03:10 -03:00
if ( pdata = = NULL ) {
2011-04-04 09:44:19 -03:00
dev_err ( & pdev - > dev , " Platform data not fully specified \n " ) ;
2012-02-14 13:23:46 -03:00
return - EINVAL ;
2011-04-04 09:44:19 -03:00
}
2012-09-17 06:03:10 -03:00
if ( ( state - > index = = 1 & & pdata - > lanes > CSIS1_MAX_LANES ) | |
2011-04-04 09:44:19 -03:00
pdata - > lanes > CSIS0_MAX_LANES ) {
dev_err ( & pdev - > dev , " Unsupported number of data lanes: %d \n " ,
pdata - > lanes ) ;
2012-02-14 13:23:46 -03:00
return - EINVAL ;
2011-04-04 09:44:19 -03:00
}
mem_res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2012-02-14 13:23:46 -03:00
state - > regs = devm_request_and_ioremap ( & pdev - > dev , mem_res ) ;
if ( state - > regs = = NULL ) {
dev_err ( & pdev - > dev , " Failed to request and remap io memory \n " ) ;
return - ENXIO ;
2011-04-04 09:44:19 -03:00
}
state - > irq = platform_get_irq ( pdev , 0 ) ;
if ( state - > irq < 0 ) {
dev_err ( & pdev - > dev , " Failed to get irq \n " ) ;
2012-02-14 13:23:46 -03:00
return state - > irq ;
2011-04-04 09:44:19 -03:00
}
2011-06-29 13:08:49 -03:00
for ( i = 0 ; i < CSIS_NUM_SUPPLIES ; i + + )
state - > supplies [ i ] . supply = csis_supply_name [ i ] ;
ret = regulator_bulk_get ( & pdev - > dev , CSIS_NUM_SUPPLIES ,
state - > supplies ) ;
2012-02-14 13:23:46 -03:00
if ( ret )
return ret ;
ret = s5pcsis_clk_get ( state ) ;
2011-06-29 13:08:49 -03:00
if ( ret )
goto e_clkput ;
2011-04-04 09:44:19 -03:00
2012-02-14 13:23:46 -03:00
clk_enable ( state - > clock [ CSIS_CLK_MUX ] ) ;
if ( pdata - > clk_rate )
clk_set_rate ( state - > clock [ CSIS_CLK_MUX ] , pdata - > clk_rate ) ;
else
dev_WARN ( & pdev - > dev , " No clock frequency specified! \n " ) ;
ret = devm_request_irq ( & pdev - > dev , state - > irq , s5pcsis_irq_handler ,
0 , dev_name ( & pdev - > dev ) , state ) ;
2011-04-04 09:44:19 -03:00
if ( ret ) {
2012-02-14 13:23:46 -03:00
dev_err ( & pdev - > dev , " Interrupt request failed \n " ) ;
2011-04-04 09:44:19 -03:00
goto e_regput ;
}
v4l2_subdev_init ( & state - > sd , & s5pcsis_subdev_ops ) ;
state - > sd . owner = THIS_MODULE ;
strlcpy ( state - > sd . name , dev_name ( & pdev - > dev ) , sizeof ( state - > sd . name ) ) ;
2011-11-15 15:34:06 -03:00
state - > sd . flags | = V4L2_SUBDEV_FL_HAS_DEVNODE ;
2011-04-04 09:44:19 -03:00
state - > csis_fmt = & s5pcsis_formats [ 0 ] ;
2011-11-15 15:34:06 -03:00
state - > format . code = s5pcsis_formats [ 0 ] . code ;
state - > format . width = S5PCSIS_DEF_PIX_WIDTH ;
state - > format . height = S5PCSIS_DEF_PIX_HEIGHT ;
2011-04-04 09:44:19 -03:00
state - > pads [ CSIS_PAD_SINK ] . flags = MEDIA_PAD_FL_SINK ;
state - > pads [ CSIS_PAD_SOURCE ] . flags = MEDIA_PAD_FL_SOURCE ;
ret = media_entity_init ( & state - > sd . entity ,
CSIS_PADS_NUM , state - > pads , 0 ) ;
if ( ret < 0 )
2012-02-14 13:23:46 -03:00
goto e_clkput ;
2011-04-04 09:44:19 -03:00
/* This allows to retrieve the platform device id by the host driver */
v4l2_set_subdevdata ( & state - > sd , pdev ) ;
/* .. and a pointer to the subdev. */
platform_set_drvdata ( pdev , & state - > sd ) ;
2012-09-05 10:10:37 -03:00
memcpy ( state - > events , s5pcsis_events , sizeof ( state - > events ) ) ;
2011-04-04 09:44:19 -03:00
pm_runtime_enable ( & pdev - > dev ) ;
return 0 ;
e_regput :
2011-06-29 13:08:49 -03:00
regulator_bulk_free ( CSIS_NUM_SUPPLIES , state - > supplies ) ;
2011-04-04 09:44:19 -03:00
e_clkput :
clk_disable ( state - > clock [ CSIS_CLK_MUX ] ) ;
s5pcsis_clk_put ( state ) ;
return ret ;
}
2011-07-07 12:13:25 -03:00
static int s5pcsis_pm_suspend ( struct device * dev , bool runtime )
2011-04-04 09:44:19 -03:00
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct v4l2_subdev * sd = platform_get_drvdata ( pdev ) ;
struct csis_state * state = sd_to_csis_state ( sd ) ;
2011-05-18 12:06:40 -03:00
int ret = 0 ;
2011-04-04 09:44:19 -03:00
v4l2_dbg ( 1 , debug , sd , " %s: flags: 0x%x \n " ,
__func__ , state - > flags ) ;
mutex_lock ( & state - > lock ) ;
if ( state - > flags & ST_POWERED ) {
s5pcsis_stop_stream ( state ) ;
2012-09-17 06:03:10 -03:00
ret = s5p_csis_phy_enable ( state - > index , false ) ;
2011-04-04 09:44:19 -03:00
if ( ret )
goto unlock ;
2011-06-29 13:08:49 -03:00
ret = regulator_bulk_disable ( CSIS_NUM_SUPPLIES ,
state - > supplies ) ;
if ( ret )
goto unlock ;
2011-04-04 09:44:19 -03:00
clk_disable ( state - > clock [ CSIS_CLK_GATE ] ) ;
state - > flags & = ~ ST_POWERED ;
2011-07-07 12:13:25 -03:00
if ( ! runtime )
state - > flags | = ST_SUSPENDED ;
2011-04-04 09:44:19 -03:00
}
unlock :
mutex_unlock ( & state - > lock ) ;
return ret ? - EAGAIN : 0 ;
}
2011-07-07 12:13:25 -03:00
static int s5pcsis_pm_resume ( struct device * dev , bool runtime )
2011-04-04 09:44:19 -03:00
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct v4l2_subdev * sd = platform_get_drvdata ( pdev ) ;
struct csis_state * state = sd_to_csis_state ( sd ) ;
int ret = 0 ;
v4l2_dbg ( 1 , debug , sd , " %s: flags: 0x%x \n " ,
__func__ , state - > flags ) ;
mutex_lock ( & state - > lock ) ;
2011-07-07 12:13:25 -03:00
if ( ! runtime & & ! ( state - > flags & ST_SUSPENDED ) )
2011-04-04 09:44:19 -03:00
goto unlock ;
if ( ! ( state - > flags & ST_POWERED ) ) {
2011-06-29 13:08:49 -03:00
ret = regulator_bulk_enable ( CSIS_NUM_SUPPLIES ,
state - > supplies ) ;
2011-04-04 09:44:19 -03:00
if ( ret )
goto unlock ;
2012-09-17 06:03:10 -03:00
ret = s5p_csis_phy_enable ( state - > index , true ) ;
2011-04-04 09:44:19 -03:00
if ( ! ret ) {
state - > flags | = ST_POWERED ;
2011-06-29 13:08:49 -03:00
} else {
regulator_bulk_disable ( CSIS_NUM_SUPPLIES ,
state - > supplies ) ;
2011-04-04 09:44:19 -03:00
goto unlock ;
}
clk_enable ( state - > clock [ CSIS_CLK_GATE ] ) ;
}
if ( state - > flags & ST_STREAMING )
s5pcsis_start_stream ( state ) ;
state - > flags & = ~ ST_SUSPENDED ;
unlock :
mutex_unlock ( & state - > lock ) ;
return ret ? - EAGAIN : 0 ;
}
# ifdef CONFIG_PM_SLEEP
2011-07-07 12:13:25 -03:00
static int s5pcsis_suspend ( struct device * dev )
2011-04-04 09:44:19 -03:00
{
2011-07-07 12:13:25 -03:00
return s5pcsis_pm_suspend ( dev , false ) ;
2011-04-04 09:44:19 -03:00
}
2011-07-07 12:13:25 -03:00
static int s5pcsis_resume ( struct device * dev )
2011-04-04 09:44:19 -03:00
{
2011-07-07 12:13:25 -03:00
return s5pcsis_pm_resume ( dev , false ) ;
}
# endif
2011-04-04 09:44:19 -03:00
2011-07-07 12:13:25 -03:00
# ifdef CONFIG_PM_RUNTIME
static int s5pcsis_runtime_suspend ( struct device * dev )
{
return s5pcsis_pm_suspend ( dev , true ) ;
}
2011-04-04 09:44:19 -03:00
2011-07-07 12:13:25 -03:00
static int s5pcsis_runtime_resume ( struct device * dev )
{
return s5pcsis_pm_resume ( dev , true ) ;
2011-04-04 09:44:19 -03:00
}
# endif
static int __devexit s5pcsis_remove ( struct platform_device * pdev )
{
struct v4l2_subdev * sd = platform_get_drvdata ( pdev ) ;
struct csis_state * state = sd_to_csis_state ( sd ) ;
pm_runtime_disable ( & pdev - > dev ) ;
2012-03-09 08:56:39 -03:00
s5pcsis_pm_suspend ( & pdev - > dev , false ) ;
2011-04-04 09:44:19 -03:00
clk_disable ( state - > clock [ CSIS_CLK_MUX ] ) ;
pm_runtime_set_suspended ( & pdev - > dev ) ;
s5pcsis_clk_put ( state ) ;
2011-06-29 13:08:49 -03:00
regulator_bulk_free ( CSIS_NUM_SUPPLIES , state - > supplies ) ;
2011-04-04 09:44:19 -03:00
media_entity_cleanup ( & state - > sd . entity ) ;
return 0 ;
}
static const struct dev_pm_ops s5pcsis_pm_ops = {
2011-07-07 12:13:25 -03:00
SET_RUNTIME_PM_OPS ( s5pcsis_runtime_suspend , s5pcsis_runtime_resume ,
NULL )
SET_SYSTEM_SLEEP_PM_OPS ( s5pcsis_suspend , s5pcsis_resume )
2011-04-04 09:44:19 -03:00
} ;
static struct platform_driver s5pcsis_driver = {
. probe = s5pcsis_probe ,
. remove = __devexit_p ( s5pcsis_remove ) ,
. driver = {
. name = CSIS_DRIVER_NAME ,
. owner = THIS_MODULE ,
. pm = & s5pcsis_pm_ops ,
} ,
} ;
2012-03-21 09:58:09 -03:00
module_platform_driver ( s5pcsis_driver ) ;
2011-04-04 09:44:19 -03:00
MODULE_AUTHOR ( " Sylwester Nawrocki <s.nawrocki@samsung.com> " ) ;
2012-03-21 09:58:09 -03:00
MODULE_DESCRIPTION ( " Samsung S5P/EXYNOS SoC MIPI-CSI2 receiver driver " ) ;
2011-04-04 09:44:19 -03:00
MODULE_LICENSE ( " GPL " ) ;