2020-09-10 09:00:12 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Support for usb functionality of Hikey series boards
* based on Hisilicon Kirin Soc .
*
* Copyright ( C ) 2017 - 2018 Hilisicon Electronics Co . , Ltd .
* http : //www.huawei.com
*
* Authors : Yu Chen < chenyu56 @ huawei . com >
*/
# include <linux/gpio/consumer.h>
# include <linux/kernel.h>
# include <linux/mod_devicetable.h>
# include <linux/module.h>
# include <linux/notifier.h>
2020-09-10 09:00:13 +03:00
# include <linux/of_gpio.h>
2020-09-10 09:00:12 +03:00
# include <linux/platform_device.h>
# include <linux/property.h>
2020-09-10 09:00:13 +03:00
# include <linux/regulator/consumer.h>
2020-09-10 09:00:12 +03:00
# include <linux/slab.h>
# include <linux/usb/role.h>
# define DEVICE_DRIVER_NAME "hisi_hikey_usb"
# define HUB_VBUS_POWER_ON 1
# define HUB_VBUS_POWER_OFF 0
# define USB_SWITCH_TO_HUB 1
# define USB_SWITCH_TO_TYPEC 0
# define TYPEC_VBUS_POWER_ON 1
# define TYPEC_VBUS_POWER_OFF 0
struct hisi_hikey_usb {
2020-09-10 09:00:13 +03:00
struct device * dev ;
2020-09-10 09:00:12 +03:00
struct gpio_desc * otg_switch ;
struct gpio_desc * typec_vbus ;
struct gpio_desc * hub_vbus ;
2020-09-10 09:00:13 +03:00
struct gpio_desc * reset ;
struct regulator * regulator ;
2020-09-10 09:00:12 +03:00
struct usb_role_switch * hub_role_sw ;
struct usb_role_switch * dev_role_sw ;
enum usb_role role ;
struct mutex lock ;
struct work_struct work ;
struct notifier_block nb ;
} ;
static void hub_power_ctrl ( struct hisi_hikey_usb * hisi_hikey_usb , int value )
{
2020-09-10 09:00:13 +03:00
int ret , status ;
if ( hisi_hikey_usb - > hub_vbus )
gpiod_set_value_cansleep ( hisi_hikey_usb - > hub_vbus , value ) ;
if ( ! hisi_hikey_usb - > regulator )
return ;
status = regulator_is_enabled ( hisi_hikey_usb - > regulator ) ;
if ( status = = ! ! value )
return ;
if ( value )
ret = regulator_enable ( hisi_hikey_usb - > regulator ) ;
else
ret = regulator_disable ( hisi_hikey_usb - > regulator ) ;
if ( ret )
dev_err ( hisi_hikey_usb - > dev ,
" Can't switch regulator state to %s \n " ,
value ? " enabled " : " disabled " ) ;
2020-09-10 09:00:12 +03:00
}
static void usb_switch_ctrl ( struct hisi_hikey_usb * hisi_hikey_usb ,
int switch_to )
{
2020-09-10 09:00:13 +03:00
if ( ! hisi_hikey_usb - > otg_switch )
return ;
2020-09-10 09:00:12 +03:00
gpiod_set_value_cansleep ( hisi_hikey_usb - > otg_switch , switch_to ) ;
}
static void usb_typec_power_ctrl ( struct hisi_hikey_usb * hisi_hikey_usb ,
int value )
{
2020-09-10 09:00:13 +03:00
if ( ! hisi_hikey_usb - > typec_vbus )
return ;
2020-09-10 09:00:12 +03:00
gpiod_set_value_cansleep ( hisi_hikey_usb - > typec_vbus , value ) ;
}
static void relay_set_role_switch ( struct work_struct * work )
{
struct hisi_hikey_usb * hisi_hikey_usb = container_of ( work ,
struct hisi_hikey_usb ,
work ) ;
struct usb_role_switch * sw ;
enum usb_role role ;
if ( ! hisi_hikey_usb | | ! hisi_hikey_usb - > dev_role_sw )
return ;
mutex_lock ( & hisi_hikey_usb - > lock ) ;
switch ( hisi_hikey_usb - > role ) {
case USB_ROLE_NONE :
usb_typec_power_ctrl ( hisi_hikey_usb , TYPEC_VBUS_POWER_OFF ) ;
usb_switch_ctrl ( hisi_hikey_usb , USB_SWITCH_TO_HUB ) ;
hub_power_ctrl ( hisi_hikey_usb , HUB_VBUS_POWER_ON ) ;
break ;
case USB_ROLE_HOST :
hub_power_ctrl ( hisi_hikey_usb , HUB_VBUS_POWER_OFF ) ;
usb_switch_ctrl ( hisi_hikey_usb , USB_SWITCH_TO_TYPEC ) ;
usb_typec_power_ctrl ( hisi_hikey_usb , TYPEC_VBUS_POWER_ON ) ;
break ;
case USB_ROLE_DEVICE :
hub_power_ctrl ( hisi_hikey_usb , HUB_VBUS_POWER_OFF ) ;
usb_typec_power_ctrl ( hisi_hikey_usb , TYPEC_VBUS_POWER_OFF ) ;
usb_switch_ctrl ( hisi_hikey_usb , USB_SWITCH_TO_TYPEC ) ;
break ;
default :
break ;
}
sw = hisi_hikey_usb - > dev_role_sw ;
role = hisi_hikey_usb - > role ;
mutex_unlock ( & hisi_hikey_usb - > lock ) ;
usb_role_switch_set_role ( sw , role ) ;
}
static int hub_usb_role_switch_set ( struct usb_role_switch * sw , enum usb_role role )
{
struct hisi_hikey_usb * hisi_hikey_usb = usb_role_switch_get_drvdata ( sw ) ;
if ( ! hisi_hikey_usb | | ! hisi_hikey_usb - > dev_role_sw )
return - EINVAL ;
mutex_lock ( & hisi_hikey_usb - > lock ) ;
hisi_hikey_usb - > role = role ;
mutex_unlock ( & hisi_hikey_usb - > lock ) ;
schedule_work ( & hisi_hikey_usb - > work ) ;
return 0 ;
}
2020-09-10 09:00:13 +03:00
static int hisi_hikey_usb_parse_kirin970 ( struct platform_device * pdev ,
struct hisi_hikey_usb * hisi_hikey_usb )
{
struct regulator * regulator ;
regulator = devm_regulator_get ( & pdev - > dev , " hub-vdd " ) ;
if ( IS_ERR ( regulator ) ) {
if ( PTR_ERR ( regulator ) = = - EPROBE_DEFER ) {
dev_info ( & pdev - > dev ,
" waiting for hub-vdd-supply to be probed \n " ) ;
return PTR_ERR ( regulator ) ;
}
dev_err ( & pdev - > dev ,
" get hub-vdd-supply failed with error %ld \n " ,
PTR_ERR ( regulator ) ) ;
2020-09-18 17:34:05 +03:00
return PTR_ERR ( regulator ) ;
2020-09-10 09:00:13 +03:00
}
hisi_hikey_usb - > regulator = regulator ;
hisi_hikey_usb - > reset = devm_gpiod_get ( & pdev - > dev , " hub_reset_en_gpio " ,
GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( hisi_hikey_usb - > reset ) )
return PTR_ERR ( hisi_hikey_usb - > reset ) ;
2020-09-14 16:56:46 +03:00
return 0 ;
2020-09-10 09:00:13 +03:00
}
2020-09-10 09:00:12 +03:00
static int hisi_hikey_usb_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct hisi_hikey_usb * hisi_hikey_usb ;
struct usb_role_switch_desc hub_role_switch = { NULL } ;
2020-09-10 09:00:13 +03:00
int ret ;
2020-09-10 09:00:12 +03:00
hisi_hikey_usb = devm_kzalloc ( dev , sizeof ( * hisi_hikey_usb ) , GFP_KERNEL ) ;
if ( ! hisi_hikey_usb )
return - ENOMEM ;
2020-09-10 09:00:13 +03:00
hisi_hikey_usb - > dev = & pdev - > dev ;
2020-09-10 09:00:12 +03:00
hisi_hikey_usb - > otg_switch = devm_gpiod_get ( dev , " otg-switch " ,
GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( hisi_hikey_usb - > otg_switch ) )
return PTR_ERR ( hisi_hikey_usb - > otg_switch ) ;
2020-09-10 09:00:13 +03:00
hisi_hikey_usb - > typec_vbus = devm_gpiod_get ( dev , " typec-vbus " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( hisi_hikey_usb - > typec_vbus ) )
return PTR_ERR ( hisi_hikey_usb - > typec_vbus ) ;
/* Parse Kirin 970-specific OF data */
if ( of_device_is_compatible ( pdev - > dev . of_node ,
" hisilicon,kirin970_hikey_usbhub " ) ) {
ret = hisi_hikey_usb_parse_kirin970 ( pdev , hisi_hikey_usb ) ;
if ( ret )
return ret ;
} else {
/* hub-vdd33-en is optional */
hisi_hikey_usb - > hub_vbus = devm_gpiod_get_optional ( dev , " hub-vdd33-en " ,
GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( hisi_hikey_usb - > hub_vbus ) )
return PTR_ERR ( hisi_hikey_usb - > hub_vbus ) ;
}
2020-09-10 09:00:12 +03:00
hisi_hikey_usb - > dev_role_sw = usb_role_switch_get ( dev ) ;
if ( ! hisi_hikey_usb - > dev_role_sw )
return - EPROBE_DEFER ;
if ( IS_ERR ( hisi_hikey_usb - > dev_role_sw ) )
return PTR_ERR ( hisi_hikey_usb - > dev_role_sw ) ;
INIT_WORK ( & hisi_hikey_usb - > work , relay_set_role_switch ) ;
mutex_init ( & hisi_hikey_usb - > lock ) ;
hub_role_switch . fwnode = dev_fwnode ( dev ) ;
hub_role_switch . set = hub_usb_role_switch_set ;
hub_role_switch . driver_data = hisi_hikey_usb ;
hisi_hikey_usb - > hub_role_sw = usb_role_switch_register ( dev ,
2020-09-10 09:00:13 +03:00
& hub_role_switch ) ;
2020-09-10 09:00:12 +03:00
if ( IS_ERR ( hisi_hikey_usb - > hub_role_sw ) ) {
usb_role_switch_put ( hisi_hikey_usb - > dev_role_sw ) ;
return PTR_ERR ( hisi_hikey_usb - > hub_role_sw ) ;
}
platform_set_drvdata ( pdev , hisi_hikey_usb ) ;
return 0 ;
}
static int hisi_hikey_usb_remove ( struct platform_device * pdev )
{
struct hisi_hikey_usb * hisi_hikey_usb = platform_get_drvdata ( pdev ) ;
if ( hisi_hikey_usb - > hub_role_sw )
usb_role_switch_unregister ( hisi_hikey_usb - > hub_role_sw ) ;
if ( hisi_hikey_usb - > dev_role_sw )
usb_role_switch_put ( hisi_hikey_usb - > dev_role_sw ) ;
return 0 ;
}
static const struct of_device_id id_table_hisi_hikey_usb [ ] = {
2020-09-10 09:00:13 +03:00
{ . compatible = " hisilicon,gpio_hubv1 " } ,
{ . compatible = " hisilicon,kirin970_hikey_usbhub " } ,
2020-09-10 09:00:12 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , id_table_hisi_hikey_usb ) ;
static struct platform_driver hisi_hikey_usb_driver = {
. probe = hisi_hikey_usb_probe ,
. remove = hisi_hikey_usb_remove ,
. driver = {
. name = DEVICE_DRIVER_NAME ,
. of_match_table = id_table_hisi_hikey_usb ,
} ,
} ;
module_platform_driver ( hisi_hikey_usb_driver ) ;
MODULE_AUTHOR ( " Yu Chen <chenyu56@huawei.com> " ) ;
MODULE_DESCRIPTION ( " Driver Support for USB functionality of Hikey " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;