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>
# include <linux/module.h>
# 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
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 ;
/*
* Yes , we really must jump through these hoops just to store a
* _pointer_ to something into the kfifo . This is utterly insane
* and idiotic , because it kfifo requires the _data_ pointed to by
* the pointer const , not the pointer itself . Not only that , but
* you have to pass a pointer _to_ the pointer you want stored .
*/
const struct drm_framebuffer * silly_api_alert = fb ;
WARN_ON ( ! kfifo_put ( & priv - > fb_unref , & silly_api_alert ) ) ;
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 ;
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 ;
}
if ( ! res [ 0 ] | | ! mem )
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 ;
priv - > variant = ( struct armada_variant * ) id - > driver_data ;
ret = priv - > variant - > init ( priv , dev - > dev ) ;
if ( ret )
return ret ;
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 + + ) {
if ( ! res [ n ] )
break ;
ret = armada_drm_crtc_create ( dev , n , res [ n ] ) ;
if ( ret )
goto err_kms ;
}
2013-05-04 20:08:27 +01:00
# ifdef CONFIG_DRM_ARMADA_TDA1998X
ret = armada_drm_connector_slave_create ( dev , & tda19988_config ) ;
if ( ret )
goto err_kms ;
# endif
2012-08-15 13:59:49 +01:00
ret = drm_vblank_init ( dev , n ) ;
if ( ret )
goto err_kms ;
ret = drm_irq_install ( dev ) ;
if ( ret )
goto err_kms ;
dev - > vblank_disable_allowed = 1 ;
ret = armada_fbdev_init ( dev ) ;
if ( ret )
goto err_irq ;
drm_kms_helper_poll_init ( dev ) ;
return 0 ;
err_irq :
drm_irq_uninstall ( dev ) ;
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 ) ;
drm_irq_uninstall ( dev ) ;
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 ) ;
}
}
void armada_drm_vbl_event_remove_unlocked ( struct armada_crtc * dcrtc ,
struct armada_vbl_event * evt )
{
unsigned long flags ;
spin_lock_irqsave ( & dcrtc - > irq_lock , flags ) ;
armada_drm_vbl_event_remove ( dcrtc , evt ) ;
spin_unlock_irqrestore ( & dcrtc - > irq_lock , flags ) ;
}
/* 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 irqreturn_t armada_drm_irq_handler ( int irq , void * arg )
{
struct drm_device * dev = arg ;
struct armada_private * priv = dev - > dev_private ;
struct armada_crtc * dcrtc = priv - > dcrtc [ 0 ] ;
uint32_t v , stat = readl_relaxed ( dcrtc - > base + LCD_SPU_IRQ_ISR ) ;
irqreturn_t handled = IRQ_NONE ;
/*
* This is rediculous - rather than writing bits to clear , we
* have to set the actual status register value . This is racy .
*/
writel_relaxed ( 0 , dcrtc - > base + LCD_SPU_IRQ_ISR ) ;
/* Mask out those interrupts we haven't enabled */
v = stat & dcrtc - > irq_ena ;
if ( v & ( VSYNC_IRQ | GRA_FRAME_IRQ | DUMB_FRAMEDONE ) ) {
armada_drm_crtc_irq ( dcrtc , stat ) ;
handled = IRQ_HANDLED ;
}
return handled ;
}
static int armada_drm_irq_postinstall ( struct drm_device * dev )
{
struct armada_private * priv = dev - > dev_private ;
struct armada_crtc * dcrtc = priv - > dcrtc [ 0 ] ;
spin_lock_irq ( & dev - > vbl_lock ) ;
writel_relaxed ( dcrtc - > irq_ena , dcrtc - > base + LCD_SPU_IRQ_ENA ) ;
writel ( 0 , dcrtc - > base + LCD_SPU_IRQ_ISR ) ;
spin_unlock_irq ( & dev - > vbl_lock ) ;
return 0 ;
}
static void armada_drm_irq_uninstall ( struct drm_device * dev )
{
struct armada_private * priv = dev - > dev_private ;
struct armada_crtc * dcrtc = priv - > dcrtc [ 0 ] ;
writel ( 0 , dcrtc - > base + LCD_SPU_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 ,
. get_vblank_counter = drm_vblank_count ,
. enable_vblank = armada_drm_enable_vblank ,
. disable_vblank = armada_drm_disable_vblank ,
. irq_handler = armada_drm_irq_handler ,
. irq_postinstall = armada_drm_irq_postinstall ,
. irq_uninstall = armada_drm_irq_uninstall ,
# 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 |
DRIVER_HAVE_IRQ | DRIVER_PRIME ,
. ioctls = armada_ioctls ,
. fops = & armada_drm_fops ,
} ;
static int armada_drm_probe ( struct platform_device * pdev )
{
return drm_platform_init ( & armada_drm_driver , pdev ) ;
}
static int armada_drm_remove ( struct platform_device * pdev )
{
2013-12-11 11:34:28 +01:00
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 " ,
. owner = THIS_MODULE ,
} ,
. id_table = armada_drm_platform_ids ,
} ;
static int __init armada_drm_init ( void )
{
armada_drm_driver . num_ioctls = DRM_ARRAY_SIZE ( armada_ioctls ) ;
return platform_driver_register ( & armada_drm_platform_driver ) ;
}
module_init ( armada_drm_init ) ;
static void __exit armada_drm_exit ( void )
{
platform_driver_unregister ( & armada_drm_platform_driver ) ;
}
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 " ) ;