2010-12-07 17:00:09 +03:00
/*
* drivers / usb / otg / ab8500_usb . c
*
* USB transceiver driver for AB8500 chip
*
* Copyright ( C ) 2010 ST - Ericsson AB
* Mian Yousaf Kaukab < mian . yousaf . kaukab @ stericsson . com >
*
* 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/usb/otg.h>
# include <linux/slab.h>
# include <linux/notifier.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/mfd/abx500.h>
2011-12-02 17:16:33 +04:00
# include <linux/mfd/abx500/ab8500.h>
2010-12-07 17:00:09 +03:00
# define AB8500_MAIN_WD_CTRL_REG 0x01
# define AB8500_USB_LINE_STAT_REG 0x80
# define AB8500_USB_PHY_CTRL_REG 0x8A
# define AB8500_BIT_OTG_STAT_ID (1 << 0)
# define AB8500_BIT_PHY_CTRL_HOST_EN (1 << 0)
# define AB8500_BIT_PHY_CTRL_DEVICE_EN (1 << 1)
# define AB8500_BIT_WD_CTRL_ENABLE (1 << 0)
# define AB8500_BIT_WD_CTRL_KICK (1 << 1)
# define AB8500_V1x_LINK_STAT_WAIT (HZ / 10)
# define AB8500_WD_KICK_DELAY_US 100 /* usec */
# define AB8500_WD_V11_DISABLE_DELAY_US 100 /* usec */
# define AB8500_WD_V10_DISABLE_DELAY_MS 100 /* ms */
/* Usb line status register */
enum ab8500_usb_link_status {
USB_LINK_NOT_CONFIGURED = 0 ,
USB_LINK_STD_HOST_NC ,
USB_LINK_STD_HOST_C_NS ,
USB_LINK_STD_HOST_C_S ,
USB_LINK_HOST_CHG_NM ,
USB_LINK_HOST_CHG_HS ,
USB_LINK_HOST_CHG_HS_CHIRP ,
USB_LINK_DEDICATED_CHG ,
USB_LINK_ACA_RID_A ,
USB_LINK_ACA_RID_B ,
USB_LINK_ACA_RID_C_NM ,
USB_LINK_ACA_RID_C_HS ,
USB_LINK_ACA_RID_C_HS_CHIRP ,
USB_LINK_HM_IDGND ,
USB_LINK_RESERVED ,
USB_LINK_NOT_VALID_LINK
} ;
struct ab8500_usb {
2012-02-13 15:24:05 +04:00
struct usb_phy phy ;
2010-12-07 17:00:09 +03:00
struct device * dev ;
int irq_num_id_rise ;
int irq_num_id_fall ;
int irq_num_vbus_rise ;
int irq_num_vbus_fall ;
int irq_num_link_status ;
unsigned vbus_draw ;
struct delayed_work dwork ;
struct work_struct phy_dis_work ;
unsigned long link_status_wait ;
int rev ;
} ;
2012-02-13 15:24:05 +04:00
static inline struct ab8500_usb * phy_to_ab ( struct usb_phy * x )
2010-12-07 17:00:09 +03:00
{
2012-02-13 15:24:05 +04:00
return container_of ( x , struct ab8500_usb , phy ) ;
2010-12-07 17:00:09 +03:00
}
static void ab8500_usb_wd_workaround ( struct ab8500_usb * ab )
{
abx500_set_register_interruptible ( ab - > dev ,
AB8500_SYS_CTRL2_BLOCK ,
AB8500_MAIN_WD_CTRL_REG ,
AB8500_BIT_WD_CTRL_ENABLE ) ;
udelay ( AB8500_WD_KICK_DELAY_US ) ;
abx500_set_register_interruptible ( ab - > dev ,
AB8500_SYS_CTRL2_BLOCK ,
AB8500_MAIN_WD_CTRL_REG ,
( AB8500_BIT_WD_CTRL_ENABLE
| AB8500_BIT_WD_CTRL_KICK ) ) ;
if ( ab - > rev > 0x10 ) /* v1.1 v2.0 */
udelay ( AB8500_WD_V11_DISABLE_DELAY_US ) ;
else /* v1.0 */
msleep ( AB8500_WD_V10_DISABLE_DELAY_MS ) ;
abx500_set_register_interruptible ( ab - > dev ,
AB8500_SYS_CTRL2_BLOCK ,
AB8500_MAIN_WD_CTRL_REG ,
0 ) ;
}
static void ab8500_usb_phy_ctrl ( struct ab8500_usb * ab , bool sel_host ,
bool enable )
{
u8 ctrl_reg ;
abx500_get_register_interruptible ( ab - > dev ,
AB8500_USB ,
AB8500_USB_PHY_CTRL_REG ,
& ctrl_reg ) ;
if ( sel_host ) {
if ( enable )
ctrl_reg | = AB8500_BIT_PHY_CTRL_HOST_EN ;
else
ctrl_reg & = ~ AB8500_BIT_PHY_CTRL_HOST_EN ;
} else {
if ( enable )
ctrl_reg | = AB8500_BIT_PHY_CTRL_DEVICE_EN ;
else
ctrl_reg & = ~ AB8500_BIT_PHY_CTRL_DEVICE_EN ;
}
abx500_set_register_interruptible ( ab - > dev ,
AB8500_USB ,
AB8500_USB_PHY_CTRL_REG ,
ctrl_reg ) ;
/* Needed to enable the phy.*/
if ( enable )
ab8500_usb_wd_workaround ( ab ) ;
}
# define ab8500_usb_host_phy_en(ab) ab8500_usb_phy_ctrl(ab, true, true)
# define ab8500_usb_host_phy_dis(ab) ab8500_usb_phy_ctrl(ab, true, false)
# define ab8500_usb_peri_phy_en(ab) ab8500_usb_phy_ctrl(ab, false, true)
# define ab8500_usb_peri_phy_dis(ab) ab8500_usb_phy_ctrl(ab, false, false)
static int ab8500_usb_link_status_update ( struct ab8500_usb * ab )
{
u8 reg ;
enum ab8500_usb_link_status lsts ;
void * v = NULL ;
2012-02-13 15:24:03 +04:00
enum usb_phy_events event ;
2010-12-07 17:00:09 +03:00
abx500_get_register_interruptible ( ab - > dev ,
AB8500_USB ,
AB8500_USB_LINE_STAT_REG ,
& reg ) ;
lsts = ( reg > > 3 ) & 0x0F ;
switch ( lsts ) {
case USB_LINK_NOT_CONFIGURED :
case USB_LINK_RESERVED :
case USB_LINK_NOT_VALID_LINK :
/* TODO: Disable regulators. */
ab8500_usb_host_phy_dis ( ab ) ;
ab8500_usb_peri_phy_dis ( ab ) ;
2012-02-13 15:24:05 +04:00
ab - > phy . state = OTG_STATE_B_IDLE ;
ab - > phy . otg - > default_a = false ;
2010-12-07 17:00:09 +03:00
ab - > vbus_draw = 0 ;
event = USB_EVENT_NONE ;
break ;
case USB_LINK_STD_HOST_NC :
case USB_LINK_STD_HOST_C_NS :
case USB_LINK_STD_HOST_C_S :
case USB_LINK_HOST_CHG_NM :
case USB_LINK_HOST_CHG_HS :
case USB_LINK_HOST_CHG_HS_CHIRP :
2012-02-13 15:24:05 +04:00
if ( ab - > phy . otg - > gadget ) {
2010-12-07 17:00:09 +03:00
/* TODO: Enable regulators. */
ab8500_usb_peri_phy_en ( ab ) ;
2012-02-13 15:24:05 +04:00
v = ab - > phy . otg - > gadget ;
2010-12-07 17:00:09 +03:00
}
event = USB_EVENT_VBUS ;
break ;
case USB_LINK_HM_IDGND :
2012-02-13 15:24:05 +04:00
if ( ab - > phy . otg - > host ) {
2010-12-07 17:00:09 +03:00
/* TODO: Enable regulators. */
ab8500_usb_host_phy_en ( ab ) ;
2012-02-13 15:24:05 +04:00
v = ab - > phy . otg - > host ;
2010-12-07 17:00:09 +03:00
}
2012-02-13 15:24:05 +04:00
ab - > phy . state = OTG_STATE_A_IDLE ;
ab - > phy . otg - > default_a = true ;
2010-12-07 17:00:09 +03:00
event = USB_EVENT_ID ;
break ;
case USB_LINK_ACA_RID_A :
case USB_LINK_ACA_RID_B :
/* TODO */
case USB_LINK_ACA_RID_C_NM :
case USB_LINK_ACA_RID_C_HS :
case USB_LINK_ACA_RID_C_HS_CHIRP :
case USB_LINK_DEDICATED_CHG :
/* TODO: vbus_draw */
event = USB_EVENT_CHARGER ;
break ;
}
2012-02-13 15:24:05 +04:00
atomic_notifier_call_chain ( & ab - > phy . notifier , event , v ) ;
2010-12-07 17:00:09 +03:00
return 0 ;
}
static void ab8500_usb_delayed_work ( struct work_struct * work )
{
struct ab8500_usb * ab = container_of ( work , struct ab8500_usb ,
dwork . work ) ;
ab8500_usb_link_status_update ( ab ) ;
}
static irqreturn_t ab8500_usb_v1x_common_irq ( int irq , void * data )
{
struct ab8500_usb * ab = ( struct ab8500_usb * ) data ;
/* Wait for link status to become stable. */
schedule_delayed_work ( & ab - > dwork , ab - > link_status_wait ) ;
return IRQ_HANDLED ;
}
static irqreturn_t ab8500_usb_v1x_vbus_fall_irq ( int irq , void * data )
{
struct ab8500_usb * ab = ( struct ab8500_usb * ) data ;
/* Link status will not be updated till phy is disabled. */
ab8500_usb_peri_phy_dis ( ab ) ;
/* Wait for link status to become stable. */
schedule_delayed_work ( & ab - > dwork , ab - > link_status_wait ) ;
return IRQ_HANDLED ;
}
static irqreturn_t ab8500_usb_v20_irq ( int irq , void * data )
{
struct ab8500_usb * ab = ( struct ab8500_usb * ) data ;
ab8500_usb_link_status_update ( ab ) ;
return IRQ_HANDLED ;
}
static void ab8500_usb_phy_disable_work ( struct work_struct * work )
{
struct ab8500_usb * ab = container_of ( work , struct ab8500_usb ,
phy_dis_work ) ;
2012-02-13 15:24:05 +04:00
if ( ! ab - > phy . otg - > host )
2010-12-07 17:00:09 +03:00
ab8500_usb_host_phy_dis ( ab ) ;
2012-02-13 15:24:05 +04:00
if ( ! ab - > phy . otg - > gadget )
2010-12-07 17:00:09 +03:00
ab8500_usb_peri_phy_dis ( ab ) ;
}
2012-02-13 15:24:05 +04:00
static int ab8500_usb_set_power ( struct usb_phy * phy , unsigned mA )
2010-12-07 17:00:09 +03:00
{
struct ab8500_usb * ab ;
2012-02-13 15:24:05 +04:00
if ( ! phy )
2010-12-07 17:00:09 +03:00
return - ENODEV ;
2012-02-13 15:24:05 +04:00
ab = phy_to_ab ( phy ) ;
2010-12-07 17:00:09 +03:00
ab - > vbus_draw = mA ;
if ( mA )
2012-02-13 15:24:05 +04:00
atomic_notifier_call_chain ( & ab - > phy . notifier ,
USB_EVENT_ENUMERATED , ab - > phy . otg - > gadget ) ;
2010-12-07 17:00:09 +03:00
return 0 ;
}
/* TODO: Implement some way for charging or other drivers to read
* ab - > vbus_draw .
*/
2012-02-13 15:24:02 +04:00
static int ab8500_usb_set_suspend ( struct usb_phy * x , int suspend )
2010-12-07 17:00:09 +03:00
{
/* TODO */
return 0 ;
}
2012-02-13 15:24:05 +04:00
static int ab8500_usb_set_peripheral ( struct usb_otg * otg ,
struct usb_gadget * gadget )
2010-12-07 17:00:09 +03:00
{
struct ab8500_usb * ab ;
if ( ! otg )
return - ENODEV ;
2012-02-13 15:24:05 +04:00
ab = phy_to_ab ( otg - > phy ) ;
2010-12-07 17:00:09 +03:00
/* Some drivers call this function in atomic context.
* Do not update ab8500 registers directly till this
* is fixed .
*/
if ( ! gadget ) {
/* TODO: Disable regulators. */
2012-02-13 15:24:05 +04:00
otg - > gadget = NULL ;
2010-12-07 17:00:09 +03:00
schedule_work ( & ab - > phy_dis_work ) ;
} else {
2012-02-13 15:24:05 +04:00
otg - > gadget = gadget ;
otg - > phy - > state = OTG_STATE_B_IDLE ;
2010-12-07 17:00:09 +03:00
/* Phy will not be enabled if cable is already
* plugged - in . Schedule to enable phy .
* Use same delay to avoid any race condition .
*/
schedule_delayed_work ( & ab - > dwork , ab - > link_status_wait ) ;
}
return 0 ;
}
2012-02-13 15:24:05 +04:00
static int ab8500_usb_set_host ( struct usb_otg * otg , struct usb_bus * host )
2010-12-07 17:00:09 +03:00
{
struct ab8500_usb * ab ;
if ( ! otg )
return - ENODEV ;
2012-02-13 15:24:05 +04:00
ab = phy_to_ab ( otg - > phy ) ;
2010-12-07 17:00:09 +03:00
/* Some drivers call this function in atomic context.
* Do not update ab8500 registers directly till this
* is fixed .
*/
if ( ! host ) {
/* TODO: Disable regulators. */
2012-02-13 15:24:05 +04:00
otg - > host = NULL ;
2010-12-07 17:00:09 +03:00
schedule_work ( & ab - > phy_dis_work ) ;
} else {
2012-02-13 15:24:05 +04:00
otg - > host = host ;
2010-12-07 17:00:09 +03:00
/* Phy will not be enabled if cable is already
* plugged - in . Schedule to enable phy .
* Use same delay to avoid any race condition .
*/
schedule_delayed_work ( & ab - > dwork , ab - > link_status_wait ) ;
}
return 0 ;
}
static void ab8500_usb_irq_free ( struct ab8500_usb * ab )
{
if ( ab - > rev < 0x20 ) {
free_irq ( ab - > irq_num_id_rise , ab ) ;
free_irq ( ab - > irq_num_id_fall , ab ) ;
free_irq ( ab - > irq_num_vbus_rise , ab ) ;
free_irq ( ab - > irq_num_vbus_fall , ab ) ;
} else {
free_irq ( ab - > irq_num_link_status , ab ) ;
}
}
static int ab8500_usb_v1x_res_setup ( struct platform_device * pdev ,
struct ab8500_usb * ab )
{
int err ;
ab - > irq_num_id_rise = platform_get_irq_byname ( pdev , " ID_WAKEUP_R " ) ;
if ( ab - > irq_num_id_rise < 0 ) {
dev_err ( & pdev - > dev , " ID rise irq not found \n " ) ;
return ab - > irq_num_id_rise ;
}
err = request_threaded_irq ( ab - > irq_num_id_rise , NULL ,
ab8500_usb_v1x_common_irq ,
IRQF_NO_SUSPEND | IRQF_SHARED ,
" usb-id-rise " , ab ) ;
if ( err < 0 ) {
dev_err ( ab - > dev , " request_irq failed for ID rise irq \n " ) ;
goto fail0 ;
}
ab - > irq_num_id_fall = platform_get_irq_byname ( pdev , " ID_WAKEUP_F " ) ;
if ( ab - > irq_num_id_fall < 0 ) {
dev_err ( & pdev - > dev , " ID fall irq not found \n " ) ;
return ab - > irq_num_id_fall ;
}
err = request_threaded_irq ( ab - > irq_num_id_fall , NULL ,
ab8500_usb_v1x_common_irq ,
IRQF_NO_SUSPEND | IRQF_SHARED ,
" usb-id-fall " , ab ) ;
if ( err < 0 ) {
dev_err ( ab - > dev , " request_irq failed for ID fall irq \n " ) ;
goto fail1 ;
}
ab - > irq_num_vbus_rise = platform_get_irq_byname ( pdev , " VBUS_DET_R " ) ;
if ( ab - > irq_num_vbus_rise < 0 ) {
dev_err ( & pdev - > dev , " VBUS rise irq not found \n " ) ;
return ab - > irq_num_vbus_rise ;
}
err = request_threaded_irq ( ab - > irq_num_vbus_rise , NULL ,
ab8500_usb_v1x_common_irq ,
IRQF_NO_SUSPEND | IRQF_SHARED ,
" usb-vbus-rise " , ab ) ;
if ( err < 0 ) {
dev_err ( ab - > dev , " request_irq failed for Vbus rise irq \n " ) ;
goto fail2 ;
}
ab - > irq_num_vbus_fall = platform_get_irq_byname ( pdev , " VBUS_DET_F " ) ;
if ( ab - > irq_num_vbus_fall < 0 ) {
dev_err ( & pdev - > dev , " VBUS fall irq not found \n " ) ;
return ab - > irq_num_vbus_fall ;
}
err = request_threaded_irq ( ab - > irq_num_vbus_fall , NULL ,
ab8500_usb_v1x_vbus_fall_irq ,
IRQF_NO_SUSPEND | IRQF_SHARED ,
" usb-vbus-fall " , ab ) ;
if ( err < 0 ) {
dev_err ( ab - > dev , " request_irq failed for Vbus fall irq \n " ) ;
goto fail3 ;
}
return 0 ;
fail3 :
free_irq ( ab - > irq_num_vbus_rise , ab ) ;
fail2 :
free_irq ( ab - > irq_num_id_fall , ab ) ;
fail1 :
free_irq ( ab - > irq_num_id_rise , ab ) ;
fail0 :
return err ;
}
static int ab8500_usb_v2_res_setup ( struct platform_device * pdev ,
struct ab8500_usb * ab )
{
int err ;
ab - > irq_num_link_status = platform_get_irq_byname ( pdev ,
" USB_LINK_STATUS " ) ;
if ( ab - > irq_num_link_status < 0 ) {
dev_err ( & pdev - > dev , " Link status irq not found \n " ) ;
return ab - > irq_num_link_status ;
}
err = request_threaded_irq ( ab - > irq_num_link_status , NULL ,
ab8500_usb_v20_irq ,
IRQF_NO_SUSPEND | IRQF_SHARED ,
" usb-link-status " , ab ) ;
if ( err < 0 ) {
dev_err ( ab - > dev ,
" request_irq failed for link status irq \n " ) ;
return err ;
}
return 0 ;
}
2012-11-19 22:21:48 +04:00
static int ab8500_usb_probe ( struct platform_device * pdev )
2010-12-07 17:00:09 +03:00
{
struct ab8500_usb * ab ;
2012-02-13 15:24:05 +04:00
struct usb_otg * otg ;
2010-12-07 17:00:09 +03:00
int err ;
int rev ;
rev = abx500_get_chip_id ( & pdev - > dev ) ;
if ( rev < 0 ) {
dev_err ( & pdev - > dev , " Chip id read failed \n " ) ;
return rev ;
} else if ( rev < 0x10 ) {
dev_err ( & pdev - > dev , " Unsupported AB8500 chip \n " ) ;
return - ENODEV ;
}
ab = kzalloc ( sizeof * ab , GFP_KERNEL ) ;
if ( ! ab )
return - ENOMEM ;
2012-02-13 15:24:05 +04:00
otg = kzalloc ( sizeof * otg , GFP_KERNEL ) ;
2012-03-02 12:08:14 +04:00
if ( ! otg ) {
2012-02-13 15:24:05 +04:00
kfree ( ab ) ;
return - ENOMEM ;
}
2010-12-07 17:00:09 +03:00
ab - > dev = & pdev - > dev ;
ab - > rev = rev ;
2012-02-13 15:24:05 +04:00
ab - > phy . dev = ab - > dev ;
ab - > phy . otg = otg ;
ab - > phy . label = " ab8500 " ;
ab - > phy . set_suspend = ab8500_usb_set_suspend ;
ab - > phy . set_power = ab8500_usb_set_power ;
ab - > phy . state = OTG_STATE_UNDEFINED ;
otg - > phy = & ab - > phy ;
otg - > set_host = ab8500_usb_set_host ;
otg - > set_peripheral = ab8500_usb_set_peripheral ;
2010-12-07 17:00:09 +03:00
platform_set_drvdata ( pdev , ab ) ;
2012-02-13 15:24:05 +04:00
ATOMIC_INIT_NOTIFIER_HEAD ( & ab - > phy . notifier ) ;
2010-12-07 17:00:09 +03:00
/* v1: Wait for link status to become stable.
* all : Updates form set_host and set_peripheral as they are atomic .
*/
INIT_DELAYED_WORK ( & ab - > dwork , ab8500_usb_delayed_work ) ;
/* all: Disable phy when called from set_host and set_peripheral */
INIT_WORK ( & ab - > phy_dis_work , ab8500_usb_phy_disable_work ) ;
if ( ab - > rev < 0x20 ) {
err = ab8500_usb_v1x_res_setup ( pdev , ab ) ;
ab - > link_status_wait = AB8500_V1x_LINK_STAT_WAIT ;
} else {
err = ab8500_usb_v2_res_setup ( pdev , ab ) ;
}
if ( err < 0 )
goto fail0 ;
2012-06-22 15:32:46 +04:00
err = usb_add_phy ( & ab - > phy , USB_PHY_TYPE_USB2 ) ;
2010-12-07 17:00:09 +03:00
if ( err ) {
dev_err ( & pdev - > dev , " Can't register transceiver \n " ) ;
goto fail1 ;
}
dev_info ( & pdev - > dev , " AB8500 usb driver initialized \n " ) ;
return 0 ;
fail1 :
ab8500_usb_irq_free ( ab ) ;
fail0 :
2012-02-13 15:24:05 +04:00
kfree ( otg ) ;
2010-12-07 17:00:09 +03:00
kfree ( ab ) ;
return err ;
}
static int __devexit ab8500_usb_remove ( struct platform_device * pdev )
{
struct ab8500_usb * ab = platform_get_drvdata ( pdev ) ;
ab8500_usb_irq_free ( ab ) ;
cancel_delayed_work_sync ( & ab - > dwork ) ;
cancel_work_sync ( & ab - > phy_dis_work ) ;
2012-06-22 15:32:46 +04:00
usb_remove_phy ( & ab - > phy ) ;
2010-12-07 17:00:09 +03:00
ab8500_usb_host_phy_dis ( ab ) ;
ab8500_usb_peri_phy_dis ( ab ) ;
platform_set_drvdata ( pdev , NULL ) ;
2012-02-13 15:24:05 +04:00
kfree ( ab - > phy . otg ) ;
2010-12-07 17:00:09 +03:00
kfree ( ab ) ;
return 0 ;
}
static struct platform_driver ab8500_usb_driver = {
. probe = ab8500_usb_probe ,
2012-11-19 22:21:08 +04:00
. remove = ab8500_usb_remove ,
2010-12-07 17:00:09 +03:00
. driver = {
. name = " ab8500-usb " ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init ab8500_usb_init ( void )
{
return platform_driver_register ( & ab8500_usb_driver ) ;
}
subsys_initcall ( ab8500_usb_init ) ;
static void __exit ab8500_usb_exit ( void )
{
platform_driver_unregister ( & ab8500_usb_driver ) ;
}
module_exit ( ab8500_usb_exit ) ;
MODULE_ALIAS ( " platform:ab8500_usb " ) ;
MODULE_AUTHOR ( " ST-Ericsson AB " ) ;
MODULE_DESCRIPTION ( " AB8500 usb transceiver driver " ) ;
MODULE_LICENSE ( " GPL " ) ;