2012-08-15 13:59:49 +01:00
/*
* Copyright ( C ) 2012 Russell King
*
* 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>
2014-02-04 11:57:06 +00:00
# include <linux/component.h>
2012-08-15 13:59:49 +01:00
# include <linux/module.h>
2014-02-04 11:57:06 +00:00
# include <linux/of_graph.h>
2012-08-15 13:59:49 +01:00
# include <drm/drmP.h>
# include <drm/drm_crtc_helper.h>
# include "armada_crtc.h"
# include "armada_drm.h"
# include "armada_gem.h"
# include "armada_hw.h"
# include <drm/armada_drm.h>
# include "armada_ioctlP.h"
2013-05-04 20:08:27 +01:00
# ifdef CONFIG_DRM_ARMADA_TDA1998X
# include <drm/i2c/tda998x.h>
# include "armada_slave.h"
static struct tda998x_encoder_params params = {
/* With 0x24, there is no translation between vp_out and int_vp
FB LCD out Pins VIP Int Vp
R : 23 : 16 R : 7 : 0 VPC7 : 0 7 : 0 7 : 0 [ R ]
G : 15 : 8 G : 15 : 8 VPB7 : 0 23 : 16 23 : 16 [ G ]
B : 7 : 0 B : 23 : 16 VPA7 : 0 15 : 8 15 : 8 [ B ]
*/
. swap_a = 2 ,
. swap_b = 3 ,
. swap_c = 4 ,
. swap_d = 5 ,
. swap_e = 0 ,
. swap_f = 1 ,
. audio_cfg = BIT ( 2 ) ,
. audio_frame [ 1 ] = 1 ,
. audio_format = AFMT_SPDIF ,
. audio_sample_rate = 44100 ,
} ;
static const struct armada_drm_slave_config tda19988_config = {
. i2c_adapter_id = 0 ,
. crtcs = 1 < < 0 , /* Only LCD0 at the moment */
. polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT ,
. interlace_allowed = true ,
. info = {
. type = " tda998x " ,
. addr = 0x70 ,
. platform_data = & params ,
} ,
} ;
# endif
2014-02-04 11:57:06 +00:00
static bool is_componentized ( struct device * dev )
{
return dev - > of_node | | dev - > platform_data ;
}
2012-08-15 13:59:49 +01:00
static void armada_drm_unref_work ( struct work_struct * work )
{
struct armada_private * priv =
container_of ( work , struct armada_private , fb_unref_work ) ;
struct drm_framebuffer * fb ;
while ( kfifo_get ( & priv - > fb_unref , & fb ) )
drm_framebuffer_unreference ( fb ) ;
}
/* Must be called with dev->event_lock held */
void __armada_drm_queue_unref_work ( struct drm_device * dev ,
struct drm_framebuffer * fb )
{
struct armada_private * priv = dev - > dev_private ;
2014-03-03 14:49:51 +00:00
WARN_ON ( ! kfifo_put ( & priv - > fb_unref , fb ) ) ;
2012-08-15 13:59:49 +01:00
schedule_work ( & priv - > fb_unref_work ) ;
}
void armada_drm_queue_unref_work ( struct drm_device * dev ,
struct drm_framebuffer * fb )
{
unsigned long flags ;
spin_lock_irqsave ( & dev - > event_lock , flags ) ;
__armada_drm_queue_unref_work ( dev , fb ) ;
spin_unlock_irqrestore ( & dev - > event_lock , flags ) ;
}
static int armada_drm_load ( struct drm_device * dev , unsigned long flags )
{
const struct platform_device_id * id ;
2014-04-22 15:24:03 +01:00
const struct armada_variant * variant ;
2012-08-15 13:59:49 +01:00
struct armada_private * priv ;
struct resource * res [ ARRAY_SIZE ( priv - > dcrtc ) ] ;
struct resource * mem = NULL ;
int ret , n , i ;
memset ( res , 0 , sizeof ( res ) ) ;
for ( n = i = 0 ; ; n + + ) {
struct resource * r = platform_get_resource ( dev - > platformdev ,
IORESOURCE_MEM , n ) ;
if ( ! r )
break ;
/* Resources above 64K are graphics memory */
if ( resource_size ( r ) > SZ_64K )
mem = r ;
else if ( i < ARRAY_SIZE ( priv - > dcrtc ) )
res [ i + + ] = r ;
else
return - EINVAL ;
}
2014-04-22 11:10:15 +01:00
if ( ! mem )
2012-08-15 13:59:49 +01:00
return - ENXIO ;
if ( ! devm_request_mem_region ( dev - > dev , mem - > start ,
resource_size ( mem ) , " armada-drm " ) )
return - EBUSY ;
priv = devm_kzalloc ( dev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv ) {
DRM_ERROR ( " failed to allocate private \n " ) ;
return - ENOMEM ;
}
2013-12-11 11:34:28 +01:00
platform_set_drvdata ( dev - > platformdev , dev ) ;
2012-08-15 13:59:49 +01:00
dev - > dev_private = priv ;
/* Get the implementation specific driver data. */
id = platform_get_device_id ( dev - > platformdev ) ;
if ( ! id )
return - ENXIO ;
2014-04-22 15:24:03 +01:00
variant = ( const struct armada_variant * ) id - > driver_data ;
2012-08-15 13:59:49 +01:00
INIT_WORK ( & priv - > fb_unref_work , armada_drm_unref_work ) ;
INIT_KFIFO ( priv - > fb_unref ) ;
/* Mode setting support */
drm_mode_config_init ( dev ) ;
dev - > mode_config . min_width = 320 ;
dev - > mode_config . min_height = 200 ;
/*
* With vscale enabled , the maximum width is 1920 due to the
* 1920 by 3 lines RAM
*/
dev - > mode_config . max_width = 1920 ;
dev - > mode_config . max_height = 2048 ;
dev - > mode_config . preferred_depth = 24 ;
dev - > mode_config . funcs = & armada_drm_mode_config_funcs ;
drm_mm_init ( & priv - > linear , mem - > start , resource_size ( mem ) ) ;
/* Create all LCD controllers */
for ( n = 0 ; n < ARRAY_SIZE ( priv - > dcrtc ) ; n + + ) {
2014-04-26 15:19:38 +01:00
int irq ;
2012-08-15 13:59:49 +01:00
if ( ! res [ n ] )
break ;
2014-04-26 15:19:38 +01:00
irq = platform_get_irq ( dev - > platformdev , n ) ;
if ( irq < 0 )
goto err_kms ;
2014-04-22 11:10:15 +01:00
ret = armada_drm_crtc_create ( dev , dev - > dev , res [ n ] , irq ,
2014-06-15 11:21:23 +01:00
variant , NULL ) ;
2012-08-15 13:59:49 +01:00
if ( ret )
goto err_kms ;
}
2014-02-04 11:57:06 +00:00
if ( is_componentized ( dev - > dev ) ) {
ret = component_bind_all ( dev - > dev , dev ) ;
if ( ret )
goto err_kms ;
} else {
2013-05-04 20:08:27 +01:00
# ifdef CONFIG_DRM_ARMADA_TDA1998X
2014-02-04 11:57:06 +00:00
ret = armada_drm_connector_slave_create ( dev , & tda19988_config ) ;
if ( ret )
goto err_kms ;
2013-05-04 20:08:27 +01:00
# endif
2014-02-04 11:57:06 +00:00
}
2013-05-04 20:08:27 +01:00
2014-04-22 11:02:23 +01:00
ret = drm_vblank_init ( dev , dev - > mode_config . num_crtc ) ;
2012-08-15 13:59:49 +01:00
if ( ret )
2014-02-04 11:57:06 +00:00
goto err_comp ;
2012-08-15 13:59:49 +01:00
2014-08-27 16:53:31 +01:00
dev - > irq_enabled = true ;
2012-08-15 13:59:49 +01:00
dev - > vblank_disable_allowed = 1 ;
ret = armada_fbdev_init ( dev ) ;
if ( ret )
2014-02-04 11:57:06 +00:00
goto err_comp ;
2012-08-15 13:59:49 +01:00
drm_kms_helper_poll_init ( dev ) ;
return 0 ;
2014-02-04 11:57:06 +00:00
err_comp :
if ( is_componentized ( dev - > dev ) )
component_unbind_all ( dev - > dev , dev ) ;
2012-08-15 13:59:49 +01:00
err_kms :
drm_mode_config_cleanup ( dev ) ;
drm_mm_takedown ( & priv - > linear ) ;
flush_work ( & priv - > fb_unref_work ) ;
return ret ;
}
static int armada_drm_unload ( struct drm_device * dev )
{
struct armada_private * priv = dev - > dev_private ;
drm_kms_helper_poll_fini ( dev ) ;
armada_fbdev_fini ( dev ) ;
2014-02-04 11:57:06 +00:00
if ( is_componentized ( dev - > dev ) )
component_unbind_all ( dev - > dev , dev ) ;
2012-08-15 13:59:49 +01:00
drm_mode_config_cleanup ( dev ) ;
drm_mm_takedown ( & priv - > linear ) ;
flush_work ( & priv - > fb_unref_work ) ;
dev - > dev_private = NULL ;
return 0 ;
}
void armada_drm_vbl_event_add ( struct armada_crtc * dcrtc ,
struct armada_vbl_event * evt )
{
unsigned long flags ;
spin_lock_irqsave ( & dcrtc - > irq_lock , flags ) ;
if ( list_empty ( & evt - > node ) ) {
list_add_tail ( & evt - > node , & dcrtc - > vbl_list ) ;
drm_vblank_get ( dcrtc - > crtc . dev , dcrtc - > num ) ;
}
spin_unlock_irqrestore ( & dcrtc - > irq_lock , flags ) ;
}
void armada_drm_vbl_event_remove ( struct armada_crtc * dcrtc ,
struct armada_vbl_event * evt )
{
if ( ! list_empty ( & evt - > node ) ) {
list_del_init ( & evt - > node ) ;
drm_vblank_put ( dcrtc - > crtc . dev , dcrtc - > num ) ;
}
}
/* These are called under the vbl_lock. */
static int armada_drm_enable_vblank ( struct drm_device * dev , int crtc )
{
struct armada_private * priv = dev - > dev_private ;
armada_drm_crtc_enable_irq ( priv - > dcrtc [ crtc ] , VSYNC_IRQ_ENA ) ;
return 0 ;
}
static void armada_drm_disable_vblank ( struct drm_device * dev , int crtc )
{
struct armada_private * priv = dev - > dev_private ;
armada_drm_crtc_disable_irq ( priv - > dcrtc [ crtc ] , VSYNC_IRQ_ENA ) ;
}
static struct drm_ioctl_desc armada_ioctls [ ] = {
DRM_IOCTL_DEF_DRV ( ARMADA_GEM_CREATE , armada_gem_create_ioctl ,
DRM_UNLOCKED ) ,
DRM_IOCTL_DEF_DRV ( ARMADA_GEM_MMAP , armada_gem_mmap_ioctl ,
DRM_UNLOCKED ) ,
DRM_IOCTL_DEF_DRV ( ARMADA_GEM_PWRITE , armada_gem_pwrite_ioctl ,
DRM_UNLOCKED ) ,
} ;
2013-10-27 15:26:33 +00:00
static void armada_drm_lastclose ( struct drm_device * dev )
{
armada_fbdev_lastclose ( dev ) ;
}
2012-08-15 13:59:49 +01:00
static const struct file_operations armada_drm_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. read = drm_read ,
. poll = drm_poll ,
. unlocked_ioctl = drm_ioctl ,
. mmap = drm_gem_mmap ,
. open = drm_open ,
. release = drm_release ,
} ;
static struct drm_driver armada_drm_driver = {
. load = armada_drm_load ,
. open = NULL ,
. preclose = NULL ,
. postclose = NULL ,
2013-10-27 15:26:33 +00:00
. lastclose = armada_drm_lastclose ,
2012-08-15 13:59:49 +01:00
. unload = armada_drm_unload ,
2014-08-29 12:12:43 +02:00
. set_busid = drm_platform_set_busid ,
2012-08-15 13:59:49 +01:00
. get_vblank_counter = drm_vblank_count ,
. enable_vblank = armada_drm_enable_vblank ,
. disable_vblank = armada_drm_disable_vblank ,
# ifdef CONFIG_DEBUG_FS
. debugfs_init = armada_drm_debugfs_init ,
. debugfs_cleanup = armada_drm_debugfs_cleanup ,
# endif
. gem_free_object = armada_gem_free_object ,
. prime_handle_to_fd = drm_gem_prime_handle_to_fd ,
. prime_fd_to_handle = drm_gem_prime_fd_to_handle ,
. gem_prime_export = armada_gem_prime_export ,
. gem_prime_import = armada_gem_prime_import ,
. dumb_create = armada_gem_dumb_create ,
. dumb_map_offset = armada_gem_dumb_map_offset ,
. dumb_destroy = armada_gem_dumb_destroy ,
. gem_vm_ops = & armada_gem_vm_ops ,
. major = 1 ,
. minor = 0 ,
. name = " armada-drm " ,
. desc = " Armada SoC DRM " ,
. date = " 20120730 " ,
. driver_features = DRIVER_GEM | DRIVER_MODESET |
2014-08-27 16:53:31 +01:00
DRIVER_HAVE_IRQ | DRIVER_PRIME ,
2012-08-15 13:59:49 +01:00
. ioctls = armada_ioctls ,
. fops = & armada_drm_fops ,
} ;
2014-02-04 11:57:06 +00:00
static int armada_drm_bind ( struct device * dev )
{
return drm_platform_init ( & armada_drm_driver , to_platform_device ( dev ) ) ;
}
static void armada_drm_unbind ( struct device * dev )
{
drm_put_dev ( dev_get_drvdata ( dev ) ) ;
}
static int compare_of ( struct device * dev , void * data )
{
return dev - > of_node = = data ;
}
static int compare_dev_name ( struct device * dev , void * data )
{
const char * name = data ;
return ! strcmp ( dev_name ( dev ) , name ) ;
}
static void armada_add_endpoints ( struct device * dev ,
struct component_match * * match , struct device_node * port )
{
struct device_node * ep , * remote ;
for_each_child_of_node ( port , ep ) {
remote = of_graph_get_remote_port_parent ( ep ) ;
if ( ! remote | | ! of_device_is_available ( remote ) ) {
of_node_put ( remote ) ;
continue ;
} else if ( ! of_device_is_available ( remote - > parent ) ) {
dev_warn ( dev , " parent device of %s is not available \n " ,
remote - > full_name ) ;
of_node_put ( remote ) ;
continue ;
}
component_match_add ( dev , match , compare_of , remote ) ;
of_node_put ( remote ) ;
}
}
static int armada_drm_find_components ( struct device * dev ,
struct component_match * * match )
{
struct device_node * port ;
int i ;
if ( dev - > of_node ) {
struct device_node * np = dev - > of_node ;
for ( i = 0 ; ; i + + ) {
port = of_parse_phandle ( np , " ports " , i ) ;
if ( ! port )
break ;
component_match_add ( dev , match , compare_of , port ) ;
of_node_put ( port ) ;
}
if ( i = = 0 ) {
dev_err ( dev , " missing 'ports' property \n " ) ;
return - ENODEV ;
}
for ( i = 0 ; ; i + + ) {
port = of_parse_phandle ( np , " ports " , i ) ;
if ( ! port )
break ;
armada_add_endpoints ( dev , match , port ) ;
of_node_put ( port ) ;
}
} else if ( dev - > platform_data ) {
char * * devices = dev - > platform_data ;
struct device * d ;
for ( i = 0 ; devices [ i ] ; i + + )
component_match_add ( dev , match , compare_dev_name ,
devices [ i ] ) ;
if ( i = = 0 ) {
dev_err ( dev , " missing 'ports' property \n " ) ;
return - ENODEV ;
}
for ( i = 0 ; devices [ i ] ; i + + ) {
d = bus_find_device_by_name ( & platform_bus_type , NULL ,
devices [ i ] ) ;
if ( d & & d - > of_node ) {
for_each_child_of_node ( d - > of_node , port )
armada_add_endpoints ( dev , match , port ) ;
}
put_device ( d ) ;
}
}
return 0 ;
}
static const struct component_master_ops armada_master_ops = {
. bind = armada_drm_bind ,
. unbind = armada_drm_unbind ,
} ;
2012-08-15 13:59:49 +01:00
static int armada_drm_probe ( struct platform_device * pdev )
{
2014-02-04 11:57:06 +00:00
if ( is_componentized ( & pdev - > dev ) ) {
struct component_match * match = NULL ;
int ret ;
ret = armada_drm_find_components ( & pdev - > dev , & match ) ;
if ( ret < 0 )
return ret ;
return component_master_add_with_match ( & pdev - > dev ,
& armada_master_ops , match ) ;
} else {
return drm_platform_init ( & armada_drm_driver , pdev ) ;
}
2012-08-15 13:59:49 +01:00
}
static int armada_drm_remove ( struct platform_device * pdev )
{
2014-02-04 11:57:06 +00:00
if ( is_componentized ( & pdev - > dev ) )
component_master_del ( & pdev - > dev , & armada_master_ops ) ;
else
drm_put_dev ( platform_get_drvdata ( pdev ) ) ;
2012-08-15 13:59:49 +01:00
return 0 ;
}
static const struct platform_device_id armada_drm_platform_ids [ ] = {
{
. name = " armada-drm " ,
. driver_data = ( unsigned long ) & armada510_ops ,
} , {
. name = " armada-510-drm " ,
. driver_data = ( unsigned long ) & armada510_ops ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( platform , armada_drm_platform_ids ) ;
static struct platform_driver armada_drm_platform_driver = {
. probe = armada_drm_probe ,
. remove = armada_drm_remove ,
. driver = {
. name = " armada-drm " ,
} ,
. id_table = armada_drm_platform_ids ,
} ;
static int __init armada_drm_init ( void )
{
2014-04-22 11:10:15 +01:00
int ret ;
2014-06-09 14:39:49 +01:00
armada_drm_driver . num_ioctls = ARRAY_SIZE ( armada_ioctls ) ;
2014-04-22 11:10:15 +01:00
ret = platform_driver_register ( & armada_lcd_platform_driver ) ;
if ( ret )
return ret ;
ret = platform_driver_register ( & armada_drm_platform_driver ) ;
if ( ret )
platform_driver_unregister ( & armada_lcd_platform_driver ) ;
return ret ;
2012-08-15 13:59:49 +01:00
}
module_init ( armada_drm_init ) ;
static void __exit armada_drm_exit ( void )
{
platform_driver_unregister ( & armada_drm_platform_driver ) ;
2014-04-22 11:10:15 +01:00
platform_driver_unregister ( & armada_lcd_platform_driver ) ;
2012-08-15 13:59:49 +01:00
}
module_exit ( armada_drm_exit ) ;
MODULE_AUTHOR ( " Russell King <rmk+kernel@arm.linux.org.uk> " ) ;
MODULE_DESCRIPTION ( " Armada DRM Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:armada-drm " ) ;