2020-07-20 15:12:09 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* BCM6328 USBH PHY Controller Driver
*
* Copyright ( C ) 2020 Á lvaro Fernández Rojas < noltari @ gmail . com >
* Copyright ( C ) 2015 Simon Arlott
*
* Derived from bcm963xx_4 .12 L .06 B_consumer / kernel / linux / arch / mips / bcm963xx / setup . c :
* Copyright ( C ) 2002 Broadcom Corporation
*
* Derived from OpenWrt patches :
* Copyright ( C ) 2013 Jonas Gorski < jonas . gorski @ gmail . com >
* Copyright ( C ) 2013 Florian Fainelli < f . fainelli @ gmail . com >
* Copyright ( C ) 2008 Maxime Bizon < mbizon @ freebox . fr >
*/
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/module.h>
2023-07-14 11:48:35 -06:00
# include <linux/of.h>
2020-07-20 15:12:09 +02:00
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
# include <linux/reset.h>
/* USBH control register offsets */
enum usbh_regs {
USBH_BRT_CONTROL1 = 0 ,
USBH_BRT_CONTROL2 ,
USBH_BRT_STATUS1 ,
USBH_BRT_STATUS2 ,
USBH_UTMI_CONTROL1 ,
# define USBH_UC1_DEV_MODE_SEL BIT(0)
USBH_TEST_PORT_CONTROL ,
USBH_PLL_CONTROL1 ,
# define USBH_PLLC_REFCLKSEL_SHIFT 0
# define USBH_PLLC_REFCLKSEL_MASK (0x3 << USBH_PLLC_REFCLKSEL_SHIFT)
# define USBH_PLLC_CLKSEL_SHIFT 2
# define USBH_PLLC_CLKSEL_MASK (0x3 << USBH_PLLC_CLKSEL_MASK)
# define USBH_PLLC_XTAL_PWRDWNB BIT(4)
# define USBH_PLLC_PLL_PWRDWNB BIT(5)
# define USBH_PLLC_PLL_CALEN BIT(6)
# define USBH_PLLC_PHYPLL_BYP BIT(7)
# define USBH_PLLC_PLL_RESET BIT(8)
# define USBH_PLLC_PLL_IDDQ_PWRDN BIT(9)
# define USBH_PLLC_PLL_PWRDN_DELAY BIT(10)
# define USBH_6318_PLLC_PLL_SUSPEND_EN BIT(27)
# define USBH_6318_PLLC_PHYPLL_BYP BIT(29)
# define USBH_6318_PLLC_PLL_RESET BIT(30)
# define USBH_6318_PLLC_PLL_IDDQ_PWRDN BIT(31)
USBH_SWAP_CONTROL ,
# define USBH_SC_OHCI_DATA_SWAP BIT(0)
# define USBH_SC_OHCI_ENDIAN_SWAP BIT(1)
# define USBH_SC_OHCI_LOGICAL_ADDR_EN BIT(2)
# define USBH_SC_EHCI_DATA_SWAP BIT(3)
# define USBH_SC_EHCI_ENDIAN_SWAP BIT(4)
# define USBH_SC_EHCI_LOGICAL_ADDR_EN BIT(5)
# define USBH_SC_USB_DEVICE_SEL BIT(6)
USBH_GENERIC_CONTROL ,
# define USBH_GC_PLL_SUSPEND_EN BIT(1)
USBH_FRAME_ADJUST_VALUE ,
USBH_SETUP ,
# define USBH_S_IOC BIT(4)
# define USBH_S_IPP BIT(5)
USBH_MDIO ,
USBH_MDIO32 ,
USBH_USB_SIM_CONTROL ,
# define USBH_USC_LADDR_SEL BIT(5)
__USBH_ENUM_SIZE
} ;
struct bcm63xx_usbh_phy_variant {
/* Registers */
long regs [ __USBH_ENUM_SIZE ] ;
/* PLLC bits to set/clear for power on */
u32 power_pllc_clr ;
u32 power_pllc_set ;
/* Setup bits to set/clear for power on */
u32 setup_clr ;
u32 setup_set ;
/* Swap Control bits to set */
u32 swapctl_dev_set ;
/* Test Port Control value to set if non-zero */
u32 tpc_val ;
/* USB Sim Control bits to set */
u32 usc_set ;
/* UTMI Control 1 bits to set */
u32 utmictl1_dev_set ;
} ;
struct bcm63xx_usbh_phy {
void __iomem * base ;
struct clk * usbh_clk ;
struct clk * usb_ref_clk ;
struct reset_control * reset ;
const struct bcm63xx_usbh_phy_variant * variant ;
bool device_mode ;
} ;
static const struct bcm63xx_usbh_phy_variant usbh_bcm6318 = {
. regs = {
[ USBH_BRT_CONTROL1 ] = - 1 ,
[ USBH_BRT_CONTROL2 ] = - 1 ,
[ USBH_BRT_STATUS1 ] = - 1 ,
[ USBH_BRT_STATUS2 ] = - 1 ,
[ USBH_UTMI_CONTROL1 ] = 0x2c ,
[ USBH_TEST_PORT_CONTROL ] = 0x1c ,
[ USBH_PLL_CONTROL1 ] = 0x04 ,
[ USBH_SWAP_CONTROL ] = 0x0c ,
[ USBH_GENERIC_CONTROL ] = - 1 ,
[ USBH_FRAME_ADJUST_VALUE ] = 0x08 ,
[ USBH_SETUP ] = 0x00 ,
[ USBH_MDIO ] = 0x14 ,
[ USBH_MDIO32 ] = 0x18 ,
[ USBH_USB_SIM_CONTROL ] = 0x20 ,
} ,
. power_pllc_clr = USBH_6318_PLLC_PLL_IDDQ_PWRDN ,
. power_pllc_set = USBH_6318_PLLC_PLL_SUSPEND_EN ,
. setup_set = USBH_S_IOC ,
. swapctl_dev_set = USBH_SC_USB_DEVICE_SEL ,
. usc_set = USBH_USC_LADDR_SEL ,
. utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL ,
} ;
static const struct bcm63xx_usbh_phy_variant usbh_bcm6328 = {
. regs = {
[ USBH_BRT_CONTROL1 ] = 0x00 ,
[ USBH_BRT_CONTROL2 ] = 0x04 ,
[ USBH_BRT_STATUS1 ] = 0x08 ,
[ USBH_BRT_STATUS2 ] = 0x0c ,
[ USBH_UTMI_CONTROL1 ] = 0x10 ,
[ USBH_TEST_PORT_CONTROL ] = 0x14 ,
[ USBH_PLL_CONTROL1 ] = 0x18 ,
[ USBH_SWAP_CONTROL ] = 0x1c ,
[ USBH_GENERIC_CONTROL ] = 0x20 ,
[ USBH_FRAME_ADJUST_VALUE ] = 0x24 ,
[ USBH_SETUP ] = 0x28 ,
[ USBH_MDIO ] = 0x2c ,
[ USBH_MDIO32 ] = 0x30 ,
[ USBH_USB_SIM_CONTROL ] = 0x34 ,
} ,
. setup_set = USBH_S_IOC ,
. swapctl_dev_set = USBH_SC_USB_DEVICE_SEL ,
. utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL ,
} ;
static const struct bcm63xx_usbh_phy_variant usbh_bcm6358 = {
. regs = {
[ USBH_BRT_CONTROL1 ] = - 1 ,
[ USBH_BRT_CONTROL2 ] = - 1 ,
[ USBH_BRT_STATUS1 ] = - 1 ,
[ USBH_BRT_STATUS2 ] = - 1 ,
[ USBH_UTMI_CONTROL1 ] = - 1 ,
[ USBH_TEST_PORT_CONTROL ] = 0x24 ,
[ USBH_PLL_CONTROL1 ] = - 1 ,
[ USBH_SWAP_CONTROL ] = 0x00 ,
[ USBH_GENERIC_CONTROL ] = - 1 ,
[ USBH_FRAME_ADJUST_VALUE ] = - 1 ,
[ USBH_SETUP ] = - 1 ,
[ USBH_MDIO ] = - 1 ,
[ USBH_MDIO32 ] = - 1 ,
[ USBH_USB_SIM_CONTROL ] = - 1 ,
} ,
/*
* The magic value comes for the original vendor BSP
* and is needed for USB to work . Datasheet does not
* help , so the magic value is used as - is .
*/
. tpc_val = 0x1c0020 ,
} ;
static const struct bcm63xx_usbh_phy_variant usbh_bcm6368 = {
. regs = {
[ USBH_BRT_CONTROL1 ] = 0x00 ,
[ USBH_BRT_CONTROL2 ] = 0x04 ,
[ USBH_BRT_STATUS1 ] = 0x08 ,
[ USBH_BRT_STATUS2 ] = 0x0c ,
[ USBH_UTMI_CONTROL1 ] = 0x10 ,
[ USBH_TEST_PORT_CONTROL ] = 0x14 ,
[ USBH_PLL_CONTROL1 ] = 0x18 ,
[ USBH_SWAP_CONTROL ] = 0x1c ,
[ USBH_GENERIC_CONTROL ] = - 1 ,
[ USBH_FRAME_ADJUST_VALUE ] = 0x24 ,
[ USBH_SETUP ] = 0x28 ,
[ USBH_MDIO ] = 0x2c ,
[ USBH_MDIO32 ] = 0x30 ,
[ USBH_USB_SIM_CONTROL ] = 0x34 ,
} ,
. power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY ,
. setup_set = USBH_S_IOC ,
. swapctl_dev_set = USBH_SC_USB_DEVICE_SEL ,
. utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL ,
} ;
static const struct bcm63xx_usbh_phy_variant usbh_bcm63268 = {
. regs = {
[ USBH_BRT_CONTROL1 ] = 0x00 ,
[ USBH_BRT_CONTROL2 ] = 0x04 ,
[ USBH_BRT_STATUS1 ] = 0x08 ,
[ USBH_BRT_STATUS2 ] = 0x0c ,
[ USBH_UTMI_CONTROL1 ] = 0x10 ,
[ USBH_TEST_PORT_CONTROL ] = 0x14 ,
[ USBH_PLL_CONTROL1 ] = 0x18 ,
[ USBH_SWAP_CONTROL ] = 0x1c ,
[ USBH_GENERIC_CONTROL ] = 0x20 ,
[ USBH_FRAME_ADJUST_VALUE ] = 0x24 ,
[ USBH_SETUP ] = 0x28 ,
[ USBH_MDIO ] = 0x2c ,
[ USBH_MDIO32 ] = 0x30 ,
[ USBH_USB_SIM_CONTROL ] = 0x34 ,
} ,
. power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY ,
. setup_clr = USBH_S_IPP ,
. setup_set = USBH_S_IOC ,
. swapctl_dev_set = USBH_SC_USB_DEVICE_SEL ,
. utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL ,
} ;
static inline bool usbh_has_reg ( struct bcm63xx_usbh_phy * usbh , int reg )
{
return ( usbh - > variant - > regs [ reg ] > = 0 ) ;
}
static inline u32 usbh_readl ( struct bcm63xx_usbh_phy * usbh , int reg )
{
return __raw_readl ( usbh - > base + usbh - > variant - > regs [ reg ] ) ;
}
static inline void usbh_writel ( struct bcm63xx_usbh_phy * usbh , int reg ,
u32 value )
{
__raw_writel ( value , usbh - > base + usbh - > variant - > regs [ reg ] ) ;
}
static int bcm63xx_usbh_phy_init ( struct phy * phy )
{
struct bcm63xx_usbh_phy * usbh = phy_get_drvdata ( phy ) ;
int ret ;
ret = clk_prepare_enable ( usbh - > usbh_clk ) ;
if ( ret ) {
dev_err ( & phy - > dev , " unable to enable usbh clock: %d \n " , ret ) ;
return ret ;
}
ret = clk_prepare_enable ( usbh - > usb_ref_clk ) ;
if ( ret ) {
dev_err ( & phy - > dev , " unable to enable usb_ref clock: %d \n " , ret ) ;
clk_disable_unprepare ( usbh - > usbh_clk ) ;
return ret ;
}
ret = reset_control_reset ( usbh - > reset ) ;
if ( ret ) {
dev_err ( & phy - > dev , " unable to reset device: %d \n " , ret ) ;
clk_disable_unprepare ( usbh - > usb_ref_clk ) ;
clk_disable_unprepare ( usbh - > usbh_clk ) ;
return ret ;
}
/* Configure to work in native CPU endian */
if ( usbh_has_reg ( usbh , USBH_SWAP_CONTROL ) ) {
u32 val = usbh_readl ( usbh , USBH_SWAP_CONTROL ) ;
val | = USBH_SC_EHCI_DATA_SWAP ;
val & = ~ USBH_SC_EHCI_ENDIAN_SWAP ;
val | = USBH_SC_OHCI_DATA_SWAP ;
val & = ~ USBH_SC_OHCI_ENDIAN_SWAP ;
if ( usbh - > device_mode & & usbh - > variant - > swapctl_dev_set )
val | = usbh - > variant - > swapctl_dev_set ;
usbh_writel ( usbh , USBH_SWAP_CONTROL , val ) ;
}
if ( usbh_has_reg ( usbh , USBH_SETUP ) ) {
u32 val = usbh_readl ( usbh , USBH_SETUP ) ;
val | = usbh - > variant - > setup_set ;
val & = ~ usbh - > variant - > setup_clr ;
usbh_writel ( usbh , USBH_SETUP , val ) ;
}
if ( usbh_has_reg ( usbh , USBH_USB_SIM_CONTROL ) ) {
u32 val = usbh_readl ( usbh , USBH_USB_SIM_CONTROL ) ;
val | = usbh - > variant - > usc_set ;
usbh_writel ( usbh , USBH_USB_SIM_CONTROL , val ) ;
}
if ( usbh - > variant - > tpc_val & &
usbh_has_reg ( usbh , USBH_TEST_PORT_CONTROL ) )
usbh_writel ( usbh , USBH_TEST_PORT_CONTROL ,
usbh - > variant - > tpc_val ) ;
if ( usbh - > device_mode & &
usbh_has_reg ( usbh , USBH_UTMI_CONTROL1 ) & &
usbh - > variant - > utmictl1_dev_set ) {
u32 val = usbh_readl ( usbh , USBH_UTMI_CONTROL1 ) ;
val | = usbh - > variant - > utmictl1_dev_set ;
usbh_writel ( usbh , USBH_UTMI_CONTROL1 , val ) ;
}
return 0 ;
}
static int bcm63xx_usbh_phy_power_on ( struct phy * phy )
{
struct bcm63xx_usbh_phy * usbh = phy_get_drvdata ( phy ) ;
if ( usbh_has_reg ( usbh , USBH_PLL_CONTROL1 ) ) {
u32 val = usbh_readl ( usbh , USBH_PLL_CONTROL1 ) ;
val | = usbh - > variant - > power_pllc_set ;
val & = ~ usbh - > variant - > power_pllc_clr ;
usbh_writel ( usbh , USBH_PLL_CONTROL1 , val ) ;
}
return 0 ;
}
static int bcm63xx_usbh_phy_power_off ( struct phy * phy )
{
struct bcm63xx_usbh_phy * usbh = phy_get_drvdata ( phy ) ;
if ( usbh_has_reg ( usbh , USBH_PLL_CONTROL1 ) ) {
u32 val = usbh_readl ( usbh , USBH_PLL_CONTROL1 ) ;
val & = ~ usbh - > variant - > power_pllc_set ;
val | = usbh - > variant - > power_pllc_clr ;
usbh_writel ( usbh , USBH_PLL_CONTROL1 , val ) ;
}
return 0 ;
}
static int bcm63xx_usbh_phy_exit ( struct phy * phy )
{
struct bcm63xx_usbh_phy * usbh = phy_get_drvdata ( phy ) ;
clk_disable_unprepare ( usbh - > usbh_clk ) ;
clk_disable_unprepare ( usbh - > usb_ref_clk ) ;
return 0 ;
}
static const struct phy_ops bcm63xx_usbh_phy_ops = {
. exit = bcm63xx_usbh_phy_exit ,
. init = bcm63xx_usbh_phy_init ,
. power_off = bcm63xx_usbh_phy_power_off ,
. power_on = bcm63xx_usbh_phy_power_on ,
. owner = THIS_MODULE ,
} ;
static struct phy * bcm63xx_usbh_phy_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct bcm63xx_usbh_phy * usbh = dev_get_drvdata ( dev ) ;
usbh - > device_mode = ! ! args - > args [ 0 ] ;
return of_phy_simple_xlate ( dev , args ) ;
}
static int __init bcm63xx_usbh_phy_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct bcm63xx_usbh_phy * usbh ;
const struct bcm63xx_usbh_phy_variant * variant ;
struct phy * phy ;
struct phy_provider * phy_provider ;
usbh = devm_kzalloc ( dev , sizeof ( * usbh ) , GFP_KERNEL ) ;
if ( ! usbh )
return - ENOMEM ;
variant = device_get_match_data ( dev ) ;
if ( ! variant )
return - EINVAL ;
usbh - > variant = variant ;
usbh - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( usbh - > base ) )
return PTR_ERR ( usbh - > base ) ;
usbh - > reset = devm_reset_control_get_exclusive ( dev , NULL ) ;
if ( IS_ERR ( usbh - > reset ) ) {
if ( PTR_ERR ( usbh - > reset ) ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get reset \n " ) ;
return PTR_ERR ( usbh - > reset ) ;
}
usbh - > usbh_clk = devm_clk_get_optional ( dev , " usbh " ) ;
if ( IS_ERR ( usbh - > usbh_clk ) )
return PTR_ERR ( usbh - > usbh_clk ) ;
usbh - > usb_ref_clk = devm_clk_get_optional ( dev , " usb_ref " ) ;
if ( IS_ERR ( usbh - > usb_ref_clk ) )
return PTR_ERR ( usbh - > usb_ref_clk ) ;
phy = devm_phy_create ( dev , NULL , & bcm63xx_usbh_phy_ops ) ;
if ( IS_ERR ( phy ) ) {
dev_err ( dev , " failed to create PHY \n " ) ;
return PTR_ERR ( phy ) ;
}
platform_set_drvdata ( pdev , usbh ) ;
phy_set_drvdata ( phy , usbh ) ;
phy_provider = devm_of_phy_provider_register ( dev ,
bcm63xx_usbh_phy_xlate ) ;
if ( IS_ERR ( phy_provider ) ) {
dev_err ( dev , " failed to register PHY provider \n " ) ;
return PTR_ERR ( phy_provider ) ;
}
dev_dbg ( dev , " Registered BCM63xx USB PHY driver \n " ) ;
return 0 ;
}
static const struct of_device_id bcm63xx_usbh_phy_ids [ ] __initconst = {
{ . compatible = " brcm,bcm6318-usbh-phy " , . data = & usbh_bcm6318 } ,
{ . compatible = " brcm,bcm6328-usbh-phy " , . data = & usbh_bcm6328 } ,
{ . compatible = " brcm,bcm6358-usbh-phy " , . data = & usbh_bcm6358 } ,
{ . compatible = " brcm,bcm6362-usbh-phy " , . data = & usbh_bcm6368 } ,
{ . compatible = " brcm,bcm6368-usbh-phy " , . data = & usbh_bcm6368 } ,
{ . compatible = " brcm,bcm63268-usbh-phy " , . data = & usbh_bcm63268 } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , bcm63xx_usbh_phy_ids ) ;
static struct platform_driver bcm63xx_usbh_phy_driver __refdata = {
. driver = {
. name = " bcm63xx-usbh-phy " ,
. of_match_table = bcm63xx_usbh_phy_ids ,
} ,
. probe = bcm63xx_usbh_phy_probe ,
} ;
module_platform_driver ( bcm63xx_usbh_phy_driver ) ;
MODULE_DESCRIPTION ( " BCM63xx USBH PHY driver " ) ;
MODULE_AUTHOR ( " Álvaro Fernández Rojas <noltari@gmail.com> " ) ;
MODULE_AUTHOR ( " Simon Arlott " ) ;
MODULE_LICENSE ( " GPL " ) ;