2021-04-01 17:34:20 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* USB4 port device
*
* Copyright ( C ) 2021 , Intel Corporation
* Author : Mika Westerberg < mika . westerberg @ linux . intel . com >
*/
# include <linux/pm_runtime.h>
2022-04-18 20:59:32 +03:00
# include <linux/component.h>
# include <linux/property.h>
2021-04-01 17:34:20 +03:00
# include "tb.h"
2022-04-18 20:59:32 +03:00
static int connector_bind ( struct device * dev , struct device * connector , void * data )
{
int ret ;
ret = sysfs_create_link ( & dev - > kobj , & connector - > kobj , " connector " ) ;
if ( ret )
return ret ;
ret = sysfs_create_link ( & connector - > kobj , & dev - > kobj , dev_name ( dev ) ) ;
if ( ret )
sysfs_remove_link ( & dev - > kobj , " connector " ) ;
return ret ;
}
static void connector_unbind ( struct device * dev , struct device * connector , void * data )
{
sysfs_remove_link ( & connector - > kobj , dev_name ( dev ) ) ;
sysfs_remove_link ( & dev - > kobj , " connector " ) ;
}
static const struct component_ops connector_ops = {
. bind = connector_bind ,
. unbind = connector_unbind ,
} ;
2021-04-01 17:34:20 +03:00
static ssize_t link_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct usb4_port * usb4 = tb_to_usb4_port_device ( dev ) ;
struct tb_port * port = usb4 - > port ;
struct tb * tb = port - > sw - > tb ;
const char * link ;
if ( mutex_lock_interruptible ( & tb - > lock ) )
return - ERESTARTSYS ;
if ( tb_is_upstream_port ( port ) )
link = port - > sw - > link_usb4 ? " usb4 " : " tbt " ;
else if ( tb_port_has_remote ( port ) )
link = port - > remote - > sw - > link_usb4 ? " usb4 " : " tbt " ;
2022-08-30 18:32:47 +03:00
else if ( port - > xdomain )
link = port - > xdomain - > link_usb4 ? " usb4 " : " tbt " ;
2021-04-01 17:34:20 +03:00
else
link = " none " ;
mutex_unlock ( & tb - > lock ) ;
return sysfs_emit ( buf , " %s \n " , link ) ;
}
static DEVICE_ATTR_RO ( link ) ;
static struct attribute * common_attrs [ ] = {
& dev_attr_link . attr ,
NULL
} ;
static const struct attribute_group common_group = {
. attrs = common_attrs ,
} ;
2021-04-01 18:42:38 +03:00
static int usb4_port_offline ( struct usb4_port * usb4 )
{
struct tb_port * port = usb4 - > port ;
int ret ;
ret = tb_acpi_power_on_retimers ( port ) ;
if ( ret )
return ret ;
ret = usb4_port_router_offline ( port ) ;
if ( ret ) {
tb_acpi_power_off_retimers ( port ) ;
return ret ;
}
ret = tb_retimer_scan ( port , false ) ;
if ( ret ) {
usb4_port_router_online ( port ) ;
tb_acpi_power_off_retimers ( port ) ;
}
return ret ;
}
static void usb4_port_online ( struct usb4_port * usb4 )
{
struct tb_port * port = usb4 - > port ;
usb4_port_router_online ( port ) ;
tb_acpi_power_off_retimers ( port ) ;
}
static ssize_t offline_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct usb4_port * usb4 = tb_to_usb4_port_device ( dev ) ;
return sysfs_emit ( buf , " %d \n " , usb4 - > offline ) ;
}
static ssize_t offline_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct usb4_port * usb4 = tb_to_usb4_port_device ( dev ) ;
struct tb_port * port = usb4 - > port ;
struct tb * tb = port - > sw - > tb ;
bool val ;
int ret ;
ret = kstrtobool ( buf , & val ) ;
if ( ret )
return ret ;
pm_runtime_get_sync ( & usb4 - > dev ) ;
if ( mutex_lock_interruptible ( & tb - > lock ) ) {
ret = - ERESTARTSYS ;
goto out_rpm ;
}
if ( val = = usb4 - > offline )
goto out_unlock ;
/* Offline mode works only for ports that are not connected */
if ( tb_port_has_remote ( port ) ) {
ret = - EBUSY ;
goto out_unlock ;
}
if ( val ) {
ret = usb4_port_offline ( usb4 ) ;
if ( ret )
goto out_unlock ;
} else {
usb4_port_online ( usb4 ) ;
tb_retimer_remove_all ( port ) ;
}
usb4 - > offline = val ;
tb_port_dbg ( port , " %s offline mode \n " , val ? " enter " : " exit " ) ;
out_unlock :
mutex_unlock ( & tb - > lock ) ;
out_rpm :
pm_runtime_mark_last_busy ( & usb4 - > dev ) ;
pm_runtime_put_autosuspend ( & usb4 - > dev ) ;
return ret ? ret : count ;
}
static DEVICE_ATTR_RW ( offline ) ;
static ssize_t rescan_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct usb4_port * usb4 = tb_to_usb4_port_device ( dev ) ;
struct tb_port * port = usb4 - > port ;
struct tb * tb = port - > sw - > tb ;
bool val ;
int ret ;
ret = kstrtobool ( buf , & val ) ;
if ( ret )
return ret ;
if ( ! val )
return count ;
pm_runtime_get_sync ( & usb4 - > dev ) ;
if ( mutex_lock_interruptible ( & tb - > lock ) ) {
ret = - ERESTARTSYS ;
goto out_rpm ;
}
/* Must be in offline mode already */
if ( ! usb4 - > offline ) {
ret = - EINVAL ;
goto out_unlock ;
}
tb_retimer_remove_all ( port ) ;
ret = tb_retimer_scan ( port , true ) ;
out_unlock :
mutex_unlock ( & tb - > lock ) ;
out_rpm :
pm_runtime_mark_last_busy ( & usb4 - > dev ) ;
pm_runtime_put_autosuspend ( & usb4 - > dev ) ;
return ret ? ret : count ;
}
static DEVICE_ATTR_WO ( rescan ) ;
static struct attribute * service_attrs [ ] = {
& dev_attr_offline . attr ,
& dev_attr_rescan . attr ,
NULL
} ;
static umode_t service_attr_is_visible ( struct kobject * kobj ,
struct attribute * attr , int n )
{
struct device * dev = kobj_to_dev ( kobj ) ;
struct usb4_port * usb4 = tb_to_usb4_port_device ( dev ) ;
/*
* Always need some platform help to cycle the modes so that
* retimers can be accessed through the sideband .
*/
return usb4 - > can_offline ? attr - > mode : 0 ;
}
static const struct attribute_group service_group = {
. attrs = service_attrs ,
. is_visible = service_attr_is_visible ,
} ;
2021-04-01 17:34:20 +03:00
static const struct attribute_group * usb4_port_device_groups [ ] = {
& common_group ,
2021-04-01 18:42:38 +03:00
& service_group ,
2021-04-01 17:34:20 +03:00
NULL
} ;
static void usb4_port_device_release ( struct device * dev )
{
struct usb4_port * usb4 = container_of ( dev , struct usb4_port , dev ) ;
kfree ( usb4 ) ;
}
2024-02-19 15:45:50 +03:00
const struct device_type usb4_port_device_type = {
2021-04-01 17:34:20 +03:00
. name = " usb4_port " ,
. groups = usb4_port_device_groups ,
. release = usb4_port_device_release ,
} ;
/**
* usb4_port_device_add ( ) - Add USB4 port device
* @ port : Lane 0 adapter port to add the USB4 port
*
* Creates and registers a USB4 port device for @ port . Returns the new
* USB4 port device pointer or ERR_PTR ( ) in case of error .
*/
struct usb4_port * usb4_port_device_add ( struct tb_port * port )
{
struct usb4_port * usb4 ;
int ret ;
usb4 = kzalloc ( sizeof ( * usb4 ) , GFP_KERNEL ) ;
if ( ! usb4 )
return ERR_PTR ( - ENOMEM ) ;
usb4 - > port = port ;
usb4 - > dev . type = & usb4_port_device_type ;
usb4 - > dev . parent = & port - > sw - > dev ;
dev_set_name ( & usb4 - > dev , " usb4_port%d " , port - > port ) ;
ret = device_register ( & usb4 - > dev ) ;
if ( ret ) {
put_device ( & usb4 - > dev ) ;
return ERR_PTR ( ret ) ;
}
2022-04-18 20:59:32 +03:00
if ( dev_fwnode ( & usb4 - > dev ) ) {
ret = component_add ( & usb4 - > dev , & connector_ops ) ;
if ( ret ) {
dev_err ( & usb4 - > dev , " failed to add component \n " ) ;
device_unregister ( & usb4 - > dev ) ;
}
}
2022-11-01 14:50:42 +03:00
if ( ! tb_is_upstream_port ( port ) )
device_set_wakeup_capable ( & usb4 - > dev , true ) ;
2021-04-01 17:34:20 +03:00
pm_runtime_no_callbacks ( & usb4 - > dev ) ;
pm_runtime_set_active ( & usb4 - > dev ) ;
pm_runtime_enable ( & usb4 - > dev ) ;
pm_runtime_set_autosuspend_delay ( & usb4 - > dev , TB_AUTOSUSPEND_DELAY ) ;
pm_runtime_mark_last_busy ( & usb4 - > dev ) ;
pm_runtime_use_autosuspend ( & usb4 - > dev ) ;
return usb4 ;
}
/**
* usb4_port_device_remove ( ) - Removes USB4 port device
* @ usb4 : USB4 port device
*
* Unregisters the USB4 port device from the system . The device will be
* released when the last reference is dropped .
*/
void usb4_port_device_remove ( struct usb4_port * usb4 )
{
2022-04-18 20:59:32 +03:00
if ( dev_fwnode ( & usb4 - > dev ) )
component_del ( & usb4 - > dev , & connector_ops ) ;
2021-04-01 17:34:20 +03:00
device_unregister ( & usb4 - > dev ) ;
}
2021-04-01 18:42:38 +03:00
/**
* usb4_port_device_resume ( ) - Resumes USB4 port device
* @ usb4 : USB4 port device
*
* Used to resume USB4 port device after sleep state .
*/
int usb4_port_device_resume ( struct usb4_port * usb4 )
{
return usb4 - > offline ? usb4_port_offline ( usb4 ) : 0 ;
}