1021 lines · 24.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (C) 2012 Avionic Design GmbH * Copyright (C) 2012-2013, NVIDIA Corporation */ #include <linux/debugfs.h>#include <linux/dma-mapping.h>#include <linux/host1x.h>#include <linux/of.h>#include <linux/seq_file.h>#include <linux/slab.h>#include <linux/of_device.h> #include "bus.h"#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 * @device: host1x device to add the subdevice to * @driver: host1x driver containing the subdevices * @np: device node */static int host1x_subdev_add(struct host1x_device *device,			     struct host1x_driver *driver,			     struct device_node *np){	struct host1x_subdev *subdev;	int err; 	subdev = kzalloc_obj(*subdev);	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); 	/* recursively add children */	for_each_child_of_node_scoped(np, child) {		if (of_match_node(driver->subdevs, child) &&		    of_device_is_available(child)) {			err = host1x_subdev_add(device, driver, child);			if (err < 0) {				/* XXX cleanup? */				return err;			}		}	} 	return 0;} /** * host1x_subdev_del() - remove subdevice * @subdev: subdevice to remove */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 * @device: host1x logical device * @driver: host1x driver */static int host1x_device_parse_dt(struct host1x_device *device,				  struct host1x_driver *driver){	int err; 	for_each_child_of_node_scoped(device->dev.parent->of_node, np) {		if (of_match_node(driver->subdevs, np) &&		    of_device_is_available(np)) {			err = host1x_subdev_add(device, driver, 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->host = &device->dev;	subdev->client = client;	mutex_unlock(&device->clients_lock);	mutex_unlock(&device->subdevs_lock); 	if (list_empty(&device->subdevs)) {		err = device_add(&device->dev);		if (err < 0)			dev_err(&device->dev, "failed to add: %d\n", err);		else			device->registered = true;	}} static void __host1x_subdev_unregister(struct host1x_device *device,				       struct host1x_subdev *subdev){	struct host1x_client *client = subdev->client; 	/*	 * If all subdevices have been activated, we're about to remove the	 * first active subdevice, so unload the driver first.	 */	if (list_empty(&device->subdevs)) {		if (device->registered) {			device->registered = false;			device_del(&device->dev);		}	} 	/*	 * 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->host = 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);} /** * host1x_device_init() - initialize a host1x logical device * @device: host1x logical device * * The driver for the host1x logical device can call this during execution of * its &host1x_driver.probe implementation to initialize each of its clients. * The client drivers access the subsystem specific driver data using the * &host1x_client.parent field and driver data associated with it (usually by * calling dev_get_drvdata()). */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->early_init) {			err = client->ops->early_init(client);			if (err < 0) {				dev_err(&device->dev, "failed to early initialize %s: %d\n",					dev_name(client->dev), err);				goto teardown_late;			}		}	} 	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);				goto teardown;			}		}	} 	mutex_unlock(&device->clients_lock); 	return 0; teardown:	list_for_each_entry_continue_reverse(client, &device->clients, list)		if (client->ops && client->ops->exit)			client->ops->exit(client); 	/* reset client to end of list for late teardown */	client = list_entry(&device->clients, struct host1x_client, list); teardown_late:	list_for_each_entry_continue_reverse(client, &device->clients, list)		if (client->ops && client->ops->late_exit)			client->ops->late_exit(client); 	mutex_unlock(&device->clients_lock);	return err;}EXPORT_SYMBOL(host1x_device_init); /** * host1x_device_exit() - uninitialize host1x logical device * @device: host1x logical device * * When the driver for a host1x logical device is unloaded, it can call this * function to tear down each of its clients. Typically this is done after a * subsystem-specific data structure is removed and the functionality can no * longer be used. */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;			}		}	} 	list_for_each_entry_reverse(client, &device->clients, list) {		if (client->ops && client->ops->late_exit) {			err = client->ops->late_exit(client);			if (err < 0) {				dev_err(&device->dev, "failed to late cleanup %s: %d\n",					dev_name(client->dev), err);				mutex_unlock(&device->clients_lock);				return err;			}		}	} 	mutex_unlock(&device->clients_lock); 	return 0;}EXPORT_SYMBOL(host1x_device_exit); static int host1x_add_client(struct host1x *host1x,			     struct host1x_client *client){	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;} static int host1x_del_client(struct host1x *host1x,			     struct host1x_client *client){	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;} static int host1x_device_match(struct device *dev, const struct device_driver *drv){	return strcmp(dev_name(dev), drv->name) == 0;} /* * Note that this is really only needed for backwards compatibility * with libdrm, which parses this information from sysfs and will * fail if it can't find the OF_FULLNAME, specifically. */static int host1x_device_uevent(const struct device *dev,				struct kobj_uevent_env *env){	of_device_uevent(dev->parent, env); 	return 0;} static int host1x_device_probe(struct device *dev){	struct host1x_driver *driver = to_host1x_driver(dev->driver);	struct host1x_device *device = to_host1x_device(dev); 	if (driver->probe)		return driver->probe(device); 	return 0;} static void host1x_device_remove(struct device *dev){	struct host1x_driver *driver = to_host1x_driver(dev->driver);	struct host1x_device *device = to_host1x_device(dev); 	if (driver->remove)		driver->remove(device);} static void host1x_device_shutdown(struct device *dev){	struct host1x_driver *driver = to_host1x_driver(dev->driver);	struct host1x_device *device = to_host1x_device(dev); 	if (dev->driver && driver->shutdown)		driver->shutdown(device);}  static const struct dev_pm_ops host1x_device_pm_ops = {	.suspend = pm_generic_suspend,	.resume = pm_generic_resume,	.freeze = pm_generic_freeze,	.thaw = pm_generic_thaw,	.poweroff = pm_generic_poweroff,	.restore = pm_generic_restore,}; const struct bus_type host1x_bus_type = {	.name = "host1x",	.match = host1x_device_match,	.uevent = host1x_device_uevent,	.probe = host1x_device_probe,	.remove = host1x_device_remove,	.shutdown = host1x_device_shutdown,	.pm = &host1x_device_pm_ops,}; static void __host1x_device_del(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);} static void host1x_device_release(struct device *dev){	struct host1x_device *device = to_host1x_device(dev); 	__host1x_device_del(device);	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_obj(*device);	if (!device)		return -ENOMEM; 	device_initialize(&device->dev); 	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;	dev_set_name(&device->dev, "%s", driver->driver.name);	device->dev.release = host1x_device_release;	device->dev.bus = &host1x_bus_type;	device->dev.parent = host1x->dev; 	device->dev.dma_parms = &device->dma_parms;	dma_set_max_seg_size(&device->dev, UINT_MAX); 	err = host1x_device_parse_dt(device, driver);	if (err < 0) {		put_device(&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;

Lines 1–500 of 1021.