2014-02-26 20:29:05 +04:00
/*
* MEN Chameleon Bus .
*
* Copyright ( C ) 2013 MEN Mikroelektronik GmbH ( www . men . de )
* Author : Johannes Thumshirn < johannes . thumshirn @ men . de >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation ; version 2 of the License .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/idr.h>
# include <linux/mcb.h>
static DEFINE_IDA ( mcb_ida ) ;
static const struct mcb_device_id * mcb_match_id ( const struct mcb_device_id * ids ,
struct mcb_device * dev )
{
if ( ids ) {
while ( ids - > device ) {
if ( ids - > device = = dev - > id )
return ids ;
ids + + ;
}
}
return NULL ;
}
static int mcb_match ( struct device * dev , struct device_driver * drv )
{
struct mcb_driver * mdrv = to_mcb_driver ( drv ) ;
struct mcb_device * mdev = to_mcb_device ( dev ) ;
const struct mcb_device_id * found_id ;
found_id = mcb_match_id ( mdrv - > id_table , mdev ) ;
if ( found_id )
return 1 ;
return 0 ;
}
static int mcb_uevent ( struct device * dev , struct kobj_uevent_env * env )
{
struct mcb_device * mdev = to_mcb_device ( dev ) ;
int ret ;
ret = add_uevent_var ( env , " MODALIAS=mcb:16z%03d " , mdev - > id ) ;
if ( ret )
return - ENOMEM ;
return 0 ;
}
static int mcb_probe ( struct device * dev )
{
struct mcb_driver * mdrv = to_mcb_driver ( dev - > driver ) ;
struct mcb_device * mdev = to_mcb_device ( dev ) ;
const struct mcb_device_id * found_id ;
2016-05-10 13:39:45 +03:00
struct module * carrier_mod ;
int ret ;
2014-02-26 20:29:05 +04:00
found_id = mcb_match_id ( mdrv - > id_table , mdev ) ;
if ( ! found_id )
return - ENODEV ;
2016-05-10 13:39:45 +03:00
carrier_mod = mdev - > dev . parent - > driver - > owner ;
if ( ! try_module_get ( carrier_mod ) )
return - EINVAL ;
2016-05-10 13:39:44 +03:00
get_device ( dev ) ;
2016-05-10 13:39:45 +03:00
ret = mdrv - > probe ( mdev , found_id ) ;
if ( ret )
module_put ( carrier_mod ) ;
return ret ;
2014-02-26 20:29:05 +04:00
}
static int mcb_remove ( struct device * dev )
{
struct mcb_driver * mdrv = to_mcb_driver ( dev - > driver ) ;
struct mcb_device * mdev = to_mcb_device ( dev ) ;
2016-05-10 13:39:45 +03:00
struct module * carrier_mod ;
2014-02-26 20:29:05 +04:00
mdrv - > remove ( mdev ) ;
2016-05-10 13:39:45 +03:00
carrier_mod = mdev - > dev . parent - > driver - > owner ;
module_put ( carrier_mod ) ;
2014-02-26 20:29:05 +04:00
put_device ( & mdev - > dev ) ;
return 0 ;
}
static void mcb_shutdown ( struct device * dev )
{
2016-05-03 13:42:03 +03:00
struct mcb_driver * mdrv = to_mcb_driver ( dev - > driver ) ;
2014-02-26 20:29:05 +04:00
struct mcb_device * mdev = to_mcb_device ( dev ) ;
if ( mdrv & & mdrv - > shutdown )
mdrv - > shutdown ( mdev ) ;
}
2016-05-03 10:46:23 +03:00
static ssize_t revision_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct mcb_bus * bus = to_mcb_bus ( dev ) ;
return scnprintf ( buf , PAGE_SIZE , " %d \n " , bus - > revision ) ;
}
static DEVICE_ATTR_RO ( revision ) ;
static ssize_t model_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct mcb_bus * bus = to_mcb_bus ( dev ) ;
return scnprintf ( buf , PAGE_SIZE , " %c \n " , bus - > model ) ;
}
static DEVICE_ATTR_RO ( model ) ;
static ssize_t minor_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct mcb_bus * bus = to_mcb_bus ( dev ) ;
return scnprintf ( buf , PAGE_SIZE , " %d \n " , bus - > minor ) ;
}
static DEVICE_ATTR_RO ( minor ) ;
static ssize_t name_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct mcb_bus * bus = to_mcb_bus ( dev ) ;
return scnprintf ( buf , PAGE_SIZE , " %s \n " , bus - > name ) ;
}
static DEVICE_ATTR_RO ( name ) ;
static struct attribute * mcb_bus_attrs [ ] = {
& dev_attr_revision . attr ,
& dev_attr_model . attr ,
& dev_attr_minor . attr ,
& dev_attr_name . attr ,
NULL ,
} ;
static const struct attribute_group mcb_carrier_group = {
. attrs = mcb_bus_attrs ,
} ;
static const struct attribute_group * mcb_carrier_groups [ ] = {
& mcb_carrier_group ,
NULL ,
} ;
2014-02-26 20:29:05 +04:00
static struct bus_type mcb_bus_type = {
. name = " mcb " ,
. match = mcb_match ,
. uevent = mcb_uevent ,
. probe = mcb_probe ,
. remove = mcb_remove ,
. shutdown = mcb_shutdown ,
} ;
2016-05-03 10:46:23 +03:00
static struct device_type mcb_carrier_device_type = {
. name = " mcb-carrier " ,
. groups = mcb_carrier_groups ,
} ;
2014-02-26 20:29:05 +04:00
/**
* __mcb_register_driver ( ) - Register a @ mcb_driver at the system
* @ drv : The @ mcb_driver
* @ owner : The @ mcb_driver ' s module
* @ mod_name : The name of the @ mcb_driver ' s module
*
* Register a @ mcb_driver at the system . Perform some sanity checks , if
* the . probe and . remove methods are provided by the driver .
*/
int __mcb_register_driver ( struct mcb_driver * drv , struct module * owner ,
const char * mod_name )
{
if ( ! drv - > probe | | ! drv - > remove )
return - EINVAL ;
drv - > driver . owner = owner ;
drv - > driver . bus = & mcb_bus_type ;
drv - > driver . mod_name = mod_name ;
return driver_register ( & drv - > driver ) ;
}
EXPORT_SYMBOL_GPL ( __mcb_register_driver ) ;
/**
* mcb_unregister_driver ( ) - Unregister a @ mcb_driver from the system
* @ drv : The @ mcb_driver
*
* Unregister a @ mcb_driver from the system .
*/
void mcb_unregister_driver ( struct mcb_driver * drv )
{
driver_unregister ( & drv - > driver ) ;
}
EXPORT_SYMBOL_GPL ( mcb_unregister_driver ) ;
static void mcb_release_dev ( struct device * dev )
{
struct mcb_device * mdev = to_mcb_device ( dev ) ;
mcb_bus_put ( mdev - > bus ) ;
kfree ( mdev ) ;
}
/**
* mcb_device_register ( ) - Register a mcb_device
* @ bus : The @ mcb_bus of the device
* @ dev : The @ mcb_device
*
* Register a specific @ mcb_device at a @ mcb_bus and the system itself .
*/
int mcb_device_register ( struct mcb_bus * bus , struct mcb_device * dev )
{
int ret ;
int device_id ;
device_initialize ( & dev - > dev ) ;
2016-05-03 13:42:03 +03:00
mcb_bus_get ( bus ) ;
2014-02-26 20:29:05 +04:00
dev - > dev . bus = & mcb_bus_type ;
dev - > dev . parent = bus - > dev . parent ;
dev - > dev . release = mcb_release_dev ;
2016-09-14 13:05:24 +03:00
dev - > dma_dev = bus - > carrier ;
2014-02-26 20:29:05 +04:00
device_id = dev - > id ;
dev_set_name ( & dev - > dev , " mcb%d-16z%03d-%d:%d:%d " ,
bus - > bus_nr , device_id , dev - > inst , dev - > group , dev - > var ) ;
ret = device_add ( & dev - > dev ) ;
if ( ret < 0 ) {
pr_err ( " Failed registering device 16z%03d on bus mcb%d (%d) \n " ,
device_id , bus - > bus_nr , ret ) ;
goto out ;
}
return 0 ;
out :
return ret ;
}
EXPORT_SYMBOL_GPL ( mcb_device_register ) ;
2016-05-03 13:42:03 +03:00
static void mcb_free_bus ( struct device * dev )
{
struct mcb_bus * bus = to_mcb_bus ( dev ) ;
put_device ( bus - > carrier ) ;
ida_simple_remove ( & mcb_ida , bus - > bus_nr ) ;
kfree ( bus ) ;
}
2014-02-26 20:29:05 +04:00
/**
* mcb_alloc_bus ( ) - Allocate a new @ mcb_bus
*
* Allocate a new @ mcb_bus .
*/
2014-04-24 16:35:25 +04:00
struct mcb_bus * mcb_alloc_bus ( struct device * carrier )
2014-02-26 20:29:05 +04:00
{
struct mcb_bus * bus ;
int bus_nr ;
2016-05-03 10:46:22 +03:00
int rc ;
2014-02-26 20:29:05 +04:00
bus = kzalloc ( sizeof ( struct mcb_bus ) , GFP_KERNEL ) ;
if ( ! bus )
2014-04-24 16:35:25 +04:00
return ERR_PTR ( - ENOMEM ) ;
2014-02-26 20:29:05 +04:00
bus_nr = ida_simple_get ( & mcb_ida , 0 , 0 , GFP_KERNEL ) ;
if ( bus_nr < 0 ) {
2016-05-03 10:46:22 +03:00
rc = bus_nr ;
goto err_free ;
2014-02-26 20:29:05 +04:00
}
bus - > bus_nr = bus_nr ;
2016-05-03 13:42:03 +03:00
bus - > carrier = get_device ( carrier ) ;
2016-05-03 10:46:22 +03:00
device_initialize ( & bus - > dev ) ;
bus - > dev . parent = carrier ;
bus - > dev . bus = & mcb_bus_type ;
2016-05-03 10:46:23 +03:00
bus - > dev . type = & mcb_carrier_device_type ;
2016-05-03 13:42:03 +03:00
bus - > dev . release = & mcb_free_bus ;
2016-05-03 10:46:22 +03:00
dev_set_name ( & bus - > dev , " mcb:%d " , bus_nr ) ;
rc = device_add ( & bus - > dev ) ;
if ( rc )
goto err_free ;
2014-02-26 20:29:05 +04:00
return bus ;
2016-05-03 10:46:22 +03:00
err_free :
2016-05-03 13:42:03 +03:00
put_device ( carrier ) ;
2016-05-03 10:46:22 +03:00
kfree ( bus ) ;
return ERR_PTR ( rc ) ;
2014-02-26 20:29:05 +04:00
}
EXPORT_SYMBOL_GPL ( mcb_alloc_bus ) ;
static int __mcb_devices_unregister ( struct device * dev , void * data )
{
device_unregister ( dev ) ;
return 0 ;
}
static void mcb_devices_unregister ( struct mcb_bus * bus )
{
bus_for_each_dev ( & mcb_bus_type , NULL , NULL , __mcb_devices_unregister ) ;
}
/**
* mcb_release_bus ( ) - Free a @ mcb_bus
* @ bus : The @ mcb_bus to release
*
* Release an allocated @ mcb_bus from the system .
*/
void mcb_release_bus ( struct mcb_bus * bus )
{
mcb_devices_unregister ( bus ) ;
}
EXPORT_SYMBOL_GPL ( mcb_release_bus ) ;
/**
* mcb_bus_put ( ) - Increment refcnt
* @ bus : The @ mcb_bus
*
* Get a @ mcb_bus ' ref
*/
struct mcb_bus * mcb_bus_get ( struct mcb_bus * bus )
{
if ( bus )
get_device ( & bus - > dev ) ;
return bus ;
}
EXPORT_SYMBOL_GPL ( mcb_bus_get ) ;
/**
* mcb_bus_put ( ) - Decrement refcnt
* @ bus : The @ mcb_bus
*
* Release a @ mcb_bus ' ref
*/
void mcb_bus_put ( struct mcb_bus * bus )
{
if ( bus )
put_device ( & bus - > dev ) ;
}
EXPORT_SYMBOL_GPL ( mcb_bus_put ) ;
/**
* mcb_alloc_dev ( ) - Allocate a device
* @ bus : The @ mcb_bus the device is part of
*
* Allocate a @ mcb_device and add bus .
*/
struct mcb_device * mcb_alloc_dev ( struct mcb_bus * bus )
{
struct mcb_device * dev ;
dev = kzalloc ( sizeof ( struct mcb_device ) , GFP_KERNEL ) ;
if ( ! dev )
return NULL ;
dev - > bus = bus ;
return dev ;
}
EXPORT_SYMBOL_GPL ( mcb_alloc_dev ) ;
/**
* mcb_free_dev ( ) - Free @ mcb_device
* @ dev : The device to free
*
* Free a @ mcb_device
*/
void mcb_free_dev ( struct mcb_device * dev )
{
kfree ( dev ) ;
}
EXPORT_SYMBOL_GPL ( mcb_free_dev ) ;
static int __mcb_bus_add_devices ( struct device * dev , void * data )
{
struct mcb_device * mdev = to_mcb_device ( dev ) ;
int retval ;
if ( mdev - > is_added )
return 0 ;
retval = device_attach ( dev ) ;
if ( retval < 0 )
dev_err ( dev , " Error adding device (%d) \n " , retval ) ;
mdev - > is_added = true ;
return 0 ;
}
/**
* mcb_bus_add_devices ( ) - Add devices in the bus ' internal device list
* @ bus : The @ mcb_bus we add the devices
*
* Add devices in the bus ' internal device list to the system .
*/
void mcb_bus_add_devices ( const struct mcb_bus * bus )
{
bus_for_each_dev ( & mcb_bus_type , NULL , NULL , __mcb_bus_add_devices ) ;
}
EXPORT_SYMBOL_GPL ( mcb_bus_add_devices ) ;
/**
* mcb_request_mem ( ) - Request memory
* @ dev : The @ mcb_device the memory is for
* @ name : The name for the memory reference .
*
* Request memory for a @ mcb_device . If @ name is NULL the driver name will
* be used .
*/
struct resource * mcb_request_mem ( struct mcb_device * dev , const char * name )
{
struct resource * mem ;
u32 size ;
if ( ! name )
name = dev - > dev . driver - > name ;
size = resource_size ( & dev - > mem ) ;
mem = request_mem_region ( dev - > mem . start , size , name ) ;
if ( ! mem )
return ERR_PTR ( - EBUSY ) ;
return mem ;
}
EXPORT_SYMBOL_GPL ( mcb_request_mem ) ;
/**
* mcb_release_mem ( ) - Release memory requested by device
* @ dev : The @ mcb_device that requested the memory
*
* Release memory that was prior requested via @ mcb_request_mem ( ) .
*/
void mcb_release_mem ( struct resource * mem )
{
u32 size ;
size = resource_size ( mem ) ;
release_mem_region ( mem - > start , size ) ;
}
EXPORT_SYMBOL_GPL ( mcb_release_mem ) ;
2014-04-24 16:35:25 +04:00
static int __mcb_get_irq ( struct mcb_device * dev )
{
struct resource * irq = & dev - > irq ;
return irq - > start ;
}
2014-02-26 20:29:05 +04:00
/**
* mcb_get_irq ( ) - Get device ' s IRQ number
* @ dev : The @ mcb_device the IRQ is for
*
* Get the IRQ number of a given @ mcb_device .
*/
int mcb_get_irq ( struct mcb_device * dev )
{
2014-04-24 16:35:25 +04:00
struct mcb_bus * bus = dev - > bus ;
2014-02-26 20:29:05 +04:00
2014-04-24 16:35:25 +04:00
if ( bus - > get_irq )
return bus - > get_irq ( dev ) ;
return __mcb_get_irq ( dev ) ;
2014-02-26 20:29:05 +04:00
}
EXPORT_SYMBOL_GPL ( mcb_get_irq ) ;
static int mcb_init ( void )
{
return bus_register ( & mcb_bus_type ) ;
}
static void mcb_exit ( void )
{
2015-10-28 10:22:16 +03:00
ida_destroy ( & mcb_ida ) ;
2014-02-26 20:29:05 +04:00
bus_unregister ( & mcb_bus_type ) ;
}
/* mcb must be initialized after PCI but before the chameleon drivers.
* That means we must use some initcall between subsys_initcall and
* device_initcall .
*/
fs_initcall ( mcb_init ) ;
module_exit ( mcb_exit ) ;
MODULE_DESCRIPTION ( " MEN Chameleon Bus Driver " ) ;
MODULE_AUTHOR ( " Johannes Thumshirn <johannes.thumshirn@men.de> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;