2016-11-10 15:29:37 +01:00
/*
* Copyright ( C ) 2016 BayLibre , SAS
* Author : Neil Armstrong < narmstrong @ baylibre . com >
* Copyright ( C ) 2014 Endless Mobile
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation ; either version 2 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , see < http : //www.gnu.org/licenses/>.
*
* Written by :
* Jasper St . Pierre < jstpierre @ mecheye . net >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/platform_device.h>
2017-04-04 14:15:23 +02:00
# include <linux/component.h>
2016-11-10 15:29:37 +01:00
# include <linux/of_graph.h>
# include <drm/drmP.h>
# include <drm/drm_atomic.h>
# include <drm/drm_atomic_helper.h>
# include <drm/drm_flip_work.h>
# include <drm/drm_crtc_helper.h>
# include <drm/drm_plane_helper.h>
# include <drm/drm_gem_cma_helper.h>
2017-09-24 14:26:19 +02:00
# include <drm/drm_gem_framebuffer_helper.h>
2016-11-10 15:29:37 +01:00
# include <drm/drm_fb_cma_helper.h>
# include <drm/drm_rect.h>
# include <drm/drm_fb_helper.h>
# include "meson_drv.h"
# include "meson_plane.h"
2018-11-06 10:40:00 +01:00
# include "meson_overlay.h"
2016-11-10 15:29:37 +01:00
# include "meson_crtc.h"
# include "meson_venc_cvbs.h"
# include "meson_vpp.h"
# include "meson_viu.h"
# include "meson_venc.h"
# include "meson_canvas.h"
# include "meson_registers.h"
# define DRIVER_NAME "meson"
# define DRIVER_DESC "Amlogic Meson DRM driver"
2017-04-04 14:15:29 +02:00
/**
* DOC : Video Processing Unit
2016-11-10 15:29:37 +01:00
*
* VPU Handles the Global Video Processing , it includes management of the
* clocks gates , blocks reset lines and power domains .
*
* What is missing :
2017-04-04 14:15:29 +02:00
*
2016-11-10 15:29:37 +01:00
* - Full reset of entire video processing HW blocks
* - Scaling and setup of the VPU clock
* - Bus clock gates
* - Powering up video processing HW blocks
* - Powering Up HDMI controller and PHY
*/
static const struct drm_mode_config_funcs meson_mode_config_funcs = {
. atomic_check = drm_atomic_helper_check ,
. atomic_commit = drm_atomic_helper_commit ,
2017-09-24 14:26:19 +02:00
. fb_create = drm_gem_fb_create ,
2016-11-10 15:29:37 +01:00
} ;
static irqreturn_t meson_irq ( int irq , void * arg )
{
struct drm_device * dev = arg ;
struct meson_drm * priv = dev - > dev_private ;
( void ) readl_relaxed ( priv - > io_base + _REG ( VENC_INTFLAG ) ) ;
meson_crtc_irq ( priv ) ;
return IRQ_HANDLED ;
}
2017-03-08 15:12:56 +01:00
DEFINE_DRM_GEM_CMA_FOPS ( fops ) ;
2016-11-10 15:29:37 +01:00
static struct drm_driver meson_driver = {
. driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM |
DRIVER_MODESET | DRIVER_PRIME |
DRIVER_ATOMIC ,
/* IRQ */
. irq_handler = meson_irq ,
/* PRIME Ops */
. prime_handle_to_fd = drm_gem_prime_handle_to_fd ,
. prime_fd_to_handle = drm_gem_prime_fd_to_handle ,
. gem_prime_import = drm_gem_prime_import ,
. gem_prime_export = drm_gem_prime_export ,
. gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table ,
. gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table ,
. gem_prime_vmap = drm_gem_cma_prime_vmap ,
. gem_prime_vunmap = drm_gem_cma_prime_vunmap ,
. gem_prime_mmap = drm_gem_cma_prime_mmap ,
/* GEM Ops */
. dumb_create = drm_gem_cma_dumb_create ,
. gem_free_object_unlocked = drm_gem_cma_free_object ,
. gem_vm_ops = & drm_gem_cma_vm_ops ,
/* Misc */
. fops = & fops ,
. name = DRIVER_NAME ,
. desc = DRIVER_DESC ,
. date = " 20161109 " ,
. major = 1 ,
. minor = 0 ,
} ;
static bool meson_vpu_has_available_connectors ( struct device * dev )
{
struct device_node * ep , * remote ;
/* Parses each endpoint and check if remote exists */
for_each_endpoint_of_node ( dev - > of_node , ep ) {
/* If the endpoint node exists, consider it enabled */
remote = of_graph_get_remote_port ( ep ) ;
if ( remote )
return true ;
}
return false ;
}
static struct regmap_config meson_regmap_config = {
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
. max_register = 0x1000 ,
} ;
2017-12-06 12:54:28 +01:00
static void meson_vpu_init ( struct meson_drm * priv )
{
writel_relaxed ( 0x210000 , priv - > io_base + _REG ( VPU_RDARB_MODE_L1C1 ) ) ;
writel_relaxed ( 0x10000 , priv - > io_base + _REG ( VPU_RDARB_MODE_L1C2 ) ) ;
writel_relaxed ( 0x900000 , priv - > io_base + _REG ( VPU_RDARB_MODE_L2C1 ) ) ;
writel_relaxed ( 0x20000 , priv - > io_base + _REG ( VPU_WRARB_MODE_L2C1 ) ) ;
}
2017-05-29 16:15:52 +02:00
static int meson_drv_bind_master ( struct device * dev , bool has_components )
2016-11-10 15:29:37 +01:00
{
2017-04-04 14:15:23 +02:00
struct platform_device * pdev = to_platform_device ( dev ) ;
2016-11-10 15:29:37 +01:00
struct meson_drm * priv ;
struct drm_device * drm ;
struct resource * res ;
void __iomem * regs ;
int ret ;
/* Checks if an output connector is available */
if ( ! meson_vpu_has_available_connectors ( dev ) ) {
dev_err ( dev , " No output connector available \n " ) ;
return - ENODEV ;
}
drm = drm_dev_alloc ( & meson_driver , dev ) ;
if ( IS_ERR ( drm ) )
return PTR_ERR ( drm ) ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv ) {
ret = - ENOMEM ;
goto free_drm ;
}
drm - > dev_private = priv ;
priv - > drm = drm ;
priv - > dev = dev ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " vpu " ) ;
regs = devm_ioremap_resource ( dev , res ) ;
2018-03-12 21:15:10 +01:00
if ( IS_ERR ( regs ) ) {
ret = PTR_ERR ( regs ) ;
goto free_drm ;
}
2016-11-10 15:29:37 +01:00
priv - > io_base = regs ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " hhi " ) ;
2018-06-11 18:53:35 +02:00
if ( ! res ) {
ret = - EINVAL ;
goto free_drm ;
}
2016-11-10 15:29:37 +01:00
/* Simply ioremap since it may be a shared register zone */
regs = devm_ioremap ( dev , res - > start , resource_size ( res ) ) ;
2018-03-12 21:15:10 +01:00
if ( ! regs ) {
ret = - EADDRNOTAVAIL ;
goto free_drm ;
}
2016-11-10 15:29:37 +01:00
priv - > hhi = devm_regmap_init_mmio ( dev , regs ,
& meson_regmap_config ) ;
if ( IS_ERR ( priv - > hhi ) ) {
dev_err ( & pdev - > dev , " Couldn't create the HHI regmap \n " ) ;
2018-03-12 21:15:10 +01:00
ret = PTR_ERR ( priv - > hhi ) ;
goto free_drm ;
2016-11-10 15:29:37 +01:00
}
2018-11-05 11:45:08 +01:00
priv - > canvas = meson_canvas_get ( dev ) ;
if ( ! IS_ERR ( priv - > canvas ) ) {
ret = meson_canvas_alloc ( priv - > canvas , & priv - > canvas_id_osd1 ) ;
if ( ret )
goto free_drm ;
2018-11-06 10:40:00 +01:00
ret = meson_canvas_alloc ( priv - > canvas , & priv - > canvas_id_vd1_0 ) ;
if ( ret ) {
meson_canvas_free ( priv - > canvas , priv - > canvas_id_osd1 ) ;
goto free_drm ;
}
ret = meson_canvas_alloc ( priv - > canvas , & priv - > canvas_id_vd1_1 ) ;
if ( ret ) {
meson_canvas_free ( priv - > canvas , priv - > canvas_id_osd1 ) ;
meson_canvas_free ( priv - > canvas , priv - > canvas_id_vd1_0 ) ;
goto free_drm ;
}
ret = meson_canvas_alloc ( priv - > canvas , & priv - > canvas_id_vd1_2 ) ;
if ( ret ) {
meson_canvas_free ( priv - > canvas , priv - > canvas_id_osd1 ) ;
meson_canvas_free ( priv - > canvas , priv - > canvas_id_vd1_0 ) ;
meson_canvas_free ( priv - > canvas , priv - > canvas_id_vd1_1 ) ;
goto free_drm ;
}
2018-11-05 11:45:08 +01:00
} else {
priv - > canvas = NULL ;
2016-11-10 15:29:37 +01:00
2018-11-05 11:45:08 +01:00
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " dmc " ) ;
if ( ! res ) {
ret = - EINVAL ;
goto free_drm ;
}
/* Simply ioremap since it may be a shared register zone */
regs = devm_ioremap ( dev , res - > start , resource_size ( res ) ) ;
if ( ! regs ) {
ret = - EADDRNOTAVAIL ;
goto free_drm ;
}
priv - > dmc = devm_regmap_init_mmio ( dev , regs ,
& meson_regmap_config ) ;
if ( IS_ERR ( priv - > dmc ) ) {
dev_err ( & pdev - > dev , " Couldn't create the DMC regmap \n " ) ;
ret = PTR_ERR ( priv - > dmc ) ;
goto free_drm ;
}
2016-11-10 15:29:37 +01:00
}
priv - > vsync_irq = platform_get_irq ( pdev , 0 ) ;
2018-03-12 21:15:08 +01:00
ret = drm_vblank_init ( drm , 1 ) ;
if ( ret )
goto free_drm ;
2016-11-10 15:29:37 +01:00
drm_mode_config_init ( drm ) ;
2017-04-04 14:15:23 +02:00
drm - > mode_config . max_width = 3840 ;
drm - > mode_config . max_height = 2160 ;
drm - > mode_config . funcs = & meson_mode_config_funcs ;
/* Hardware Initialization */
2017-12-06 12:54:28 +01:00
meson_vpu_init ( priv ) ;
2017-04-04 14:15:23 +02:00
meson_venc_init ( priv ) ;
meson_vpp_init ( priv ) ;
meson_viu_init ( priv ) ;
2016-11-10 15:29:37 +01:00
/* Encoder Initialization */
ret = meson_venc_cvbs_create ( priv ) ;
if ( ret )
goto free_drm ;
2017-05-29 16:15:52 +02:00
if ( has_components ) {
ret = component_bind_all ( drm - > dev , drm ) ;
if ( ret ) {
dev_err ( drm - > dev , " Couldn't bind all components \n " ) ;
goto free_drm ;
}
2017-04-04 14:15:23 +02:00
}
2016-11-10 15:29:37 +01:00
ret = meson_plane_create ( priv ) ;
if ( ret )
goto free_drm ;
2018-11-06 10:40:00 +01:00
ret = meson_overlay_create ( priv ) ;
if ( ret )
goto free_drm ;
2016-11-10 15:29:37 +01:00
ret = meson_crtc_create ( priv ) ;
if ( ret )
goto free_drm ;
ret = drm_irq_install ( drm , priv - > vsync_irq ) ;
if ( ret )
goto free_drm ;
drm_mode_config_reset ( drm ) ;
drm_kms_helper_poll_init ( drm ) ;
platform_set_drvdata ( pdev , priv ) ;
ret = drm_dev_register ( drm , 0 ) ;
if ( ret )
goto free_drm ;
2018-09-08 15:46:33 +02:00
drm_fbdev_generic_setup ( drm , 32 ) ;
2016-11-10 15:29:37 +01:00
return 0 ;
free_drm :
2018-03-12 21:15:09 +01:00
drm_dev_put ( drm ) ;
2016-11-10 15:29:37 +01:00
return ret ;
}
2017-05-29 16:15:52 +02:00
static int meson_drv_bind ( struct device * dev )
{
return meson_drv_bind_master ( dev , true ) ;
}
2017-04-04 14:15:23 +02:00
static void meson_drv_unbind ( struct device * dev )
2016-11-10 15:29:37 +01:00
{
2017-04-04 14:15:23 +02:00
struct drm_device * drm = dev_get_drvdata ( dev ) ;
2018-11-05 11:45:08 +01:00
struct meson_drm * priv = drm - > dev_private ;
2018-11-06 10:40:00 +01:00
if ( priv - > canvas ) {
2018-11-05 11:45:08 +01:00
meson_canvas_free ( priv - > canvas , priv - > canvas_id_osd1 ) ;
2018-11-06 10:40:00 +01:00
meson_canvas_free ( priv - > canvas , priv - > canvas_id_vd1_0 ) ;
meson_canvas_free ( priv - > canvas , priv - > canvas_id_vd1_1 ) ;
meson_canvas_free ( priv - > canvas , priv - > canvas_id_vd1_2 ) ;
}
2016-11-10 15:29:37 +01:00
drm_dev_unregister ( drm ) ;
drm_kms_helper_poll_fini ( drm ) ;
drm_mode_config_cleanup ( drm ) ;
2018-03-12 21:15:09 +01:00
drm_dev_put ( drm ) ;
2016-11-10 15:29:37 +01:00
}
2017-04-04 14:15:23 +02:00
static const struct component_master_ops meson_drv_master_ops = {
. bind = meson_drv_bind ,
. unbind = meson_drv_unbind ,
} ;
static int compare_of ( struct device * dev , void * data )
{
2017-07-18 16:43:04 -05:00
DRM_DEBUG_DRIVER ( " Comparing of node %pOF with %pOF \n " ,
dev - > of_node , data ) ;
2017-04-04 14:15:23 +02:00
return dev - > of_node = = data ;
}
/* Possible connectors nodes to ignore */
static const struct of_device_id connectors_match [ ] = {
{ . compatible = " composite-video-connector " } ,
{ . compatible = " svideo-connector " } ,
{ . compatible = " hdmi-connector " } ,
{ . compatible = " dvi-connector " } ,
{ }
} ;
static int meson_probe_remote ( struct platform_device * pdev ,
struct component_match * * match ,
struct device_node * parent ,
struct device_node * remote )
{
struct device_node * ep , * remote_node ;
int count = 1 ;
/* If node is a connector, return and do not add to match table */
if ( of_match_node ( connectors_match , remote ) )
return 1 ;
component_match_add ( & pdev - > dev , match , compare_of , remote ) ;
for_each_endpoint_of_node ( remote , ep ) {
remote_node = of_graph_get_remote_port_parent ( ep ) ;
if ( ! remote_node | |
remote_node = = parent | | /* Ignore parent endpoint */
! of_device_is_available ( remote_node ) )
continue ;
count + = meson_probe_remote ( pdev , match , remote , remote_node ) ;
of_node_put ( remote_node ) ;
}
return count ;
}
static int meson_drv_probe ( struct platform_device * pdev )
{
struct component_match * match = NULL ;
struct device_node * np = pdev - > dev . of_node ;
struct device_node * ep , * remote ;
int count = 0 ;
for_each_endpoint_of_node ( np , ep ) {
remote = of_graph_get_remote_port_parent ( ep ) ;
if ( ! remote | | ! of_device_is_available ( remote ) )
continue ;
count + = meson_probe_remote ( pdev , & match , np , remote ) ;
}
2017-05-29 16:15:52 +02:00
if ( count & & ! match )
return meson_drv_bind_master ( & pdev - > dev , false ) ;
2017-04-04 14:15:23 +02:00
/* If some endpoints were found, initialize the nodes */
if ( count ) {
dev_info ( & pdev - > dev , " Queued %d outputs on vpu \n " , count ) ;
return component_master_add_with_match ( & pdev - > dev ,
& meson_drv_master_ops ,
match ) ;
}
/* If no output endpoints were available, simply bail out */
return 0 ;
} ;
2016-11-10 15:29:37 +01:00
static const struct of_device_id dt_match [ ] = {
{ . compatible = " amlogic,meson-gxbb-vpu " } ,
{ . compatible = " amlogic,meson-gxl-vpu " } ,
{ . compatible = " amlogic,meson-gxm-vpu " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , dt_match ) ;
static struct platform_driver meson_drm_platform_driver = {
. probe = meson_drv_probe ,
. driver = {
2017-02-02 10:47:44 +01:00
. name = " meson-drm " ,
2016-11-10 15:29:37 +01:00
. of_match_table = dt_match ,
} ,
} ;
module_platform_driver ( meson_drm_platform_driver ) ;
MODULE_AUTHOR ( " Jasper St. Pierre <jstpierre@mecheye.net> " ) ;
MODULE_AUTHOR ( " Neil Armstrong <narmstrong@baylibre.com> " ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;