2019-05-27 08:55:19 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2011-06-11 14:46:49 -03:00
/*
* Support for the camera device found on Marvell MMP processors ; known
* to work with the Armada 610 as used in the OLPC 1.75 system .
*
* Copyright 2011 Jonathan Corbet < corbet @ lwn . net >
2019-05-28 05:07:30 -04:00
* Copyright 2018 Lubomir Rintel < lkundrak @ v3 . sk >
2011-06-11 14:46:49 -03:00
*/
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/interrupt.h>
# include <linux/spinlock.h>
# include <linux/slab.h>
# include <linux/videodev2.h>
# include <media/v4l2-device.h>
2015-11-16 08:35:53 -02:00
# include <linux/platform_data/media/mmp-camera.h>
2011-06-11 14:46:49 -03:00
# include <linux/device.h>
2019-05-28 05:07:29 -04:00
# include <linux/of.h>
# include <linux/of_platform.h>
2011-06-11 14:46:49 -03:00
# include <linux/platform_device.h>
# include <linux/io.h>
# include <linux/list.h>
2011-12-30 14:13:41 -03:00
# include <linux/pm.h>
2013-07-03 01:55:58 -03:00
# include <linux/clk.h>
2011-06-11 14:46:49 -03:00
# include "mcam-core.h"
2011-09-07 05:36:46 -03:00
MODULE_ALIAS ( " platform:mmp-camera " ) ;
2011-06-11 14:46:49 -03:00
MODULE_AUTHOR ( " Jonathan Corbet <corbet@lwn.net> " ) ;
MODULE_LICENSE ( " GPL " ) ;
2019-05-28 05:07:28 -04:00
static char * mcam_clks [ ] = { " axi " , " func " , " phy " } ;
2013-07-03 01:55:59 -03:00
2011-06-11 14:46:49 -03:00
struct mmp_camera {
struct platform_device * pdev ;
struct mcam_camera mcam ;
struct list_head devlist ;
2013-07-03 01:55:58 -03:00
struct clk * mipi_clk ;
2011-06-11 14:46:49 -03:00
int irq ;
} ;
static inline struct mmp_camera * mcam_to_cam ( struct mcam_camera * mcam )
{
return container_of ( mcam , struct mmp_camera , mcam ) ;
}
/*
* A silly little infrastructure so we can keep track of our devices .
* Chances are that we will never have more than one of them , but
* the Armada 610 * does * have two controllers . . .
*/
static LIST_HEAD ( mmpcam_devices ) ;
static struct mutex mmpcam_devices_lock ;
static void mmpcam_add_device ( struct mmp_camera * cam )
{
mutex_lock ( & mmpcam_devices_lock ) ;
list_add ( & cam - > devlist , & mmpcam_devices ) ;
mutex_unlock ( & mmpcam_devices_lock ) ;
}
static void mmpcam_remove_device ( struct mmp_camera * cam )
{
mutex_lock ( & mmpcam_devices_lock ) ;
list_del ( & cam - > devlist ) ;
mutex_unlock ( & mmpcam_devices_lock ) ;
}
/*
* Platform dev remove passes us a platform_device , and there ' s
* no handy unused drvdata to stash a backpointer in . So just
* dig it out of our list .
*/
static struct mmp_camera * mmpcam_find_device ( struct platform_device * pdev )
{
struct mmp_camera * cam ;
mutex_lock ( & mmpcam_devices_lock ) ;
list_for_each_entry ( cam , & mmpcam_devices , devlist ) {
if ( cam - > pdev = = pdev ) {
mutex_unlock ( & mmpcam_devices_lock ) ;
return cam ;
}
}
mutex_unlock ( & mmpcam_devices_lock ) ;
return NULL ;
}
2013-07-03 01:55:58 -03:00
/*
* calc the dphy register values
* There are three dphy registers being used .
* dphy [ 0 ] - CSI2_DPHY3
* dphy [ 1 ] - CSI2_DPHY5
* dphy [ 2 ] - CSI2_DPHY6
* CSI2_DPHY3 and CSI2_DPHY6 can be set with a default value
* or be calculated dynamically
*/
2018-04-05 12:14:59 -04:00
static void mmpcam_calc_dphy ( struct mcam_camera * mcam )
2013-07-03 01:55:58 -03:00
{
struct mmp_camera * cam = mcam_to_cam ( mcam ) ;
struct mmp_camera_platform_data * pdata = cam - > pdev - > dev . platform_data ;
struct device * dev = & cam - > pdev - > dev ;
unsigned long tx_clk_esc ;
/*
* If CSI2_DPHY3 is calculated dynamically ,
* pdata - > lane_clk should be already set
* either in the board driver statically
* or in the sensor driver dynamically .
*/
/*
* dphy [ 0 ] - CSI2_DPHY3 :
* bit 0 ~ bit 7 : HS Term Enable .
* defines the time that the DPHY
* wait before enabling the data
* lane termination after detecting
* that the sensor has driven the data
* lanes to the LP00 bridge state .
* The value is calculated by :
* ( Max T ( D_TERM_EN ) / Period ( DDR ) ) - 1
* bit 8 ~ bit 15 : HS_SETTLE
* Time interval during which the HS
* receiver shall ignore any Data Lane
2019-02-18 14:29:00 -05:00
* HS transitions .
* The value has been calibrated on
2013-07-03 01:55:58 -03:00
* different boards . It seems to work well .
*
* More detail please refer
* MIPI Alliance Spectification for D - PHY
* document for explanation of HS - SETTLE
* and D - TERM - EN .
*/
switch ( pdata - > dphy3_algo ) {
case DPHY3_ALGO_PXA910 :
/*
* Calculate CSI2_DPHY3 algo for PXA910
*/
pdata - > dphy [ 0 ] =
( ( ( 1 + ( pdata - > lane_clk * 80 ) / 1000 ) & 0xff ) < < 8 )
| ( 1 + pdata - > lane_clk * 35 / 1000 ) ;
break ;
case DPHY3_ALGO_PXA2128 :
/*
* Calculate CSI2_DPHY3 algo for PXA2128
*/
pdata - > dphy [ 0 ] =
( ( ( 2 + ( pdata - > lane_clk * 110 ) / 1000 ) & 0xff ) < < 8 )
| ( 1 + pdata - > lane_clk * 35 / 1000 ) ;
break ;
default :
/*
* Use default CSI2_DPHY3 value for PXA688 / PXA988
*/
dev_dbg ( dev , " camera: use the default CSI2_DPHY3 value \n " ) ;
}
/*
* mipi_clk will never be changed , it is a fixed value on MMP
*/
if ( IS_ERR ( cam - > mipi_clk ) )
return ;
/* get the escape clk, this is hard coded */
2013-11-05 05:29:07 -03:00
clk_prepare_enable ( cam - > mipi_clk ) ;
2013-07-03 01:55:58 -03:00
tx_clk_esc = ( clk_get_rate ( cam - > mipi_clk ) / 1000000 ) / 12 ;
2013-11-05 05:29:07 -03:00
clk_disable_unprepare ( cam - > mipi_clk ) ;
2013-07-03 01:55:58 -03:00
/*
* dphy [ 2 ] - CSI2_DPHY6 :
* bit 0 ~ bit 7 : CK Term Enable
* Time for the Clock Lane receiver to enable the HS line
* termination . The value is calculated similarly with
* HS Term Enable
* bit 8 ~ bit 15 : CK Settle
* Time interval during which the HS receiver shall ignore
* any Clock Lane HS transitions .
* The value is calibrated on the boards .
*/
pdata - > dphy [ 2 ] =
( ( ( ( 534 * tx_clk_esc ) / 2000 - 1 ) & 0xff ) < < 8 )
| ( ( ( 38 * tx_clk_esc ) / 1000 - 1 ) & 0xff ) ;
dev_dbg ( dev , " camera: DPHY sets: dphy3=0x%x, dphy5=0x%x, dphy6=0x%x \n " ,
pdata - > dphy [ 0 ] , pdata - > dphy [ 1 ] , pdata - > dphy [ 2 ] ) ;
}
2011-06-11 14:46:49 -03:00
static irqreturn_t mmpcam_irq ( int irq , void * data )
{
struct mcam_camera * mcam = data ;
unsigned int irqs , handled ;
spin_lock ( & mcam - > dev_lock ) ;
irqs = mcam_reg_read ( mcam , REG_IRQSTAT ) ;
handled = mccic_irq ( mcam , irqs ) ;
spin_unlock ( & mcam - > dev_lock ) ;
return IRQ_RETVAL ( handled ) ;
}
2013-07-03 01:55:59 -03:00
static void mcam_init_clk ( struct mcam_camera * mcam )
{
unsigned int i ;
for ( i = 0 ; i < NR_MCAM_CLK ; i + + ) {
if ( mcam_clks [ i ] ! = NULL ) {
/* Some clks are not necessary on some boards
* We still try to run even it fails getting clk
*/
mcam - > clk [ i ] = devm_clk_get ( mcam - > dev , mcam_clks [ i ] ) ;
if ( IS_ERR ( mcam - > clk [ i ] ) )
dev_warn ( mcam - > dev , " Could not get clk: %s \n " ,
mcam_clks [ i ] ) ;
}
}
}
2011-06-11 14:46:49 -03:00
static int mmpcam_probe ( struct platform_device * pdev )
{
struct mmp_camera * cam ;
struct mcam_camera * mcam ;
struct resource * res ;
2019-05-28 05:07:30 -04:00
struct fwnode_handle * ep ;
2011-06-11 14:46:49 -03:00
struct mmp_camera_platform_data * pdata ;
int ret ;
2013-07-03 01:56:04 -03:00
cam = devm_kzalloc ( & pdev - > dev , sizeof ( * cam ) , GFP_KERNEL ) ;
2011-06-11 14:46:49 -03:00
if ( cam = = NULL )
return - ENOMEM ;
cam - > pdev = pdev ;
INIT_LIST_HEAD ( & cam - > devlist ) ;
mcam = & cam - > mcam ;
2013-07-03 01:55:58 -03:00
mcam - > calc_dphy = mmpcam_calc_dphy ;
2011-06-11 14:46:49 -03:00
mcam - > dev = & pdev - > dev ;
2019-05-28 05:07:29 -04:00
pdata = pdev - > dev . platform_data ;
if ( pdata ) {
mcam - > mclk_src = pdata - > mclk_src ;
mcam - > mclk_div = pdata - > mclk_div ;
mcam - > bus_type = pdata - > bus_type ;
mcam - > dphy = pdata - > dphy ;
mcam - > lane = pdata - > lane ;
} else {
/*
* These are values that used to be hardcoded in mcam - core and
* work well on a OLPC XO 1.75 with a parallel bus sensor .
* If it turns out other setups make sense , the values should
* be obtained from the device tree .
*/
mcam - > mclk_src = 3 ;
mcam - > mclk_div = 2 ;
}
2018-07-03 17:19:27 -04:00
if ( mcam - > bus_type = = V4L2_MBUS_CSI2_DPHY ) {
2013-11-05 05:29:07 -03:00
cam - > mipi_clk = devm_clk_get ( mcam - > dev , " mipi " ) ;
if ( ( IS_ERR ( cam - > mipi_clk ) & & mcam - > dphy [ 2 ] = = 0 ) )
return PTR_ERR ( cam - > mipi_clk ) ;
}
2013-07-03 01:55:58 -03:00
mcam - > mipi_enabled = false ;
2013-05-29 06:59:44 -03:00
mcam - > chip_id = MCAM_ARMADA610 ;
2011-06-30 17:05:28 -03:00
mcam - > buffer_mode = B_DMA_sg ;
2018-09-10 08:19:14 -04:00
strscpy ( mcam - > bus_info , " platform:mmp-camera " , sizeof ( mcam - > bus_info ) ) ;
2011-06-11 14:46:49 -03:00
spin_lock_init ( & mcam - > dev_lock ) ;
/*
* Get our I / O memory .
*/
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-07-03 01:56:04 -03:00
mcam - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( mcam - > regs ) )
return PTR_ERR ( mcam - > regs ) ;
2013-05-29 07:00:02 -03:00
mcam - > regs_size = resource_size ( res ) ;
2013-07-03 01:55:59 -03:00
mcam_init_clk ( mcam ) ;
2019-05-28 05:07:30 -04:00
/*
* Create a match of the sensor against its OF node .
*/
ep = fwnode_graph_get_next_endpoint ( of_fwnode_handle ( pdev - > dev . of_node ) ,
NULL ) ;
if ( ! ep )
return - ENODEV ;
mcam - > asd . match_type = V4L2_ASYNC_MATCH_FWNODE ;
mcam - > asd . match . fwnode = fwnode_graph_get_remote_port_parent ( ep ) ;
fwnode_handle_put ( ep ) ;
2011-06-11 14:46:49 -03:00
/*
2019-05-28 05:07:31 -04:00
* Register the device with the core .
2011-06-11 14:46:49 -03:00
*/
2013-07-03 01:55:58 -03:00
ret = mccic_register ( mcam ) ;
if ( ret )
2019-05-28 05:07:31 -04:00
return ret ;
/*
* Add OF clock provider .
*/
ret = of_clk_add_provider ( pdev - > dev . of_node , of_clk_src_simple_get ,
mcam - > mclk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " can't add DT clock provider \n " ) ;
goto out ;
}
2019-05-28 05:07:30 -04:00
2011-06-11 14:46:49 -03:00
/*
* Finally , set up our IRQ now that the core is ready to
* deal with it .
*/
res = platform_get_resource ( pdev , IORESOURCE_IRQ , 0 ) ;
if ( res = = NULL ) {
ret = - ENODEV ;
2019-05-28 05:07:31 -04:00
goto out ;
2011-06-11 14:46:49 -03:00
}
cam - > irq = res - > start ;
2013-07-03 01:56:04 -03:00
ret = devm_request_irq ( & pdev - > dev , cam - > irq , mmpcam_irq , IRQF_SHARED ,
" mmp-camera " , mcam ) ;
2011-06-11 14:46:49 -03:00
if ( ret = = 0 ) {
mmpcam_add_device ( cam ) ;
return 0 ;
}
2019-05-28 05:07:31 -04:00
out :
fwnode_handle_put ( mcam - > asd . match . fwnode ) ;
2011-06-11 14:46:49 -03:00
mccic_shutdown ( mcam ) ;
2019-05-28 05:07:31 -04:00
2011-06-11 14:46:49 -03:00
return ret ;
}
static int mmpcam_remove ( struct mmp_camera * cam )
{
struct mcam_camera * mcam = & cam - > mcam ;
mmpcam_remove_device ( cam ) ;
mccic_shutdown ( mcam ) ;
return 0 ;
}
static int mmpcam_platform_remove ( struct platform_device * pdev )
{
struct mmp_camera * cam = mmpcam_find_device ( pdev ) ;
if ( cam = = NULL )
return - ENODEV ;
return mmpcam_remove ( cam ) ;
}
2011-12-30 14:13:41 -03:00
/*
* Suspend / resume support .
*/
# ifdef CONFIG_PM
static int mmpcam_suspend ( struct platform_device * pdev , pm_message_t state )
{
struct mmp_camera * cam = mmpcam_find_device ( pdev ) ;
if ( state . event ! = PM_EVENT_SUSPEND )
return 0 ;
mccic_suspend ( & cam - > mcam ) ;
return 0 ;
}
static int mmpcam_resume ( struct platform_device * pdev )
{
struct mmp_camera * cam = mmpcam_find_device ( pdev ) ;
return mccic_resume ( & cam - > mcam ) ;
}
# endif
2019-05-28 05:07:29 -04:00
static const struct of_device_id mmpcam_of_match [ ] = {
{ . compatible = " marvell,mmp2-ccic " , } ,
{ } ,
} ;
2019-07-22 07:33:10 -04:00
MODULE_DEVICE_TABLE ( of , mmpcam_of_match ) ;
2011-06-11 14:46:49 -03:00
static struct platform_driver mmpcam_driver = {
. probe = mmpcam_probe ,
. remove = mmpcam_platform_remove ,
2011-12-30 14:13:41 -03:00
# ifdef CONFIG_PM
. suspend = mmpcam_suspend ,
. resume = mmpcam_resume ,
# endif
2011-06-11 14:46:49 -03:00
. driver = {
. name = " mmp-camera " ,
2019-05-28 05:07:29 -04:00
. of_match_table = of_match_ptr ( mmpcam_of_match ) ,
2011-06-11 14:46:49 -03:00
}
} ;
static int __init mmpcam_init_module ( void )
{
mutex_init ( & mmpcam_devices_lock ) ;
return platform_driver_register ( & mmpcam_driver ) ;
}
static void __exit mmpcam_exit_module ( void )
{
platform_driver_unregister ( & mmpcam_driver ) ;
/*
* platform_driver_unregister ( ) should have emptied the list
*/
if ( ! list_empty ( & mmpcam_devices ) )
printk ( KERN_ERR " mmp_camera leaving devices behind \n " ) ;
}
module_init ( mmpcam_init_module ) ;
module_exit ( mmpcam_exit_module ) ;