1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
225c7d49eSTony Lindgren /*
325c7d49eSTony Lindgren  * omap_device implementation
425c7d49eSTony Lindgren  *
525c7d49eSTony Lindgren  * Copyright (C) 2009-2010 Nokia Corporation
625c7d49eSTony Lindgren  * Paul Walmsley, Kevin Hilman
725c7d49eSTony Lindgren  *
825c7d49eSTony Lindgren  * Developed in collaboration with (alphabetical order): Benoit
925c7d49eSTony Lindgren  * Cousson, Thara Gopinath, Tony Lindgren, Rajendra Nayak, Vikram
1025c7d49eSTony Lindgren  * Pandita, Sakari Poussa, Anand Sawant, Santosh Shilimkar, Richard
1125c7d49eSTony Lindgren  * Woodruff
1225c7d49eSTony Lindgren  *
1325c7d49eSTony Lindgren  * This code provides a consistent interface for OMAP device drivers
1425c7d49eSTony Lindgren  * to control power management and interconnect properties of their
1525c7d49eSTony Lindgren  * devices.
1625c7d49eSTony Lindgren  *
17c1d1cd59SPaul Walmsley  * In the medium- to long-term, this code should be implemented as a
18c1d1cd59SPaul Walmsley  * proper omap_bus/omap_device in Linux, no more platform_data func
19c1d1cd59SPaul Walmsley  * pointers
2025c7d49eSTony Lindgren  */
2125c7d49eSTony Lindgren #undef DEBUG
2225c7d49eSTony Lindgren 
2325c7d49eSTony Lindgren #include <linux/kernel.h>
2425c7d49eSTony Lindgren #include <linux/platform_device.h>
2525c7d49eSTony Lindgren #include <linux/slab.h>
2625c7d49eSTony Lindgren #include <linux/err.h>
2725c7d49eSTony Lindgren #include <linux/io.h>
2825c7d49eSTony Lindgren #include <linux/clk.h>
2925c7d49eSTony Lindgren #include <linux/clkdev.h>
30989561deSTomeu Vizoso #include <linux/pm_domain.h>
3125c7d49eSTony Lindgren #include <linux/pm_runtime.h>
3225c7d49eSTony Lindgren #include <linux/of.h>
33d85a2d61STony Lindgren #include <linux/of_address.h>
34d85a2d61STony Lindgren #include <linux/of_irq.h>
3525c7d49eSTony Lindgren #include <linux/notifier.h>
3625c7d49eSTony Lindgren 
37dad12d11STony Lindgren #include "common.h"
38b76c8b19STony Lindgren #include "soc.h"
3925c7d49eSTony Lindgren #include "omap_device.h"
402a296c8fSTony Lindgren #include "omap_hwmod.h"
4125c7d49eSTony Lindgren 
42*6aeb51c1SArnd Bergmann static struct omap_device *omap_device_alloc(struct platform_device *pdev,
43*6aeb51c1SArnd Bergmann 				struct omap_hwmod **ohs, int oh_cnt);
44*6aeb51c1SArnd Bergmann static void omap_device_delete(struct omap_device *od);
45*6aeb51c1SArnd Bergmann static struct dev_pm_domain omap_device_fail_pm_domain;
46*6aeb51c1SArnd Bergmann static struct dev_pm_domain omap_device_pm_domain;
47*6aeb51c1SArnd Bergmann 
4825c7d49eSTony Lindgren /* Private functions */
4925c7d49eSTony Lindgren 
_add_clkdev(struct omap_device * od,const char * clk_alias,const char * clk_name)5025c7d49eSTony Lindgren static void _add_clkdev(struct omap_device *od, const char *clk_alias,
5125c7d49eSTony Lindgren 		       const char *clk_name)
5225c7d49eSTony Lindgren {
5325c7d49eSTony Lindgren 	struct clk *r;
541ca90bd4SRussell King 	int rc;
5525c7d49eSTony Lindgren 
5625c7d49eSTony Lindgren 	if (!clk_alias || !clk_name)
5725c7d49eSTony Lindgren 		return;
5825c7d49eSTony Lindgren 
5925c7d49eSTony Lindgren 	dev_dbg(&od->pdev->dev, "Creating %s -> %s\n", clk_alias, clk_name);
6025c7d49eSTony Lindgren 
6125c7d49eSTony Lindgren 	r = clk_get_sys(dev_name(&od->pdev->dev), clk_alias);
6225c7d49eSTony Lindgren 	if (!IS_ERR(r)) {
639a02ae4eSMarkus Pargmann 		dev_dbg(&od->pdev->dev,
6425c7d49eSTony Lindgren 			 "alias %s already exists\n", clk_alias);
6525c7d49eSTony Lindgren 		clk_put(r);
6625c7d49eSTony Lindgren 		return;
6725c7d49eSTony Lindgren 	}
6825c7d49eSTony Lindgren 
6959dcfc48STero Kristo 	r = clk_get_sys(NULL, clk_name);
7059dcfc48STero Kristo 
711aa8f0cbSTony Lindgren 	if (IS_ERR(r)) {
7259dcfc48STero Kristo 		struct of_phandle_args clkspec;
7359dcfc48STero Kristo 
7459dcfc48STero Kristo 		clkspec.np = of_find_node_by_name(NULL, clk_name);
7559dcfc48STero Kristo 
7659dcfc48STero Kristo 		r = of_clk_get_from_provider(&clkspec);
7759dcfc48STero Kristo 
7859dcfc48STero Kristo 		rc = clk_register_clkdev(r, clk_alias,
7959dcfc48STero Kristo 					 dev_name(&od->pdev->dev));
8059dcfc48STero Kristo 	} else {
8159dcfc48STero Kristo 		rc = clk_add_alias(clk_alias, dev_name(&od->pdev->dev),
8259dcfc48STero Kristo 				   clk_name, NULL);
8359dcfc48STero Kristo 	}
8459dcfc48STero Kristo 
851ca90bd4SRussell King 	if (rc) {
861ca90bd4SRussell King 		if (rc == -ENODEV || rc == -ENOMEM)
8725c7d49eSTony Lindgren 			dev_err(&od->pdev->dev,
8825c7d49eSTony Lindgren 				"clkdev_alloc for %s failed\n", clk_alias);
891ca90bd4SRussell King 		else
901ca90bd4SRussell King 			dev_err(&od->pdev->dev,
911ca90bd4SRussell King 				"clk_get for %s failed\n", clk_name);
9225c7d49eSTony Lindgren 	}
9325c7d49eSTony Lindgren }
9425c7d49eSTony Lindgren 
9525c7d49eSTony Lindgren /**
9625c7d49eSTony Lindgren  * _add_hwmod_clocks_clkdev - Add clkdev entry for hwmod optional clocks
9725c7d49eSTony Lindgren  * and main clock
9825c7d49eSTony Lindgren  * @od: struct omap_device *od
9925c7d49eSTony Lindgren  * @oh: struct omap_hwmod *oh
10025c7d49eSTony Lindgren  *
10125c7d49eSTony Lindgren  * For the main clock and every optional clock present per hwmod per
10225c7d49eSTony Lindgren  * omap_device, this function adds an entry in the clkdev table of the
10325c7d49eSTony Lindgren  * form <dev-id=dev_name, con-id=role> if it does not exist already.
10425c7d49eSTony Lindgren  *
10525c7d49eSTony Lindgren  * This allows drivers to get a pointer to its optional clocks based on its role
10625c7d49eSTony Lindgren  * by calling clk_get(<dev*>, <role>).
10725c7d49eSTony Lindgren  * In the case of the main clock, a "fck" alias is used.
10825c7d49eSTony Lindgren  *
10925c7d49eSTony Lindgren  * No return value.
11025c7d49eSTony Lindgren  */
_add_hwmod_clocks_clkdev(struct omap_device * od,struct omap_hwmod * oh)11125c7d49eSTony Lindgren static void _add_hwmod_clocks_clkdev(struct omap_device *od,
11225c7d49eSTony Lindgren 				     struct omap_hwmod *oh)
11325c7d49eSTony Lindgren {
11425c7d49eSTony Lindgren 	int i;
11525c7d49eSTony Lindgren 
11625c7d49eSTony Lindgren 	_add_clkdev(od, "fck", oh->main_clk);
11725c7d49eSTony Lindgren 
11825c7d49eSTony Lindgren 	for (i = 0; i < oh->opt_clks_cnt; i++)
11925c7d49eSTony Lindgren 		_add_clkdev(od, oh->opt_clks[i].role, oh->opt_clks[i].clk);
12025c7d49eSTony Lindgren }
12125c7d49eSTony Lindgren 
12225c7d49eSTony Lindgren 
12325c7d49eSTony Lindgren /**
12425c7d49eSTony Lindgren  * omap_device_build_from_dt - build an omap_device with multiple hwmods
1257b6560b4SBen Dooks (Codethink)  * @pdev: The platform device to update.
12625c7d49eSTony Lindgren  *
12725c7d49eSTony Lindgren  * Function for building an omap_device already registered from device-tree
12825c7d49eSTony Lindgren  *
12925c7d49eSTony Lindgren  * Returns 0 or PTR_ERR() on error.
13025c7d49eSTony Lindgren  */
omap_device_build_from_dt(struct platform_device * pdev)13125c7d49eSTony Lindgren static int omap_device_build_from_dt(struct platform_device *pdev)
13225c7d49eSTony Lindgren {
13325c7d49eSTony Lindgren 	struct omap_hwmod **hwmods;
13425c7d49eSTony Lindgren 	struct omap_device *od;
13525c7d49eSTony Lindgren 	struct omap_hwmod *oh;
13625c7d49eSTony Lindgren 	struct device_node *node = pdev->dev.of_node;
137695eea3dSTony Lindgren 	struct resource res;
13825c7d49eSTony Lindgren 	const char *oh_name;
13925c7d49eSTony Lindgren 	int oh_cnt, i, ret = 0;
14071941002STony Lindgren 	bool device_active = false, skip_pm_domain = false;
14125c7d49eSTony Lindgren 
14225c7d49eSTony Lindgren 	oh_cnt = of_property_count_strings(node, "ti,hwmods");
143c48cd659SRussell King 	if (oh_cnt <= 0) {
14425c7d49eSTony Lindgren 		dev_dbg(&pdev->dev, "No 'hwmods' to build omap_device\n");
14525c7d49eSTony Lindgren 		return -ENODEV;
14625c7d49eSTony Lindgren 	}
14725c7d49eSTony Lindgren 
14871941002STony Lindgren 	/* SDMA still needs special handling for omap_device_build() */
14971941002STony Lindgren 	ret = of_property_read_string_index(node, "ti,hwmods", 0, &oh_name);
15071941002STony Lindgren 	if (!ret && (!strncmp("dma_system", oh_name, 10) ||
15171941002STony Lindgren 		     !strncmp("dma", oh_name, 3)))
15271941002STony Lindgren 		skip_pm_domain = true;
15371941002STony Lindgren 
154695eea3dSTony Lindgren 	/* Use ti-sysc driver instead of omap_device? */
15571941002STony Lindgren 	if (!skip_pm_domain &&
15671941002STony Lindgren 	    !omap_hwmod_parse_module_range(NULL, node, &res))
157695eea3dSTony Lindgren 		return -ENODEV;
158695eea3dSTony Lindgren 
1596396bb22SKees Cook 	hwmods = kcalloc(oh_cnt, sizeof(struct omap_hwmod *), GFP_KERNEL);
16025c7d49eSTony Lindgren 	if (!hwmods) {
16125c7d49eSTony Lindgren 		ret = -ENOMEM;
16225c7d49eSTony Lindgren 		goto odbfd_exit;
16325c7d49eSTony Lindgren 	}
16425c7d49eSTony Lindgren 
16525c7d49eSTony Lindgren 	for (i = 0; i < oh_cnt; i++) {
16625c7d49eSTony Lindgren 		of_property_read_string_index(node, "ti,hwmods", i, &oh_name);
16725c7d49eSTony Lindgren 		oh = omap_hwmod_lookup(oh_name);
16825c7d49eSTony Lindgren 		if (!oh) {
16925c7d49eSTony Lindgren 			dev_err(&pdev->dev, "Cannot lookup hwmod '%s'\n",
17025c7d49eSTony Lindgren 				oh_name);
17125c7d49eSTony Lindgren 			ret = -EINVAL;
17225c7d49eSTony Lindgren 			goto odbfd_exit1;
17325c7d49eSTony Lindgren 		}
17425c7d49eSTony Lindgren 		hwmods[i] = oh;
1757268032dSRajendra Nayak 		if (oh->flags & HWMOD_INIT_NO_IDLE)
1767268032dSRajendra Nayak 			device_active = true;
17725c7d49eSTony Lindgren 	}
17825c7d49eSTony Lindgren 
179c1d1cd59SPaul Walmsley 	od = omap_device_alloc(pdev, hwmods, oh_cnt);
1804cf9cf89SWei Yongjun 	if (IS_ERR(od)) {
18125c7d49eSTony Lindgren 		dev_err(&pdev->dev, "Cannot allocate omap_device for :%s\n",
18225c7d49eSTony Lindgren 			oh_name);
18325c7d49eSTony Lindgren 		ret = PTR_ERR(od);
18425c7d49eSTony Lindgren 		goto odbfd_exit1;
18525c7d49eSTony Lindgren 	}
18625c7d49eSTony Lindgren 
18725c7d49eSTony Lindgren 	/* Fix up missing resource names */
18825c7d49eSTony Lindgren 	for (i = 0; i < pdev->num_resources; i++) {
18925c7d49eSTony Lindgren 		struct resource *r = &pdev->resource[i];
19025c7d49eSTony Lindgren 
19125c7d49eSTony Lindgren 		if (r->name == NULL)
19225c7d49eSTony Lindgren 			r->name = dev_name(&pdev->dev);
19325c7d49eSTony Lindgren 	}
19425c7d49eSTony Lindgren 
19571941002STony Lindgren 	if (!skip_pm_domain) {
196989561deSTomeu Vizoso 		dev_pm_domain_set(&pdev->dev, &omap_device_pm_domain);
1977268032dSRajendra Nayak 		if (device_active) {
1987268032dSRajendra Nayak 			omap_device_enable(pdev);
1997268032dSRajendra Nayak 			pm_runtime_set_active(&pdev->dev);
2007268032dSRajendra Nayak 		}
20171941002STony Lindgren 	}
2027268032dSRajendra Nayak 
20325c7d49eSTony Lindgren odbfd_exit1:
20425c7d49eSTony Lindgren 	kfree(hwmods);
20525c7d49eSTony Lindgren odbfd_exit:
206f5c33b07SNishanth Menon 	/* if data/we are at fault.. load up a fail handler */
207f5c33b07SNishanth Menon 	if (ret)
208989561deSTomeu Vizoso 		dev_pm_domain_set(&pdev->dev, &omap_device_fail_pm_domain);
209f5c33b07SNishanth Menon 
21025c7d49eSTony Lindgren 	return ret;
21125c7d49eSTony Lindgren }
21225c7d49eSTony Lindgren 
_omap_device_notifier_call(struct notifier_block * nb,unsigned long event,void * dev)21325c7d49eSTony Lindgren static int _omap_device_notifier_call(struct notifier_block *nb,
21425c7d49eSTony Lindgren 				      unsigned long event, void *dev)
21525c7d49eSTony Lindgren {
21625c7d49eSTony Lindgren 	struct platform_device *pdev = to_platform_device(dev);
21725c7d49eSTony Lindgren 	struct omap_device *od;
218cf26f113STony Lindgren 	int err;
21925c7d49eSTony Lindgren 
22025c7d49eSTony Lindgren 	switch (event) {
221213fa10dSGrygorii Strashko 	case BUS_NOTIFY_REMOVED_DEVICE:
22225c7d49eSTony Lindgren 		if (pdev->archdata.od)
22325c7d49eSTony Lindgren 			omap_device_delete(pdev->archdata.od);
22425c7d49eSTony Lindgren 		break;
225cf26f113STony Lindgren 	case BUS_NOTIFY_UNBOUND_DRIVER:
226cf26f113STony Lindgren 		od = to_omap_device(pdev);
227cf26f113STony Lindgren 		if (od && (od->_state == OMAP_DEVICE_STATE_ENABLED)) {
228cf26f113STony Lindgren 			dev_info(dev, "enabled after unload, idling\n");
229cf26f113STony Lindgren 			err = omap_device_idle(pdev);
230cf26f113STony Lindgren 			if (err)
231cf26f113STony Lindgren 				dev_err(dev, "failed to idle\n");
232cf26f113STony Lindgren 		}
233cf26f113STony Lindgren 		break;
23404abaf07SDave Gerlach 	case BUS_NOTIFY_BIND_DRIVER:
23504abaf07SDave Gerlach 		od = to_omap_device(pdev);
236ec76c2eeSAndreas Kemnade 		if (od) {
23704abaf07SDave Gerlach 			od->_driver_status = BUS_NOTIFY_BIND_DRIVER;
238ec76c2eeSAndreas Kemnade 			if (od->_state == OMAP_DEVICE_STATE_ENABLED &&
239ec76c2eeSAndreas Kemnade 			    pm_runtime_status_suspended(dev)) {
24004abaf07SDave Gerlach 				pm_runtime_set_active(dev);
24104abaf07SDave Gerlach 			}
242ec76c2eeSAndreas Kemnade 		}
24304abaf07SDave Gerlach 		break;
24425c7d49eSTony Lindgren 	case BUS_NOTIFY_ADD_DEVICE:
24525c7d49eSTony Lindgren 		if (pdev->dev.of_node)
24625c7d49eSTony Lindgren 			omap_device_build_from_dt(pdev);
247df561f66SGustavo A. R. Silva 		fallthrough;
24825c7d49eSTony Lindgren 	default:
24925c7d49eSTony Lindgren 		od = to_omap_device(pdev);
25025c7d49eSTony Lindgren 		if (od)
25125c7d49eSTony Lindgren 			od->_driver_status = event;
25225c7d49eSTony Lindgren 	}
25325c7d49eSTony Lindgren 
25425c7d49eSTony Lindgren 	return NOTIFY_DONE;
25525c7d49eSTony Lindgren }
25625c7d49eSTony Lindgren 
257c1d1cd59SPaul Walmsley /**
258c1d1cd59SPaul Walmsley  * _omap_device_enable_hwmods - call omap_hwmod_enable() on all hwmods
259c1d1cd59SPaul Walmsley  * @od: struct omap_device *od
260c1d1cd59SPaul Walmsley  *
261c1d1cd59SPaul Walmsley  * Enable all underlying hwmods.  Returns 0.
262c1d1cd59SPaul Walmsley  */
_omap_device_enable_hwmods(struct omap_device * od)263c1d1cd59SPaul Walmsley static int _omap_device_enable_hwmods(struct omap_device *od)
264c1d1cd59SPaul Walmsley {
2656da23358SPali Rohár 	int ret = 0;
266c1d1cd59SPaul Walmsley 	int i;
267c1d1cd59SPaul Walmsley 
268c1d1cd59SPaul Walmsley 	for (i = 0; i < od->hwmods_cnt; i++)
2696da23358SPali Rohár 		ret |= omap_hwmod_enable(od->hwmods[i]);
270c1d1cd59SPaul Walmsley 
2716da23358SPali Rohár 	return ret;
272c1d1cd59SPaul Walmsley }
273c1d1cd59SPaul Walmsley 
274c1d1cd59SPaul Walmsley /**
275c1d1cd59SPaul Walmsley  * _omap_device_idle_hwmods - call omap_hwmod_idle() on all hwmods
276c1d1cd59SPaul Walmsley  * @od: struct omap_device *od
277c1d1cd59SPaul Walmsley  *
278c1d1cd59SPaul Walmsley  * Idle all underlying hwmods.  Returns 0.
279c1d1cd59SPaul Walmsley  */
_omap_device_idle_hwmods(struct omap_device * od)280c1d1cd59SPaul Walmsley static int _omap_device_idle_hwmods(struct omap_device *od)
281c1d1cd59SPaul Walmsley {
2826da23358SPali Rohár 	int ret = 0;
283c1d1cd59SPaul Walmsley 	int i;
284c1d1cd59SPaul Walmsley 
285c1d1cd59SPaul Walmsley 	for (i = 0; i < od->hwmods_cnt; i++)
2866da23358SPali Rohár 		ret |= omap_hwmod_idle(od->hwmods[i]);
287c1d1cd59SPaul Walmsley 
2886da23358SPali Rohár 	return ret;
289c1d1cd59SPaul Walmsley }
29025c7d49eSTony Lindgren 
29125c7d49eSTony Lindgren /* Public functions for use by core code */
29225c7d49eSTony Lindgren 
29325c7d49eSTony Lindgren /**
29425c7d49eSTony Lindgren  * omap_device_alloc - allocate an omap_device
29525c7d49eSTony Lindgren  * @pdev: platform_device that will be included in this omap_device
2967b6560b4SBen Dooks (Codethink)  * @ohs: ptr to the omap_hwmod for this omap_device
2977b6560b4SBen Dooks (Codethink)  * @oh_cnt: the size of the ohs list
29825c7d49eSTony Lindgren  *
29925c7d49eSTony Lindgren  * Convenience function for allocating an omap_device structure and filling
300c1d1cd59SPaul Walmsley  * hwmods, and resources.
30125c7d49eSTony Lindgren  *
30225c7d49eSTony Lindgren  * Returns an struct omap_device pointer or ERR_PTR() on error;
30325c7d49eSTony Lindgren  */
omap_device_alloc(struct platform_device * pdev,struct omap_hwmod ** ohs,int oh_cnt)304*6aeb51c1SArnd Bergmann static struct omap_device *omap_device_alloc(struct platform_device *pdev,
305c1d1cd59SPaul Walmsley 					struct omap_hwmod **ohs, int oh_cnt)
30625c7d49eSTony Lindgren {
30725c7d49eSTony Lindgren 	int ret = -ENOMEM;
30825c7d49eSTony Lindgren 	struct omap_device *od;
309c2b84a9bSTony Lindgren 	int i;
31025c7d49eSTony Lindgren 	struct omap_hwmod **hwmods;
31125c7d49eSTony Lindgren 
31225c7d49eSTony Lindgren 	od = kzalloc(sizeof(struct omap_device), GFP_KERNEL);
3136d9be937SJing Xiangfeng 	if (!od)
31425c7d49eSTony Lindgren 		goto oda_exit1;
3156d9be937SJing Xiangfeng 
31625c7d49eSTony Lindgren 	od->hwmods_cnt = oh_cnt;
31725c7d49eSTony Lindgren 
31825c7d49eSTony Lindgren 	hwmods = kmemdup(ohs, sizeof(struct omap_hwmod *) * oh_cnt, GFP_KERNEL);
31925c7d49eSTony Lindgren 	if (!hwmods)
32025c7d49eSTony Lindgren 		goto oda_exit2;
32125c7d49eSTony Lindgren 
32225c7d49eSTony Lindgren 	od->hwmods = hwmods;
32325c7d49eSTony Lindgren 	od->pdev = pdev;
32425c7d49eSTony Lindgren 	pdev->archdata.od = od;
32525c7d49eSTony Lindgren 
32625c7d49eSTony Lindgren 	for (i = 0; i < oh_cnt; i++) {
32725c7d49eSTony Lindgren 		hwmods[i]->od = od;
32825c7d49eSTony Lindgren 		_add_hwmod_clocks_clkdev(od, hwmods[i]);
32925c7d49eSTony Lindgren 	}
33025c7d49eSTony Lindgren 
33125c7d49eSTony Lindgren 	return od;
33225c7d49eSTony Lindgren 
33325c7d49eSTony Lindgren oda_exit2:
33425c7d49eSTony Lindgren 	kfree(od);
33525c7d49eSTony Lindgren oda_exit1:
33625c7d49eSTony Lindgren 	dev_err(&pdev->dev, "omap_device: build failed (%d)\n", ret);
33725c7d49eSTony Lindgren 
33825c7d49eSTony Lindgren 	return ERR_PTR(ret);
33925c7d49eSTony Lindgren }
34025c7d49eSTony Lindgren 
omap_device_delete(struct omap_device * od)341*6aeb51c1SArnd Bergmann static void omap_device_delete(struct omap_device *od)
34225c7d49eSTony Lindgren {
34325c7d49eSTony Lindgren 	if (!od)
34425c7d49eSTony Lindgren 		return;
34525c7d49eSTony Lindgren 
34625c7d49eSTony Lindgren 	od->pdev->archdata.od = NULL;
34725c7d49eSTony Lindgren 	kfree(od->hwmods);
34825c7d49eSTony Lindgren 	kfree(od);
34925c7d49eSTony Lindgren }
35025c7d49eSTony Lindgren 
351bf7c5449SRafael J. Wysocki #ifdef CONFIG_PM
_od_runtime_suspend(struct device * dev)35225c7d49eSTony Lindgren static int _od_runtime_suspend(struct device *dev)
35325c7d49eSTony Lindgren {
35425c7d49eSTony Lindgren 	struct platform_device *pdev = to_platform_device(dev);
35525c7d49eSTony Lindgren 	int ret;
35625c7d49eSTony Lindgren 
35725c7d49eSTony Lindgren 	ret = pm_generic_runtime_suspend(dev);
3586da23358SPali Rohár 	if (ret)
35925c7d49eSTony Lindgren 		return ret;
3606da23358SPali Rohár 
3616da23358SPali Rohár 	return omap_device_idle(pdev);
36225c7d49eSTony Lindgren }
36325c7d49eSTony Lindgren 
_od_runtime_resume(struct device * dev)36425c7d49eSTony Lindgren static int _od_runtime_resume(struct device *dev)
36525c7d49eSTony Lindgren {
36625c7d49eSTony Lindgren 	struct platform_device *pdev = to_platform_device(dev);
3676da23358SPali Rohár 	int ret;
36825c7d49eSTony Lindgren 
3696da23358SPali Rohár 	ret = omap_device_enable(pdev);
37008c78e9dSTony Lindgren 	if (ret) {
37108c78e9dSTony Lindgren 		dev_err(dev, "use pm_runtime_put_sync_suspend() in driver?\n");
3726da23358SPali Rohár 		return ret;
37308c78e9dSTony Lindgren 	}
37425c7d49eSTony Lindgren 
37525c7d49eSTony Lindgren 	return pm_generic_runtime_resume(dev);
37625c7d49eSTony Lindgren }
377f5c33b07SNishanth Menon 
_od_fail_runtime_suspend(struct device * dev)378f5c33b07SNishanth Menon static int _od_fail_runtime_suspend(struct device *dev)
379f5c33b07SNishanth Menon {
380f5c33b07SNishanth Menon 	dev_warn(dev, "%s: FIXME: missing hwmod/omap_dev info\n", __func__);
381f5c33b07SNishanth Menon 	return -ENODEV;
382f5c33b07SNishanth Menon }
383f5c33b07SNishanth Menon 
_od_fail_runtime_resume(struct device * dev)384f5c33b07SNishanth Menon static int _od_fail_runtime_resume(struct device *dev)
385f5c33b07SNishanth Menon {
386f5c33b07SNishanth Menon 	dev_warn(dev, "%s: FIXME: missing hwmod/omap_dev info\n", __func__);
387f5c33b07SNishanth Menon 	return -ENODEV;
388f5c33b07SNishanth Menon }
389f5c33b07SNishanth Menon 
39025c7d49eSTony Lindgren #endif
39125c7d49eSTony Lindgren 
39225c7d49eSTony Lindgren #ifdef CONFIG_SUSPEND
_od_suspend_noirq(struct device * dev)39325c7d49eSTony Lindgren static int _od_suspend_noirq(struct device *dev)
39425c7d49eSTony Lindgren {
39525c7d49eSTony Lindgren 	struct platform_device *pdev = to_platform_device(dev);
39625c7d49eSTony Lindgren 	struct omap_device *od = to_omap_device(pdev);
39725c7d49eSTony Lindgren 	int ret;
39825c7d49eSTony Lindgren 
39925c7d49eSTony Lindgren 	/* Don't attempt late suspend on a driver that is not bound */
40025c7d49eSTony Lindgren 	if (od->_driver_status != BUS_NOTIFY_BOUND_DRIVER)
40125c7d49eSTony Lindgren 		return 0;
40225c7d49eSTony Lindgren 
40325c7d49eSTony Lindgren 	ret = pm_generic_suspend_noirq(dev);
40425c7d49eSTony Lindgren 
40525c7d49eSTony Lindgren 	if (!ret && !pm_runtime_status_suspended(dev)) {
40625c7d49eSTony Lindgren 		if (pm_generic_runtime_suspend(dev) == 0) {
40725c7d49eSTony Lindgren 			omap_device_idle(pdev);
40825c7d49eSTony Lindgren 			od->flags |= OMAP_DEVICE_SUSPENDED;
40925c7d49eSTony Lindgren 		}
41025c7d49eSTony Lindgren 	}
41125c7d49eSTony Lindgren 
41225c7d49eSTony Lindgren 	return ret;
41325c7d49eSTony Lindgren }
41425c7d49eSTony Lindgren 
_od_resume_noirq(struct device * dev)41525c7d49eSTony Lindgren static int _od_resume_noirq(struct device *dev)
41625c7d49eSTony Lindgren {
41725c7d49eSTony Lindgren 	struct platform_device *pdev = to_platform_device(dev);
41825c7d49eSTony Lindgren 	struct omap_device *od = to_omap_device(pdev);
41925c7d49eSTony Lindgren 
4203522bf7bSNishanth Menon 	if (od->flags & OMAP_DEVICE_SUSPENDED) {
42125c7d49eSTony Lindgren 		od->flags &= ~OMAP_DEVICE_SUSPENDED;
42225c7d49eSTony Lindgren 		omap_device_enable(pdev);
42325c7d49eSTony Lindgren 		pm_generic_runtime_resume(dev);
42425c7d49eSTony Lindgren 	}
42525c7d49eSTony Lindgren 
42625c7d49eSTony Lindgren 	return pm_generic_resume_noirq(dev);
42725c7d49eSTony Lindgren }
42825c7d49eSTony Lindgren #else
42925c7d49eSTony Lindgren #define _od_suspend_noirq NULL
43025c7d49eSTony Lindgren #define _od_resume_noirq NULL
43125c7d49eSTony Lindgren #endif
43225c7d49eSTony Lindgren 
433*6aeb51c1SArnd Bergmann static struct dev_pm_domain omap_device_fail_pm_domain = {
434f5c33b07SNishanth Menon 	.ops = {
435f5c33b07SNishanth Menon 		SET_RUNTIME_PM_OPS(_od_fail_runtime_suspend,
436f5c33b07SNishanth Menon 				   _od_fail_runtime_resume, NULL)
437f5c33b07SNishanth Menon 	}
438f5c33b07SNishanth Menon };
439f5c33b07SNishanth Menon 
440*6aeb51c1SArnd Bergmann static struct dev_pm_domain omap_device_pm_domain = {
44125c7d49eSTony Lindgren 	.ops = {
44225c7d49eSTony Lindgren 		SET_RUNTIME_PM_OPS(_od_runtime_suspend, _od_runtime_resume,
44345f0a85cSRafael J. Wysocki 				   NULL)
44425c7d49eSTony Lindgren 		USE_PLATFORM_PM_SLEEP_OPS
445c381f229SGrygorii Strashko 		SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(_od_suspend_noirq,
446c381f229SGrygorii Strashko 					      _od_resume_noirq)
44725c7d49eSTony Lindgren 	}
44825c7d49eSTony Lindgren };
44925c7d49eSTony Lindgren 
45025c7d49eSTony Lindgren /* Public functions for use by device drivers through struct platform_data */
45125c7d49eSTony Lindgren 
45225c7d49eSTony Lindgren /**
45325c7d49eSTony Lindgren  * omap_device_enable - fully activate an omap_device
4547b6560b4SBen Dooks (Codethink)  * @pdev: the platform device to activate
45525c7d49eSTony Lindgren  *
45625c7d49eSTony Lindgren  * Do whatever is necessary for the hwmods underlying omap_device @od
45725c7d49eSTony Lindgren  * to be accessible and ready to operate.  This generally involves
45825c7d49eSTony Lindgren  * enabling clocks, setting SYSCONFIG registers; and in the future may
45925c7d49eSTony Lindgren  * involve remuxing pins.  Device drivers should call this function
460c1d1cd59SPaul Walmsley  * indirectly via pm_runtime_get*().  Returns -EINVAL if called when
461c1d1cd59SPaul Walmsley  * the omap_device is already enabled, or passes along the return
462c1d1cd59SPaul Walmsley  * value of _omap_device_enable_hwmods().
46325c7d49eSTony Lindgren  */
omap_device_enable(struct platform_device * pdev)46425c7d49eSTony Lindgren int omap_device_enable(struct platform_device *pdev)
46525c7d49eSTony Lindgren {
46625c7d49eSTony Lindgren 	int ret;
46725c7d49eSTony Lindgren 	struct omap_device *od;
46825c7d49eSTony Lindgren 
46925c7d49eSTony Lindgren 	od = to_omap_device(pdev);
47025c7d49eSTony Lindgren 
47125c7d49eSTony Lindgren 	if (od->_state == OMAP_DEVICE_STATE_ENABLED) {
47225c7d49eSTony Lindgren 		dev_warn(&pdev->dev,
47325c7d49eSTony Lindgren 			 "omap_device: %s() called from invalid state %d\n",
47425c7d49eSTony Lindgren 			 __func__, od->_state);
47525c7d49eSTony Lindgren 		return -EINVAL;
47625c7d49eSTony Lindgren 	}
47725c7d49eSTony Lindgren 
478c1d1cd59SPaul Walmsley 	ret = _omap_device_enable_hwmods(od);
47925c7d49eSTony Lindgren 
4806da23358SPali Rohár 	if (ret == 0)
48125c7d49eSTony Lindgren 		od->_state = OMAP_DEVICE_STATE_ENABLED;
48225c7d49eSTony Lindgren 
48325c7d49eSTony Lindgren 	return ret;
48425c7d49eSTony Lindgren }
48525c7d49eSTony Lindgren 
48625c7d49eSTony Lindgren /**
48725c7d49eSTony Lindgren  * omap_device_idle - idle an omap_device
4887b6560b4SBen Dooks (Codethink)  * @pdev: The platform_device (omap_device) to idle
48925c7d49eSTony Lindgren  *
490c1d1cd59SPaul Walmsley  * Idle omap_device @od.  Device drivers call this function indirectly
491c1d1cd59SPaul Walmsley  * via pm_runtime_put*().  Returns -EINVAL if the omap_device is not
49225c7d49eSTony Lindgren  * currently enabled, or passes along the return value of
493c1d1cd59SPaul Walmsley  * _omap_device_idle_hwmods().
49425c7d49eSTony Lindgren  */
omap_device_idle(struct platform_device * pdev)49525c7d49eSTony Lindgren int omap_device_idle(struct platform_device *pdev)
49625c7d49eSTony Lindgren {
49725c7d49eSTony Lindgren 	int ret;
49825c7d49eSTony Lindgren 	struct omap_device *od;
49925c7d49eSTony Lindgren 
50025c7d49eSTony Lindgren 	od = to_omap_device(pdev);
50125c7d49eSTony Lindgren 
50225c7d49eSTony Lindgren 	if (od->_state != OMAP_DEVICE_STATE_ENABLED) {
50325c7d49eSTony Lindgren 		dev_warn(&pdev->dev,
50425c7d49eSTony Lindgren 			 "omap_device: %s() called from invalid state %d\n",
50525c7d49eSTony Lindgren 			 __func__, od->_state);
50625c7d49eSTony Lindgren 		return -EINVAL;
50725c7d49eSTony Lindgren 	}
50825c7d49eSTony Lindgren 
509c1d1cd59SPaul Walmsley 	ret = _omap_device_idle_hwmods(od);
51025c7d49eSTony Lindgren 
5116da23358SPali Rohár 	if (ret == 0)
51225c7d49eSTony Lindgren 		od->_state = OMAP_DEVICE_STATE_IDLE;
51325c7d49eSTony Lindgren 
51425c7d49eSTony Lindgren 	return ret;
51525c7d49eSTony Lindgren }
51625c7d49eSTony Lindgren 
51725c7d49eSTony Lindgren /**
51825c7d49eSTony Lindgren  * omap_device_assert_hardreset - set a device's hardreset line
51925c7d49eSTony Lindgren  * @pdev: struct platform_device * to reset
52025c7d49eSTony Lindgren  * @name: const char * name of the reset line
52125c7d49eSTony Lindgren  *
52225c7d49eSTony Lindgren  * Set the hardreset line identified by @name on the IP blocks
52325c7d49eSTony Lindgren  * associated with the hwmods backing the platform_device @pdev.  All
52425c7d49eSTony Lindgren  * of the hwmods associated with @pdev must have the same hardreset
52525c7d49eSTony Lindgren  * line linked to them for this to work.  Passes along the return value
52625c7d49eSTony Lindgren  * of omap_hwmod_assert_hardreset() in the event of any failure, or
52725c7d49eSTony Lindgren  * returns 0 upon success.
52825c7d49eSTony Lindgren  */
omap_device_assert_hardreset(struct platform_device * pdev,const char * name)52925c7d49eSTony Lindgren int omap_device_assert_hardreset(struct platform_device *pdev, const char *name)
53025c7d49eSTony Lindgren {
53125c7d49eSTony Lindgren 	struct omap_device *od = to_omap_device(pdev);
53225c7d49eSTony Lindgren 	int ret = 0;
53325c7d49eSTony Lindgren 	int i;
53425c7d49eSTony Lindgren 
53525c7d49eSTony Lindgren 	for (i = 0; i < od->hwmods_cnt; i++) {
53625c7d49eSTony Lindgren 		ret = omap_hwmod_assert_hardreset(od->hwmods[i], name);
53725c7d49eSTony Lindgren 		if (ret)
53825c7d49eSTony Lindgren 			break;
53925c7d49eSTony Lindgren 	}
54025c7d49eSTony Lindgren 
54125c7d49eSTony Lindgren 	return ret;
54225c7d49eSTony Lindgren }
54325c7d49eSTony Lindgren 
54425c7d49eSTony Lindgren /**
54525c7d49eSTony Lindgren  * omap_device_deassert_hardreset - release a device's hardreset line
54625c7d49eSTony Lindgren  * @pdev: struct platform_device * to reset
54725c7d49eSTony Lindgren  * @name: const char * name of the reset line
54825c7d49eSTony Lindgren  *
54925c7d49eSTony Lindgren  * Release the hardreset line identified by @name on the IP blocks
55025c7d49eSTony Lindgren  * associated with the hwmods backing the platform_device @pdev.  All
55125c7d49eSTony Lindgren  * of the hwmods associated with @pdev must have the same hardreset
55225c7d49eSTony Lindgren  * line linked to them for this to work.  Passes along the return
55325c7d49eSTony Lindgren  * value of omap_hwmod_deassert_hardreset() in the event of any
55425c7d49eSTony Lindgren  * failure, or returns 0 upon success.
55525c7d49eSTony Lindgren  */
omap_device_deassert_hardreset(struct platform_device * pdev,const char * name)55625c7d49eSTony Lindgren int omap_device_deassert_hardreset(struct platform_device *pdev,
55725c7d49eSTony Lindgren 				   const char *name)
55825c7d49eSTony Lindgren {
55925c7d49eSTony Lindgren 	struct omap_device *od = to_omap_device(pdev);
56025c7d49eSTony Lindgren 	int ret = 0;
56125c7d49eSTony Lindgren 	int i;
56225c7d49eSTony Lindgren 
56325c7d49eSTony Lindgren 	for (i = 0; i < od->hwmods_cnt; i++) {
56425c7d49eSTony Lindgren 		ret = omap_hwmod_deassert_hardreset(od->hwmods[i], name);
56525c7d49eSTony Lindgren 		if (ret)
56625c7d49eSTony Lindgren 			break;
56725c7d49eSTony Lindgren 	}
56825c7d49eSTony Lindgren 
56925c7d49eSTony Lindgren 	return ret;
57025c7d49eSTony Lindgren }
57125c7d49eSTony Lindgren 
57225c7d49eSTony Lindgren static struct notifier_block platform_nb = {
57325c7d49eSTony Lindgren 	.notifier_call = _omap_device_notifier_call,
57425c7d49eSTony Lindgren };
57525c7d49eSTony Lindgren 
omap_device_init(void)57625c7d49eSTony Lindgren static int __init omap_device_init(void)
57725c7d49eSTony Lindgren {
57825c7d49eSTony Lindgren 	bus_register_notifier(&platform_bus_type, &platform_nb);
57925c7d49eSTony Lindgren 	return 0;
58025c7d49eSTony Lindgren }
5818dd5ea72STony Lindgren omap_postcore_initcall(omap_device_init);
58225c7d49eSTony Lindgren 
58325c7d49eSTony Lindgren /**
58425c7d49eSTony Lindgren  * omap_device_late_idle - idle devices without drivers
58525c7d49eSTony Lindgren  * @dev: struct device * associated with omap_device
58625c7d49eSTony Lindgren  * @data: unused
58725c7d49eSTony Lindgren  *
58825c7d49eSTony Lindgren  * Check the driver bound status of this device, and idle it
58925c7d49eSTony Lindgren  * if there is no driver attached.
59025c7d49eSTony Lindgren  */
omap_device_late_idle(struct device * dev,void * data)59125c7d49eSTony Lindgren static int __init omap_device_late_idle(struct device *dev, void *data)
59225c7d49eSTony Lindgren {
59325c7d49eSTony Lindgren 	struct platform_device *pdev = to_platform_device(dev);
59425c7d49eSTony Lindgren 	struct omap_device *od = to_omap_device(pdev);
595f66e329dSRajendra Nayak 	int i;
59625c7d49eSTony Lindgren 
59725c7d49eSTony Lindgren 	if (!od)
59825c7d49eSTony Lindgren 		return 0;
59925c7d49eSTony Lindgren 
60025c7d49eSTony Lindgren 	/*
60125c7d49eSTony Lindgren 	 * If omap_device state is enabled, but has no driver bound,
60225c7d49eSTony Lindgren 	 * idle it.
60325c7d49eSTony Lindgren 	 */
604f66e329dSRajendra Nayak 
605f66e329dSRajendra Nayak 	/*
606f66e329dSRajendra Nayak 	 * Some devices (like memory controllers) are always kept
607f66e329dSRajendra Nayak 	 * enabled, and should not be idled even with no drivers.
608f66e329dSRajendra Nayak 	 */
609f66e329dSRajendra Nayak 	for (i = 0; i < od->hwmods_cnt; i++)
610f66e329dSRajendra Nayak 		if (od->hwmods[i]->flags & HWMOD_INIT_NO_IDLE)
611f66e329dSRajendra Nayak 			return 0;
612f66e329dSRajendra Nayak 
613fe8291e8SGrygorii Strashko 	if (od->_driver_status != BUS_NOTIFY_BOUND_DRIVER &&
614fe8291e8SGrygorii Strashko 	    od->_driver_status != BUS_NOTIFY_BIND_DRIVER) {
61525c7d49eSTony Lindgren 		if (od->_state == OMAP_DEVICE_STATE_ENABLED) {
61625c7d49eSTony Lindgren 			dev_warn(dev, "%s: enabled but no driver.  Idling\n",
61725c7d49eSTony Lindgren 				 __func__);
61825c7d49eSTony Lindgren 			omap_device_idle(pdev);
61925c7d49eSTony Lindgren 		}
62025c7d49eSTony Lindgren 	}
62125c7d49eSTony Lindgren 
62225c7d49eSTony Lindgren 	return 0;
62325c7d49eSTony Lindgren }
62425c7d49eSTony Lindgren 
omap_device_late_init(void)62525c7d49eSTony Lindgren static int __init omap_device_late_init(void)
62625c7d49eSTony Lindgren {
62725c7d49eSTony Lindgren 	bus_for_each_dev(&platform_bus_type, NULL, NULL, omap_device_late_idle);
6284b91f7f3STony Lindgren 
62925c7d49eSTony Lindgren 	return 0;
63025c7d49eSTony Lindgren }
631e7e17c53SKevin Hilman omap_late_initcall_sync(omap_device_late_init);
632