xref: /openbmc/linux/drivers/mfd/mfd-core.c (revision a9bbba99)
1aa613de6SDmitry Baryshkov /*
2aa613de6SDmitry Baryshkov  * drivers/mfd/mfd-core.c
3aa613de6SDmitry Baryshkov  *
4aa613de6SDmitry Baryshkov  * core MFD support
5aa613de6SDmitry Baryshkov  * Copyright (c) 2006 Ian Molton
6aa613de6SDmitry Baryshkov  * Copyright (c) 2007,2008 Dmitry Baryshkov
7aa613de6SDmitry Baryshkov  *
8aa613de6SDmitry Baryshkov  * This program is free software; you can redistribute it and/or modify
9aa613de6SDmitry Baryshkov  * it under the terms of the GNU General Public License version 2 as
10aa613de6SDmitry Baryshkov  * published by the Free Software Foundation.
11aa613de6SDmitry Baryshkov  *
12aa613de6SDmitry Baryshkov  */
13aa613de6SDmitry Baryshkov 
14aa613de6SDmitry Baryshkov #include <linux/kernel.h>
15aa613de6SDmitry Baryshkov #include <linux/platform_device.h>
1691fededeSSamuel Ortiz #include <linux/acpi.h>
17aa613de6SDmitry Baryshkov #include <linux/mfd/core.h>
184c90aa94SMark Brown #include <linux/pm_runtime.h>
195a0e3ad6STejun Heo #include <linux/slab.h>
20aa613de6SDmitry Baryshkov 
211e29af62SAndres Salomon int mfd_shared_cell_enable(struct platform_device *pdev)
221e29af62SAndres Salomon {
231e29af62SAndres Salomon 	const struct mfd_cell *cell = mfd_get_cell(pdev);
241e29af62SAndres Salomon 	int err = 0;
251e29af62SAndres Salomon 
261e29af62SAndres Salomon 	/* only call enable hook if the cell wasn't previously enabled */
271e29af62SAndres Salomon 	if (atomic_inc_return(cell->usage_count) == 1)
281e29af62SAndres Salomon 		err = cell->enable(pdev);
291e29af62SAndres Salomon 
301e29af62SAndres Salomon 	/* if the enable hook failed, decrement counter to allow retries */
311e29af62SAndres Salomon 	if (err)
321e29af62SAndres Salomon 		atomic_dec(cell->usage_count);
331e29af62SAndres Salomon 
341e29af62SAndres Salomon 	return err;
351e29af62SAndres Salomon }
361e29af62SAndres Salomon EXPORT_SYMBOL(mfd_shared_cell_enable);
371e29af62SAndres Salomon 
381e29af62SAndres Salomon int mfd_shared_cell_disable(struct platform_device *pdev)
391e29af62SAndres Salomon {
401e29af62SAndres Salomon 	const struct mfd_cell *cell = mfd_get_cell(pdev);
411e29af62SAndres Salomon 	int err = 0;
421e29af62SAndres Salomon 
431e29af62SAndres Salomon 	/* only disable if no other clients are using it */
441e29af62SAndres Salomon 	if (atomic_dec_return(cell->usage_count) == 0)
451e29af62SAndres Salomon 		err = cell->disable(pdev);
461e29af62SAndres Salomon 
471e29af62SAndres Salomon 	/* if the disable hook failed, increment to allow retries */
481e29af62SAndres Salomon 	if (err)
491e29af62SAndres Salomon 		atomic_inc(cell->usage_count);
501e29af62SAndres Salomon 
511e29af62SAndres Salomon 	/* sanity check; did someone call disable too many times? */
521e29af62SAndres Salomon 	WARN_ON(atomic_read(cell->usage_count) < 0);
531e29af62SAndres Salomon 
541e29af62SAndres Salomon 	return err;
551e29af62SAndres Salomon }
561e29af62SAndres Salomon EXPORT_SYMBOL(mfd_shared_cell_disable);
571e29af62SAndres Salomon 
58424f525aSDmitry Baryshkov static int mfd_add_device(struct device *parent, int id,
59aa613de6SDmitry Baryshkov 			  const struct mfd_cell *cell,
60aa613de6SDmitry Baryshkov 			  struct resource *mem_base,
61aa613de6SDmitry Baryshkov 			  int irq_base)
62aa613de6SDmitry Baryshkov {
63a87903f3SIan Molton 	struct resource *res;
64aa613de6SDmitry Baryshkov 	struct platform_device *pdev;
65aa613de6SDmitry Baryshkov 	int ret = -ENOMEM;
66aa613de6SDmitry Baryshkov 	int r;
67aa613de6SDmitry Baryshkov 
683bed6e41SMark Brown 	pdev = platform_device_alloc(cell->name, id + cell->id);
69aa613de6SDmitry Baryshkov 	if (!pdev)
70aa613de6SDmitry Baryshkov 		goto fail_alloc;
71aa613de6SDmitry Baryshkov 
72a87903f3SIan Molton 	res = kzalloc(sizeof(*res) * cell->num_resources, GFP_KERNEL);
73a87903f3SIan Molton 	if (!res)
74a87903f3SIan Molton 		goto fail_device;
75a87903f3SIan Molton 
76424f525aSDmitry Baryshkov 	pdev->dev.parent = parent;
77aa613de6SDmitry Baryshkov 
78fe891a00SAndres Salomon 	ret = platform_device_add_data(pdev, cell, sizeof(*cell));
79aa613de6SDmitry Baryshkov 	if (ret)
80a87903f3SIan Molton 		goto fail_res;
81aa613de6SDmitry Baryshkov 
82aa613de6SDmitry Baryshkov 	for (r = 0; r < cell->num_resources; r++) {
83aa613de6SDmitry Baryshkov 		res[r].name = cell->resources[r].name;
84aa613de6SDmitry Baryshkov 		res[r].flags = cell->resources[r].flags;
85aa613de6SDmitry Baryshkov 
86aa613de6SDmitry Baryshkov 		/* Find out base to use */
87f03cfcbcSSamuel Ortiz 		if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) {
88aa613de6SDmitry Baryshkov 			res[r].parent = mem_base;
89aa613de6SDmitry Baryshkov 			res[r].start = mem_base->start +
90aa613de6SDmitry Baryshkov 				cell->resources[r].start;
91aa613de6SDmitry Baryshkov 			res[r].end = mem_base->start +
92aa613de6SDmitry Baryshkov 				cell->resources[r].end;
93aa613de6SDmitry Baryshkov 		} else if (cell->resources[r].flags & IORESOURCE_IRQ) {
94aa613de6SDmitry Baryshkov 			res[r].start = irq_base +
95aa613de6SDmitry Baryshkov 				cell->resources[r].start;
96aa613de6SDmitry Baryshkov 			res[r].end   = irq_base +
97aa613de6SDmitry Baryshkov 				cell->resources[r].end;
98aa613de6SDmitry Baryshkov 		} else {
99aa613de6SDmitry Baryshkov 			res[r].parent = cell->resources[r].parent;
100aa613de6SDmitry Baryshkov 			res[r].start = cell->resources[r].start;
101aa613de6SDmitry Baryshkov 			res[r].end   = cell->resources[r].end;
102aa613de6SDmitry Baryshkov 		}
10391fededeSSamuel Ortiz 
1045f2545faSDaniel Drake 		if (!cell->ignore_resource_conflicts) {
10591fededeSSamuel Ortiz 			ret = acpi_check_resource_conflict(res);
10691fededeSSamuel Ortiz 			if (ret)
10791fededeSSamuel Ortiz 				goto fail_res;
108aa613de6SDmitry Baryshkov 		}
1095f2545faSDaniel Drake 	}
110aa613de6SDmitry Baryshkov 
1118af5fe3bSAxel Lin 	ret = platform_device_add_resources(pdev, res, cell->num_resources);
1128af5fe3bSAxel Lin 	if (ret)
1138af5fe3bSAxel Lin 		goto fail_res;
114aa613de6SDmitry Baryshkov 
115aa613de6SDmitry Baryshkov 	ret = platform_device_add(pdev);
116aa613de6SDmitry Baryshkov 	if (ret)
117a87903f3SIan Molton 		goto fail_res;
118a87903f3SIan Molton 
1194c90aa94SMark Brown 	if (cell->pm_runtime_no_callbacks)
1204c90aa94SMark Brown 		pm_runtime_no_callbacks(&pdev->dev);
1214c90aa94SMark Brown 
122a87903f3SIan Molton 	kfree(res);
123aa613de6SDmitry Baryshkov 
124aa613de6SDmitry Baryshkov 	return 0;
125aa613de6SDmitry Baryshkov 
126aa613de6SDmitry Baryshkov /*	platform_device_del(pdev); */
127a87903f3SIan Molton fail_res:
128a87903f3SIan Molton 	kfree(res);
129aa613de6SDmitry Baryshkov fail_device:
130aa613de6SDmitry Baryshkov 	platform_device_put(pdev);
131aa613de6SDmitry Baryshkov fail_alloc:
132aa613de6SDmitry Baryshkov 	return ret;
133aa613de6SDmitry Baryshkov }
134aa613de6SDmitry Baryshkov 
135424f525aSDmitry Baryshkov int mfd_add_devices(struct device *parent, int id,
1361e29af62SAndres Salomon 		    struct mfd_cell *cells, int n_devs,
137aa613de6SDmitry Baryshkov 		    struct resource *mem_base,
138aa613de6SDmitry Baryshkov 		    int irq_base)
139aa613de6SDmitry Baryshkov {
140aa613de6SDmitry Baryshkov 	int i;
141aa613de6SDmitry Baryshkov 	int ret = 0;
1421e29af62SAndres Salomon 	atomic_t *cnts;
1431e29af62SAndres Salomon 
1441e29af62SAndres Salomon 	/* initialize reference counting for all cells */
1451e29af62SAndres Salomon 	cnts = kcalloc(sizeof(*cnts), n_devs, GFP_KERNEL);
1461e29af62SAndres Salomon 	if (!cnts)
1471e29af62SAndres Salomon 		return -ENOMEM;
148aa613de6SDmitry Baryshkov 
149aa613de6SDmitry Baryshkov 	for (i = 0; i < n_devs; i++) {
1501e29af62SAndres Salomon 		atomic_set(&cnts[i], 0);
1511e29af62SAndres Salomon 		cells[i].usage_count = &cnts[i];
152424f525aSDmitry Baryshkov 		ret = mfd_add_device(parent, id, cells + i, mem_base, irq_base);
153aa613de6SDmitry Baryshkov 		if (ret)
154aa613de6SDmitry Baryshkov 			break;
155aa613de6SDmitry Baryshkov 	}
156aa613de6SDmitry Baryshkov 
157aa613de6SDmitry Baryshkov 	if (ret)
158aa613de6SDmitry Baryshkov 		mfd_remove_devices(parent);
159aa613de6SDmitry Baryshkov 
160aa613de6SDmitry Baryshkov 	return ret;
161aa613de6SDmitry Baryshkov }
162aa613de6SDmitry Baryshkov EXPORT_SYMBOL(mfd_add_devices);
163aa613de6SDmitry Baryshkov 
1641e29af62SAndres Salomon static int mfd_remove_devices_fn(struct device *dev, void *c)
165aa613de6SDmitry Baryshkov {
1661e29af62SAndres Salomon 	struct platform_device *pdev = to_platform_device(dev);
1671e29af62SAndres Salomon 	const struct mfd_cell *cell = mfd_get_cell(pdev);
1681e29af62SAndres Salomon 	atomic_t **usage_count = c;
1691e29af62SAndres Salomon 
1701e29af62SAndres Salomon 	/* find the base address of usage_count pointers (for freeing) */
1711e29af62SAndres Salomon 	if (!*usage_count || (cell->usage_count < *usage_count))
1721e29af62SAndres Salomon 		*usage_count = cell->usage_count;
1731e29af62SAndres Salomon 
1741e29af62SAndres Salomon 	platform_device_unregister(pdev);
175aa613de6SDmitry Baryshkov 	return 0;
176aa613de6SDmitry Baryshkov }
177aa613de6SDmitry Baryshkov 
178424f525aSDmitry Baryshkov void mfd_remove_devices(struct device *parent)
179aa613de6SDmitry Baryshkov {
1801e29af62SAndres Salomon 	atomic_t *cnts = NULL;
1811e29af62SAndres Salomon 
1821e29af62SAndres Salomon 	device_for_each_child(parent, &cnts, mfd_remove_devices_fn);
1831e29af62SAndres Salomon 	kfree(cnts);
184aa613de6SDmitry Baryshkov }
185aa613de6SDmitry Baryshkov EXPORT_SYMBOL(mfd_remove_devices);
186aa613de6SDmitry Baryshkov 
187a9bbba99SAndres Salomon static int add_shared_platform_device(const char *cell, const char *name)
188a9bbba99SAndres Salomon {
189a9bbba99SAndres Salomon 	struct mfd_cell cell_entry;
190a9bbba99SAndres Salomon 	struct device *dev;
191a9bbba99SAndres Salomon 	struct platform_device *pdev;
192a9bbba99SAndres Salomon 	int err;
193a9bbba99SAndres Salomon 
194a9bbba99SAndres Salomon 	/* check if we've already registered a device (don't fail if we have) */
195a9bbba99SAndres Salomon 	if (bus_find_device_by_name(&platform_bus_type, NULL, name))
196a9bbba99SAndres Salomon 		return 0;
197a9bbba99SAndres Salomon 
198a9bbba99SAndres Salomon 	/* fetch the parent cell's device (should already be registered!) */
199a9bbba99SAndres Salomon 	dev = bus_find_device_by_name(&platform_bus_type, NULL, cell);
200a9bbba99SAndres Salomon 	if (!dev) {
201a9bbba99SAndres Salomon 		printk(KERN_ERR "failed to find device for cell %s\n", cell);
202a9bbba99SAndres Salomon 		return -ENODEV;
203a9bbba99SAndres Salomon 	}
204a9bbba99SAndres Salomon 	pdev = to_platform_device(dev);
205a9bbba99SAndres Salomon 	memcpy(&cell_entry, mfd_get_cell(pdev), sizeof(cell_entry));
206a9bbba99SAndres Salomon 
207a9bbba99SAndres Salomon 	WARN_ON(!cell_entry.enable);
208a9bbba99SAndres Salomon 
209a9bbba99SAndres Salomon 	cell_entry.name = name;
210a9bbba99SAndres Salomon 	err = mfd_add_device(pdev->dev.parent, -1, &cell_entry, NULL, 0);
211a9bbba99SAndres Salomon 	if (err)
212a9bbba99SAndres Salomon 		dev_err(dev, "MFD add devices failed: %d\n", err);
213a9bbba99SAndres Salomon 	return err;
214a9bbba99SAndres Salomon }
215a9bbba99SAndres Salomon 
216a9bbba99SAndres Salomon int mfd_shared_platform_driver_register(struct platform_driver *drv,
217a9bbba99SAndres Salomon 		const char *cellname)
218a9bbba99SAndres Salomon {
219a9bbba99SAndres Salomon 	int err;
220a9bbba99SAndres Salomon 
221a9bbba99SAndres Salomon 	err = add_shared_platform_device(cellname, drv->driver.name);
222a9bbba99SAndres Salomon 	if (err)
223a9bbba99SAndres Salomon 		printk(KERN_ERR "failed to add platform device %s\n",
224a9bbba99SAndres Salomon 				drv->driver.name);
225a9bbba99SAndres Salomon 
226a9bbba99SAndres Salomon 	err = platform_driver_register(drv);
227a9bbba99SAndres Salomon 	if (err)
228a9bbba99SAndres Salomon 		printk(KERN_ERR "failed to add platform driver %s\n",
229a9bbba99SAndres Salomon 				drv->driver.name);
230a9bbba99SAndres Salomon 
231a9bbba99SAndres Salomon 	return err;
232a9bbba99SAndres Salomon }
233a9bbba99SAndres Salomon EXPORT_SYMBOL(mfd_shared_platform_driver_register);
234a9bbba99SAndres Salomon 
235a9bbba99SAndres Salomon void mfd_shared_platform_driver_unregister(struct platform_driver *drv)
236a9bbba99SAndres Salomon {
237a9bbba99SAndres Salomon 	struct device *dev;
238a9bbba99SAndres Salomon 
239a9bbba99SAndres Salomon 	dev = bus_find_device_by_name(&platform_bus_type, NULL,
240a9bbba99SAndres Salomon 			drv->driver.name);
241a9bbba99SAndres Salomon 	if (dev)
242a9bbba99SAndres Salomon 		platform_device_unregister(to_platform_device(dev));
243a9bbba99SAndres Salomon 
244a9bbba99SAndres Salomon 	platform_driver_unregister(drv);
245a9bbba99SAndres Salomon }
246a9bbba99SAndres Salomon EXPORT_SYMBOL(mfd_shared_platform_driver_unregister);
247a9bbba99SAndres Salomon 
248aa613de6SDmitry Baryshkov MODULE_LICENSE("GPL");
249aa613de6SDmitry Baryshkov MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov");
250