2015-11-30 04:44:30 +03:00
/*
* Renesas R - Car Gen3 for USB2 .0 PHY driver
*
* Copyright ( C ) 2015 Renesas Electronics Corporation
*
* This is based on the phy - rcar - gen2 driver :
* Copyright ( C ) 2014 Renesas Solutions Corp .
* Copyright ( C ) 2014 Cogent Embedded , Inc .
*
* 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 .
*/
2016-04-29 11:52:25 +03:00
# include <linux/extcon.h>
2015-11-30 04:44:32 +03:00
# include <linux/interrupt.h>
2015-11-30 04:44:30 +03:00
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
2016-03-03 13:09:05 +03:00
# include <linux/regulator/consumer.h>
2016-06-27 09:36:53 +03:00
# include <linux/workqueue.h>
2015-11-30 04:44:30 +03:00
/******* USB2.0 Host registers (original offset is +0x200) *******/
# define USB2_INT_ENABLE 0x000
# define USB2_USBCTR 0x00c
# define USB2_SPD_RSM_TIMSET 0x10c
# define USB2_OC_TIMSET 0x110
2015-11-30 04:44:31 +03:00
# define USB2_COMMCTRL 0x600
2015-11-30 04:44:32 +03:00
# define USB2_OBINTSTA 0x604
# define USB2_OBINTEN 0x608
2015-11-30 04:44:31 +03:00
# define USB2_VBCTRL 0x60c
# define USB2_LINECTRL1 0x610
# define USB2_ADPCTRL 0x630
2015-11-30 04:44:30 +03:00
/* INT_ENABLE */
2015-11-30 04:44:32 +03:00
# define USB2_INT_ENABLE_UCOM_INTEN BIT(3)
2015-11-30 04:44:30 +03:00
# define USB2_INT_ENABLE_USBH_INTB_EN BIT(2)
# define USB2_INT_ENABLE_USBH_INTA_EN BIT(1)
2015-11-30 04:44:32 +03:00
# define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \
USB2_INT_ENABLE_USBH_INTB_EN | \
2015-11-30 04:44:30 +03:00
USB2_INT_ENABLE_USBH_INTA_EN )
/* USBCTR */
# define USB2_USBCTR_DIRPD BIT(2)
# define USB2_USBCTR_PLL_RST BIT(1)
/* SPD_RSM_TIMSET */
# define USB2_SPD_RSM_TIMSET_INIT 0x014e029b
/* OC_TIMSET */
# define USB2_OC_TIMSET_INIT 0x000209ab
2015-11-30 04:44:31 +03:00
/* COMMCTRL */
# define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */
2015-11-30 04:44:32 +03:00
/* OBINTSTA and OBINTEN */
# define USB2_OBINT_SESSVLDCHG BIT(12)
# define USB2_OBINT_IDDIGCHG BIT(11)
# define USB2_OBINT_BITS (USB2_OBINT_SESSVLDCHG | \
USB2_OBINT_IDDIGCHG )
2015-11-30 04:44:31 +03:00
/* VBCTRL */
# define USB2_VBCTRL_DRVVBUSSEL BIT(8)
/* LINECTRL1 */
# define USB2_LINECTRL1_DPRPD_EN BIT(19)
# define USB2_LINECTRL1_DP_RPD BIT(18)
# define USB2_LINECTRL1_DMRPD_EN BIT(17)
# define USB2_LINECTRL1_DM_RPD BIT(16)
2016-11-09 05:30:25 +03:00
# define USB2_LINECTRL1_OPMODE_NODRV BIT(6)
2015-11-30 04:44:31 +03:00
/* ADPCTRL */
# define USB2_ADPCTRL_OTGSESSVLD BIT(20)
# define USB2_ADPCTRL_IDDIG BIT(19)
# define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */
# define USB2_ADPCTRL_DRVVBUS BIT(4)
2015-11-30 04:44:30 +03:00
struct rcar_gen3_chan {
2016-03-03 13:09:04 +03:00
void __iomem * base ;
2016-04-29 11:52:25 +03:00
struct extcon_dev * extcon ;
2015-11-30 04:44:30 +03:00
struct phy * phy ;
2016-03-03 13:09:05 +03:00
struct regulator * vbus ;
2016-06-27 09:36:53 +03:00
struct work_struct work ;
bool extcon_host ;
2016-01-07 12:16:44 +03:00
bool has_otg ;
2015-11-30 04:44:30 +03:00
} ;
2016-06-27 09:36:53 +03:00
static void rcar_gen3_phy_usb2_work ( struct work_struct * work )
{
struct rcar_gen3_chan * ch = container_of ( work , struct rcar_gen3_chan ,
work ) ;
if ( ch - > extcon_host ) {
2016-12-30 07:11:28 +03:00
extcon_set_state_sync ( ch - > extcon , EXTCON_USB_HOST , true ) ;
extcon_set_state_sync ( ch - > extcon , EXTCON_USB , false ) ;
2016-06-27 09:36:53 +03:00
} else {
2016-12-30 07:11:28 +03:00
extcon_set_state_sync ( ch - > extcon , EXTCON_USB_HOST , false ) ;
extcon_set_state_sync ( ch - > extcon , EXTCON_USB , true ) ;
2016-06-27 09:36:53 +03:00
}
}
2015-11-30 04:44:31 +03:00
static void rcar_gen3_set_host_mode ( struct rcar_gen3_chan * ch , int host )
{
2016-03-03 13:09:04 +03:00
void __iomem * usb2_base = ch - > base ;
2015-11-30 04:44:31 +03:00
u32 val = readl ( usb2_base + USB2_COMMCTRL ) ;
dev_vdbg ( & ch - > phy - > dev , " %s: %08x, %d \n " , __func__ , val , host ) ;
if ( host )
val & = ~ USB2_COMMCTRL_OTG_PERI ;
else
val | = USB2_COMMCTRL_OTG_PERI ;
writel ( val , usb2_base + USB2_COMMCTRL ) ;
}
static void rcar_gen3_set_linectrl ( struct rcar_gen3_chan * ch , int dp , int dm )
{
2016-03-03 13:09:04 +03:00
void __iomem * usb2_base = ch - > base ;
2015-11-30 04:44:31 +03:00
u32 val = readl ( usb2_base + USB2_LINECTRL1 ) ;
dev_vdbg ( & ch - > phy - > dev , " %s: %08x, %d, %d \n " , __func__ , val , dp , dm ) ;
val & = ~ ( USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD ) ;
if ( dp )
val | = USB2_LINECTRL1_DP_RPD ;
if ( dm )
val | = USB2_LINECTRL1_DM_RPD ;
writel ( val , usb2_base + USB2_LINECTRL1 ) ;
}
static void rcar_gen3_enable_vbus_ctrl ( struct rcar_gen3_chan * ch , int vbus )
{
2016-03-03 13:09:04 +03:00
void __iomem * usb2_base = ch - > base ;
2015-11-30 04:44:31 +03:00
u32 val = readl ( usb2_base + USB2_ADPCTRL ) ;
dev_vdbg ( & ch - > phy - > dev , " %s: %08x, %d \n " , __func__ , val , vbus ) ;
if ( vbus )
val | = USB2_ADPCTRL_DRVVBUS ;
else
val & = ~ USB2_ADPCTRL_DRVVBUS ;
writel ( val , usb2_base + USB2_ADPCTRL ) ;
}
static void rcar_gen3_init_for_host ( struct rcar_gen3_chan * ch )
{
rcar_gen3_set_linectrl ( ch , 1 , 1 ) ;
rcar_gen3_set_host_mode ( ch , 1 ) ;
rcar_gen3_enable_vbus_ctrl ( ch , 1 ) ;
2016-04-29 11:52:25 +03:00
2016-06-27 09:36:53 +03:00
ch - > extcon_host = true ;
schedule_work ( & ch - > work ) ;
2015-11-30 04:44:31 +03:00
}
static void rcar_gen3_init_for_peri ( struct rcar_gen3_chan * ch )
{
rcar_gen3_set_linectrl ( ch , 0 , 1 ) ;
rcar_gen3_set_host_mode ( ch , 0 ) ;
rcar_gen3_enable_vbus_ctrl ( ch , 0 ) ;
2016-04-29 11:52:25 +03:00
2016-06-27 09:36:53 +03:00
ch - > extcon_host = false ;
schedule_work ( & ch - > work ) ;
2015-11-30 04:44:31 +03:00
}
2016-11-09 05:30:25 +03:00
static void rcar_gen3_init_for_b_host ( struct rcar_gen3_chan * ch )
{
void __iomem * usb2_base = ch - > base ;
u32 val ;
val = readl ( usb2_base + USB2_LINECTRL1 ) ;
writel ( val | USB2_LINECTRL1_OPMODE_NODRV , usb2_base + USB2_LINECTRL1 ) ;
rcar_gen3_set_linectrl ( ch , 1 , 1 ) ;
rcar_gen3_set_host_mode ( ch , 1 ) ;
rcar_gen3_enable_vbus_ctrl ( ch , 0 ) ;
val = readl ( usb2_base + USB2_LINECTRL1 ) ;
writel ( val & ~ USB2_LINECTRL1_OPMODE_NODRV , usb2_base + USB2_LINECTRL1 ) ;
}
static void rcar_gen3_init_for_a_peri ( struct rcar_gen3_chan * ch )
{
rcar_gen3_set_linectrl ( ch , 0 , 1 ) ;
rcar_gen3_set_host_mode ( ch , 0 ) ;
rcar_gen3_enable_vbus_ctrl ( ch , 1 ) ;
}
static void rcar_gen3_init_from_a_peri_to_a_host ( struct rcar_gen3_chan * ch )
{
void __iomem * usb2_base = ch - > base ;
u32 val ;
val = readl ( usb2_base + USB2_OBINTEN ) ;
writel ( val & ~ USB2_OBINT_BITS , usb2_base + USB2_OBINTEN ) ;
rcar_gen3_enable_vbus_ctrl ( ch , 0 ) ;
rcar_gen3_init_for_host ( ch ) ;
writel ( val | USB2_OBINT_BITS , usb2_base + USB2_OBINTEN ) ;
}
2015-11-30 04:44:31 +03:00
static bool rcar_gen3_check_id ( struct rcar_gen3_chan * ch )
{
2016-03-03 13:09:04 +03:00
return ! ! ( readl ( ch - > base + USB2_ADPCTRL ) & USB2_ADPCTRL_IDDIG ) ;
2015-11-30 04:44:31 +03:00
}
static void rcar_gen3_device_recognition ( struct rcar_gen3_chan * ch )
{
2016-05-31 15:47:17 +03:00
if ( ! rcar_gen3_check_id ( ch ) )
2015-11-30 04:44:31 +03:00
rcar_gen3_init_for_host ( ch ) ;
else
rcar_gen3_init_for_peri ( ch ) ;
}
2016-11-09 05:30:25 +03:00
static bool rcar_gen3_is_host ( struct rcar_gen3_chan * ch )
{
return ! ( readl ( ch - > base + USB2_COMMCTRL ) & USB2_COMMCTRL_OTG_PERI ) ;
}
static ssize_t role_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct rcar_gen3_chan * ch = dev_get_drvdata ( dev ) ;
bool is_b_device , is_host , new_mode_is_host ;
if ( ! ch - > has_otg | | ! ch - > phy - > init_count )
return - EIO ;
/*
* is_b_device : true is B - Device . false is A - Device .
* If { new_mode_ } is_host : true is Host mode . false is Peripheral mode .
*/
is_b_device = rcar_gen3_check_id ( ch ) ;
is_host = rcar_gen3_is_host ( ch ) ;
if ( ! strncmp ( buf , " host " , strlen ( " host " ) ) )
new_mode_is_host = true ;
else if ( ! strncmp ( buf , " peripheral " , strlen ( " peripheral " ) ) )
new_mode_is_host = false ;
else
return - EINVAL ;
/* If current and new mode is the same, this returns the error */
if ( is_host = = new_mode_is_host )
return - EINVAL ;
if ( new_mode_is_host ) { /* And is_host must be false */
if ( ! is_b_device ) /* A-Peripheral */
rcar_gen3_init_from_a_peri_to_a_host ( ch ) ;
else /* B-Peripheral */
rcar_gen3_init_for_b_host ( ch ) ;
} else { /* And is_host must be true */
if ( ! is_b_device ) /* A-Host */
rcar_gen3_init_for_a_peri ( ch ) ;
else /* B-Host */
rcar_gen3_init_for_peri ( ch ) ;
}
return count ;
}
static ssize_t role_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct rcar_gen3_chan * ch = dev_get_drvdata ( dev ) ;
if ( ! ch - > has_otg | | ! ch - > phy - > init_count )
return - EIO ;
return sprintf ( buf , " %s \n " , rcar_gen3_is_host ( ch ) ? " host " :
" peripheral " ) ;
}
static DEVICE_ATTR_RW ( role ) ;
2015-11-30 04:44:31 +03:00
static void rcar_gen3_init_otg ( struct rcar_gen3_chan * ch )
{
2016-03-03 13:09:04 +03:00
void __iomem * usb2_base = ch - > base ;
2015-11-30 04:44:31 +03:00
u32 val ;
val = readl ( usb2_base + USB2_VBCTRL ) ;
writel ( val | USB2_VBCTRL_DRVVBUSSEL , usb2_base + USB2_VBCTRL ) ;
2015-11-30 04:44:32 +03:00
writel ( USB2_OBINT_BITS , usb2_base + USB2_OBINTSTA ) ;
val = readl ( usb2_base + USB2_OBINTEN ) ;
writel ( val | USB2_OBINT_BITS , usb2_base + USB2_OBINTEN ) ;
2015-11-30 04:44:31 +03:00
val = readl ( usb2_base + USB2_ADPCTRL ) ;
writel ( val | USB2_ADPCTRL_IDPULLUP , usb2_base + USB2_ADPCTRL ) ;
val = readl ( usb2_base + USB2_LINECTRL1 ) ;
rcar_gen3_set_linectrl ( ch , 0 , 0 ) ;
writel ( val | USB2_LINECTRL1_DPRPD_EN | USB2_LINECTRL1_DMRPD_EN ,
usb2_base + USB2_LINECTRL1 ) ;
rcar_gen3_device_recognition ( ch ) ;
}
2015-11-30 04:44:30 +03:00
static int rcar_gen3_phy_usb2_init ( struct phy * p )
{
struct rcar_gen3_chan * channel = phy_get_drvdata ( p ) ;
2016-03-03 13:09:04 +03:00
void __iomem * usb2_base = channel - > base ;
2015-11-30 04:44:30 +03:00
/* Initialize USB2 part */
writel ( USB2_INT_ENABLE_INIT , usb2_base + USB2_INT_ENABLE ) ;
writel ( USB2_SPD_RSM_TIMSET_INIT , usb2_base + USB2_SPD_RSM_TIMSET ) ;
writel ( USB2_OC_TIMSET_INIT , usb2_base + USB2_OC_TIMSET ) ;
2016-01-07 12:16:44 +03:00
/* Initialize otg part */
if ( channel - > has_otg )
2015-11-30 04:44:31 +03:00
rcar_gen3_init_otg ( channel ) ;
2015-11-30 04:44:30 +03:00
return 0 ;
}
static int rcar_gen3_phy_usb2_exit ( struct phy * p )
{
struct rcar_gen3_chan * channel = phy_get_drvdata ( p ) ;
2016-03-03 13:09:04 +03:00
writel ( 0 , channel - > base + USB2_INT_ENABLE ) ;
2015-11-30 04:44:30 +03:00
return 0 ;
}
static int rcar_gen3_phy_usb2_power_on ( struct phy * p )
{
struct rcar_gen3_chan * channel = phy_get_drvdata ( p ) ;
2016-03-03 13:09:04 +03:00
void __iomem * usb2_base = channel - > base ;
2015-11-30 04:44:30 +03:00
u32 val ;
2016-03-03 13:09:05 +03:00
int ret ;
if ( channel - > vbus ) {
ret = regulator_enable ( channel - > vbus ) ;
if ( ret )
return ret ;
}
2015-11-30 04:44:30 +03:00
val = readl ( usb2_base + USB2_USBCTR ) ;
val | = USB2_USBCTR_PLL_RST ;
writel ( val , usb2_base + USB2_USBCTR ) ;
val & = ~ USB2_USBCTR_PLL_RST ;
writel ( val , usb2_base + USB2_USBCTR ) ;
return 0 ;
}
2016-03-03 13:09:05 +03:00
static int rcar_gen3_phy_usb2_power_off ( struct phy * p )
{
struct rcar_gen3_chan * channel = phy_get_drvdata ( p ) ;
int ret = 0 ;
if ( channel - > vbus )
ret = regulator_disable ( channel - > vbus ) ;
return ret ;
}
2017-01-08 13:35:56 +03:00
static const struct phy_ops rcar_gen3_phy_usb2_ops = {
2015-11-30 04:44:30 +03:00
. init = rcar_gen3_phy_usb2_init ,
. exit = rcar_gen3_phy_usb2_exit ,
. power_on = rcar_gen3_phy_usb2_power_on ,
2016-03-03 13:09:05 +03:00
. power_off = rcar_gen3_phy_usb2_power_off ,
2015-11-30 04:44:30 +03:00
. owner = THIS_MODULE ,
} ;
2015-11-30 04:44:32 +03:00
static irqreturn_t rcar_gen3_phy_usb2_irq ( int irq , void * _ch )
{
struct rcar_gen3_chan * ch = _ch ;
2016-03-03 13:09:04 +03:00
void __iomem * usb2_base = ch - > base ;
2015-11-30 04:44:32 +03:00
u32 status = readl ( usb2_base + USB2_OBINTSTA ) ;
irqreturn_t ret = IRQ_NONE ;
if ( status & USB2_OBINT_BITS ) {
dev_vdbg ( & ch - > phy - > dev , " %s: %08x \n " , __func__ , status ) ;
writel ( USB2_OBINT_BITS , usb2_base + USB2_OBINTSTA ) ;
rcar_gen3_device_recognition ( ch ) ;
ret = IRQ_HANDLED ;
}
return ret ;
}
2015-11-30 04:44:30 +03:00
static const struct of_device_id rcar_gen3_phy_usb2_match_table [ ] = {
{ . compatible = " renesas,usb2-phy-r8a7795 " } ,
2016-08-24 09:49:22 +03:00
{ . compatible = " renesas,usb2-phy-r8a7796 " } ,
2016-03-07 04:58:41 +03:00
{ . compatible = " renesas,rcar-gen3-usb2-phy " } ,
2015-11-30 04:44:30 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , rcar_gen3_phy_usb2_match_table ) ;
2016-04-29 11:52:25 +03:00
static const unsigned int rcar_gen3_phy_cable [ ] = {
EXTCON_USB ,
EXTCON_USB_HOST ,
EXTCON_NONE ,
} ;
2015-11-30 04:44:30 +03:00
static int rcar_gen3_phy_usb2_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct rcar_gen3_chan * channel ;
struct phy_provider * provider ;
struct resource * res ;
2016-01-07 12:16:44 +03:00
int irq ;
2015-11-30 04:44:30 +03:00
if ( ! dev - > of_node ) {
dev_err ( dev , " This driver needs device tree \n " ) ;
return - EINVAL ;
}
channel = devm_kzalloc ( dev , sizeof ( * channel ) , GFP_KERNEL ) ;
if ( ! channel )
return - ENOMEM ;
2016-01-07 12:16:44 +03:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2016-03-03 13:09:04 +03:00
channel - > base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( channel - > base ) )
return PTR_ERR ( channel - > base ) ;
2015-11-30 04:44:30 +03:00
2016-01-07 12:16:44 +03:00
/* call request_irq for OTG */
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq > = 0 ) {
2016-04-29 11:52:25 +03:00
int ret ;
2016-06-27 09:36:53 +03:00
INIT_WORK ( & channel - > work , rcar_gen3_phy_usb2_work ) ;
2016-01-07 12:16:44 +03:00
irq = devm_request_irq ( dev , irq , rcar_gen3_phy_usb2_irq ,
IRQF_SHARED , dev_name ( dev ) , channel ) ;
2015-11-30 04:44:32 +03:00
if ( irq < 0 )
dev_err ( dev , " No irq handler (%d) \n " , irq ) ;
2016-01-07 12:16:44 +03:00
channel - > has_otg = true ;
2016-04-29 11:52:25 +03:00
channel - > extcon = devm_extcon_dev_allocate ( dev ,
rcar_gen3_phy_cable ) ;
if ( IS_ERR ( channel - > extcon ) )
return PTR_ERR ( channel - > extcon ) ;
ret = devm_extcon_dev_register ( dev , channel - > extcon ) ;
if ( ret < 0 ) {
dev_err ( dev , " Failed to register extcon \n " ) ;
return ret ;
}
2015-11-30 04:44:30 +03:00
}
/* devm_phy_create() will call pm_runtime_enable(dev); */
channel - > phy = devm_phy_create ( dev , NULL , & rcar_gen3_phy_usb2_ops ) ;
if ( IS_ERR ( channel - > phy ) ) {
dev_err ( dev , " Failed to create USB2 PHY \n " ) ;
return PTR_ERR ( channel - > phy ) ;
}
2016-03-03 13:09:05 +03:00
channel - > vbus = devm_regulator_get_optional ( dev , " vbus " ) ;
if ( IS_ERR ( channel - > vbus ) ) {
if ( PTR_ERR ( channel - > vbus ) = = - EPROBE_DEFER )
return PTR_ERR ( channel - > vbus ) ;
channel - > vbus = NULL ;
}
2016-11-09 05:30:25 +03:00
platform_set_drvdata ( pdev , channel ) ;
2015-11-30 04:44:30 +03:00
phy_set_drvdata ( channel - > phy , channel ) ;
provider = devm_of_phy_provider_register ( dev , of_phy_simple_xlate ) ;
2016-11-09 05:30:25 +03:00
if ( IS_ERR ( provider ) ) {
2015-11-30 04:44:30 +03:00
dev_err ( dev , " Failed to register PHY provider \n " ) ;
2016-11-09 05:30:25 +03:00
} else if ( channel - > has_otg ) {
int ret ;
ret = device_create_file ( dev , & dev_attr_role ) ;
if ( ret < 0 )
return ret ;
}
2015-11-30 04:44:30 +03:00
return PTR_ERR_OR_ZERO ( provider ) ;
}
2016-11-09 05:30:25 +03:00
static int rcar_gen3_phy_usb2_remove ( struct platform_device * pdev )
{
struct rcar_gen3_chan * channel = platform_get_drvdata ( pdev ) ;
if ( channel - > has_otg )
device_remove_file ( & pdev - > dev , & dev_attr_role ) ;
return 0 ;
} ;
2015-11-30 04:44:30 +03:00
static struct platform_driver rcar_gen3_phy_usb2_driver = {
. driver = {
. name = " phy_rcar_gen3_usb2 " ,
. of_match_table = rcar_gen3_phy_usb2_match_table ,
} ,
. probe = rcar_gen3_phy_usb2_probe ,
2016-11-09 05:30:25 +03:00
. remove = rcar_gen3_phy_usb2_remove ,
2015-11-30 04:44:30 +03:00
} ;
module_platform_driver ( rcar_gen3_phy_usb2_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " Renesas R-Car Gen3 USB 2.0 PHY " ) ;
MODULE_AUTHOR ( " Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> " ) ;