2018-06-25 15:02:45 +03:00
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
# include <linux/bitfield.h>
# include <linux/component.h>
# include <linux/device.h>
2019-07-16 09:42:06 +03:00
# include <linux/io.h>
2018-06-25 15:02:45 +03:00
# include <linux/module.h>
2018-11-04 21:27:01 +03:00
# include <linux/of_device.h>
2018-06-25 15:02:45 +03:00
# include <linux/of_graph.h>
# include <linux/platform_device.h>
2019-07-16 09:42:06 +03:00
# include <dt-bindings/clock/sun8i-tcon-top.h>
2018-06-25 15:02:45 +03:00
# include "sun8i_tcon_top.h"
2018-11-04 21:27:01 +03:00
struct sun8i_tcon_top_quirks {
bool has_tcon_tv1 ;
bool has_dsi ;
} ;
2018-07-10 23:35:04 +03:00
static bool sun8i_tcon_top_node_is_tcon_top ( struct device_node * node )
{
return ! ! of_match_node ( sun8i_tcon_top_of_table , node ) ;
}
int sun8i_tcon_top_set_hdmi_src ( struct device * dev , int tcon )
{
struct sun8i_tcon_top * tcon_top = dev_get_drvdata ( dev ) ;
unsigned long flags ;
u32 val ;
if ( ! sun8i_tcon_top_node_is_tcon_top ( dev - > of_node ) ) {
dev_err ( dev , " Device is not TCON TOP! \n " ) ;
return - EINVAL ;
}
if ( tcon < 2 | | tcon > 3 ) {
dev_err ( dev , " TCON index must be 2 or 3! \n " ) ;
return - EINVAL ;
}
spin_lock_irqsave ( & tcon_top - > reg_lock , flags ) ;
val = readl ( tcon_top - > regs + TCON_TOP_GATE_SRC_REG ) ;
val & = ~ TCON_TOP_HDMI_SRC_MSK ;
val | = FIELD_PREP ( TCON_TOP_HDMI_SRC_MSK , tcon - 1 ) ;
writel ( val , tcon_top - > regs + TCON_TOP_GATE_SRC_REG ) ;
spin_unlock_irqrestore ( & tcon_top - > reg_lock , flags ) ;
return 0 ;
}
EXPORT_SYMBOL ( sun8i_tcon_top_set_hdmi_src ) ;
int sun8i_tcon_top_de_config ( struct device * dev , int mixer , int tcon )
{
struct sun8i_tcon_top * tcon_top = dev_get_drvdata ( dev ) ;
unsigned long flags ;
u32 reg ;
if ( ! sun8i_tcon_top_node_is_tcon_top ( dev - > of_node ) ) {
dev_err ( dev , " Device is not TCON TOP! \n " ) ;
return - EINVAL ;
}
if ( mixer > 1 ) {
dev_err ( dev , " Mixer index is too high! \n " ) ;
return - EINVAL ;
}
if ( tcon > 3 ) {
dev_err ( dev , " TCON index is too high! \n " ) ;
return - EINVAL ;
}
spin_lock_irqsave ( & tcon_top - > reg_lock , flags ) ;
reg = readl ( tcon_top - > regs + TCON_TOP_PORT_SEL_REG ) ;
if ( mixer = = 0 ) {
reg & = ~ TCON_TOP_PORT_DE0_MSK ;
reg | = FIELD_PREP ( TCON_TOP_PORT_DE0_MSK , tcon ) ;
} else {
reg & = ~ TCON_TOP_PORT_DE1_MSK ;
reg | = FIELD_PREP ( TCON_TOP_PORT_DE1_MSK , tcon ) ;
}
writel ( reg , tcon_top - > regs + TCON_TOP_PORT_SEL_REG ) ;
spin_unlock_irqrestore ( & tcon_top - > reg_lock , flags ) ;
return 0 ;
}
EXPORT_SYMBOL ( sun8i_tcon_top_de_config ) ;
2018-06-25 15:02:45 +03:00
static struct clk_hw * sun8i_tcon_top_register_gate ( struct device * dev ,
2018-07-10 23:34:57 +03:00
const char * parent ,
2018-06-25 15:02:45 +03:00
void __iomem * regs ,
spinlock_t * lock ,
u8 bit , int name_index )
{
const char * clk_name , * parent_name ;
2018-07-10 23:34:57 +03:00
int ret , index ;
index = of_property_match_string ( dev - > of_node , " clock-names " , parent ) ;
if ( index < 0 )
2018-07-12 11:08:18 +03:00
return ERR_PTR ( index ) ;
2018-07-10 23:34:57 +03:00
parent_name = of_clk_get_parent_name ( dev - > of_node , index ) ;
2018-06-25 15:02:45 +03:00
ret = of_property_read_string_index ( dev - > of_node ,
" clock-output-names " , name_index ,
& clk_name ) ;
if ( ret )
return ERR_PTR ( ret ) ;
return clk_hw_register_gate ( dev , clk_name , parent_name ,
CLK_SET_RATE_PARENT ,
regs + TCON_TOP_GATE_SRC_REG ,
bit , 0 , lock ) ;
} ;
static int sun8i_tcon_top_bind ( struct device * dev , struct device * master ,
void * data )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct clk_hw_onecell_data * clk_data ;
struct sun8i_tcon_top * tcon_top ;
2018-11-04 21:27:01 +03:00
const struct sun8i_tcon_top_quirks * quirks ;
2018-06-25 15:02:45 +03:00
struct resource * res ;
void __iomem * regs ;
2018-07-10 23:35:10 +03:00
int ret , i ;
2018-06-25 15:02:45 +03:00
2018-11-04 21:27:01 +03:00
quirks = of_device_get_match_data ( & pdev - > dev ) ;
2018-06-25 15:02:45 +03:00
tcon_top = devm_kzalloc ( dev , sizeof ( * tcon_top ) , GFP_KERNEL ) ;
if ( ! tcon_top )
return - ENOMEM ;
2018-08-24 04:05:21 +03:00
clk_data = devm_kzalloc ( dev , struct_size ( clk_data , hws , CLK_NUM ) ,
2018-06-25 15:02:45 +03:00
GFP_KERNEL ) ;
if ( ! clk_data )
return - ENOMEM ;
tcon_top - > clk_data = clk_data ;
spin_lock_init ( & tcon_top - > reg_lock ) ;
tcon_top - > rst = devm_reset_control_get ( dev , NULL ) ;
if ( IS_ERR ( tcon_top - > rst ) ) {
dev_err ( dev , " Couldn't get our reset line \n " ) ;
return PTR_ERR ( tcon_top - > rst ) ;
}
tcon_top - > bus = devm_clk_get ( dev , " bus " ) ;
if ( IS_ERR ( tcon_top - > bus ) ) {
dev_err ( dev , " Couldn't get the bus clock \n " ) ;
return PTR_ERR ( tcon_top - > bus ) ;
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
regs = devm_ioremap_resource ( dev , res ) ;
2018-07-10 23:35:04 +03:00
tcon_top - > regs = regs ;
2018-06-25 15:02:45 +03:00
if ( IS_ERR ( regs ) )
return PTR_ERR ( regs ) ;
ret = reset_control_deassert ( tcon_top - > rst ) ;
if ( ret ) {
dev_err ( dev , " Could not deassert ctrl reset control \n " ) ;
return ret ;
}
ret = clk_prepare_enable ( tcon_top - > bus ) ;
if ( ret ) {
dev_err ( dev , " Could not enable bus clock \n " ) ;
goto err_assert_reset ;
}
2018-11-04 21:27:00 +03:00
/*
* At least on H6 , some registers have some bits set by default
* which may cause issues . Clear them here .
*/
writel ( 0 , regs + TCON_TOP_PORT_SEL_REG ) ;
writel ( 0 , regs + TCON_TOP_GATE_SRC_REG ) ;
2018-06-25 15:02:45 +03:00
/*
* TCON TOP has two muxes , which select parent clock for each TCON TV
* channel clock . Parent could be either TCON TV or TVE clock . For now
* we leave this fixed to TCON TV , since TVE driver for R40 is not yet
* implemented . Once it is , graph needs to be traversed to determine
* if TVE is active on each TCON TV . If it is , mux should be switched
* to TVE clock parent .
*/
clk_data - > hws [ CLK_TCON_TOP_TV0 ] =
2018-07-10 23:34:57 +03:00
sun8i_tcon_top_register_gate ( dev , " tcon-tv0 " , regs ,
2018-06-25 15:02:45 +03:00
& tcon_top - > reg_lock ,
TCON_TOP_TCON_TV0_GATE , 0 ) ;
2018-11-04 21:27:01 +03:00
if ( quirks - > has_tcon_tv1 )
clk_data - > hws [ CLK_TCON_TOP_TV1 ] =
sun8i_tcon_top_register_gate ( dev , " tcon-tv1 " , regs ,
& tcon_top - > reg_lock ,
TCON_TOP_TCON_TV1_GATE , 1 ) ;
2018-06-25 15:02:45 +03:00
2018-11-04 21:27:01 +03:00
if ( quirks - > has_dsi )
clk_data - > hws [ CLK_TCON_TOP_DSI ] =
sun8i_tcon_top_register_gate ( dev , " dsi " , regs ,
& tcon_top - > reg_lock ,
TCON_TOP_TCON_DSI_GATE , 2 ) ;
2018-06-25 15:02:45 +03:00
for ( i = 0 ; i < CLK_NUM ; i + + )
if ( IS_ERR ( clk_data - > hws [ i ] ) ) {
ret = PTR_ERR ( clk_data - > hws [ i ] ) ;
goto err_unregister_gates ;
}
clk_data - > num = CLK_NUM ;
ret = of_clk_add_hw_provider ( dev - > of_node , of_clk_hw_onecell_get ,
clk_data ) ;
if ( ret )
goto err_unregister_gates ;
dev_set_drvdata ( dev , tcon_top ) ;
return 0 ;
err_unregister_gates :
for ( i = 0 ; i < CLK_NUM ; i + + )
2019-04-06 02:30:48 +03:00
if ( ! IS_ERR_OR_NULL ( clk_data - > hws [ i ] ) )
2018-06-25 15:02:45 +03:00
clk_hw_unregister_gate ( clk_data - > hws [ i ] ) ;
clk_disable_unprepare ( tcon_top - > bus ) ;
err_assert_reset :
reset_control_assert ( tcon_top - > rst ) ;
return ret ;
}
static void sun8i_tcon_top_unbind ( struct device * dev , struct device * master ,
void * data )
{
struct sun8i_tcon_top * tcon_top = dev_get_drvdata ( dev ) ;
struct clk_hw_onecell_data * clk_data = tcon_top - > clk_data ;
int i ;
of_clk_del_provider ( dev - > of_node ) ;
for ( i = 0 ; i < CLK_NUM ; i + + )
2019-04-06 02:30:48 +03:00
if ( clk_data - > hws [ i ] )
clk_hw_unregister_gate ( clk_data - > hws [ i ] ) ;
2018-06-25 15:02:45 +03:00
clk_disable_unprepare ( tcon_top - > bus ) ;
reset_control_assert ( tcon_top - > rst ) ;
}
static const struct component_ops sun8i_tcon_top_ops = {
. bind = sun8i_tcon_top_bind ,
. unbind = sun8i_tcon_top_unbind ,
} ;
static int sun8i_tcon_top_probe ( struct platform_device * pdev )
{
return component_add ( & pdev - > dev , & sun8i_tcon_top_ops ) ;
}
static int sun8i_tcon_top_remove ( struct platform_device * pdev )
{
component_del ( & pdev - > dev , & sun8i_tcon_top_ops ) ;
return 0 ;
}
2019-04-16 17:58:55 +03:00
static const struct sun8i_tcon_top_quirks sun8i_r40_tcon_top_quirks = {
2018-11-04 21:27:01 +03:00
. has_tcon_tv1 = true ,
. has_dsi = true ,
} ;
2019-04-16 17:58:55 +03:00
static const struct sun8i_tcon_top_quirks sun50i_h6_tcon_top_quirks = {
2018-11-04 21:27:03 +03:00
/* Nothing special */
} ;
2018-06-25 15:02:45 +03:00
/* sun4i_drv uses this list to check if a device node is a TCON TOP */
const struct of_device_id sun8i_tcon_top_of_table [ ] = {
2018-11-04 21:27:01 +03:00
{
. compatible = " allwinner,sun8i-r40-tcon-top " ,
. data = & sun8i_r40_tcon_top_quirks
} ,
2018-11-04 21:27:03 +03:00
{
. compatible = " allwinner,sun50i-h6-tcon-top " ,
. data = & sun50i_h6_tcon_top_quirks
} ,
2018-06-25 15:02:45 +03:00
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , sun8i_tcon_top_of_table ) ;
EXPORT_SYMBOL ( sun8i_tcon_top_of_table ) ;
static struct platform_driver sun8i_tcon_top_platform_driver = {
. probe = sun8i_tcon_top_probe ,
. remove = sun8i_tcon_top_remove ,
. driver = {
. name = " sun8i-tcon-top " ,
. of_match_table = sun8i_tcon_top_of_table ,
} ,
} ;
module_platform_driver ( sun8i_tcon_top_platform_driver ) ;
MODULE_AUTHOR ( " Jernej Skrabec <jernej.skrabec@siol.net> " ) ;
MODULE_DESCRIPTION ( " Allwinner R40 TCON TOP driver " ) ;
MODULE_LICENSE ( " GPL " ) ;