2015-10-29 09:36:23 +01:00
/*
* Copyright ( C ) 2015 Free Electrons
* Copyright ( C ) 2015 NextThing Co
*
* Maxime Ripard < maxime . ripard @ free - electrons . com >
*
* 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 .
*/
# include <drm/drmP.h>
# include <drm/drm_atomic_helper.h>
# include <drm/drm_crtc.h>
# include <drm/drm_crtc_helper.h>
# include <drm/drm_fb_cma_helper.h>
# include <drm/drm_gem_cma_helper.h>
# include <drm/drm_plane_helper.h>
# include <linux/component.h>
2017-04-21 16:38:50 +08:00
# include <linux/list.h>
2017-04-21 16:38:52 +08:00
# include <linux/of_graph.h>
2015-10-29 09:36:23 +01:00
# include <linux/reset.h>
# include "sun4i_backend.h"
# include "sun4i_drv.h"
2017-02-23 16:05:33 +08:00
static const u32 sunxi_rgb2yuv_coef [ 12 ] = {
2015-10-29 09:36:23 +01:00
0x00000107 , 0x00000204 , 0x00000064 , 0x00000108 ,
0x00003f69 , 0x00003ed6 , 0x000001c1 , 0x00000808 ,
0x000001c1 , 0x00003e88 , 0x00003fb8 , 0x00000808
} ;
void sun4i_backend_apply_color_correction ( struct sun4i_backend * backend )
{
int i ;
DRM_DEBUG_DRIVER ( " Applying RGB to YUV color correction \n " ) ;
/* Set color correction */
regmap_write ( backend - > regs , SUN4I_BACKEND_OCCTL_REG ,
SUN4I_BACKEND_OCCTL_ENABLE ) ;
for ( i = 0 ; i < 12 ; i + + )
regmap_write ( backend - > regs , SUN4I_BACKEND_OCRCOEF_REG ( i ) ,
sunxi_rgb2yuv_coef [ i ] ) ;
}
EXPORT_SYMBOL ( sun4i_backend_apply_color_correction ) ;
void sun4i_backend_disable_color_correction ( struct sun4i_backend * backend )
{
DRM_DEBUG_DRIVER ( " Disabling color correction \n " ) ;
/* Disable color correction */
regmap_update_bits ( backend - > regs , SUN4I_BACKEND_OCCTL_REG ,
SUN4I_BACKEND_OCCTL_ENABLE , 0 ) ;
}
EXPORT_SYMBOL ( sun4i_backend_disable_color_correction ) ;
void sun4i_backend_commit ( struct sun4i_backend * backend )
{
DRM_DEBUG_DRIVER ( " Committing changes \n " ) ;
regmap_write ( backend - > regs , SUN4I_BACKEND_REGBUFFCTL_REG ,
SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS |
SUN4I_BACKEND_REGBUFFCTL_LOADCTL ) ;
}
EXPORT_SYMBOL ( sun4i_backend_commit ) ;
void sun4i_backend_layer_enable ( struct sun4i_backend * backend ,
int layer , bool enable )
{
u32 val ;
2017-04-25 23:25:05 +08:00
DRM_DEBUG_DRIVER ( " %sabling layer %d \n " , enable ? " En " : " Dis " ,
layer ) ;
2015-10-29 09:36:23 +01:00
if ( enable )
val = SUN4I_BACKEND_MODCTL_LAY_EN ( layer ) ;
else
val = 0 ;
regmap_update_bits ( backend - > regs , SUN4I_BACKEND_MODCTL_REG ,
SUN4I_BACKEND_MODCTL_LAY_EN ( layer ) , val ) ;
}
EXPORT_SYMBOL ( sun4i_backend_layer_enable ) ;
2016-09-19 22:17:50 +02:00
static int sun4i_backend_drm_format_to_layer ( struct drm_plane * plane ,
u32 format , u32 * mode )
2015-10-29 09:36:23 +01:00
{
2016-09-19 22:17:50 +02:00
if ( ( plane - > type = = DRM_PLANE_TYPE_PRIMARY ) & &
( format = = DRM_FORMAT_ARGB8888 ) )
format = DRM_FORMAT_XRGB8888 ;
2015-10-29 09:36:23 +01:00
switch ( format ) {
case DRM_FORMAT_ARGB8888 :
* mode = SUN4I_BACKEND_LAY_FBFMT_ARGB8888 ;
break ;
2016-10-18 10:46:14 +02:00
case DRM_FORMAT_ARGB4444 :
* mode = SUN4I_BACKEND_LAY_FBFMT_ARGB4444 ;
break ;
case DRM_FORMAT_ARGB1555 :
* mode = SUN4I_BACKEND_LAY_FBFMT_ARGB1555 ;
break ;
case DRM_FORMAT_RGBA5551 :
* mode = SUN4I_BACKEND_LAY_FBFMT_RGBA5551 ;
break ;
case DRM_FORMAT_RGBA4444 :
* mode = SUN4I_BACKEND_LAY_FBFMT_RGBA4444 ;
break ;
2015-10-29 09:36:23 +01:00
case DRM_FORMAT_XRGB8888 :
* mode = SUN4I_BACKEND_LAY_FBFMT_XRGB8888 ;
break ;
case DRM_FORMAT_RGB888 :
* mode = SUN4I_BACKEND_LAY_FBFMT_RGB888 ;
break ;
2016-10-18 10:46:14 +02:00
case DRM_FORMAT_RGB565 :
* mode = SUN4I_BACKEND_LAY_FBFMT_RGB565 ;
break ;
2015-10-29 09:36:23 +01:00
default :
return - EINVAL ;
}
return 0 ;
}
int sun4i_backend_update_layer_coord ( struct sun4i_backend * backend ,
int layer , struct drm_plane * plane )
{
struct drm_plane_state * state = plane - > state ;
struct drm_framebuffer * fb = state - > fb ;
DRM_DEBUG_DRIVER ( " Updating layer %d \n " , layer ) ;
if ( plane - > type = = DRM_PLANE_TYPE_PRIMARY ) {
DRM_DEBUG_DRIVER ( " Primary layer, updating global size W: %u H: %u \n " ,
state - > crtc_w , state - > crtc_h ) ;
regmap_write ( backend - > regs , SUN4I_BACKEND_DISSIZE_REG ,
SUN4I_BACKEND_DISSIZE ( state - > crtc_w ,
state - > crtc_h ) ) ;
}
/* Set the line width */
DRM_DEBUG_DRIVER ( " Layer line width: %d bits \n " , fb - > pitches [ 0 ] * 8 ) ;
regmap_write ( backend - > regs , SUN4I_BACKEND_LAYLINEWIDTH_REG ( layer ) ,
fb - > pitches [ 0 ] * 8 ) ;
/* Set height and width */
DRM_DEBUG_DRIVER ( " Layer size W: %u H: %u \n " ,
state - > crtc_w , state - > crtc_h ) ;
regmap_write ( backend - > regs , SUN4I_BACKEND_LAYSIZE_REG ( layer ) ,
SUN4I_BACKEND_LAYSIZE ( state - > crtc_w ,
state - > crtc_h ) ) ;
/* Set base coordinates */
DRM_DEBUG_DRIVER ( " Layer coordinates X: %d Y: %d \n " ,
state - > crtc_x , state - > crtc_y ) ;
regmap_write ( backend - > regs , SUN4I_BACKEND_LAYCOOR_REG ( layer ) ,
SUN4I_BACKEND_LAYCOOR ( state - > crtc_x ,
state - > crtc_y ) ) ;
return 0 ;
}
EXPORT_SYMBOL ( sun4i_backend_update_layer_coord ) ;
int sun4i_backend_update_layer_formats ( struct sun4i_backend * backend ,
int layer , struct drm_plane * plane )
{
struct drm_plane_state * state = plane - > state ;
struct drm_framebuffer * fb = state - > fb ;
bool interlaced = false ;
u32 val ;
int ret ;
if ( plane - > state - > crtc )
interlaced = plane - > state - > crtc - > state - > adjusted_mode . flags
& DRM_MODE_FLAG_INTERLACE ;
regmap_update_bits ( backend - > regs , SUN4I_BACKEND_MODCTL_REG ,
SUN4I_BACKEND_MODCTL_ITLMOD_EN ,
interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0 ) ;
DRM_DEBUG_DRIVER ( " Switching display backend interlaced mode %s \n " ,
interlaced ? " on " : " off " ) ;
2016-12-14 23:32:55 +02:00
ret = sun4i_backend_drm_format_to_layer ( plane , fb - > format - > format ,
& val ) ;
2015-10-29 09:36:23 +01:00
if ( ret ) {
DRM_DEBUG_DRIVER ( " Invalid format \n " ) ;
2016-11-18 19:18:47 +01:00
return ret ;
2015-10-29 09:36:23 +01:00
}
regmap_update_bits ( backend - > regs , SUN4I_BACKEND_ATTCTL_REG1 ( layer ) ,
SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT , val ) ;
return 0 ;
}
EXPORT_SYMBOL ( sun4i_backend_update_layer_formats ) ;
int sun4i_backend_update_layer_buffer ( struct sun4i_backend * backend ,
int layer , struct drm_plane * plane )
{
struct drm_plane_state * state = plane - > state ;
struct drm_framebuffer * fb = state - > fb ;
struct drm_gem_cma_object * gem ;
u32 lo_paddr , hi_paddr ;
dma_addr_t paddr ;
int bpp ;
/* Get the physical address of the buffer in memory */
gem = drm_fb_cma_get_gem_obj ( fb , 0 ) ;
2016-05-03 17:23:28 +02:00
DRM_DEBUG_DRIVER ( " Using GEM @ %pad \n " , & gem - > paddr ) ;
2015-10-29 09:36:23 +01:00
/* Compute the start of the displayed memory */
2016-12-14 23:30:57 +02:00
bpp = fb - > format - > cpp [ 0 ] ;
2015-10-29 09:36:23 +01:00
paddr = gem - > paddr + fb - > offsets [ 0 ] ;
paddr + = ( state - > src_x > > 16 ) * bpp ;
paddr + = ( state - > src_y > > 16 ) * fb - > pitches [ 0 ] ;
2016-05-03 17:23:28 +02:00
DRM_DEBUG_DRIVER ( " Setting buffer address to %pad \n " , & paddr ) ;
2015-10-29 09:36:23 +01:00
/* Write the 32 lower bits of the address (in bits) */
lo_paddr = paddr < < 3 ;
DRM_DEBUG_DRIVER ( " Setting address lower bits to 0x%x \n " , lo_paddr ) ;
regmap_write ( backend - > regs , SUN4I_BACKEND_LAYFB_L32ADD_REG ( layer ) ,
lo_paddr ) ;
/* And the upper bits */
hi_paddr = paddr > > 29 ;
DRM_DEBUG_DRIVER ( " Setting address high bits to 0x%x \n " , hi_paddr ) ;
regmap_update_bits ( backend - > regs , SUN4I_BACKEND_LAYFB_H4ADD_REG ,
SUN4I_BACKEND_LAYFB_H4ADD_MSK ( layer ) ,
SUN4I_BACKEND_LAYFB_H4ADD ( layer , hi_paddr ) ) ;
return 0 ;
}
EXPORT_SYMBOL ( sun4i_backend_update_layer_buffer ) ;
2016-09-06 15:23:03 +02:00
static int sun4i_backend_init_sat ( struct device * dev ) {
struct sun4i_backend * backend = dev_get_drvdata ( dev ) ;
int ret ;
backend - > sat_reset = devm_reset_control_get ( dev , " sat " ) ;
if ( IS_ERR ( backend - > sat_reset ) ) {
dev_err ( dev , " Couldn't get the SAT reset line \n " ) ;
return PTR_ERR ( backend - > sat_reset ) ;
}
ret = reset_control_deassert ( backend - > sat_reset ) ;
if ( ret ) {
dev_err ( dev , " Couldn't deassert the SAT reset line \n " ) ;
return ret ;
}
backend - > sat_clk = devm_clk_get ( dev , " sat " ) ;
if ( IS_ERR ( backend - > sat_clk ) ) {
dev_err ( dev , " Couldn't get our SAT clock \n " ) ;
ret = PTR_ERR ( backend - > sat_clk ) ;
goto err_assert_reset ;
}
ret = clk_prepare_enable ( backend - > sat_clk ) ;
if ( ret ) {
dev_err ( dev , " Couldn't enable the SAT clock \n " ) ;
return ret ;
}
return 0 ;
err_assert_reset :
reset_control_assert ( backend - > sat_reset ) ;
return ret ;
}
static int sun4i_backend_free_sat ( struct device * dev ) {
struct sun4i_backend * backend = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( backend - > sat_clk ) ;
reset_control_assert ( backend - > sat_reset ) ;
return 0 ;
}
2017-04-21 16:38:52 +08:00
/*
* The display backend can take video output from the display frontend , or
* the display enhancement unit on the A80 , as input for one it its layers .
* This relationship within the display pipeline is encoded in the device
* tree with of_graph , and we use it here to figure out which backend , if
* there are 2 or more , we are currently probing . The number would be in
* the " reg " property of the upstream output port endpoint .
*/
static int sun4i_backend_of_get_id ( struct device_node * node )
{
struct device_node * port , * ep ;
int ret = - EINVAL ;
/* input is port 0 */
port = of_graph_get_port_by_id ( node , 0 ) ;
if ( ! port )
return - EINVAL ;
/* try finding an upstream endpoint */
for_each_available_child_of_node ( port , ep ) {
struct device_node * remote ;
u32 reg ;
remote = of_parse_phandle ( ep , " remote-endpoint " , 0 ) ;
if ( ! remote )
continue ;
ret = of_property_read_u32 ( remote , " reg " , & reg ) ;
if ( ret )
continue ;
ret = reg ;
}
of_node_put ( port ) ;
return ret ;
}
2015-10-29 09:36:23 +01:00
static struct regmap_config sun4i_backend_regmap_config = {
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
. max_register = 0x5800 ,
} ;
static int sun4i_backend_bind ( struct device * dev , struct device * master ,
void * data )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct drm_device * drm = data ;
struct sun4i_drv * drv = drm - > dev_private ;
struct sun4i_backend * backend ;
struct resource * res ;
void __iomem * regs ;
int i , ret ;
backend = devm_kzalloc ( dev , sizeof ( * backend ) , GFP_KERNEL ) ;
if ( ! backend )
return - ENOMEM ;
dev_set_drvdata ( dev , backend ) ;
2017-04-21 16:38:53 +08:00
backend - > node = dev - > of_node ;
2017-04-21 16:38:52 +08:00
backend - > id = sun4i_backend_of_get_id ( dev - > of_node ) ;
if ( backend - > id < 0 )
return backend - > id ;
2015-10-29 09:36:23 +01:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
regs = devm_ioremap_resource ( dev , res ) ;
2016-09-15 03:25:58 +00:00
if ( IS_ERR ( regs ) )
2015-10-29 09:36:23 +01:00
return PTR_ERR ( regs ) ;
backend - > regs = devm_regmap_init_mmio ( dev , regs ,
& sun4i_backend_regmap_config ) ;
if ( IS_ERR ( backend - > regs ) ) {
2017-04-21 16:38:51 +08:00
dev_err ( dev , " Couldn't create the backend regmap \n " ) ;
2015-10-29 09:36:23 +01:00
return PTR_ERR ( backend - > regs ) ;
}
backend - > reset = devm_reset_control_get ( dev , NULL ) ;
if ( IS_ERR ( backend - > reset ) ) {
dev_err ( dev , " Couldn't get our reset line \n " ) ;
return PTR_ERR ( backend - > reset ) ;
}
ret = reset_control_deassert ( backend - > reset ) ;
if ( ret ) {
dev_err ( dev , " Couldn't deassert our reset line \n " ) ;
return ret ;
}
backend - > bus_clk = devm_clk_get ( dev , " ahb " ) ;
if ( IS_ERR ( backend - > bus_clk ) ) {
dev_err ( dev , " Couldn't get the backend bus clock \n " ) ;
ret = PTR_ERR ( backend - > bus_clk ) ;
goto err_assert_reset ;
}
clk_prepare_enable ( backend - > bus_clk ) ;
backend - > mod_clk = devm_clk_get ( dev , " mod " ) ;
if ( IS_ERR ( backend - > mod_clk ) ) {
dev_err ( dev , " Couldn't get the backend module clock \n " ) ;
ret = PTR_ERR ( backend - > mod_clk ) ;
goto err_disable_bus_clk ;
}
clk_prepare_enable ( backend - > mod_clk ) ;
backend - > ram_clk = devm_clk_get ( dev , " ram " ) ;
if ( IS_ERR ( backend - > ram_clk ) ) {
dev_err ( dev , " Couldn't get the backend RAM clock \n " ) ;
ret = PTR_ERR ( backend - > ram_clk ) ;
goto err_disable_mod_clk ;
}
clk_prepare_enable ( backend - > ram_clk ) ;
2016-09-06 15:23:03 +02:00
if ( of_device_is_compatible ( dev - > of_node ,
" allwinner,sun8i-a33-display-backend " ) ) {
ret = sun4i_backend_init_sat ( dev ) ;
if ( ret ) {
dev_err ( dev , " Couldn't init SAT resources \n " ) ;
goto err_disable_ram_clk ;
}
}
2017-04-21 16:38:50 +08:00
list_add_tail ( & backend - > list , & drv - > backend_list ) ;
2015-10-29 09:36:23 +01:00
/* Reset the registers */
for ( i = 0x800 ; i < 0x1000 ; i + = 4 )
regmap_write ( backend - > regs , i , 0 ) ;
/* Disable registers autoloading */
regmap_write ( backend - > regs , SUN4I_BACKEND_REGBUFFCTL_REG ,
SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS ) ;
/* Enable the backend */
regmap_write ( backend - > regs , SUN4I_BACKEND_MODCTL_REG ,
SUN4I_BACKEND_MODCTL_DEBE_EN |
SUN4I_BACKEND_MODCTL_START_CTL ) ;
return 0 ;
2016-09-06 15:23:03 +02:00
err_disable_ram_clk :
clk_disable_unprepare ( backend - > ram_clk ) ;
2015-10-29 09:36:23 +01:00
err_disable_mod_clk :
clk_disable_unprepare ( backend - > mod_clk ) ;
err_disable_bus_clk :
clk_disable_unprepare ( backend - > bus_clk ) ;
err_assert_reset :
reset_control_assert ( backend - > reset ) ;
return ret ;
}
static void sun4i_backend_unbind ( struct device * dev , struct device * master ,
void * data )
{
struct sun4i_backend * backend = dev_get_drvdata ( dev ) ;
2017-04-21 16:38:50 +08:00
list_del ( & backend - > list ) ;
2016-09-06 15:23:03 +02:00
if ( of_device_is_compatible ( dev - > of_node ,
" allwinner,sun8i-a33-display-backend " ) )
sun4i_backend_free_sat ( dev ) ;
2015-10-29 09:36:23 +01:00
clk_disable_unprepare ( backend - > ram_clk ) ;
clk_disable_unprepare ( backend - > mod_clk ) ;
clk_disable_unprepare ( backend - > bus_clk ) ;
reset_control_assert ( backend - > reset ) ;
}
2016-11-12 18:19:58 +01:00
static const struct component_ops sun4i_backend_ops = {
2015-10-29 09:36:23 +01:00
. bind = sun4i_backend_bind ,
. unbind = sun4i_backend_unbind ,
} ;
static int sun4i_backend_probe ( struct platform_device * pdev )
{
return component_add ( & pdev - > dev , & sun4i_backend_ops ) ;
}
static int sun4i_backend_remove ( struct platform_device * pdev )
{
component_del ( & pdev - > dev , & sun4i_backend_ops ) ;
return 0 ;
}
static const struct of_device_id sun4i_backend_of_table [ ] = {
{ . compatible = " allwinner,sun5i-a13-display-backend " } ,
2016-10-20 11:43:41 +08:00
{ . compatible = " allwinner,sun6i-a31-display-backend " } ,
2016-01-07 12:32:25 +01:00
{ . compatible = " allwinner,sun8i-a33-display-backend " } ,
2015-10-29 09:36:23 +01:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , sun4i_backend_of_table ) ;
static struct platform_driver sun4i_backend_platform_driver = {
. probe = sun4i_backend_probe ,
. remove = sun4i_backend_remove ,
. driver = {
. name = " sun4i-backend " ,
. of_match_table = sun4i_backend_of_table ,
} ,
} ;
module_platform_driver ( sun4i_backend_platform_driver ) ;
MODULE_AUTHOR ( " Maxime Ripard <maxime.ripard@free-electrons.com> " ) ;
MODULE_DESCRIPTION ( " Allwinner A10 Display Backend Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;