2018-08-22 00:02:14 +02:00
// SPDX-License-Identifier: GPL-2.0
2017-07-25 15:26:27 +09:00
/*
* Renesas R - Car USB2 .0 clock selector
*
* Copyright ( C ) 2017 Renesas Electronics Corp .
*
* Based on renesas - cpg - mssr . c
*
* Copyright ( C ) 2015 Glider bvba
*/
# include <linux/clk.h>
# include <linux/clk-provider.h>
# include <linux/device.h>
# include <linux/init.h>
2019-04-18 15:20:22 -07:00
# include <linux/io.h>
2017-07-25 15:26:27 +09:00
# include <linux/module.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/pm.h>
# include <linux/pm_runtime.h>
2020-03-04 15:42:17 +09:00
# include <linux/reset.h>
2017-07-25 15:26:27 +09:00
# include <linux/slab.h>
# define USB20_CLKSET0 0x00
# define CLKSET0_INTCLK_EN BIT(11)
# define CLKSET0_PRIVATE BIT(0)
# define CLKSET0_EXTAL_ONLY (CLKSET0_INTCLK_EN | CLKSET0_PRIVATE)
2020-03-04 15:42:16 +09:00
static const struct clk_bulk_data rcar_usb2_clocks [ ] = {
{ . id = " ehci_ohci " , } ,
{ . id = " hs-usb-if " , } ,
} ;
2017-07-25 15:26:27 +09:00
struct usb2_clock_sel_priv {
void __iomem * base ;
struct clk_hw hw ;
2020-03-04 15:42:16 +09:00
struct clk_bulk_data clks [ ARRAY_SIZE ( rcar_usb2_clocks ) ] ;
2020-03-04 15:42:17 +09:00
struct reset_control * rsts ;
2017-07-25 15:26:27 +09:00
bool extal ;
bool xtal ;
} ;
# define to_priv(_hw) container_of(_hw, struct usb2_clock_sel_priv, hw)
static void usb2_clock_sel_enable_extal_only ( struct usb2_clock_sel_priv * priv )
{
u16 val = readw ( priv - > base + USB20_CLKSET0 ) ;
pr_debug ( " %s: enter %d %d %x \n " , __func__ ,
priv - > extal , priv - > xtal , val ) ;
if ( priv - > extal & & ! priv - > xtal & & val ! = CLKSET0_EXTAL_ONLY )
writew ( CLKSET0_EXTAL_ONLY , priv - > base + USB20_CLKSET0 ) ;
}
static void usb2_clock_sel_disable_extal_only ( struct usb2_clock_sel_priv * priv )
{
if ( priv - > extal & & ! priv - > xtal )
writew ( CLKSET0_PRIVATE , priv - > base + USB20_CLKSET0 ) ;
}
static int usb2_clock_sel_enable ( struct clk_hw * hw )
{
2020-03-04 15:42:16 +09:00
struct usb2_clock_sel_priv * priv = to_priv ( hw ) ;
int ret ;
2020-03-04 15:42:17 +09:00
ret = reset_control_deassert ( priv - > rsts ) ;
2020-03-04 15:42:16 +09:00
if ( ret )
return ret ;
2020-03-04 15:42:17 +09:00
ret = clk_bulk_prepare_enable ( ARRAY_SIZE ( priv - > clks ) , priv - > clks ) ;
if ( ret ) {
reset_control_assert ( priv - > rsts ) ;
return ret ;
}
2020-03-04 15:42:16 +09:00
usb2_clock_sel_enable_extal_only ( priv ) ;
2017-07-25 15:26:27 +09:00
return 0 ;
}
static void usb2_clock_sel_disable ( struct clk_hw * hw )
{
2020-03-04 15:42:16 +09:00
struct usb2_clock_sel_priv * priv = to_priv ( hw ) ;
usb2_clock_sel_disable_extal_only ( priv ) ;
clk_bulk_disable_unprepare ( ARRAY_SIZE ( priv - > clks ) , priv - > clks ) ;
2020-03-04 15:42:17 +09:00
reset_control_assert ( priv - > rsts ) ;
2017-07-25 15:26:27 +09:00
}
/*
* This module seems a mux , but this driver assumes a gate because
* ehci / ohci platform drivers don ' t support clk_set_parent ( ) for now .
* If this driver acts as a gate , ehci / ohci - platform drivers don ' t need
* any modification .
*/
static const struct clk_ops usb2_clock_sel_clock_ops = {
. enable = usb2_clock_sel_enable ,
. disable = usb2_clock_sel_disable ,
} ;
static const struct of_device_id rcar_usb2_clock_sel_match [ ] = {
{ . compatible = " renesas,rcar-gen3-usb2-clock-sel " } ,
{ }
} ;
static int rcar_usb2_clock_sel_suspend ( struct device * dev )
{
struct usb2_clock_sel_priv * priv = dev_get_drvdata ( dev ) ;
usb2_clock_sel_disable_extal_only ( priv ) ;
pm_runtime_put ( dev ) ;
return 0 ;
}
static int rcar_usb2_clock_sel_resume ( struct device * dev )
{
struct usb2_clock_sel_priv * priv = dev_get_drvdata ( dev ) ;
pm_runtime_get_sync ( dev ) ;
usb2_clock_sel_enable_extal_only ( priv ) ;
return 0 ;
}
static int rcar_usb2_clock_sel_remove ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
of_clk_del_provider ( dev - > of_node ) ;
pm_runtime_put ( dev ) ;
pm_runtime_disable ( dev ) ;
return 0 ;
}
static int rcar_usb2_clock_sel_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
struct usb2_clock_sel_priv * priv ;
struct clk * clk ;
2021-03-26 11:54:34 +01:00
struct clk_init_data init = { } ;
2020-03-04 15:42:16 +09:00
int ret ;
2017-07-25 15:26:27 +09:00
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2019-08-07 09:14:16 +02:00
priv - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2017-07-25 15:26:27 +09:00
if ( IS_ERR ( priv - > base ) )
return PTR_ERR ( priv - > base ) ;
2020-03-04 15:42:16 +09:00
memcpy ( priv - > clks , rcar_usb2_clocks , sizeof ( priv - > clks ) ) ;
ret = devm_clk_bulk_get ( dev , ARRAY_SIZE ( priv - > clks ) , priv - > clks ) ;
if ( ret < 0 )
return ret ;
2020-11-03 11:44:53 +08:00
priv - > rsts = devm_reset_control_array_get_shared ( dev ) ;
2020-03-04 15:42:17 +09:00
if ( IS_ERR ( priv - > rsts ) )
return PTR_ERR ( priv - > rsts ) ;
2017-07-25 15:26:27 +09:00
clk = devm_clk_get ( dev , " usb_extal " ) ;
if ( ! IS_ERR ( clk ) & & ! clk_prepare_enable ( clk ) ) {
priv - > extal = ! ! clk_get_rate ( clk ) ;
clk_disable_unprepare ( clk ) ;
}
clk = devm_clk_get ( dev , " usb_xtal " ) ;
if ( ! IS_ERR ( clk ) & & ! clk_prepare_enable ( clk ) ) {
priv - > xtal = ! ! clk_get_rate ( clk ) ;
clk_disable_unprepare ( clk ) ;
}
if ( ! priv - > extal & & ! priv - > xtal ) {
dev_err ( dev , " This driver needs usb_extal or usb_xtal \n " ) ;
return - ENOENT ;
}
2021-04-15 15:33:38 +08:00
pm_runtime_enable ( dev ) ;
pm_runtime_get_sync ( dev ) ;
2017-07-25 15:26:27 +09:00
platform_set_drvdata ( pdev , priv ) ;
dev_set_drvdata ( dev , priv ) ;
init . name = " rcar_usb2_clock_sel " ;
init . ops = & usb2_clock_sel_clock_ops ;
priv - > hw . init = & init ;
2021-08-26 09:17:21 -05:00
ret = devm_clk_hw_register ( dev , & priv - > hw ) ;
2021-04-15 15:33:38 +08:00
if ( ret )
goto pm_put ;
ret = of_clk_add_hw_provider ( np , of_clk_hw_simple_get , & priv - > hw ) ;
if ( ret )
goto pm_put ;
return 0 ;
2017-07-25 15:26:27 +09:00
2021-04-15 15:33:38 +08:00
pm_put :
pm_runtime_put ( dev ) ;
pm_runtime_disable ( dev ) ;
return ret ;
2017-07-25 15:26:27 +09:00
}
static const struct dev_pm_ops rcar_usb2_clock_sel_pm_ops = {
. suspend = rcar_usb2_clock_sel_suspend ,
. resume = rcar_usb2_clock_sel_resume ,
} ;
static struct platform_driver rcar_usb2_clock_sel_driver = {
. driver = {
. name = " rcar-usb2-clock-sel " ,
. of_match_table = rcar_usb2_clock_sel_match ,
. pm = & rcar_usb2_clock_sel_pm_ops ,
} ,
. probe = rcar_usb2_clock_sel_probe ,
. remove = rcar_usb2_clock_sel_remove ,
} ;
builtin_platform_driver ( rcar_usb2_clock_sel_driver ) ;
MODULE_DESCRIPTION ( " Renesas R-Car USB2 clock selector Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;