2013-10-14 16:43:22 +04:00
/*
* Copyright ( C ) 2012 Avionic Design GmbH
* Copyright ( C ) 2012 - 2013 , NVIDIA Corporation
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/host1x.h>
# include <linux/of.h>
# include <linux/slab.h>
2013-11-08 16:20:23 +04:00
# include "bus.h"
2013-10-14 16:43:22 +04:00
# include "dev.h"
static DEFINE_MUTEX ( clients_lock ) ;
static LIST_HEAD ( clients ) ;
static DEFINE_MUTEX ( drivers_lock ) ;
static LIST_HEAD ( drivers ) ;
static DEFINE_MUTEX ( devices_lock ) ;
static LIST_HEAD ( devices ) ;
struct host1x_subdev {
struct host1x_client * client ;
struct device_node * np ;
struct list_head list ;
} ;
/**
* host1x_subdev_add ( ) - add a new subdevice with an associated device node
*/
static int host1x_subdev_add ( struct host1x_device * device ,
struct device_node * np )
{
struct host1x_subdev * subdev ;
subdev = kzalloc ( sizeof ( * subdev ) , GFP_KERNEL ) ;
if ( ! subdev )
return - ENOMEM ;
INIT_LIST_HEAD ( & subdev - > list ) ;
subdev - > np = of_node_get ( np ) ;
mutex_lock ( & device - > subdevs_lock ) ;
list_add_tail ( & subdev - > list , & device - > subdevs ) ;
mutex_unlock ( & device - > subdevs_lock ) ;
return 0 ;
}
/**
* host1x_subdev_del ( ) - remove subdevice
*/
static void host1x_subdev_del ( struct host1x_subdev * subdev )
{
list_del ( & subdev - > list ) ;
of_node_put ( subdev - > np ) ;
kfree ( subdev ) ;
}
/**
* host1x_device_parse_dt ( ) - scan device tree and add matching subdevices
*/
static int host1x_device_parse_dt ( struct host1x_device * device )
{
struct device_node * np ;
int err ;
for_each_child_of_node ( device - > dev . parent - > of_node , np ) {
if ( of_match_node ( device - > driver - > subdevs , np ) & &
of_device_is_available ( np ) ) {
err = host1x_subdev_add ( device , np ) ;
if ( err < 0 )
return err ;
}
}
return 0 ;
}
static void host1x_subdev_register ( struct host1x_device * device ,
struct host1x_subdev * subdev ,
struct host1x_client * client )
{
int err ;
/*
* Move the subdevice to the list of active ( registered ) subdevices
* and associate it with a client . At the same time , associate the
* client with its parent device .
*/
mutex_lock ( & device - > subdevs_lock ) ;
mutex_lock ( & device - > clients_lock ) ;
list_move_tail ( & client - > list , & device - > clients ) ;
list_move_tail ( & subdev - > list , & device - > active ) ;
client - > parent = & device - > dev ;
subdev - > client = client ;
mutex_unlock ( & device - > clients_lock ) ;
mutex_unlock ( & device - > subdevs_lock ) ;
/*
* When all subdevices have been registered , the composite device is
* ready to be probed .
*/
if ( list_empty ( & device - > subdevs ) ) {
err = device - > driver - > probe ( device ) ;
if ( err < 0 )
2014-11-05 13:43:26 +03:00
dev_err ( & device - > dev , " probe failed for %ps: %d \n " ,
device - > driver , err ) ;
else
device - > bound = true ;
2013-10-14 16:43:22 +04:00
}
}
static void __host1x_subdev_unregister ( struct host1x_device * device ,
struct host1x_subdev * subdev )
{
struct host1x_client * client = subdev - > client ;
int err ;
/*
* If all subdevices have been activated , we ' re about to remove the
* first active subdevice , so unload the driver first .
*/
2014-11-05 13:43:26 +03:00
if ( list_empty ( & device - > subdevs ) & & device - > bound ) {
2013-10-14 16:43:22 +04:00
err = device - > driver - > remove ( device ) ;
if ( err < 0 )
dev_err ( & device - > dev , " remove failed: %d \n " , err ) ;
2014-11-05 13:43:26 +03:00
device - > bound = false ;
2013-10-14 16:43:22 +04:00
}
/*
* Move the subdevice back to the list of idle subdevices and remove
* it from list of clients .
*/
mutex_lock ( & device - > clients_lock ) ;
subdev - > client = NULL ;
client - > parent = NULL ;
list_move_tail ( & subdev - > list , & device - > subdevs ) ;
/*
* XXX : Perhaps don ' t do this here , but rather explicitly remove it
* when the device is about to be deleted .
*
* This is somewhat complicated by the fact that this function is
* used to remove the subdevice when a client is unregistered but
* also when the composite device is about to be removed .
*/
list_del_init ( & client - > list ) ;
mutex_unlock ( & device - > clients_lock ) ;
}
static void host1x_subdev_unregister ( struct host1x_device * device ,
struct host1x_subdev * subdev )
{
mutex_lock ( & device - > subdevs_lock ) ;
__host1x_subdev_unregister ( device , subdev ) ;
mutex_unlock ( & device - > subdevs_lock ) ;
}
int host1x_device_init ( struct host1x_device * device )
{
struct host1x_client * client ;
int err ;
mutex_lock ( & device - > clients_lock ) ;
list_for_each_entry ( client , & device - > clients , list ) {
if ( client - > ops & & client - > ops - > init ) {
err = client - > ops - > init ( client ) ;
if ( err < 0 ) {
dev_err ( & device - > dev ,
" failed to initialize %s: %d \n " ,
dev_name ( client - > dev ) , err ) ;
mutex_unlock ( & device - > clients_lock ) ;
return err ;
}
}
}
mutex_unlock ( & device - > clients_lock ) ;
return 0 ;
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_device_init ) ;
2013-10-14 16:43:22 +04:00
int host1x_device_exit ( struct host1x_device * device )
{
struct host1x_client * client ;
int err ;
mutex_lock ( & device - > clients_lock ) ;
list_for_each_entry_reverse ( client , & device - > clients , list ) {
if ( client - > ops & & client - > ops - > exit ) {
err = client - > ops - > exit ( client ) ;
if ( err < 0 ) {
dev_err ( & device - > dev ,
" failed to cleanup %s: %d \n " ,
dev_name ( client - > dev ) , err ) ;
mutex_unlock ( & device - > clients_lock ) ;
return err ;
}
}
}
mutex_unlock ( & device - > clients_lock ) ;
return 0 ;
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_device_exit ) ;
2013-10-14 16:43:22 +04:00
2014-05-22 13:12:17 +04:00
static int host1x_add_client ( struct host1x * host1x ,
struct host1x_client * client )
2013-10-14 16:43:22 +04:00
{
struct host1x_device * device ;
struct host1x_subdev * subdev ;
mutex_lock ( & host1x - > devices_lock ) ;
list_for_each_entry ( device , & host1x - > devices , list ) {
list_for_each_entry ( subdev , & device - > subdevs , list ) {
if ( subdev - > np = = client - > dev - > of_node ) {
host1x_subdev_register ( device , subdev , client ) ;
mutex_unlock ( & host1x - > devices_lock ) ;
return 0 ;
}
}
}
mutex_unlock ( & host1x - > devices_lock ) ;
return - ENODEV ;
}
2014-05-22 13:12:17 +04:00
static int host1x_del_client ( struct host1x * host1x ,
struct host1x_client * client )
2013-10-14 16:43:22 +04:00
{
struct host1x_device * device , * dt ;
struct host1x_subdev * subdev ;
mutex_lock ( & host1x - > devices_lock ) ;
list_for_each_entry_safe ( device , dt , & host1x - > devices , list ) {
list_for_each_entry ( subdev , & device - > active , list ) {
if ( subdev - > client = = client ) {
host1x_subdev_unregister ( device , subdev ) ;
mutex_unlock ( & host1x - > devices_lock ) ;
return 0 ;
}
}
}
mutex_unlock ( & host1x - > devices_lock ) ;
return - ENODEV ;
}
2013-11-08 16:20:23 +04:00
static struct bus_type host1x_bus_type = {
2013-10-14 16:43:22 +04:00
. name = " host1x " ,
} ;
int host1x_bus_init ( void )
{
return bus_register ( & host1x_bus_type ) ;
}
void host1x_bus_exit ( void )
{
bus_unregister ( & host1x_bus_type ) ;
}
static void host1x_device_release ( struct device * dev )
{
struct host1x_device * device = to_host1x_device ( dev ) ;
kfree ( device ) ;
}
static int host1x_device_add ( struct host1x * host1x ,
struct host1x_driver * driver )
{
struct host1x_client * client , * tmp ;
struct host1x_subdev * subdev ;
struct host1x_device * device ;
int err ;
device = kzalloc ( sizeof ( * device ) , GFP_KERNEL ) ;
if ( ! device )
return - ENOMEM ;
mutex_init ( & device - > subdevs_lock ) ;
INIT_LIST_HEAD ( & device - > subdevs ) ;
INIT_LIST_HEAD ( & device - > active ) ;
mutex_init ( & device - > clients_lock ) ;
INIT_LIST_HEAD ( & device - > clients ) ;
INIT_LIST_HEAD ( & device - > list ) ;
device - > driver = driver ;
device - > dev . coherent_dma_mask = host1x - > dev - > coherent_dma_mask ;
device - > dev . dma_mask = & device - > dev . coherent_dma_mask ;
device - > dev . release = host1x_device_release ;
2013-11-08 16:20:23 +04:00
dev_set_name ( & device - > dev , " %s " , driver - > name ) ;
2013-10-14 16:43:22 +04:00
device - > dev . bus = & host1x_bus_type ;
device - > dev . parent = host1x - > dev ;
err = device_register ( & device - > dev ) ;
if ( err < 0 )
return err ;
err = host1x_device_parse_dt ( device ) ;
if ( err < 0 ) {
device_unregister ( & device - > dev ) ;
return err ;
}
list_add_tail ( & device - > list , & host1x - > devices ) ;
mutex_lock ( & clients_lock ) ;
list_for_each_entry_safe ( client , tmp , & clients , list ) {
list_for_each_entry ( subdev , & device - > subdevs , list ) {
if ( subdev - > np = = client - > dev - > of_node ) {
host1x_subdev_register ( device , subdev , client ) ;
break ;
}
}
}
mutex_unlock ( & clients_lock ) ;
return 0 ;
}
/*
* Removes a device by first unregistering any subdevices and then removing
* itself from the list of devices .
*
* This function must be called with the host1x - > devices_lock held .
*/
static void host1x_device_del ( struct host1x * host1x ,
struct host1x_device * device )
{
struct host1x_subdev * subdev , * sd ;
struct host1x_client * client , * cl ;
mutex_lock ( & device - > subdevs_lock ) ;
/* unregister subdevices */
list_for_each_entry_safe ( subdev , sd , & device - > active , list ) {
/*
* host1x_subdev_unregister ( ) will remove the client from
* any lists , so we ' ll need to manually add it back to the
* list of idle clients .
*
* XXX : Alternatively , perhaps don ' t remove the client from
* any lists in host1x_subdev_unregister ( ) and instead do
* that explicitly from host1x_unregister_client ( ) ?
*/
client = subdev - > client ;
__host1x_subdev_unregister ( device , subdev ) ;
/* add the client to the list of idle clients */
mutex_lock ( & clients_lock ) ;
list_add_tail ( & client - > list , & clients ) ;
mutex_unlock ( & clients_lock ) ;
}
/* remove subdevices */
list_for_each_entry_safe ( subdev , sd , & device - > subdevs , list )
host1x_subdev_del ( subdev ) ;
mutex_unlock ( & device - > subdevs_lock ) ;
/* move clients to idle list */
mutex_lock ( & clients_lock ) ;
mutex_lock ( & device - > clients_lock ) ;
list_for_each_entry_safe ( client , cl , & device - > clients , list )
list_move_tail ( & client - > list , & clients ) ;
mutex_unlock ( & device - > clients_lock ) ;
mutex_unlock ( & clients_lock ) ;
/* finally remove the device */
list_del_init ( & device - > list ) ;
device_unregister ( & device - > dev ) ;
}
static void host1x_attach_driver ( struct host1x * host1x ,
struct host1x_driver * driver )
{
struct host1x_device * device ;
int err ;
mutex_lock ( & host1x - > devices_lock ) ;
list_for_each_entry ( device , & host1x - > devices , list ) {
if ( device - > driver = = driver ) {
mutex_unlock ( & host1x - > devices_lock ) ;
return ;
}
}
err = host1x_device_add ( host1x , driver ) ;
if ( err < 0 )
dev_err ( host1x - > dev , " failed to allocate device: %d \n " , err ) ;
2014-12-18 17:06:56 +03:00
mutex_unlock ( & host1x - > devices_lock ) ;
2013-10-14 16:43:22 +04:00
}
static void host1x_detach_driver ( struct host1x * host1x ,
struct host1x_driver * driver )
{
struct host1x_device * device , * tmp ;
mutex_lock ( & host1x - > devices_lock ) ;
list_for_each_entry_safe ( device , tmp , & host1x - > devices , list )
if ( device - > driver = = driver )
host1x_device_del ( host1x , device ) ;
mutex_unlock ( & host1x - > devices_lock ) ;
}
int host1x_register ( struct host1x * host1x )
{
struct host1x_driver * driver ;
mutex_lock ( & devices_lock ) ;
list_add_tail ( & host1x - > list , & devices ) ;
mutex_unlock ( & devices_lock ) ;
mutex_lock ( & drivers_lock ) ;
list_for_each_entry ( driver , & drivers , list )
host1x_attach_driver ( host1x , driver ) ;
mutex_unlock ( & drivers_lock ) ;
return 0 ;
}
int host1x_unregister ( struct host1x * host1x )
{
struct host1x_driver * driver ;
mutex_lock ( & drivers_lock ) ;
list_for_each_entry ( driver , & drivers , list )
host1x_detach_driver ( host1x , driver ) ;
mutex_unlock ( & drivers_lock ) ;
mutex_lock ( & devices_lock ) ;
list_del_init ( & host1x - > list ) ;
mutex_unlock ( & devices_lock ) ;
return 0 ;
}
int host1x_driver_register ( struct host1x_driver * driver )
{
struct host1x * host1x ;
INIT_LIST_HEAD ( & driver - > list ) ;
mutex_lock ( & drivers_lock ) ;
list_add_tail ( & driver - > list , & drivers ) ;
mutex_unlock ( & drivers_lock ) ;
mutex_lock ( & devices_lock ) ;
list_for_each_entry ( host1x , & devices , list )
host1x_attach_driver ( host1x , driver ) ;
mutex_unlock ( & devices_lock ) ;
return 0 ;
}
EXPORT_SYMBOL ( host1x_driver_register ) ;
void host1x_driver_unregister ( struct host1x_driver * driver )
{
mutex_lock ( & drivers_lock ) ;
list_del_init ( & driver - > list ) ;
mutex_unlock ( & drivers_lock ) ;
}
EXPORT_SYMBOL ( host1x_driver_unregister ) ;
int host1x_client_register ( struct host1x_client * client )
{
struct host1x * host1x ;
int err ;
mutex_lock ( & devices_lock ) ;
list_for_each_entry ( host1x , & devices , list ) {
2014-05-22 13:12:17 +04:00
err = host1x_add_client ( host1x , client ) ;
2013-10-14 16:43:22 +04:00
if ( ! err ) {
mutex_unlock ( & devices_lock ) ;
return 0 ;
}
}
mutex_unlock ( & devices_lock ) ;
mutex_lock ( & clients_lock ) ;
list_add_tail ( & client - > list , & clients ) ;
mutex_unlock ( & clients_lock ) ;
return 0 ;
}
EXPORT_SYMBOL ( host1x_client_register ) ;
int host1x_client_unregister ( struct host1x_client * client )
{
struct host1x_client * c ;
struct host1x * host1x ;
int err ;
mutex_lock ( & devices_lock ) ;
list_for_each_entry ( host1x , & devices , list ) {
2014-05-22 13:12:17 +04:00
err = host1x_del_client ( host1x , client ) ;
2013-10-14 16:43:22 +04:00
if ( ! err ) {
mutex_unlock ( & devices_lock ) ;
return 0 ;
}
}
mutex_unlock ( & devices_lock ) ;
mutex_lock ( & clients_lock ) ;
list_for_each_entry ( c , & clients , list ) {
if ( c = = client ) {
list_del_init ( & c - > list ) ;
break ;
}
}
mutex_unlock ( & clients_lock ) ;
return 0 ;
}
EXPORT_SYMBOL ( host1x_client_unregister ) ;