2017-06-09 14:43:01 +03:00
/*
* Copyright ( C ) 2017 Broadcom
*
* 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 version 2.
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/delay.h>
2017-09-21 06:11:24 +03:00
# include <linux/extcon-provider.h>
2017-06-09 14:43:01 +03:00
# include <linux/gpio.h>
# include <linux/gpio/consumer.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
# define ICFG_DRD_AFE 0x0
# define ICFG_MISC_STAT 0x18
# define ICFG_DRD_P0CTL 0x1C
# define ICFG_STRAP_CTRL 0x20
# define ICFG_FSM_CTRL 0x24
# define ICFG_DEV_BIT BIT(2)
# define IDM_RST_BIT BIT(0)
# define AFE_CORERDY_VDDC BIT(18)
# define PHY_PLL_RESETB BIT(15)
# define PHY_RESETB BIT(14)
# define PHY_PLL_LOCK BIT(0)
# define DRD_DEV_MODE BIT(20)
# define OHCI_OVRCUR_POL BIT(11)
# define ICFG_OFF_MODE BIT(6)
# define PLL_LOCK_RETRY 1000
# define EVT_DEVICE 0
# define EVT_HOST 1
# define DRD_HOST_MODE (BIT(2) | BIT(3))
# define DRD_DEVICE_MODE (BIT(4) | BIT(5))
# define DRD_HOST_VAL 0x803
# define DRD_DEV_VAL 0x807
# define GPIO_DELAY 20
struct ns2_phy_data ;
struct ns2_phy_driver {
void __iomem * icfgdrd_regs ;
void __iomem * idmdrd_rst_ctrl ;
void __iomem * crmu_usb2_ctrl ;
void __iomem * usb2h_strap_reg ;
struct ns2_phy_data * data ;
struct extcon_dev * edev ;
struct gpio_desc * vbus_gpiod ;
struct gpio_desc * id_gpiod ;
int id_irq ;
int vbus_irq ;
unsigned long debounce_jiffies ;
struct delayed_work wq_extcon ;
} ;
struct ns2_phy_data {
struct ns2_phy_driver * driver ;
struct phy * phy ;
int new_state ;
} ;
static const unsigned int usb_extcon_cable [ ] = {
EXTCON_USB ,
EXTCON_USB_HOST ,
EXTCON_NONE ,
} ;
static inline int pll_lock_stat ( u32 usb_reg , int reg_mask ,
struct ns2_phy_driver * driver )
{
int retry = PLL_LOCK_RETRY ;
u32 val ;
do {
udelay ( 1 ) ;
val = readl ( driver - > icfgdrd_regs + usb_reg ) ;
if ( val & reg_mask )
return 0 ;
} while ( - - retry > 0 ) ;
return - EBUSY ;
}
static int ns2_drd_phy_init ( struct phy * phy )
{
struct ns2_phy_data * data = phy_get_drvdata ( phy ) ;
struct ns2_phy_driver * driver = data - > driver ;
u32 val ;
val = readl ( driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
if ( data - > new_state = = EVT_HOST ) {
val & = ~ DRD_DEVICE_MODE ;
val | = DRD_HOST_MODE ;
} else {
val & = ~ DRD_HOST_MODE ;
val | = DRD_DEVICE_MODE ;
}
writel ( val , driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
return 0 ;
}
static int ns2_drd_phy_poweroff ( struct phy * phy )
{
struct ns2_phy_data * data = phy_get_drvdata ( phy ) ;
struct ns2_phy_driver * driver = data - > driver ;
u32 val ;
val = readl ( driver - > crmu_usb2_ctrl ) ;
val & = ~ AFE_CORERDY_VDDC ;
writel ( val , driver - > crmu_usb2_ctrl ) ;
val = readl ( driver - > crmu_usb2_ctrl ) ;
val & = ~ DRD_DEV_MODE ;
writel ( val , driver - > crmu_usb2_ctrl ) ;
/* Disable Host and Device Mode */
val = readl ( driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
val & = ~ ( DRD_HOST_MODE | DRD_DEVICE_MODE | ICFG_OFF_MODE ) ;
writel ( val , driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
return 0 ;
}
static int ns2_drd_phy_poweron ( struct phy * phy )
{
struct ns2_phy_data * data = phy_get_drvdata ( phy ) ;
struct ns2_phy_driver * driver = data - > driver ;
u32 extcon_event = data - > new_state ;
int ret ;
u32 val ;
if ( extcon_event = = EVT_DEVICE ) {
writel ( DRD_DEV_VAL , driver - > icfgdrd_regs + ICFG_DRD_P0CTL ) ;
val = readl ( driver - > idmdrd_rst_ctrl ) ;
val & = ~ IDM_RST_BIT ;
writel ( val , driver - > idmdrd_rst_ctrl ) ;
val = readl ( driver - > crmu_usb2_ctrl ) ;
val | = ( AFE_CORERDY_VDDC | DRD_DEV_MODE ) ;
writel ( val , driver - > crmu_usb2_ctrl ) ;
/* Bring PHY and PHY_PLL out of Reset */
val = readl ( driver - > crmu_usb2_ctrl ) ;
val | = ( PHY_PLL_RESETB | PHY_RESETB ) ;
writel ( val , driver - > crmu_usb2_ctrl ) ;
ret = pll_lock_stat ( ICFG_MISC_STAT , PHY_PLL_LOCK , driver ) ;
if ( ret < 0 ) {
dev_err ( & phy - > dev , " Phy PLL lock failed \n " ) ;
return ret ;
}
} else {
writel ( DRD_HOST_VAL , driver - > icfgdrd_regs + ICFG_DRD_P0CTL ) ;
val = readl ( driver - > crmu_usb2_ctrl ) ;
val | = AFE_CORERDY_VDDC ;
writel ( val , driver - > crmu_usb2_ctrl ) ;
ret = pll_lock_stat ( ICFG_MISC_STAT , PHY_PLL_LOCK , driver ) ;
if ( ret < 0 ) {
dev_err ( & phy - > dev , " Phy PLL lock failed \n " ) ;
return ret ;
}
val = readl ( driver - > idmdrd_rst_ctrl ) ;
val & = ~ IDM_RST_BIT ;
writel ( val , driver - > idmdrd_rst_ctrl ) ;
/* port over current Polarity */
val = readl ( driver - > usb2h_strap_reg ) ;
val | = OHCI_OVRCUR_POL ;
writel ( val , driver - > usb2h_strap_reg ) ;
}
return 0 ;
}
static void connect_change ( struct ns2_phy_driver * driver )
{
u32 extcon_event ;
u32 val ;
extcon_event = driver - > data - > new_state ;
val = readl ( driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
switch ( extcon_event ) {
case EVT_DEVICE :
val & = ~ ( DRD_HOST_MODE | DRD_DEVICE_MODE ) ;
writel ( val , driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
val = ( val & ~ DRD_HOST_MODE ) | DRD_DEVICE_MODE ;
writel ( val , driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
val = readl ( driver - > icfgdrd_regs + ICFG_DRD_P0CTL ) ;
val | = ICFG_DEV_BIT ;
writel ( val , driver - > icfgdrd_regs + ICFG_DRD_P0CTL ) ;
break ;
case EVT_HOST :
val & = ~ ( DRD_HOST_MODE | DRD_DEVICE_MODE ) ;
writel ( val , driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
val = ( val & ~ DRD_DEVICE_MODE ) | DRD_HOST_MODE ;
writel ( val , driver - > icfgdrd_regs + ICFG_FSM_CTRL ) ;
val = readl ( driver - > usb2h_strap_reg ) ;
val | = OHCI_OVRCUR_POL ;
writel ( val , driver - > usb2h_strap_reg ) ;
val = readl ( driver - > icfgdrd_regs + ICFG_DRD_P0CTL ) ;
val & = ~ ICFG_DEV_BIT ;
writel ( val , driver - > icfgdrd_regs + ICFG_DRD_P0CTL ) ;
break ;
default :
pr_err ( " Invalid extcon event \n " ) ;
break ;
}
}
static void extcon_work ( struct work_struct * work )
{
struct ns2_phy_driver * driver ;
int vbus ;
int id ;
driver = container_of ( to_delayed_work ( work ) ,
struct ns2_phy_driver , wq_extcon ) ;
id = gpiod_get_value_cansleep ( driver - > id_gpiod ) ;
vbus = gpiod_get_value_cansleep ( driver - > vbus_gpiod ) ;
if ( ! id & & vbus ) { /* Host connected */
2017-07-14 09:16:41 +03:00
extcon_set_state_sync ( driver - > edev , EXTCON_USB_HOST , true ) ;
2017-06-09 14:43:01 +03:00
pr_debug ( " Host cable connected \n " ) ;
driver - > data - > new_state = EVT_HOST ;
connect_change ( driver ) ;
} else if ( id & & ! vbus ) { /* Disconnected */
2017-07-14 09:16:41 +03:00
extcon_set_state_sync ( driver - > edev , EXTCON_USB_HOST , false ) ;
extcon_set_state_sync ( driver - > edev , EXTCON_USB , false ) ;
2017-06-09 14:43:01 +03:00
pr_debug ( " Cable disconnected \n " ) ;
} else if ( id & & vbus ) { /* Device connected */
2017-07-14 09:16:41 +03:00
extcon_set_state_sync ( driver - > edev , EXTCON_USB , true ) ;
2017-06-09 14:43:01 +03:00
pr_debug ( " Device cable connected \n " ) ;
driver - > data - > new_state = EVT_DEVICE ;
connect_change ( driver ) ;
}
}
static irqreturn_t gpio_irq_handler ( int irq , void * dev_id )
{
struct ns2_phy_driver * driver = dev_id ;
queue_delayed_work ( system_power_efficient_wq , & driver - > wq_extcon ,
driver - > debounce_jiffies ) ;
return IRQ_HANDLED ;
}
static struct phy_ops ops = {
. init = ns2_drd_phy_init ,
. power_on = ns2_drd_phy_poweron ,
. power_off = ns2_drd_phy_poweroff ,
. owner = THIS_MODULE ,
} ;
static const struct of_device_id ns2_drd_phy_dt_ids [ ] = {
{ . compatible = " brcm,ns2-drd-phy " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , ns2_drd_phy_dt_ids ) ;
static int ns2_drd_phy_probe ( struct platform_device * pdev )
{
struct phy_provider * phy_provider ;
struct device * dev = & pdev - > dev ;
struct ns2_phy_driver * driver ;
struct ns2_phy_data * data ;
struct resource * res ;
int ret ;
u32 val ;
driver = devm_kzalloc ( dev , sizeof ( struct ns2_phy_driver ) ,
GFP_KERNEL ) ;
if ( ! driver )
return - ENOMEM ;
driver - > data = devm_kzalloc ( dev , sizeof ( struct ns2_phy_data ) ,
GFP_KERNEL ) ;
if ( ! driver - > data )
return - ENOMEM ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " icfg " ) ;
driver - > icfgdrd_regs = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( driver - > icfgdrd_regs ) )
return PTR_ERR ( driver - > icfgdrd_regs ) ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " rst-ctrl " ) ;
driver - > idmdrd_rst_ctrl = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( driver - > idmdrd_rst_ctrl ) )
return PTR_ERR ( driver - > idmdrd_rst_ctrl ) ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " crmu-ctrl " ) ;
driver - > crmu_usb2_ctrl = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( driver - > crmu_usb2_ctrl ) )
return PTR_ERR ( driver - > crmu_usb2_ctrl ) ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " usb2-strap " ) ;
driver - > usb2h_strap_reg = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( driver - > usb2h_strap_reg ) )
return PTR_ERR ( driver - > usb2h_strap_reg ) ;
/* create extcon */
driver - > id_gpiod = devm_gpiod_get ( & pdev - > dev , " id " , GPIOD_IN ) ;
if ( IS_ERR ( driver - > id_gpiod ) ) {
dev_err ( dev , " failed to get ID GPIO \n " ) ;
return PTR_ERR ( driver - > id_gpiod ) ;
}
driver - > vbus_gpiod = devm_gpiod_get ( & pdev - > dev , " vbus " , GPIOD_IN ) ;
if ( IS_ERR ( driver - > vbus_gpiod ) ) {
dev_err ( dev , " failed to get VBUS GPIO \n " ) ;
return PTR_ERR ( driver - > vbus_gpiod ) ;
}
driver - > edev = devm_extcon_dev_allocate ( dev , usb_extcon_cable ) ;
if ( IS_ERR ( driver - > edev ) ) {
dev_err ( dev , " failed to allocate extcon device \n " ) ;
return - ENOMEM ;
}
ret = devm_extcon_dev_register ( dev , driver - > edev ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to register extcon device \n " ) ;
return ret ;
}
ret = gpiod_set_debounce ( driver - > id_gpiod , GPIO_DELAY * 1000 ) ;
if ( ret < 0 )
driver - > debounce_jiffies = msecs_to_jiffies ( GPIO_DELAY ) ;
INIT_DELAYED_WORK ( & driver - > wq_extcon , extcon_work ) ;
driver - > id_irq = gpiod_to_irq ( driver - > id_gpiod ) ;
if ( driver - > id_irq < 0 ) {
dev_err ( dev , " failed to get ID IRQ \n " ) ;
return driver - > id_irq ;
}
driver - > vbus_irq = gpiod_to_irq ( driver - > vbus_gpiod ) ;
if ( driver - > vbus_irq < 0 ) {
dev_err ( dev , " failed to get ID IRQ \n " ) ;
return driver - > vbus_irq ;
}
ret = devm_request_irq ( dev , driver - > id_irq , gpio_irq_handler ,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING ,
" usb_id " , driver ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to request handler for ID IRQ \n " ) ;
return ret ;
}
ret = devm_request_irq ( dev , driver - > vbus_irq , gpio_irq_handler ,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING ,
" usb_vbus " , driver ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to request handler for VBUS IRQ \n " ) ;
return ret ;
}
dev_set_drvdata ( dev , driver ) ;
/* Shutdown all ports. They can be powered up as required */
val = readl ( driver - > crmu_usb2_ctrl ) ;
val & = ~ ( AFE_CORERDY_VDDC | PHY_RESETB ) ;
writel ( val , driver - > crmu_usb2_ctrl ) ;
data = driver - > data ;
data - > phy = devm_phy_create ( dev , dev - > of_node , & ops ) ;
if ( IS_ERR ( data - > phy ) ) {
dev_err ( dev , " Failed to create usb drd phy \n " ) ;
return PTR_ERR ( data - > phy ) ;
}
data - > driver = driver ;
phy_set_drvdata ( data - > phy , data ) ;
phy_provider = devm_of_phy_provider_register ( dev , of_phy_simple_xlate ) ;
if ( IS_ERR ( phy_provider ) ) {
dev_err ( dev , " Failed to register as phy provider \n " ) ;
return PTR_ERR ( phy_provider ) ;
}
platform_set_drvdata ( pdev , driver ) ;
dev_info ( dev , " Registered NS2 DRD Phy device \n " ) ;
queue_delayed_work ( system_power_efficient_wq , & driver - > wq_extcon ,
driver - > debounce_jiffies ) ;
return 0 ;
}
static struct platform_driver ns2_drd_phy_driver = {
. probe = ns2_drd_phy_probe ,
. driver = {
. name = " bcm-ns2-usbphy " ,
. of_match_table = of_match_ptr ( ns2_drd_phy_dt_ids ) ,
} ,
} ;
module_platform_driver ( ns2_drd_phy_driver ) ;
MODULE_ALIAS ( " platform:bcm-ns2-drd-phy " ) ;
MODULE_AUTHOR ( " Broadcom " ) ;
MODULE_DESCRIPTION ( " Broadcom NS2 USB2 PHY driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;