2018-03-28 18:46:15 +03:00
// SPDX-License-Identifier: GPL-2.0
2015-09-22 15:47:14 +03:00
/*
* Intel ( R ) Trace Hub driver core
*
* Copyright ( C ) 2014 - 2015 Intel Corporation .
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/types.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/sysfs.h>
# include <linux/kdev_t.h>
# include <linux/debugfs.h>
# include <linux/idr.h>
# include <linux/pci.h>
2016-06-22 13:48:21 +03:00
# include <linux/pm_runtime.h>
2015-09-22 15:47:14 +03:00
# include <linux/dma-mapping.h>
# include "intel_th.h"
# include "debug.h"
2016-09-19 17:07:47 +03:00
static bool host_mode __read_mostly ;
module_param ( host_mode , bool , 0444 ) ;
2015-09-22 15:47:14 +03:00
static DEFINE_IDA ( intel_th_ida ) ;
static int intel_th_match ( struct device * dev , struct device_driver * driver )
{
struct intel_th_driver * thdrv = to_intel_th_driver ( driver ) ;
struct intel_th_device * thdev = to_intel_th_device ( dev ) ;
if ( thdev - > type = = INTEL_TH_SWITCH & &
( ! thdrv - > enable | | ! thdrv - > disable ) )
return 0 ;
return ! strcmp ( thdev - > name , driver - > name ) ;
}
static int intel_th_child_remove ( struct device * dev , void * data )
{
device_release_driver ( dev ) ;
return 0 ;
}
static int intel_th_probe ( struct device * dev )
{
struct intel_th_driver * thdrv = to_intel_th_driver ( dev - > driver ) ;
struct intel_th_device * thdev = to_intel_th_device ( dev ) ;
struct intel_th_driver * hubdrv ;
struct intel_th_device * hub = NULL ;
int ret ;
if ( thdev - > type = = INTEL_TH_SWITCH )
hub = thdev ;
else if ( dev - > parent )
hub = to_intel_th_device ( dev - > parent ) ;
if ( ! hub | | ! hub - > dev . driver )
return - EPROBE_DEFER ;
hubdrv = to_intel_th_driver ( hub - > dev . driver ) ;
2016-06-22 13:48:21 +03:00
pm_runtime_set_active ( dev ) ;
pm_runtime_no_callbacks ( dev ) ;
pm_runtime_enable ( dev ) ;
2015-09-22 15:47:14 +03:00
ret = thdrv - > probe ( to_intel_th_device ( dev ) ) ;
if ( ret )
2016-06-22 13:48:21 +03:00
goto out_pm ;
2015-09-22 15:47:14 +03:00
2016-03-04 19:42:48 +02:00
if ( thdrv - > attr_group ) {
ret = sysfs_create_group ( & thdev - > dev . kobj , thdrv - > attr_group ) ;
2016-06-22 13:48:21 +03:00
if ( ret )
goto out ;
2016-03-04 19:42:48 +02:00
}
2015-09-22 15:47:14 +03:00
if ( thdev - > type = = INTEL_TH_OUTPUT & &
! intel_th_output_assigned ( thdev ) )
2016-06-22 13:48:21 +03:00
/* does not talk to hardware */
2015-09-22 15:47:14 +03:00
ret = hubdrv - > assign ( hub , thdev ) ;
2016-06-22 13:48:21 +03:00
out :
if ( ret )
thdrv - > remove ( thdev ) ;
out_pm :
if ( ret )
pm_runtime_disable ( dev ) ;
2015-09-22 15:47:14 +03:00
return ret ;
}
2017-08-10 18:28:38 +03:00
static void intel_th_device_remove ( struct intel_th_device * thdev ) ;
2021-07-13 21:35:22 +02:00
static void intel_th_remove ( struct device * dev )
2015-09-22 15:47:14 +03:00
{
struct intel_th_driver * thdrv = to_intel_th_driver ( dev - > driver ) ;
struct intel_th_device * thdev = to_intel_th_device ( dev ) ;
2017-08-10 18:28:38 +03:00
struct intel_th_device * hub = to_intel_th_hub ( thdev ) ;
2015-09-22 15:47:14 +03:00
if ( thdev - > type = = INTEL_TH_SWITCH ) {
2017-08-10 18:28:38 +03:00
struct intel_th * th = to_intel_th ( hub ) ;
int i , lowest ;
2021-06-21 18:12:44 +03:00
/*
* disconnect outputs
*
* intel_th_child_remove returns 0 unconditionally , so there is
* no need to check the return value of device_for_each_child .
*/
device_for_each_child ( dev , thdev , intel_th_child_remove ) ;
2017-08-10 18:28:38 +03:00
/*
* Remove outputs , that is , hub ' s children : they are created
* at hub ' s probe time by having the hub call
* intel_th_output_enable ( ) for each of them .
*/
for ( i = 0 , lowest = - 1 ; i < th - > num_thdevs ; i + + ) {
/*
* Move the non - output devices from higher up the
* th - > thdev [ ] array to lower positions to maintain
* a contiguous array .
*/
if ( th - > thdev [ i ] - > type ! = INTEL_TH_OUTPUT ) {
if ( lowest > = 0 ) {
th - > thdev [ lowest ] = th - > thdev [ i ] ;
th - > thdev [ i ] = NULL ;
+ + lowest ;
}
continue ;
}
if ( lowest = = - 1 )
lowest = i ;
intel_th_device_remove ( th - > thdev [ i ] ) ;
th - > thdev [ i ] = NULL ;
}
2018-09-18 16:10:47 +03:00
if ( lowest > = 0 )
th - > num_thdevs = lowest ;
2015-09-22 15:47:14 +03:00
}
2016-03-04 19:42:48 +02:00
if ( thdrv - > attr_group )
sysfs_remove_group ( & thdev - > dev . kobj , thdrv - > attr_group ) ;
2016-06-22 13:48:21 +03:00
pm_runtime_get_sync ( dev ) ;
2015-09-22 15:47:14 +03:00
thdrv - > remove ( thdev ) ;
if ( intel_th_output_assigned ( thdev ) ) {
struct intel_th_driver * hubdrv =
to_intel_th_driver ( dev - > parent - > driver ) ;
if ( hub - > dev . driver )
2016-06-22 13:48:21 +03:00
/* does not talk to hardware */
2015-09-22 15:47:14 +03:00
hubdrv - > unassign ( hub , thdev ) ;
}
2016-06-22 13:48:21 +03:00
pm_runtime_disable ( dev ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
2015-09-22 15:47:14 +03:00
}
static struct bus_type intel_th_bus = {
. name = " intel_th " ,
. match = intel_th_match ,
. probe = intel_th_probe ,
. remove = intel_th_remove ,
} ;
static void intel_th_device_free ( struct intel_th_device * thdev ) ;
static void intel_th_device_release ( struct device * dev )
{
intel_th_device_free ( to_intel_th_device ( dev ) ) ;
}
static struct device_type intel_th_source_device_type = {
. name = " intel_th_source_device " ,
. release = intel_th_device_release ,
} ;
static char * intel_th_output_devnode ( struct device * dev , umode_t * mode ,
kuid_t * uid , kgid_t * gid )
{
struct intel_th_device * thdev = to_intel_th_device ( dev ) ;
2016-02-15 19:12:00 +02:00
struct intel_th * th = to_intel_th ( thdev ) ;
2015-09-22 15:47:14 +03:00
char * node ;
if ( thdev - > id > = 0 )
2016-02-15 19:12:00 +02:00
node = kasprintf ( GFP_KERNEL , " intel_th%d/%s%d " , th - > id ,
thdev - > name , thdev - > id ) ;
2015-09-22 15:47:14 +03:00
else
2016-02-15 19:12:00 +02:00
node = kasprintf ( GFP_KERNEL , " intel_th%d/%s " , th - > id ,
thdev - > name ) ;
2015-09-22 15:47:14 +03:00
return node ;
}
static ssize_t port_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct intel_th_device * thdev = to_intel_th_device ( dev ) ;
if ( thdev - > output . port > = 0 )
return scnprintf ( buf , PAGE_SIZE , " %u \n " , thdev - > output . port ) ;
return scnprintf ( buf , PAGE_SIZE , " unassigned \n " ) ;
}
static DEVICE_ATTR_RO ( port ) ;
2021-06-21 18:12:46 +03:00
static void intel_th_trace_prepare ( struct intel_th_device * thdev )
{
struct intel_th_device * hub = to_intel_th_hub ( thdev ) ;
struct intel_th_driver * hubdrv = to_intel_th_driver ( hub - > dev . driver ) ;
if ( hub - > type ! = INTEL_TH_SWITCH )
return ;
if ( thdev - > type ! = INTEL_TH_OUTPUT )
return ;
pm_runtime_get_sync ( & thdev - > dev ) ;
hubdrv - > prepare ( hub , & thdev - > output ) ;
pm_runtime_put ( & thdev - > dev ) ;
}
2015-09-22 15:47:14 +03:00
static int intel_th_output_activate ( struct intel_th_device * thdev )
{
2016-03-07 17:04:45 +02:00
struct intel_th_driver * thdrv =
to_intel_th_driver_or_null ( thdev - > dev . driver ) ;
2017-08-25 15:47:22 +03:00
struct intel_th * th = to_intel_th ( thdev ) ;
2016-06-22 13:48:21 +03:00
int ret = 0 ;
2016-03-07 17:04:45 +02:00
if ( ! thdrv )
return - ENODEV ;
2015-09-22 15:47:14 +03:00
2016-04-08 17:35:04 +03:00
if ( ! try_module_get ( thdrv - > driver . owner ) )
return - ENODEV ;
2016-06-22 13:48:21 +03:00
pm_runtime_get_sync ( & thdev - > dev ) ;
2017-08-25 15:47:22 +03:00
if ( th - > activate )
ret = th - > activate ( th ) ;
if ( ret )
goto fail_put ;
2021-06-21 18:12:46 +03:00
intel_th_trace_prepare ( thdev ) ;
2015-09-22 15:47:14 +03:00
if ( thdrv - > activate )
2016-06-22 13:48:21 +03:00
ret = thdrv - > activate ( thdev ) ;
else
intel_th_trace_enable ( thdev ) ;
2015-09-22 15:47:14 +03:00
2017-08-25 15:47:22 +03:00
if ( ret )
goto fail_deactivate ;
return 0 ;
fail_deactivate :
if ( th - > deactivate )
th - > deactivate ( th ) ;
fail_put :
pm_runtime_put ( & thdev - > dev ) ;
module_put ( thdrv - > driver . owner ) ;
2015-09-22 15:47:14 +03:00
2016-06-22 13:48:21 +03:00
return ret ;
2015-09-22 15:47:14 +03:00
}
static void intel_th_output_deactivate ( struct intel_th_device * thdev )
{
2016-03-07 17:04:45 +02:00
struct intel_th_driver * thdrv =
to_intel_th_driver_or_null ( thdev - > dev . driver ) ;
2017-08-25 15:47:22 +03:00
struct intel_th * th = to_intel_th ( thdev ) ;
2016-03-07 17:04:45 +02:00
if ( ! thdrv )
return ;
2015-09-22 15:47:14 +03:00
if ( thdrv - > deactivate )
thdrv - > deactivate ( thdev ) ;
else
intel_th_trace_disable ( thdev ) ;
2016-04-08 17:35:04 +03:00
2017-08-25 15:47:22 +03:00
if ( th - > deactivate )
th - > deactivate ( th ) ;
2016-06-22 13:48:21 +03:00
pm_runtime_put ( & thdev - > dev ) ;
2016-04-08 17:35:04 +03:00
module_put ( thdrv - > driver . owner ) ;
2015-09-22 15:47:14 +03:00
}
static ssize_t active_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct intel_th_device * thdev = to_intel_th_device ( dev ) ;
return scnprintf ( buf , PAGE_SIZE , " %d \n " , thdev - > output . active ) ;
}
static ssize_t active_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t size )
{
struct intel_th_device * thdev = to_intel_th_device ( dev ) ;
unsigned long val ;
int ret ;
ret = kstrtoul ( buf , 10 , & val ) ;
if ( ret )
return ret ;
if ( ! ! val ! = thdev - > output . active ) {
if ( val )
ret = intel_th_output_activate ( thdev ) ;
else
intel_th_output_deactivate ( thdev ) ;
}
return ret ? ret : size ;
}
static DEVICE_ATTR_RW ( active ) ;
static struct attribute * intel_th_output_attrs [ ] = {
& dev_attr_port . attr ,
& dev_attr_active . attr ,
NULL ,
} ;
ATTRIBUTE_GROUPS ( intel_th_output ) ;
static struct device_type intel_th_output_device_type = {
. name = " intel_th_output_device " ,
. groups = intel_th_output_groups ,
. release = intel_th_device_release ,
. devnode = intel_th_output_devnode ,
} ;
static struct device_type intel_th_switch_device_type = {
. name = " intel_th_switch_device " ,
. release = intel_th_device_release ,
} ;
static struct device_type * intel_th_device_type [ ] = {
[ INTEL_TH_SOURCE ] = & intel_th_source_device_type ,
[ INTEL_TH_OUTPUT ] = & intel_th_output_device_type ,
[ INTEL_TH_SWITCH ] = & intel_th_switch_device_type ,
} ;
int intel_th_driver_register ( struct intel_th_driver * thdrv )
{
if ( ! thdrv - > probe | | ! thdrv - > remove )
return - EINVAL ;
thdrv - > driver . bus = & intel_th_bus ;
return driver_register ( & thdrv - > driver ) ;
}
EXPORT_SYMBOL_GPL ( intel_th_driver_register ) ;
void intel_th_driver_unregister ( struct intel_th_driver * thdrv )
{
driver_unregister ( & thdrv - > driver ) ;
}
EXPORT_SYMBOL_GPL ( intel_th_driver_unregister ) ;
static struct intel_th_device *
intel_th_device_alloc ( struct intel_th * th , unsigned int type , const char * name ,
int id )
{
struct device * parent ;
struct intel_th_device * thdev ;
2016-11-18 15:36:39 +02:00
if ( type = = INTEL_TH_OUTPUT )
2015-09-22 15:47:14 +03:00
parent = & th - > hub - > dev ;
2016-11-18 15:36:39 +02:00
else
parent = th - > dev ;
2015-09-22 15:47:14 +03:00
thdev = kzalloc ( sizeof ( * thdev ) + strlen ( name ) + 1 , GFP_KERNEL ) ;
if ( ! thdev )
return NULL ;
thdev - > id = id ;
thdev - > type = type ;
strcpy ( thdev - > name , name ) ;
device_initialize ( & thdev - > dev ) ;
thdev - > dev . bus = & intel_th_bus ;
thdev - > dev . type = intel_th_device_type [ type ] ;
thdev - > dev . parent = parent ;
thdev - > dev . dma_mask = parent - > dma_mask ;
thdev - > dev . dma_parms = parent - > dma_parms ;
dma_set_coherent_mask ( & thdev - > dev , parent - > coherent_dma_mask ) ;
if ( id > = 0 )
dev_set_name ( & thdev - > dev , " %d-%s%d " , th - > id , name , id ) ;
else
dev_set_name ( & thdev - > dev , " %d-%s " , th - > id , name ) ;
return thdev ;
}
static int intel_th_device_add_resources ( struct intel_th_device * thdev ,
struct resource * res , int nres )
{
struct resource * r ;
r = kmemdup ( res , sizeof ( * res ) * nres , GFP_KERNEL ) ;
if ( ! r )
return - ENOMEM ;
thdev - > resource = r ;
thdev - > num_resources = nres ;
return 0 ;
}
static void intel_th_device_remove ( struct intel_th_device * thdev )
{
device_del ( & thdev - > dev ) ;
put_device ( & thdev - > dev ) ;
}
static void intel_th_device_free ( struct intel_th_device * thdev )
{
kfree ( thdev - > resource ) ;
kfree ( thdev ) ;
}
/*
* Intel ( R ) Trace Hub subdevices
*/
2016-11-18 16:52:08 +02:00
static const struct intel_th_subdevice {
2015-09-22 15:47:14 +03:00
const char * name ;
struct resource res [ 3 ] ;
unsigned nres ;
unsigned type ;
unsigned otype ;
2018-11-12 13:30:24 +02:00
bool mknode ;
2016-02-15 19:11:55 +02:00
unsigned scrpd ;
2015-09-22 15:47:14 +03:00
int id ;
2017-08-10 18:28:38 +03:00
} intel_th_subdevices [ ] = {
2015-09-22 15:47:14 +03:00
{
. nres = 1 ,
. res = {
{
2019-05-03 11:44:48 +03:00
/* Handle TSCU and CTS from GTH driver */
2015-09-22 15:47:14 +03:00
. start = REG_GTH_OFFSET ,
2019-05-03 11:44:48 +03:00
. end = REG_CTS_OFFSET + REG_CTS_LENGTH - 1 ,
2015-09-22 15:47:14 +03:00
. flags = IORESOURCE_MEM ,
} ,
} ,
. name = " gth " ,
. type = INTEL_TH_SWITCH ,
. id = - 1 ,
} ,
{
. nres = 2 ,
. res = {
{
. start = REG_MSU_OFFSET ,
. end = REG_MSU_OFFSET + REG_MSU_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
{
. start = BUF_MSU_OFFSET ,
. end = BUF_MSU_OFFSET + BUF_MSU_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
} ,
. name = " msc " ,
. id = 0 ,
. type = INTEL_TH_OUTPUT ,
2018-11-12 13:30:24 +02:00
. mknode = true ,
2015-09-22 15:47:14 +03:00
. otype = GTH_MSU ,
2016-02-15 19:11:55 +02:00
. scrpd = SCRPD_MEM_IS_PRIM_DEST | SCRPD_MSC0_IS_ENABLED ,
2015-09-22 15:47:14 +03:00
} ,
{
. nres = 2 ,
. res = {
{
. start = REG_MSU_OFFSET ,
. end = REG_MSU_OFFSET + REG_MSU_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
{
. start = BUF_MSU_OFFSET ,
. end = BUF_MSU_OFFSET + BUF_MSU_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
} ,
. name = " msc " ,
. id = 1 ,
. type = INTEL_TH_OUTPUT ,
2018-11-12 13:30:24 +02:00
. mknode = true ,
2015-09-22 15:47:14 +03:00
. otype = GTH_MSU ,
2016-02-15 19:11:55 +02:00
. scrpd = SCRPD_MEM_IS_PRIM_DEST | SCRPD_MSC1_IS_ENABLED ,
2015-09-22 15:47:14 +03:00
} ,
{
. nres = 2 ,
. res = {
{
. start = REG_STH_OFFSET ,
. end = REG_STH_OFFSET + REG_STH_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
{
2019-05-03 11:44:36 +03:00
. start = TH_MMIO_SW ,
2015-09-22 15:47:14 +03:00
. end = 0 ,
. flags = IORESOURCE_MEM ,
} ,
} ,
. id = - 1 ,
. name = " sth " ,
. type = INTEL_TH_SOURCE ,
} ,
2019-05-03 11:44:38 +03:00
{
. nres = 2 ,
. res = {
{
. start = REG_STH_OFFSET ,
. end = REG_STH_OFFSET + REG_STH_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
{
. start = TH_MMIO_RTIT ,
. end = 0 ,
. flags = IORESOURCE_MEM ,
} ,
} ,
. id = - 1 ,
. name = " rtit " ,
. type = INTEL_TH_SOURCE ,
} ,
2015-09-22 15:47:14 +03:00
{
. nres = 1 ,
. res = {
{
. start = REG_PTI_OFFSET ,
. end = REG_PTI_OFFSET + REG_PTI_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
} ,
. id = - 1 ,
. name = " pti " ,
. type = INTEL_TH_OUTPUT ,
. otype = GTH_PTI ,
2016-02-15 19:11:55 +02:00
. scrpd = SCRPD_PTI_IS_PRIM_DEST ,
2015-09-22 15:47:14 +03:00
} ,
2016-11-11 12:09:11 +02:00
{
. nres = 1 ,
. res = {
{
. start = REG_PTI_OFFSET ,
. end = REG_PTI_OFFSET + REG_PTI_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
} ,
. id = - 1 ,
. name = " lpp " ,
. type = INTEL_TH_OUTPUT ,
. otype = GTH_LPP ,
. scrpd = SCRPD_PTI_IS_PRIM_DEST ,
} ,
2015-09-22 15:47:14 +03:00
{
. nres = 1 ,
. res = {
{
. start = REG_DCIH_OFFSET ,
. end = REG_DCIH_OFFSET + REG_DCIH_LENGTH - 1 ,
. flags = IORESOURCE_MEM ,
} ,
} ,
. id = - 1 ,
. name = " dcih " ,
. type = INTEL_TH_OUTPUT ,
} ,
} ;
2016-06-30 11:51:44 +03:00
# ifdef CONFIG_MODULES
static void __intel_th_request_hub_module ( struct work_struct * work )
{
struct intel_th * th = container_of ( work , struct intel_th ,
request_module_work ) ;
request_module ( " intel_th_%s " , th - > hub - > name ) ;
}
static int intel_th_request_hub_module ( struct intel_th * th )
{
INIT_WORK ( & th - > request_module_work , __intel_th_request_hub_module ) ;
schedule_work ( & th - > request_module_work ) ;
return 0 ;
}
static void intel_th_request_hub_module_flush ( struct intel_th * th )
{
flush_work ( & th - > request_module_work ) ;
}
# else
static inline int intel_th_request_hub_module ( struct intel_th * th )
{
return - EINVAL ;
}
static inline void intel_th_request_hub_module_flush ( struct intel_th * th )
{
}
# endif /* CONFIG_MODULES */
2017-08-10 18:28:38 +03:00
static struct intel_th_device *
intel_th_subdevice_alloc ( struct intel_th * th ,
const struct intel_th_subdevice * subdev )
2015-09-22 15:47:14 +03:00
{
2017-08-10 18:28:38 +03:00
struct intel_th_device * thdev ;
2015-09-22 15:47:14 +03:00
struct resource res [ 3 ] ;
unsigned int req = 0 ;
2017-08-10 18:28:38 +03:00
int r , err ;
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
thdev = intel_th_device_alloc ( th , subdev - > type , subdev - > name ,
subdev - > id ) ;
if ( ! thdev )
return ERR_PTR ( - ENOMEM ) ;
2015-09-22 15:47:14 +03:00
2017-08-18 17:57:35 +03:00
thdev - > drvdata = th - > drvdata ;
2016-09-19 17:07:47 +03:00
2017-08-10 18:28:38 +03:00
memcpy ( res , subdev - > res ,
sizeof ( struct resource ) * subdev - > nres ) ;
for ( r = 0 ; r < subdev - > nres ; r + + ) {
struct resource * devres = th - > resource ;
2019-05-03 11:44:36 +03:00
int bar = TH_MMIO_CONFIG ;
2017-08-10 18:28:38 +03:00
/*
* Take . end = = 0 to mean ' take the whole bar ' ,
* . start then tells us which bar it is . Default to
* TH_MMIO_CONFIG .
*/
if ( ! res [ r ] . end & & res [ r ] . flags = = IORESOURCE_MEM ) {
bar = res [ r ] . start ;
2019-05-03 11:44:37 +03:00
err = - ENODEV ;
if ( bar > = th - > num_resources )
goto fail_put_device ;
2017-08-10 18:28:38 +03:00
res [ r ] . start = 0 ;
res [ r ] . end = resource_size ( & devres [ bar ] ) - 1 ;
2015-09-22 15:47:14 +03:00
}
2017-08-10 18:28:38 +03:00
if ( res [ r ] . flags & IORESOURCE_MEM ) {
res [ r ] . start + = devres [ bar ] . start ;
res [ r ] . end + = devres [ bar ] . start ;
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
dev_dbg ( th - > dev , " %s:%d @ %pR \n " ,
subdev - > name , r , & res [ r ] ) ;
} else if ( res [ r ] . flags & IORESOURCE_IRQ ) {
2019-05-03 11:44:42 +03:00
/*
* Only pass on the IRQ if we have useful interrupts :
* the ones that can be configured via MINTCTL .
*/
if ( INTEL_TH_CAP ( th , has_mintctl ) & & th - > irq ! = - 1 )
res [ r ] . start = th - > irq ;
2017-08-10 18:28:38 +03:00
}
}
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
err = intel_th_device_add_resources ( thdev , res , subdev - > nres ) ;
2019-11-20 15:08:04 +02:00
if ( err )
2017-08-10 18:28:38 +03:00
goto fail_put_device ;
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
if ( subdev - > type = = INTEL_TH_OUTPUT ) {
2018-11-12 13:30:24 +02:00
if ( subdev - > mknode )
thdev - > dev . devt = MKDEV ( th - > major , th - > num_thdevs ) ;
2017-08-10 18:28:38 +03:00
thdev - > output . type = subdev - > otype ;
thdev - > output . port = - 1 ;
thdev - > output . scratchpad = subdev - > scrpd ;
} else if ( subdev - > type = = INTEL_TH_SWITCH ) {
2017-08-23 09:52:00 +03:00
thdev - > host_mode =
INTEL_TH_CAP ( th , host_mode_only ) ? true : host_mode ;
2017-08-10 18:28:38 +03:00
th - > hub = thdev ;
}
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
err = device_add ( & thdev - > dev ) ;
2019-11-20 15:08:04 +02:00
if ( err )
2017-08-10 18:28:38 +03:00
goto fail_free_res ;
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
/* need switch driver to be loaded to enumerate the rest */
if ( subdev - > type = = INTEL_TH_SWITCH & & ! req ) {
err = intel_th_request_hub_module ( th ) ;
if ( ! err )
req + + ;
}
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
return thdev ;
fail_free_res :
kfree ( thdev - > resource ) ;
fail_put_device :
put_device ( & thdev - > dev ) ;
return ERR_PTR ( err ) ;
}
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
/**
* intel_th_output_enable ( ) - find and enable a device for a given output type
* @ th : Intel TH instance
* @ otype : output type
*
* Go through the unallocated output devices , find the first one whos type
* matches @ otype and instantiate it . These devices are removed when the hub
* device is removed , see intel_th_remove ( ) .
*/
int intel_th_output_enable ( struct intel_th * th , unsigned int otype )
{
struct intel_th_device * thdev ;
int src = 0 , dst = 0 ;
for ( src = 0 , dst = 0 ; dst < = th - > num_thdevs ; src + + , dst + + ) {
for ( ; src < ARRAY_SIZE ( intel_th_subdevices ) ; src + + ) {
if ( intel_th_subdevices [ src ] . type ! = INTEL_TH_OUTPUT )
continue ;
if ( intel_th_subdevices [ src ] . otype ! = otype )
continue ;
break ;
2015-09-22 15:47:14 +03:00
}
2017-08-10 18:28:38 +03:00
/* no unallocated matching subdevices */
if ( src = = ARRAY_SIZE ( intel_th_subdevices ) )
return - ENODEV ;
for ( ; dst < th - > num_thdevs ; dst + + ) {
if ( th - > thdev [ dst ] - > type ! = INTEL_TH_OUTPUT )
continue ;
if ( th - > thdev [ dst ] - > output . type ! = otype )
continue ;
break ;
2015-09-22 15:47:14 +03:00
}
2017-08-10 18:28:38 +03:00
/*
* intel_th_subdevices [ src ] matches our requirements and is
* not matched in th : : thdev [ ]
*/
if ( dst = = th - > num_thdevs )
goto found ;
2015-09-22 15:47:14 +03:00
}
2017-08-10 18:28:38 +03:00
return - ENODEV ;
found :
thdev = intel_th_subdevice_alloc ( th , & intel_th_subdevices [ src ] ) ;
if ( IS_ERR ( thdev ) )
return PTR_ERR ( thdev ) ;
th - > thdev [ th - > num_thdevs + + ] = thdev ;
2015-09-22 15:47:14 +03:00
return 0 ;
2017-08-10 18:28:38 +03:00
}
EXPORT_SYMBOL_GPL ( intel_th_output_enable ) ;
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
static int intel_th_populate ( struct intel_th * th )
{
int src ;
2015-09-22 15:47:14 +03:00
2017-08-10 18:28:38 +03:00
/* create devices for each intel_th_subdevice */
for ( src = 0 ; src < ARRAY_SIZE ( intel_th_subdevices ) ; src + + ) {
const struct intel_th_subdevice * subdev =
& intel_th_subdevices [ src ] ;
struct intel_th_device * thdev ;
/* only allow SOURCE and SWITCH devices in host mode */
2017-08-23 09:52:00 +03:00
if ( ( INTEL_TH_CAP ( th , host_mode_only ) | | host_mode ) & &
subdev - > type = = INTEL_TH_OUTPUT )
2017-08-10 18:28:38 +03:00
continue ;
/*
* don ' t enable port OUTPUTs in this path ; SWITCH enables them
* via intel_th_output_enable ( )
*/
if ( subdev - > type = = INTEL_TH_OUTPUT & &
subdev - > otype ! = GTH_NONE )
continue ;
thdev = intel_th_subdevice_alloc ( th , subdev ) ;
/* note: caller should free subdevices from th::thdev[] */
2019-05-03 11:44:37 +03:00
if ( IS_ERR ( thdev ) ) {
/* ENODEV for individual subdevices is allowed */
if ( PTR_ERR ( thdev ) = = - ENODEV )
continue ;
2017-08-10 18:28:38 +03:00
return PTR_ERR ( thdev ) ;
2019-05-03 11:44:37 +03:00
}
2017-08-10 18:28:38 +03:00
th - > thdev [ th - > num_thdevs + + ] = thdev ;
}
return 0 ;
2015-09-22 15:47:14 +03:00
}
static int intel_th_output_open ( struct inode * inode , struct file * file )
{
const struct file_operations * fops ;
struct intel_th_driver * thdrv ;
struct device * dev ;
int err ;
2019-07-23 23:18:35 +01:00
dev = bus_find_device_by_devt ( & intel_th_bus , inode - > i_rdev ) ;
2015-09-22 15:47:14 +03:00
if ( ! dev | | ! dev - > driver )
return - ENODEV ;
thdrv = to_intel_th_driver ( dev - > driver ) ;
fops = fops_get ( thdrv - > fops ) ;
if ( ! fops )
return - ENODEV ;
replace_fops ( file , fops ) ;
file - > private_data = to_intel_th_device ( dev ) ;
if ( file - > f_op - > open ) {
err = file - > f_op - > open ( inode , file ) ;
return err ;
}
return 0 ;
}
static const struct file_operations intel_th_output_fops = {
. open = intel_th_output_open ,
. llseek = noop_llseek ,
} ;
2019-05-03 11:44:41 +03:00
static irqreturn_t intel_th_irq ( int irq , void * data )
{
struct intel_th * th = data ;
irqreturn_t ret = IRQ_NONE ;
struct intel_th_driver * d ;
int i ;
for ( i = 0 ; i < th - > num_thdevs ; i + + ) {
if ( th - > thdev [ i ] - > type ! = INTEL_TH_OUTPUT )
continue ;
d = to_intel_th_driver ( th - > thdev [ i ] - > dev . driver ) ;
if ( d & & d - > irq )
ret | = d - > irq ( th - > thdev [ i ] ) ;
}
return ret ;
}
2015-09-22 15:47:14 +03:00
/**
* intel_th_alloc ( ) - allocate a new Intel TH device and its subdevices
* @ dev : parent device
2019-05-03 11:44:36 +03:00
* @ devres : resources indexed by th_mmio_idx
2015-09-22 15:47:14 +03:00
* @ irq : irq number
*/
struct intel_th *
2021-04-14 20:12:47 +03:00
intel_th_alloc ( struct device * dev , const struct intel_th_drvdata * drvdata ,
2019-05-03 11:44:39 +03:00
struct resource * devres , unsigned int ndevres )
2015-09-22 15:47:14 +03:00
{
2019-05-03 11:44:39 +03:00
int err , r , nr_mmios = 0 ;
2015-09-22 15:47:14 +03:00
struct intel_th * th ;
2017-08-23 09:47:19 +03:00
2015-09-22 15:47:14 +03:00
th = kzalloc ( sizeof ( * th ) , GFP_KERNEL ) ;
if ( ! th )
return ERR_PTR ( - ENOMEM ) ;
th - > id = ida_simple_get ( & intel_th_ida , 0 , 0 , GFP_KERNEL ) ;
if ( th - > id < 0 ) {
err = th - > id ;
goto err_alloc ;
}
th - > major = __register_chrdev ( 0 , 0 , TH_POSSIBLE_OUTPUTS ,
" intel_th/output " , & intel_th_output_fops ) ;
if ( th - > major < 0 ) {
err = th - > major ;
goto err_ida ;
}
2019-05-03 11:44:39 +03:00
th - > irq = - 1 ;
2015-09-22 15:47:14 +03:00
th - > dev = dev ;
2017-08-18 17:57:35 +03:00
th - > drvdata = drvdata ;
2015-09-22 15:47:14 +03:00
2019-05-03 11:44:36 +03:00
for ( r = 0 ; r < ndevres ; r + + )
2019-05-03 11:44:39 +03:00
switch ( devres [ r ] . flags & IORESOURCE_TYPE_BITS ) {
case IORESOURCE_MEM :
th - > resource [ nr_mmios + + ] = devres [ r ] ;
break ;
case IORESOURCE_IRQ :
2019-05-03 11:44:41 +03:00
err = devm_request_irq ( dev , devres [ r ] . start ,
intel_th_irq , IRQF_SHARED ,
dev_name ( dev ) , th ) ;
if ( err )
goto err_chrdev ;
2019-05-03 11:44:39 +03:00
if ( th - > irq = = - 1 )
th - > irq = devres [ r ] . start ;
2019-12-17 13:55:26 +02:00
th - > num_irqs + + ;
2019-05-03 11:44:39 +03:00
break ;
default :
dev_warn ( dev , " Unknown resource type %lx \n " ,
devres [ r ] . flags ) ;
break ;
}
th - > num_resources = nr_mmios ;
2017-08-10 18:28:38 +03:00
2016-02-15 19:11:59 +02:00
dev_set_drvdata ( dev , th ) ;
2016-06-22 13:48:21 +03:00
pm_runtime_no_callbacks ( dev ) ;
pm_runtime_put ( dev ) ;
pm_runtime_allow ( dev ) ;
2017-08-10 18:28:38 +03:00
err = intel_th_populate ( th ) ;
if ( err ) {
/* free the subdevices and undo everything */
intel_th_free ( th ) ;
return ERR_PTR ( err ) ;
}
2015-09-22 15:47:14 +03:00
return th ;
2019-05-03 11:44:41 +03:00
err_chrdev :
__unregister_chrdev ( th - > major , 0 , TH_POSSIBLE_OUTPUTS ,
" intel_th/output " ) ;
2015-09-22 15:47:14 +03:00
err_ida :
ida_simple_remove ( & intel_th_ida , th - > id ) ;
err_alloc :
kfree ( th ) ;
return ERR_PTR ( err ) ;
}
EXPORT_SYMBOL_GPL ( intel_th_alloc ) ;
void intel_th_free ( struct intel_th * th )
{
int i ;
2016-06-30 11:51:44 +03:00
intel_th_request_hub_module_flush ( th ) ;
2015-09-22 15:47:14 +03:00
intel_th_device_remove ( th - > hub ) ;
2017-08-10 18:28:38 +03:00
for ( i = 0 ; i < th - > num_thdevs ; i + + ) {
if ( th - > thdev [ i ] ! = th - > hub )
intel_th_device_remove ( th - > thdev [ i ] ) ;
th - > thdev [ i ] = NULL ;
}
th - > num_thdevs = 0 ;
2015-09-22 15:47:14 +03:00
2019-12-17 13:55:26 +02:00
for ( i = 0 ; i < th - > num_irqs ; i + + )
devm_free_irq ( th - > dev , th - > irq + i , th ) ;
2016-06-22 13:48:21 +03:00
pm_runtime_get_sync ( th - > dev ) ;
pm_runtime_forbid ( th - > dev ) ;
2015-09-22 15:47:14 +03:00
__unregister_chrdev ( th - > major , 0 , TH_POSSIBLE_OUTPUTS ,
" intel_th/output " ) ;
ida_simple_remove ( & intel_th_ida , th - > id ) ;
kfree ( th ) ;
}
EXPORT_SYMBOL_GPL ( intel_th_free ) ;
/**
* intel_th_trace_enable ( ) - enable tracing for an output device
* @ thdev : output device that requests tracing be enabled
*/
int intel_th_trace_enable ( struct intel_th_device * thdev )
{
struct intel_th_device * hub = to_intel_th_device ( thdev - > dev . parent ) ;
struct intel_th_driver * hubdrv = to_intel_th_driver ( hub - > dev . driver ) ;
if ( WARN_ON_ONCE ( hub - > type ! = INTEL_TH_SWITCH ) )
return - EINVAL ;
if ( WARN_ON_ONCE ( thdev - > type ! = INTEL_TH_OUTPUT ) )
return - EINVAL ;
2016-06-22 13:48:21 +03:00
pm_runtime_get_sync ( & thdev - > dev ) ;
2015-09-22 15:47:14 +03:00
hubdrv - > enable ( hub , & thdev - > output ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( intel_th_trace_enable ) ;
2019-05-03 11:44:48 +03:00
/**
* intel_th_trace_switch ( ) - execute a switch sequence
* @ thdev : output device that requests tracing switch
*/
int intel_th_trace_switch ( struct intel_th_device * thdev )
{
struct intel_th_device * hub = to_intel_th_device ( thdev - > dev . parent ) ;
struct intel_th_driver * hubdrv = to_intel_th_driver ( hub - > dev . driver ) ;
if ( WARN_ON_ONCE ( hub - > type ! = INTEL_TH_SWITCH ) )
return - EINVAL ;
if ( WARN_ON_ONCE ( thdev - > type ! = INTEL_TH_OUTPUT ) )
return - EINVAL ;
hubdrv - > trig_switch ( hub , & thdev - > output ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( intel_th_trace_switch ) ;
2015-09-22 15:47:14 +03:00
/**
* intel_th_trace_disable ( ) - disable tracing for an output device
* @ thdev : output device that requests tracing be disabled
*/
int intel_th_trace_disable ( struct intel_th_device * thdev )
{
struct intel_th_device * hub = to_intel_th_device ( thdev - > dev . parent ) ;
struct intel_th_driver * hubdrv = to_intel_th_driver ( hub - > dev . driver ) ;
WARN_ON_ONCE ( hub - > type ! = INTEL_TH_SWITCH ) ;
if ( WARN_ON_ONCE ( thdev - > type ! = INTEL_TH_OUTPUT ) )
return - EINVAL ;
hubdrv - > disable ( hub , & thdev - > output ) ;
2016-06-22 13:48:21 +03:00
pm_runtime_put ( & thdev - > dev ) ;
2015-09-22 15:47:14 +03:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( intel_th_trace_disable ) ;
int intel_th_set_output ( struct intel_th_device * thdev ,
unsigned int master )
{
2018-03-01 10:15:32 +02:00
struct intel_th_device * hub = to_intel_th_hub ( thdev ) ;
2015-09-22 15:47:14 +03:00
struct intel_th_driver * hubdrv = to_intel_th_driver ( hub - > dev . driver ) ;
2020-07-06 19:13:39 +03:00
int ret ;
2015-09-22 15:47:14 +03:00
2018-03-01 10:17:25 +02:00
/* In host mode, this is up to the external debugger, do nothing. */
if ( hub - > host_mode )
return 0 ;
2020-07-06 19:13:39 +03:00
/*
* hub is instantiated together with the source device that
* calls here , so guaranteed to be present .
*/
hubdrv = to_intel_th_driver ( hub - > dev . driver ) ;
if ( ! hubdrv | | ! try_module_get ( hubdrv - > driver . owner ) )
return - EINVAL ;
if ( ! hubdrv - > set_output ) {
ret = - ENOTSUPP ;
goto out ;
}
ret = hubdrv - > set_output ( hub , master ) ;
2015-09-22 15:47:14 +03:00
2020-07-06 19:13:39 +03:00
out :
module_put ( hubdrv - > driver . owner ) ;
return ret ;
2015-09-22 15:47:14 +03:00
}
EXPORT_SYMBOL_GPL ( intel_th_set_output ) ;
static int __init intel_th_init ( void )
{
intel_th_debug_init ( ) ;
return bus_register ( & intel_th_bus ) ;
}
subsys_initcall ( intel_th_init ) ;
static void __exit intel_th_exit ( void )
{
intel_th_debug_done ( ) ;
bus_unregister ( & intel_th_bus ) ;
}
module_exit ( intel_th_exit ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " Intel(R) Trace Hub controller driver " ) ;
MODULE_AUTHOR ( " Alexander Shishkin <alexander.shishkin@linux.intel.com> " ) ;