2018-03-08 18:37:50 -08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Motorola Mapphone MDM6600 modem GPIO controlled USB PHY driver
* Copyright ( C ) 2018 Tony Lindgren < tony @ atomide . com >
*/
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/gpio/consumer.h>
# include <linux/of_platform.h>
# include <linux/phy/phy.h>
2018-12-02 15:51:35 -08:00
# include <linux/pinctrl/consumer.h>
2018-03-08 18:37:50 -08:00
# define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */
# define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */
2018-05-06 09:48:25 -07:00
# define MDM6600_MODEM_IDLE_DELAY_MS 1000 /* modem after USB suspend */
# define MDM6600_MODEM_WAKE_DELAY_MS 200 /* modem response after idle */
2018-03-08 18:37:50 -08:00
enum phy_mdm6600_ctrl_lines {
PHY_MDM6600_ENABLE , /* USB PHY enable */
PHY_MDM6600_POWER , /* Device power */
PHY_MDM6600_RESET , /* Device reset */
PHY_MDM6600_NR_CTRL_LINES ,
} ;
enum phy_mdm6600_bootmode_lines {
PHY_MDM6600_MODE0 , /* out USB mode0 and OOB wake */
PHY_MDM6600_MODE1 , /* out USB mode1, in OOB wake */
PHY_MDM6600_NR_MODE_LINES ,
} ;
enum phy_mdm6600_cmd_lines {
PHY_MDM6600_CMD0 ,
PHY_MDM6600_CMD1 ,
PHY_MDM6600_CMD2 ,
PHY_MDM6600_NR_CMD_LINES ,
} ;
enum phy_mdm6600_status_lines {
PHY_MDM6600_STATUS0 ,
PHY_MDM6600_STATUS1 ,
PHY_MDM6600_STATUS2 ,
PHY_MDM6600_NR_STATUS_LINES ,
} ;
/*
* MDM6600 command codes . These are based on Motorola Mapphone Linux
* kernel tree .
*/
enum phy_mdm6600_cmd {
PHY_MDM6600_CMD_BP_PANIC_ACK ,
PHY_MDM6600_CMD_DATA_ONLY_BYPASS , /* Reroute USB to CPCAP PHY */
PHY_MDM6600_CMD_FULL_BYPASS , /* Reroute USB to CPCAP PHY */
PHY_MDM6600_CMD_NO_BYPASS , /* Request normal USB mode */
PHY_MDM6600_CMD_BP_SHUTDOWN_REQ , /* Request device power off */
PHY_MDM6600_CMD_BP_UNKNOWN_5 ,
PHY_MDM6600_CMD_BP_UNKNOWN_6 ,
PHY_MDM6600_CMD_UNDEFINED ,
} ;
/*
* MDM6600 status codes . These are based on Motorola Mapphone Linux
* kernel tree .
*/
enum phy_mdm6600_status {
PHY_MDM6600_STATUS_PANIC , /* Seems to be really off */
PHY_MDM6600_STATUS_PANIC_BUSY_WAIT ,
PHY_MDM6600_STATUS_QC_DLOAD ,
PHY_MDM6600_STATUS_RAM_DOWNLOADER , /* MDM6600 USB flashing mode */
PHY_MDM6600_STATUS_PHONE_CODE_AWAKE , /* MDM6600 normal USB mode */
PHY_MDM6600_STATUS_PHONE_CODE_ASLEEP ,
PHY_MDM6600_STATUS_SHUTDOWN_ACK ,
PHY_MDM6600_STATUS_UNDEFINED ,
} ;
static const char * const
phy_mdm6600_status_name [ ] = {
" off " , " busy " , " qc_dl " , " ram_dl " , " awake " ,
" asleep " , " shutdown " , " undefined " ,
} ;
struct phy_mdm6600 {
struct device * dev ;
struct phy * generic_phy ;
struct phy_provider * phy_provider ;
struct gpio_desc * ctrl_gpios [ PHY_MDM6600_NR_CTRL_LINES ] ;
struct gpio_descs * mode_gpios ;
struct gpio_descs * status_gpios ;
struct gpio_descs * cmd_gpios ;
struct delayed_work bootup_work ;
struct delayed_work status_work ;
2018-05-06 09:48:25 -07:00
struct delayed_work modem_wake_work ;
2018-03-08 18:37:50 -08:00
struct completion ack ;
bool enabled ; /* mdm6600 phy enabled */
bool running ; /* mdm6600 boot done */
2018-05-06 09:48:25 -07:00
bool awake ; /* mdm6600 respnds on n_gsm */
2018-03-08 18:37:50 -08:00
int status ;
} ;
static int phy_mdm6600_init ( struct phy * x )
{
struct phy_mdm6600 * ddata = phy_get_drvdata ( x ) ;
struct gpio_desc * enable_gpio = ddata - > ctrl_gpios [ PHY_MDM6600_ENABLE ] ;
if ( ! ddata - > enabled )
return - EPROBE_DEFER ;
gpiod_set_value_cansleep ( enable_gpio , 0 ) ;
return 0 ;
}
static int phy_mdm6600_power_on ( struct phy * x )
{
struct phy_mdm6600 * ddata = phy_get_drvdata ( x ) ;
struct gpio_desc * enable_gpio = ddata - > ctrl_gpios [ PHY_MDM6600_ENABLE ] ;
2018-12-02 15:51:35 -08:00
int error ;
2018-03-08 18:37:50 -08:00
if ( ! ddata - > enabled )
return - ENODEV ;
2018-12-02 15:51:35 -08:00
error = pinctrl_pm_select_default_state ( ddata - > dev ) ;
if ( error )
dev_warn ( ddata - > dev , " %s: error with default_state: %i \n " ,
__func__ , error ) ;
2018-03-08 18:37:50 -08:00
gpiod_set_value_cansleep ( enable_gpio , 1 ) ;
2018-12-02 15:51:35 -08:00
/* Allow aggressive PM for USB, it's only needed for n_gsm port */
if ( pm_runtime_enabled ( & x - > dev ) )
phy_pm_runtime_put ( x ) ;
2018-03-08 18:37:50 -08:00
return 0 ;
}
static int phy_mdm6600_power_off ( struct phy * x )
{
struct phy_mdm6600 * ddata = phy_get_drvdata ( x ) ;
struct gpio_desc * enable_gpio = ddata - > ctrl_gpios [ PHY_MDM6600_ENABLE ] ;
2018-12-02 15:51:35 -08:00
int error ;
2018-03-08 18:37:50 -08:00
if ( ! ddata - > enabled )
return - ENODEV ;
2018-12-02 15:51:35 -08:00
/* Paired with phy_pm_runtime_put() in phy_mdm6600_power_on() */
if ( pm_runtime_enabled ( & x - > dev ) ) {
error = phy_pm_runtime_get ( x ) ;
if ( error < 0 & & error ! = - EINPROGRESS )
dev_warn ( ddata - > dev , " %s: phy_pm_runtime_get: %i \n " ,
__func__ , error ) ;
}
2018-03-08 18:37:50 -08:00
gpiod_set_value_cansleep ( enable_gpio , 0 ) ;
2018-12-02 15:51:35 -08:00
error = pinctrl_pm_select_sleep_state ( ddata - > dev ) ;
if ( error )
dev_warn ( ddata - > dev , " %s: error with sleep_state: %i \n " ,
__func__ , error ) ;
2018-03-08 18:37:50 -08:00
return 0 ;
}
static const struct phy_ops gpio_usb_ops = {
. init = phy_mdm6600_init ,
. power_on = phy_mdm6600_power_on ,
. power_off = phy_mdm6600_power_off ,
. owner = THIS_MODULE ,
} ;
/**
* phy_mdm6600_cmd ( ) - send a command request to mdm6600
* @ ddata : device driver data
*
* Configures the three command request GPIOs to the specified value .
*/
static void phy_mdm6600_cmd ( struct phy_mdm6600 * ddata , int val )
{
2018-09-05 23:50:05 +02:00
DECLARE_BITMAP ( values , PHY_MDM6600_NR_CMD_LINES ) ;
2018-03-08 18:37:50 -08:00
2018-09-05 23:50:05 +02:00
values [ 0 ] = val ;
2018-03-08 18:37:50 -08:00
gpiod_set_array_value_cansleep ( PHY_MDM6600_NR_CMD_LINES ,
2018-09-05 23:50:07 +02:00
ddata - > cmd_gpios - > desc ,
ddata - > cmd_gpios - > info , values ) ;
2018-03-08 18:37:50 -08:00
}
/**
* phy_mdm6600_status ( ) - read mdm6600 status lines
* @ ddata : device driver data
*/
static void phy_mdm6600_status ( struct work_struct * work )
{
struct phy_mdm6600 * ddata ;
struct device * dev ;
2018-09-05 23:50:05 +02:00
DECLARE_BITMAP ( values , PHY_MDM6600_NR_STATUS_LINES ) ;
2018-03-08 18:37:50 -08:00
int error , i , val = 0 ;
ddata = container_of ( work , struct phy_mdm6600 , status_work . work ) ;
dev = ddata - > dev ;
2018-05-31 10:05:38 -07:00
error = gpiod_get_array_value_cansleep ( PHY_MDM6600_NR_STATUS_LINES ,
2018-03-08 18:37:50 -08:00
ddata - > status_gpios - > desc ,
2018-09-05 23:50:07 +02:00
ddata - > status_gpios - > info ,
2018-03-08 18:37:50 -08:00
values ) ;
if ( error )
return ;
2018-05-31 10:05:38 -07:00
for ( i = 0 ; i < PHY_MDM6600_NR_STATUS_LINES ; i + + ) {
2018-09-05 23:50:05 +02:00
val | = test_bit ( i , values ) < < i ;
2018-03-08 18:37:50 -08:00
dev_dbg ( ddata - > dev , " XXX %s: i: %i values[i]: %i val: %i \n " ,
2018-09-05 23:50:05 +02:00
__func__ , i , test_bit ( i , values ) , val ) ;
2018-03-08 18:37:50 -08:00
}
2018-09-05 23:50:05 +02:00
ddata - > status = values [ 0 ] ;
2018-03-08 18:37:50 -08:00
dev_info ( dev , " modem status: %i %s \n " ,
ddata - > status ,
phy_mdm6600_status_name [ ddata - > status & 7 ] ) ;
complete ( & ddata - > ack ) ;
}
static irqreturn_t phy_mdm6600_irq_thread ( int irq , void * data )
{
struct phy_mdm6600 * ddata = data ;
schedule_delayed_work ( & ddata - > status_work , msecs_to_jiffies ( 10 ) ) ;
return IRQ_HANDLED ;
}
/**
* phy_mdm6600_wakeirq_thread - handle mode1 line OOB wake after booting
* @ irq : interrupt
* @ data : interrupt handler data
*
* GPIO mode1 is used initially as output to configure the USB boot
* mode for mdm6600 . After booting it is used as input for OOB wake
* signal from mdm6600 to the SoC . Just use it for debug info only
* for now .
*/
static irqreturn_t phy_mdm6600_wakeirq_thread ( int irq , void * data )
{
struct phy_mdm6600 * ddata = data ;
struct gpio_desc * mode_gpio1 ;
mode_gpio1 = ddata - > mode_gpios - > desc [ PHY_MDM6600_MODE1 ] ;
dev_dbg ( ddata - > dev , " OOB wake on mode_gpio1: %i \n " ,
gpiod_get_value ( mode_gpio1 ) ) ;
return IRQ_HANDLED ;
}
/**
* phy_mdm6600_init_irq ( ) - initialize mdm6600 status IRQ lines
* @ ddata : device driver data
*/
static void phy_mdm6600_init_irq ( struct phy_mdm6600 * ddata )
{
struct device * dev = ddata - > dev ;
int i , error , irq ;
for ( i = PHY_MDM6600_STATUS0 ;
i < = PHY_MDM6600_STATUS2 ; i + + ) {
struct gpio_desc * gpio = ddata - > status_gpios - > desc [ i ] ;
irq = gpiod_to_irq ( gpio ) ;
if ( irq < = 0 )
continue ;
error = devm_request_threaded_irq ( dev , irq , NULL ,
phy_mdm6600_irq_thread ,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT ,
" mdm6600 " ,
ddata ) ;
if ( error )
dev_warn ( dev , " no modem status irq%i: %i \n " ,
irq , error ) ;
}
}
struct phy_mdm6600_map {
const char * name ;
int direction ;
} ;
static const struct phy_mdm6600_map
phy_mdm6600_ctrl_gpio_map [ PHY_MDM6600_NR_CTRL_LINES ] = {
{ " enable " , GPIOD_OUT_LOW , } , /* low = phy disabled */
{ " power " , GPIOD_OUT_LOW , } , /* low = off */
{ " reset " , GPIOD_OUT_HIGH , } , /* high = reset */
} ;
/**
* phy_mdm6600_init_lines ( ) - initialize mdm6600 GPIO lines
* @ ddata : device driver data
*/
static int phy_mdm6600_init_lines ( struct phy_mdm6600 * ddata )
{
struct device * dev = ddata - > dev ;
int i ;
/* MDM6600 control lines */
for ( i = 0 ; i < ARRAY_SIZE ( phy_mdm6600_ctrl_gpio_map ) ; i + + ) {
const struct phy_mdm6600_map * map =
& phy_mdm6600_ctrl_gpio_map [ i ] ;
struct gpio_desc * * gpio = & ddata - > ctrl_gpios [ i ] ;
* gpio = devm_gpiod_get ( dev , map - > name , map - > direction ) ;
if ( IS_ERR ( * gpio ) ) {
dev_info ( dev , " gpio %s error %li \n " ,
map - > name , PTR_ERR ( * gpio ) ) ;
return PTR_ERR ( * gpio ) ;
}
}
/* MDM6600 USB start-up mode output lines */
ddata - > mode_gpios = devm_gpiod_get_array ( dev , " motorola,mode " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( ddata - > mode_gpios ) )
return PTR_ERR ( ddata - > mode_gpios ) ;
if ( ddata - > mode_gpios - > ndescs ! = PHY_MDM6600_NR_MODE_LINES )
return - EINVAL ;
/* MDM6600 status input lines */
ddata - > status_gpios = devm_gpiod_get_array ( dev , " motorola,status " ,
GPIOD_IN ) ;
if ( IS_ERR ( ddata - > status_gpios ) )
return PTR_ERR ( ddata - > status_gpios ) ;
if ( ddata - > status_gpios - > ndescs ! = PHY_MDM6600_NR_STATUS_LINES )
return - EINVAL ;
/* MDM6600 cmd output lines */
ddata - > cmd_gpios = devm_gpiod_get_array ( dev , " motorola,cmd " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( ddata - > cmd_gpios ) )
return PTR_ERR ( ddata - > cmd_gpios ) ;
if ( ddata - > cmd_gpios - > ndescs ! = PHY_MDM6600_NR_CMD_LINES )
return - EINVAL ;
return 0 ;
}
/**
* phy_mdm6600_device_power_on ( ) - power on mdm6600 device
* @ ddata : device driver data
*
* To get the integrated USB phy in MDM6600 takes some hoops . We must ensure
* the shared USB bootmode GPIOs are configured , then request modem start - up ,
* reset and power - up . . And then we need to recycle the shared USB bootmode
* GPIOs as they are also used for Out of Band ( OOB ) wake for the USB and
* TS 27.010 serial mux .
*/
static int phy_mdm6600_device_power_on ( struct phy_mdm6600 * ddata )
{
struct gpio_desc * mode_gpio0 , * mode_gpio1 , * reset_gpio , * power_gpio ;
int error = 0 , wakeirq ;
mode_gpio0 = ddata - > mode_gpios - > desc [ PHY_MDM6600_MODE0 ] ;
mode_gpio1 = ddata - > mode_gpios - > desc [ PHY_MDM6600_MODE1 ] ;
reset_gpio = ddata - > ctrl_gpios [ PHY_MDM6600_RESET ] ;
power_gpio = ddata - > ctrl_gpios [ PHY_MDM6600_POWER ] ;
/*
* Shared GPIOs must be low for normal USB mode . After booting
* they are used for OOB wake signaling . These can be also used
* to configure USB flashing mode later on based on a module
* parameter .
*/
gpiod_set_value_cansleep ( mode_gpio0 , 0 ) ;
gpiod_set_value_cansleep ( mode_gpio1 , 0 ) ;
/* Request start-up mode */
phy_mdm6600_cmd ( ddata , PHY_MDM6600_CMD_NO_BYPASS ) ;
/* Request a reset first */
gpiod_set_value_cansleep ( reset_gpio , 0 ) ;
msleep ( 100 ) ;
/* Toggle power GPIO to request mdm6600 to start */
gpiod_set_value_cansleep ( power_gpio , 1 ) ;
msleep ( 100 ) ;
gpiod_set_value_cansleep ( power_gpio , 0 ) ;
/*
* Looks like the USB PHY needs between 2.2 to 4 seconds .
* If we try to use it before that , we will get L3 errors
* from omap - usb - host trying to access the PHY . See also
* phy_mdm6600_init ( ) for - EPROBE_DEFER .
*/
msleep ( PHY_MDM6600_PHY_DELAY_MS ) ;
ddata - > enabled = true ;
/* Booting up the rest of MDM6600 will take total about 8 seconds */
dev_info ( ddata - > dev , " Waiting for power up request to complete.. \n " ) ;
if ( wait_for_completion_timeout ( & ddata - > ack ,
msecs_to_jiffies ( PHY_MDM6600_ENABLED_DELAY_MS ) ) ) {
if ( ddata - > status > PHY_MDM6600_STATUS_PANIC & &
ddata - > status < PHY_MDM6600_STATUS_SHUTDOWN_ACK )
dev_info ( ddata - > dev , " Powered up OK \n " ) ;
} else {
ddata - > enabled = false ;
error = - ETIMEDOUT ;
dev_err ( ddata - > dev , " Timed out powering up \n " ) ;
}
/* Reconfigure mode1 GPIO as input for OOB wake */
gpiod_direction_input ( mode_gpio1 ) ;
wakeirq = gpiod_to_irq ( mode_gpio1 ) ;
if ( wakeirq < = 0 )
return wakeirq ;
error = devm_request_threaded_irq ( ddata - > dev , wakeirq , NULL ,
phy_mdm6600_wakeirq_thread ,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT ,
" mdm6600-wake " ,
ddata ) ;
if ( error )
dev_warn ( ddata - > dev , " no modem wakeirq irq%i: %i \n " ,
wakeirq , error ) ;
ddata - > running = true ;
return error ;
}
/**
* phy_mdm6600_device_power_off ( ) - power off mdm6600 device
* @ ddata : device driver data
*/
static void phy_mdm6600_device_power_off ( struct phy_mdm6600 * ddata )
{
struct gpio_desc * reset_gpio =
ddata - > ctrl_gpios [ PHY_MDM6600_RESET ] ;
ddata - > enabled = false ;
phy_mdm6600_cmd ( ddata , PHY_MDM6600_CMD_BP_SHUTDOWN_REQ ) ;
msleep ( 100 ) ;
gpiod_set_value_cansleep ( reset_gpio , 1 ) ;
dev_info ( ddata - > dev , " Waiting for power down request to complete.. " ) ;
if ( wait_for_completion_timeout ( & ddata - > ack ,
msecs_to_jiffies ( 5000 ) ) ) {
if ( ddata - > status = = PHY_MDM6600_STATUS_PANIC )
dev_info ( ddata - > dev , " Powered down OK \n " ) ;
} else {
dev_err ( ddata - > dev , " Timed out powering down \n " ) ;
}
}
static void phy_mdm6600_deferred_power_on ( struct work_struct * work )
{
struct phy_mdm6600 * ddata ;
int error ;
ddata = container_of ( work , struct phy_mdm6600 , bootup_work . work ) ;
error = phy_mdm6600_device_power_on ( ddata ) ;
if ( error )
dev_err ( ddata - > dev , " Device not functional \n " ) ;
}
2018-05-06 09:48:25 -07:00
/*
* USB suspend puts mdm6600 into low power mode . For any n_gsm using apps ,
* we need to keep the modem awake by kicking it ' s mode0 GPIO . This will
* keep the modem awake for about 1.2 seconds . When no n_gsm apps are using
* the modem , runtime PM auto mode can be enabled so modem can enter low
* power mode .
*/
static void phy_mdm6600_wake_modem ( struct phy_mdm6600 * ddata )
{
struct gpio_desc * mode_gpio0 ;
mode_gpio0 = ddata - > mode_gpios - > desc [ PHY_MDM6600_MODE0 ] ;
gpiod_set_value_cansleep ( mode_gpio0 , 1 ) ;
usleep_range ( 5 , 15 ) ;
gpiod_set_value_cansleep ( mode_gpio0 , 0 ) ;
if ( ddata - > awake )
usleep_range ( 5 , 15 ) ;
else
msleep ( MDM6600_MODEM_WAKE_DELAY_MS ) ;
}
static void phy_mdm6600_modem_wake ( struct work_struct * work )
{
struct phy_mdm6600 * ddata ;
ddata = container_of ( work , struct phy_mdm6600 , modem_wake_work . work ) ;
phy_mdm6600_wake_modem ( ddata ) ;
schedule_delayed_work ( & ddata - > modem_wake_work ,
msecs_to_jiffies ( MDM6600_MODEM_IDLE_DELAY_MS ) ) ;
}
static int __maybe_unused phy_mdm6600_runtime_suspend ( struct device * dev )
{
struct phy_mdm6600 * ddata = dev_get_drvdata ( dev ) ;
cancel_delayed_work_sync ( & ddata - > modem_wake_work ) ;
ddata - > awake = false ;
return 0 ;
}
static int __maybe_unused phy_mdm6600_runtime_resume ( struct device * dev )
{
struct phy_mdm6600 * ddata = dev_get_drvdata ( dev ) ;
phy_mdm6600_modem_wake ( & ddata - > modem_wake_work . work ) ;
ddata - > awake = true ;
return 0 ;
}
static const struct dev_pm_ops phy_mdm6600_pm_ops = {
SET_RUNTIME_PM_OPS ( phy_mdm6600_runtime_suspend ,
phy_mdm6600_runtime_resume , NULL )
} ;
2018-03-08 18:37:50 -08:00
static const struct of_device_id phy_mdm6600_id_table [ ] = {
{ . compatible = " motorola,mapphone-mdm6600 " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , phy_mdm6600_id_table ) ;
static int phy_mdm6600_probe ( struct platform_device * pdev )
{
struct phy_mdm6600 * ddata ;
int error ;
ddata = devm_kzalloc ( & pdev - > dev , sizeof ( * ddata ) , GFP_KERNEL ) ;
if ( ! ddata )
return - ENOMEM ;
INIT_DELAYED_WORK ( & ddata - > bootup_work ,
phy_mdm6600_deferred_power_on ) ;
INIT_DELAYED_WORK ( & ddata - > status_work , phy_mdm6600_status ) ;
2018-05-06 09:48:25 -07:00
INIT_DELAYED_WORK ( & ddata - > modem_wake_work , phy_mdm6600_modem_wake ) ;
2018-03-08 18:37:50 -08:00
init_completion ( & ddata - > ack ) ;
ddata - > dev = & pdev - > dev ;
platform_set_drvdata ( pdev , ddata ) ;
2018-12-02 15:51:35 -08:00
/* Active state selected in phy_mdm6600_power_on() */
error = pinctrl_pm_select_sleep_state ( ddata - > dev ) ;
if ( error )
dev_warn ( ddata - > dev , " %s: error with sleep_state: %i \n " ,
__func__ , error ) ;
2018-03-08 18:37:50 -08:00
error = phy_mdm6600_init_lines ( ddata ) ;
if ( error )
return error ;
phy_mdm6600_init_irq ( ddata ) ;
schedule_delayed_work ( & ddata - > bootup_work , 0 ) ;
/*
* See phy_mdm6600_device_power_on ( ) . We should be able
* to remove this eventually when ohci - platform can deal
* with - EPROBE_DEFER .
*/
msleep ( PHY_MDM6600_PHY_DELAY_MS + 500 ) ;
2018-05-06 09:48:25 -07:00
/*
* Enable PM runtime only after PHY has been powered up properly .
* It is currently only needed after USB suspends mdm6600 and n_gsm
* needs to access the device . We don ' t want to do this earlier as
* gpio mode0 pin doubles as mdm6600 wake - up gpio .
*/
pm_runtime_use_autosuspend ( ddata - > dev ) ;
pm_runtime_set_autosuspend_delay ( ddata - > dev ,
MDM6600_MODEM_IDLE_DELAY_MS ) ;
pm_runtime_enable ( ddata - > dev ) ;
error = pm_runtime_get_sync ( ddata - > dev ) ;
if ( error < 0 ) {
dev_warn ( ddata - > dev , " failed to wake modem: %i \n " , error ) ;
pm_runtime_put_noidle ( ddata - > dev ) ;
2018-12-02 15:51:35 -08:00
goto cleanup ;
}
ddata - > generic_phy = devm_phy_create ( ddata - > dev , NULL , & gpio_usb_ops ) ;
if ( IS_ERR ( ddata - > generic_phy ) ) {
error = PTR_ERR ( ddata - > generic_phy ) ;
goto idle ;
2018-05-06 09:48:25 -07:00
}
2018-12-02 15:51:35 -08:00
phy_set_drvdata ( ddata - > generic_phy , ddata ) ;
ddata - > phy_provider =
devm_of_phy_provider_register ( ddata - > dev ,
of_phy_simple_xlate ) ;
if ( IS_ERR ( ddata - > phy_provider ) )
error = PTR_ERR ( ddata - > phy_provider ) ;
idle :
2018-05-06 09:48:25 -07:00
pm_runtime_mark_last_busy ( ddata - > dev ) ;
pm_runtime_put_autosuspend ( ddata - > dev ) ;
2018-03-08 18:37:50 -08:00
cleanup :
2018-12-02 15:51:35 -08:00
if ( error < 0 )
phy_mdm6600_device_power_off ( ddata ) ;
2018-03-08 18:37:50 -08:00
return error ;
}
static int phy_mdm6600_remove ( struct platform_device * pdev )
{
struct phy_mdm6600 * ddata = platform_get_drvdata ( pdev ) ;
struct gpio_desc * reset_gpio = ddata - > ctrl_gpios [ PHY_MDM6600_RESET ] ;
2018-05-06 09:48:25 -07:00
pm_runtime_dont_use_autosuspend ( ddata - > dev ) ;
pm_runtime_put_sync ( ddata - > dev ) ;
pm_runtime_disable ( ddata - > dev ) ;
2018-03-08 18:37:50 -08:00
if ( ! ddata - > running )
wait_for_completion_timeout ( & ddata - > ack ,
msecs_to_jiffies ( PHY_MDM6600_ENABLED_DELAY_MS ) ) ;
gpiod_set_value_cansleep ( reset_gpio , 1 ) ;
phy_mdm6600_device_power_off ( ddata ) ;
2018-05-06 09:48:25 -07:00
cancel_delayed_work_sync ( & ddata - > modem_wake_work ) ;
2018-03-08 18:37:50 -08:00
cancel_delayed_work_sync ( & ddata - > bootup_work ) ;
cancel_delayed_work_sync ( & ddata - > status_work ) ;
return 0 ;
}
static struct platform_driver phy_mdm6600_driver = {
. probe = phy_mdm6600_probe ,
. remove = phy_mdm6600_remove ,
. driver = {
. name = " phy-mapphone-mdm6600 " ,
2018-05-06 09:48:25 -07:00
. pm = & phy_mdm6600_pm_ops ,
2018-03-08 18:37:50 -08:00
. of_match_table = of_match_ptr ( phy_mdm6600_id_table ) ,
} ,
} ;
module_platform_driver ( phy_mdm6600_driver ) ;
MODULE_ALIAS ( " platform:gpio_usb " ) ;
MODULE_AUTHOR ( " Tony Lindgren <tony@atomide.com> " ) ;
MODULE_DESCRIPTION ( " mdm6600 gpio usb phy driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;