2016-02-10 11:42:32 -08:00
/*
* Copyright ( C ) 2016 Broadcom Limited
*
* 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 .
*
* 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/>.
*/
/**
* DOC : VC4 DPI module
*
* The VC4 DPI hardware supports MIPI DPI type 4 and Nokia ViSSI
* signals , which are routed out to GPIO0 - 27 with the ALT2 function .
*/
# include "drm_atomic_helper.h"
# include "drm_crtc_helper.h"
# include "drm_edid.h"
# include "drm_panel.h"
# include "linux/clk.h"
# include "linux/component.h"
# include "linux/of_graph.h"
# include "linux/of_platform.h"
# include "vc4_drv.h"
# include "vc4_regs.h"
# define DPI_C 0x00
# define DPI_OUTPUT_ENABLE_MODE BIT(16)
/* The order field takes the incoming 24 bit RGB from the pixel valve
* and shuffles the 3 channels .
*/
# define DPI_ORDER_MASK VC4_MASK(15, 14)
# define DPI_ORDER_SHIFT 14
# define DPI_ORDER_RGB 0
# define DPI_ORDER_BGR 1
# define DPI_ORDER_GRB 2
# define DPI_ORDER_BRG 3
/* The format field takes the ORDER-shuffled pixel valve data and
* formats it onto the output lines .
*/
# define DPI_FORMAT_MASK VC4_MASK(13, 11)
# define DPI_FORMAT_SHIFT 11
/* This define is named in the hardware, but actually just outputs 0. */
# define DPI_FORMAT_9BIT_666_RGB 0
/* Outputs 00000000rrrrrggggggbbbbb */
# define DPI_FORMAT_16BIT_565_RGB_1 1
/* Outputs 000rrrrr00gggggg000bbbbb */
# define DPI_FORMAT_16BIT_565_RGB_2 2
/* Outputs 00rrrrr000gggggg00bbbbb0 */
# define DPI_FORMAT_16BIT_565_RGB_3 3
/* Outputs 000000rrrrrrggggggbbbbbb */
# define DPI_FORMAT_18BIT_666_RGB_1 4
/* Outputs 00rrrrrr00gggggg00bbbbbb */
# define DPI_FORMAT_18BIT_666_RGB_2 5
/* Outputs rrrrrrrrggggggggbbbbbbbb */
# define DPI_FORMAT_24BIT_888_RGB 6
/* Reverses the polarity of the corresponding signal */
# define DPI_PIXEL_CLK_INVERT BIT(10)
# define DPI_HSYNC_INVERT BIT(9)
# define DPI_VSYNC_INVERT BIT(8)
# define DPI_OUTPUT_ENABLE_INVERT BIT(7)
/* Outputs the signal the falling clock edge instead of rising. */
# define DPI_HSYNC_NEGATE BIT(6)
# define DPI_VSYNC_NEGATE BIT(5)
# define DPI_OUTPUT_ENABLE_NEGATE BIT(4)
/* Disables the signal */
# define DPI_HSYNC_DISABLE BIT(3)
# define DPI_VSYNC_DISABLE BIT(2)
# define DPI_OUTPUT_ENABLE_DISABLE BIT(1)
/* Power gate to the device, full reset at 0 -> 1 transition */
# define DPI_ENABLE BIT(0)
/* All other registers besides DPI_C return the ID */
# define DPI_ID 0x04
# define DPI_ID_VALUE 0x00647069
/* General DPI hardware state. */
struct vc4_dpi {
struct platform_device * pdev ;
struct drm_encoder * encoder ;
struct drm_connector * connector ;
struct drm_panel * panel ;
void __iomem * regs ;
struct clk * pixel_clock ;
struct clk * core_clock ;
} ;
# define DPI_READ(offset) readl(dpi->regs + (offset))
# define DPI_WRITE(offset, val) writel(val, dpi->regs + (offset))
/* VC4 DPI encoder KMS struct */
struct vc4_dpi_encoder {
struct vc4_encoder base ;
struct vc4_dpi * dpi ;
} ;
static inline struct vc4_dpi_encoder *
to_vc4_dpi_encoder ( struct drm_encoder * encoder )
{
return container_of ( encoder , struct vc4_dpi_encoder , base . base ) ;
}
/* VC4 DPI connector KMS struct */
struct vc4_dpi_connector {
struct drm_connector base ;
struct vc4_dpi * dpi ;
/* Since the connector is attached to just the one encoder,
* this is the reference to it so we can do the best_encoder ( )
* hook .
*/
struct drm_encoder * encoder ;
} ;
static inline struct vc4_dpi_connector *
to_vc4_dpi_connector ( struct drm_connector * connector )
{
return container_of ( connector , struct vc4_dpi_connector , base ) ;
}
# define DPI_REG(reg) { reg, #reg }
static const struct {
u32 reg ;
const char * name ;
} dpi_regs [ ] = {
DPI_REG ( DPI_C ) ,
DPI_REG ( DPI_ID ) ,
} ;
static void vc4_dpi_dump_regs ( struct vc4_dpi * dpi )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( dpi_regs ) ; i + + ) {
DRM_INFO ( " 0x%04x (%s): 0x%08x \n " ,
dpi_regs [ i ] . reg , dpi_regs [ i ] . name ,
DPI_READ ( dpi_regs [ i ] . reg ) ) ;
}
}
# ifdef CONFIG_DEBUG_FS
int vc4_dpi_debugfs_regs ( struct seq_file * m , void * unused )
{
struct drm_info_node * node = ( struct drm_info_node * ) m - > private ;
struct drm_device * dev = node - > minor - > dev ;
struct vc4_dev * vc4 = to_vc4_dev ( dev ) ;
struct vc4_dpi * dpi = vc4 - > dpi ;
int i ;
if ( ! dpi )
return 0 ;
for ( i = 0 ; i < ARRAY_SIZE ( dpi_regs ) ; i + + ) {
seq_printf ( m , " %s (0x%04x): 0x%08x \n " ,
dpi_regs [ i ] . name , dpi_regs [ i ] . reg ,
DPI_READ ( dpi_regs [ i ] . reg ) ) ;
}
return 0 ;
}
# endif
static enum drm_connector_status
vc4_dpi_connector_detect ( struct drm_connector * connector , bool force )
{
struct vc4_dpi_connector * vc4_connector =
to_vc4_dpi_connector ( connector ) ;
struct vc4_dpi * dpi = vc4_connector - > dpi ;
if ( dpi - > panel )
return connector_status_connected ;
else
return connector_status_disconnected ;
}
static void vc4_dpi_connector_destroy ( struct drm_connector * connector )
{
drm_connector_unregister ( connector ) ;
drm_connector_cleanup ( connector ) ;
}
static int vc4_dpi_connector_get_modes ( struct drm_connector * connector )
{
struct vc4_dpi_connector * vc4_connector =
to_vc4_dpi_connector ( connector ) ;
struct vc4_dpi * dpi = vc4_connector - > dpi ;
if ( dpi - > panel )
return drm_panel_get_modes ( dpi - > panel ) ;
return 0 ;
}
static struct drm_encoder *
vc4_dpi_connector_best_encoder ( struct drm_connector * connector )
{
struct vc4_dpi_connector * dpi_connector =
to_vc4_dpi_connector ( connector ) ;
return dpi_connector - > encoder ;
}
static const struct drm_connector_funcs vc4_dpi_connector_funcs = {
. dpms = drm_atomic_helper_connector_dpms ,
. detect = vc4_dpi_connector_detect ,
. fill_modes = drm_helper_probe_single_connector_modes ,
. destroy = vc4_dpi_connector_destroy ,
. reset = drm_atomic_helper_connector_reset ,
. atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_connector_destroy_state ,
} ;
static const struct drm_connector_helper_funcs vc4_dpi_connector_helper_funcs = {
. get_modes = vc4_dpi_connector_get_modes ,
. best_encoder = vc4_dpi_connector_best_encoder ,
} ;
static struct drm_connector * vc4_dpi_connector_init ( struct drm_device * dev ,
struct vc4_dpi * dpi )
{
struct drm_connector * connector = NULL ;
struct vc4_dpi_connector * dpi_connector ;
dpi_connector = devm_kzalloc ( dev - > dev , sizeof ( * dpi_connector ) ,
GFP_KERNEL ) ;
2016-06-02 10:38:29 +01:00
if ( ! dpi_connector )
return ERR_PTR ( - ENOMEM ) ;
2016-02-10 11:42:32 -08:00
connector = & dpi_connector - > base ;
dpi_connector - > encoder = dpi - > encoder ;
dpi_connector - > dpi = dpi ;
drm_connector_init ( dev , connector , & vc4_dpi_connector_funcs ,
DRM_MODE_CONNECTOR_DPI ) ;
drm_connector_helper_add ( connector , & vc4_dpi_connector_helper_funcs ) ;
connector - > polled = 0 ;
connector - > interlace_allowed = 0 ;
connector - > doublescan_allowed = 0 ;
drm_mode_connector_attach_encoder ( connector , dpi - > encoder ) ;
return connector ;
}
static const struct drm_encoder_funcs vc4_dpi_encoder_funcs = {
. destroy = drm_encoder_cleanup ,
} ;
static void vc4_dpi_encoder_disable ( struct drm_encoder * encoder )
{
struct vc4_dpi_encoder * vc4_encoder = to_vc4_dpi_encoder ( encoder ) ;
struct vc4_dpi * dpi = vc4_encoder - > dpi ;
drm_panel_disable ( dpi - > panel ) ;
clk_disable_unprepare ( dpi - > pixel_clock ) ;
drm_panel_unprepare ( dpi - > panel ) ;
}
static void vc4_dpi_encoder_enable ( struct drm_encoder * encoder )
{
struct drm_display_mode * mode = & encoder - > crtc - > mode ;
struct vc4_dpi_encoder * vc4_encoder = to_vc4_dpi_encoder ( encoder ) ;
struct vc4_dpi * dpi = vc4_encoder - > dpi ;
u32 dpi_c = DPI_ENABLE | DPI_OUTPUT_ENABLE_MODE ;
int ret ;
ret = drm_panel_prepare ( dpi - > panel ) ;
if ( ret ) {
DRM_ERROR ( " Panel failed to prepare \n " ) ;
return ;
}
if ( dpi - > connector - > display_info . num_bus_formats ) {
u32 bus_format = dpi - > connector - > display_info . bus_formats [ 0 ] ;
switch ( bus_format ) {
case MEDIA_BUS_FMT_RGB888_1X24 :
dpi_c | = VC4_SET_FIELD ( DPI_FORMAT_24BIT_888_RGB ,
DPI_FORMAT ) ;
break ;
case MEDIA_BUS_FMT_BGR888_1X24 :
dpi_c | = VC4_SET_FIELD ( DPI_FORMAT_24BIT_888_RGB ,
DPI_FORMAT ) ;
dpi_c | = VC4_SET_FIELD ( DPI_ORDER_BGR , DPI_ORDER ) ;
break ;
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI :
dpi_c | = VC4_SET_FIELD ( DPI_FORMAT_18BIT_666_RGB_2 ,
DPI_FORMAT ) ;
break ;
case MEDIA_BUS_FMT_RGB666_1X18 :
dpi_c | = VC4_SET_FIELD ( DPI_FORMAT_18BIT_666_RGB_1 ,
DPI_FORMAT ) ;
break ;
case MEDIA_BUS_FMT_RGB565_1X16 :
dpi_c | = VC4_SET_FIELD ( DPI_FORMAT_16BIT_565_RGB_3 ,
DPI_FORMAT ) ;
break ;
default :
DRM_ERROR ( " Unknown media bus format %d \n " , bus_format ) ;
break ;
}
}
if ( mode - > flags & DRM_MODE_FLAG_NHSYNC )
dpi_c | = DPI_HSYNC_INVERT ;
else if ( ! ( mode - > flags & DRM_MODE_FLAG_PHSYNC ) )
dpi_c | = DPI_HSYNC_DISABLE ;
if ( mode - > flags & DRM_MODE_FLAG_NVSYNC )
dpi_c | = DPI_VSYNC_INVERT ;
else if ( ! ( mode - > flags & DRM_MODE_FLAG_PVSYNC ) )
dpi_c | = DPI_VSYNC_DISABLE ;
DPI_WRITE ( DPI_C , dpi_c ) ;
ret = clk_set_rate ( dpi - > pixel_clock , mode - > clock * 1000 ) ;
if ( ret )
DRM_ERROR ( " Failed to set clock rate: %d \n " , ret ) ;
ret = clk_prepare_enable ( dpi - > pixel_clock ) ;
if ( ret )
DRM_ERROR ( " Failed to set clock rate: %d \n " , ret ) ;
ret = drm_panel_enable ( dpi - > panel ) ;
if ( ret ) {
DRM_ERROR ( " Panel failed to enable \n " ) ;
drm_panel_unprepare ( dpi - > panel ) ;
return ;
}
}
static const struct drm_encoder_helper_funcs vc4_dpi_encoder_helper_funcs = {
. disable = vc4_dpi_encoder_disable ,
. enable = vc4_dpi_encoder_enable ,
} ;
static const struct of_device_id vc4_dpi_dt_match [ ] = {
{ . compatible = " brcm,bcm2835-dpi " , . data = NULL } ,
{ }
} ;
/* Walks the OF graph to find the panel node and then asks DRM to look
* up the panel .
*/
static struct drm_panel * vc4_dpi_get_panel ( struct device * dev )
{
struct device_node * endpoint , * panel_node ;
struct device_node * np = dev - > of_node ;
struct drm_panel * panel ;
endpoint = of_graph_get_next_endpoint ( np , NULL ) ;
if ( ! endpoint ) {
dev_err ( dev , " no endpoint to fetch DPI panel \n " ) ;
return NULL ;
}
/* don't proceed if we have an endpoint but no panel_node tied to it */
panel_node = of_graph_get_remote_port_parent ( endpoint ) ;
of_node_put ( endpoint ) ;
if ( ! panel_node ) {
dev_err ( dev , " no valid panel node \n " ) ;
return NULL ;
}
panel = of_drm_find_panel ( panel_node ) ;
of_node_put ( panel_node ) ;
return panel ;
}
static int vc4_dpi_bind ( struct device * dev , struct device * master , void * data )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct drm_device * drm = dev_get_drvdata ( master ) ;
struct vc4_dev * vc4 = to_vc4_dev ( drm ) ;
struct vc4_dpi * dpi ;
struct vc4_dpi_encoder * vc4_dpi_encoder ;
int ret ;
dpi = devm_kzalloc ( dev , sizeof ( * dpi ) , GFP_KERNEL ) ;
if ( ! dpi )
return - ENOMEM ;
vc4_dpi_encoder = devm_kzalloc ( dev , sizeof ( * vc4_dpi_encoder ) ,
GFP_KERNEL ) ;
if ( ! vc4_dpi_encoder )
return - ENOMEM ;
vc4_dpi_encoder - > base . type = VC4_ENCODER_TYPE_DPI ;
vc4_dpi_encoder - > dpi = dpi ;
dpi - > encoder = & vc4_dpi_encoder - > base . base ;
dpi - > pdev = pdev ;
dpi - > regs = vc4_ioremap_regs ( pdev , 0 ) ;
if ( IS_ERR ( dpi - > regs ) )
return PTR_ERR ( dpi - > regs ) ;
vc4_dpi_dump_regs ( dpi ) ;
if ( DPI_READ ( DPI_ID ) ! = DPI_ID_VALUE ) {
dev_err ( dev , " Port returned 0x%08x for ID instead of 0x%08x \n " ,
DPI_READ ( DPI_ID ) , DPI_ID_VALUE ) ;
return - ENODEV ;
}
dpi - > core_clock = devm_clk_get ( dev , " core " ) ;
if ( IS_ERR ( dpi - > core_clock ) ) {
ret = PTR_ERR ( dpi - > core_clock ) ;
if ( ret ! = - EPROBE_DEFER )
DRM_ERROR ( " Failed to get core clock: %d \n " , ret ) ;
return ret ;
}
dpi - > pixel_clock = devm_clk_get ( dev , " pixel " ) ;
if ( IS_ERR ( dpi - > pixel_clock ) ) {
ret = PTR_ERR ( dpi - > pixel_clock ) ;
if ( ret ! = - EPROBE_DEFER )
DRM_ERROR ( " Failed to get pixel clock: %d \n " , ret ) ;
return ret ;
}
ret = clk_prepare_enable ( dpi - > core_clock ) ;
if ( ret )
DRM_ERROR ( " Failed to turn on core clock: %d \n " , ret ) ;
dpi - > panel = vc4_dpi_get_panel ( dev ) ;
drm_encoder_init ( drm , dpi - > encoder , & vc4_dpi_encoder_funcs ,
DRM_MODE_ENCODER_DPI , NULL ) ;
drm_encoder_helper_add ( dpi - > encoder , & vc4_dpi_encoder_helper_funcs ) ;
dpi - > connector = vc4_dpi_connector_init ( drm , dpi ) ;
if ( IS_ERR ( dpi - > connector ) ) {
ret = PTR_ERR ( dpi - > connector ) ;
goto err_destroy_encoder ;
}
if ( dpi - > panel )
drm_panel_attach ( dpi - > panel , dpi - > connector ) ;
dev_set_drvdata ( dev , dpi ) ;
vc4 - > dpi = dpi ;
return 0 ;
err_destroy_encoder :
drm_encoder_cleanup ( dpi - > encoder ) ;
clk_disable_unprepare ( dpi - > core_clock ) ;
return ret ;
}
static void vc4_dpi_unbind ( struct device * dev , struct device * master ,
void * data )
{
struct drm_device * drm = dev_get_drvdata ( master ) ;
struct vc4_dev * vc4 = to_vc4_dev ( drm ) ;
struct vc4_dpi * dpi = dev_get_drvdata ( dev ) ;
if ( dpi - > panel )
drm_panel_detach ( dpi - > panel ) ;
vc4_dpi_connector_destroy ( dpi - > connector ) ;
drm_encoder_cleanup ( dpi - > encoder ) ;
clk_disable_unprepare ( dpi - > core_clock ) ;
vc4 - > dpi = NULL ;
}
static const struct component_ops vc4_dpi_ops = {
. bind = vc4_dpi_bind ,
. unbind = vc4_dpi_unbind ,
} ;
static int vc4_dpi_dev_probe ( struct platform_device * pdev )
{
return component_add ( & pdev - > dev , & vc4_dpi_ops ) ;
}
static int vc4_dpi_dev_remove ( struct platform_device * pdev )
{
component_del ( & pdev - > dev , & vc4_dpi_ops ) ;
return 0 ;
}
struct platform_driver vc4_dpi_driver = {
. probe = vc4_dpi_dev_probe ,
. remove = vc4_dpi_dev_remove ,
. driver = {
. name = " vc4_dpi " ,
. of_match_table = vc4_dpi_dt_match ,
} ,
} ;