2018-03-20 15:57:02 +03:00
// SPDX-License-Identifier: GPL-2.0
/**
* Device connections
*
* Copyright ( C ) 2018 Intel Corporation
* Author : Heikki Krogerus < heikki . krogerus @ linux . intel . com >
*/
# include <linux/device.h>
2019-02-13 10:45:57 +03:00
# include <linux/property.h>
2018-03-20 15:57:02 +03:00
static DEFINE_MUTEX ( devcon_lock ) ;
static LIST_HEAD ( devcon_list ) ;
2019-02-13 10:45:57 +03:00
static void *
fwnode_graph_devcon_match ( struct fwnode_handle * fwnode , const char * con_id ,
void * data , devcon_match_fn_t match )
{
struct device_connection con = { . id = con_id } ;
struct fwnode_handle * ep ;
void * ret ;
fwnode_graph_for_each_endpoint ( fwnode , ep ) {
con . fwnode = fwnode_graph_get_remote_port_parent ( ep ) ;
if ( ! fwnode_device_is_available ( con . fwnode ) )
continue ;
ret = match ( & con , - 1 , data ) ;
fwnode_handle_put ( con . fwnode ) ;
if ( ret ) {
fwnode_handle_put ( ep ) ;
return ret ;
}
}
return NULL ;
}
2019-05-31 17:15:40 +03:00
static void *
fwnode_devcon_match ( struct fwnode_handle * fwnode , const char * con_id ,
void * data , devcon_match_fn_t match )
{
struct device_connection con = { } ;
void * ret ;
int i ;
for ( i = 0 ; ; i + + ) {
con . fwnode = fwnode_find_reference ( fwnode , con_id , i ) ;
if ( IS_ERR ( con . fwnode ) )
break ;
ret = match ( & con , - 1 , data ) ;
fwnode_handle_put ( con . fwnode ) ;
if ( ret )
return ret ;
}
return NULL ;
}
2019-08-29 12:22:33 +03:00
/**
* fwnode_connection_find_match - Find connection from a device node
* @ fwnode : Device node with the connection
* @ con_id : Identifier for the connection
* @ data : Data for the match function
* @ match : Function to check and convert the connection description
*
* Find a connection with unique identifier @ con_id between @ fwnode and another
* device node . @ match will be used to convert the connection description to
* data the caller is expecting to be returned .
*/
void * fwnode_connection_find_match ( struct fwnode_handle * fwnode ,
const char * con_id , void * data ,
devcon_match_fn_t match )
{
void * ret ;
if ( ! fwnode | | ! match )
return NULL ;
ret = fwnode_graph_devcon_match ( fwnode , con_id , data , match ) ;
if ( ret )
return ret ;
return fwnode_devcon_match ( fwnode , con_id , data , match ) ;
}
EXPORT_SYMBOL_GPL ( fwnode_connection_find_match ) ;
2018-03-20 15:57:02 +03:00
/**
* device_connection_find_match - Find physical connection to a device
* @ dev : Device with the connection
* @ con_id : Identifier for the connection
* @ data : Data for the match function
* @ match : Function to check and convert the connection description
*
* Find a connection with unique identifier @ con_id between @ dev and another
* device . @ match will be used to convert the connection description to data the
* caller is expecting to be returned .
*/
void * device_connection_find_match ( struct device * dev , const char * con_id ,
2019-02-13 10:45:57 +03:00
void * data , devcon_match_fn_t match )
2018-03-20 15:57:02 +03:00
{
2019-02-13 10:45:57 +03:00
struct fwnode_handle * fwnode = dev_fwnode ( dev ) ;
2018-03-20 15:57:02 +03:00
const char * devname = dev_name ( dev ) ;
struct device_connection * con ;
void * ret = NULL ;
int ep ;
if ( ! match )
return NULL ;
2019-08-29 12:22:33 +03:00
ret = fwnode_connection_find_match ( fwnode , con_id , data , match ) ;
if ( ret )
return ret ;
2019-02-13 10:45:57 +03:00
2018-03-20 15:57:02 +03:00
mutex_lock ( & devcon_lock ) ;
list_for_each_entry ( con , & devcon_list , list ) {
ep = match_string ( con - > endpoint , 2 , devname ) ;
if ( ep < 0 )
continue ;
if ( con_id & & strcmp ( con - > id , con_id ) )
continue ;
ret = match ( con , ! ep , data ) ;
if ( ret )
break ;
}
mutex_unlock ( & devcon_lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( device_connection_find_match ) ;
extern struct bus_type platform_bus_type ;
extern struct bus_type pci_bus_type ;
extern struct bus_type i2c_bus_type ;
extern struct bus_type spi_bus_type ;
static struct bus_type * generic_match_buses [ ] = {
& platform_bus_type ,
# ifdef CONFIG_PCI
& pci_bus_type ,
# endif
# ifdef CONFIG_I2C
& i2c_bus_type ,
# endif
# ifdef CONFIG_SPI_MASTER
& spi_bus_type ,
# endif
NULL ,
} ;
2019-02-13 10:45:56 +03:00
static void * device_connection_fwnode_match ( struct device_connection * con )
{
struct bus_type * bus ;
struct device * dev ;
for ( bus = generic_match_buses [ 0 ] ; bus ; bus + + ) {
2019-07-24 01:18:34 +03:00
dev = bus_find_device_by_fwnode ( bus , con - > fwnode ) ;
2019-02-13 10:45:56 +03:00
if ( dev & & ! strncmp ( dev_name ( dev ) , con - > id , strlen ( con - > id ) ) )
return dev ;
put_device ( dev ) ;
}
return NULL ;
}
2018-03-20 15:57:02 +03:00
/* This tries to find the device from the most common bus types by name. */
static void * generic_match ( struct device_connection * con , int ep , void * data )
{
struct bus_type * bus ;
struct device * dev ;
2019-02-13 10:45:56 +03:00
if ( con - > fwnode )
return device_connection_fwnode_match ( con ) ;
2018-03-20 15:57:02 +03:00
for ( bus = generic_match_buses [ 0 ] ; bus ; bus + + ) {
dev = bus_find_device_by_name ( bus , NULL , con - > endpoint [ ep ] ) ;
if ( dev )
return dev ;
}
/*
* We only get called if a connection was found , tell the caller to
* wait for the other device to show up .
*/
return ERR_PTR ( - EPROBE_DEFER ) ;
}
/**
* device_connection_find - Find two devices connected together
* @ dev : Device with the connection
* @ con_id : Identifier for the connection
*
* Find a connection with unique identifier @ con_id between @ dev and
* another device . On success returns handle to the device that is connected
* to @ dev , with the reference count for the found device incremented . Returns
* NULL if no matching connection was found , or ERR_PTR ( - EPROBE_DEFER ) when a
* connection was found but the other device has not been enumerated yet .
*/
struct device * device_connection_find ( struct device * dev , const char * con_id )
{
return device_connection_find_match ( dev , con_id , NULL , generic_match ) ;
}
EXPORT_SYMBOL_GPL ( device_connection_find ) ;
/**
* device_connection_add - Register a connection description
* @ con : The connection description to be registered
*/
void device_connection_add ( struct device_connection * con )
{
mutex_lock ( & devcon_lock ) ;
list_add_tail ( & con - > list , & devcon_list ) ;
mutex_unlock ( & devcon_lock ) ;
}
EXPORT_SYMBOL_GPL ( device_connection_add ) ;
/**
* device_connections_remove - Unregister connection description
* @ con : The connection description to be unregistered
*/
void device_connection_remove ( struct device_connection * con )
{
mutex_lock ( & devcon_lock ) ;
list_del ( & con - > list ) ;
mutex_unlock ( & devcon_lock ) ;
}
EXPORT_SYMBOL_GPL ( device_connection_remove ) ;