mfd-core.c 5.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * drivers/mfd/mfd-core.c
 *
 * core MFD support
 * Copyright (c) 2006 Ian Molton
 * Copyright (c) 2007,2008 Dmitry Baryshkov
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/kernel.h>
#include <linux/platform_device.h>
S
Samuel Ortiz 已提交
16
#include <linux/acpi.h>
17
#include <linux/mfd/core.h>
18
#include <linux/pm_runtime.h>
19
#include <linux/slab.h>
20

21
int mfd_cell_enable(struct platform_device *pdev)
22 23 24 25 26 27 28 29 30 31 32 33 34 35
{
	const struct mfd_cell *cell = mfd_get_cell(pdev);
	int err = 0;

	/* only call enable hook if the cell wasn't previously enabled */
	if (atomic_inc_return(cell->usage_count) == 1)
		err = cell->enable(pdev);

	/* if the enable hook failed, decrement counter to allow retries */
	if (err)
		atomic_dec(cell->usage_count);

	return err;
}
36
EXPORT_SYMBOL(mfd_cell_enable);
37

38
int mfd_cell_disable(struct platform_device *pdev)
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
{
	const struct mfd_cell *cell = mfd_get_cell(pdev);
	int err = 0;

	/* only disable if no other clients are using it */
	if (atomic_dec_return(cell->usage_count) == 0)
		err = cell->disable(pdev);

	/* if the disable hook failed, increment to allow retries */
	if (err)
		atomic_inc(cell->usage_count);

	/* sanity check; did someone call disable too many times? */
	WARN_ON(atomic_read(cell->usage_count) < 0);

	return err;
}
56
EXPORT_SYMBOL(mfd_cell_disable);
57

58
static int mfd_add_device(struct device *parent, int id,
B
Ben Dooks 已提交
59 60 61
			  const struct mfd_cell *cell,
			  struct resource *mem_base,
			  int irq_base)
62
{
I
Ian Molton 已提交
63
	struct resource *res;
64 65 66 67
	struct platform_device *pdev;
	int ret = -ENOMEM;
	int r;

68
	pdev = platform_device_alloc(cell->name, id + cell->id);
69 70 71
	if (!pdev)
		goto fail_alloc;

I
Ian Molton 已提交
72 73 74 75
	res = kzalloc(sizeof(*res) * cell->num_resources, GFP_KERNEL);
	if (!res)
		goto fail_device;

76
	pdev->dev.parent = parent;
77

78 79 80
	ret = platform_device_add_data(pdev, cell, sizeof(*cell));
	if (ret)
		goto fail_res;
81 82 83 84 85 86

	for (r = 0; r < cell->num_resources; r++) {
		res[r].name = cell->resources[r].name;
		res[r].flags = cell->resources[r].flags;

		/* Find out base to use */
87
		if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) {
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
			res[r].parent = mem_base;
			res[r].start = mem_base->start +
				cell->resources[r].start;
			res[r].end = mem_base->start +
				cell->resources[r].end;
		} else if (cell->resources[r].flags & IORESOURCE_IRQ) {
			res[r].start = irq_base +
				cell->resources[r].start;
			res[r].end   = irq_base +
				cell->resources[r].end;
		} else {
			res[r].parent = cell->resources[r].parent;
			res[r].start = cell->resources[r].start;
			res[r].end   = cell->resources[r].end;
		}
S
Samuel Ortiz 已提交
103

104 105 106 107 108
		if (!cell->ignore_resource_conflicts) {
			ret = acpi_check_resource_conflict(res);
			if (ret)
				goto fail_res;
		}
109 110
	}

111 112 113
	ret = platform_device_add_resources(pdev, res, cell->num_resources);
	if (ret)
		goto fail_res;
114 115 116

	ret = platform_device_add(pdev);
	if (ret)
I
Ian Molton 已提交
117 118
		goto fail_res;

119 120 121
	if (cell->pm_runtime_no_callbacks)
		pm_runtime_no_callbacks(&pdev->dev);

I
Ian Molton 已提交
122
	kfree(res);
123 124 125 126

	return 0;

/*	platform_device_del(pdev); */
I
Ian Molton 已提交
127 128
fail_res:
	kfree(res);
129 130 131 132 133 134
fail_device:
	platform_device_put(pdev);
fail_alloc:
	return ret;
}

135
int mfd_add_devices(struct device *parent, int id,
136
		    struct mfd_cell *cells, int n_devs,
B
Ben Dooks 已提交
137 138
		    struct resource *mem_base,
		    int irq_base)
139 140 141
{
	int i;
	int ret = 0;
142 143 144 145 146 147
	atomic_t *cnts;

	/* initialize reference counting for all cells */
	cnts = kcalloc(sizeof(*cnts), n_devs, GFP_KERNEL);
	if (!cnts)
		return -ENOMEM;
148 149

	for (i = 0; i < n_devs; i++) {
150 151
		atomic_set(&cnts[i], 0);
		cells[i].usage_count = &cnts[i];
152
		ret = mfd_add_device(parent, id, cells + i, mem_base, irq_base);
153 154 155 156 157 158 159 160 161 162 163
		if (ret)
			break;
	}

	if (ret)
		mfd_remove_devices(parent);

	return ret;
}
EXPORT_SYMBOL(mfd_add_devices);

164
static int mfd_remove_devices_fn(struct device *dev, void *c)
165
{
166 167 168 169 170 171 172 173 174
	struct platform_device *pdev = to_platform_device(dev);
	const struct mfd_cell *cell = mfd_get_cell(pdev);
	atomic_t **usage_count = c;

	/* find the base address of usage_count pointers (for freeing) */
	if (!*usage_count || (cell->usage_count < *usage_count))
		*usage_count = cell->usage_count;

	platform_device_unregister(pdev);
175 176 177
	return 0;
}

178
void mfd_remove_devices(struct device *parent)
179
{
180 181 182 183
	atomic_t *cnts = NULL;

	device_for_each_child(parent, &cnts, mfd_remove_devices_fn);
	kfree(cnts);
184 185 186
}
EXPORT_SYMBOL(mfd_remove_devices);

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
static int add_shared_platform_device(const char *cell, const char *name)
{
	struct mfd_cell cell_entry;
	struct device *dev;
	struct platform_device *pdev;
	int err;

	/* check if we've already registered a device (don't fail if we have) */
	if (bus_find_device_by_name(&platform_bus_type, NULL, name))
		return 0;

	/* fetch the parent cell's device (should already be registered!) */
	dev = bus_find_device_by_name(&platform_bus_type, NULL, cell);
	if (!dev) {
		printk(KERN_ERR "failed to find device for cell %s\n", cell);
		return -ENODEV;
	}
	pdev = to_platform_device(dev);
	memcpy(&cell_entry, mfd_get_cell(pdev), sizeof(cell_entry));

	WARN_ON(!cell_entry.enable);

	cell_entry.name = name;
	err = mfd_add_device(pdev->dev.parent, -1, &cell_entry, NULL, 0);
	if (err)
		dev_err(dev, "MFD add devices failed: %d\n", err);
	return err;
}

int mfd_shared_platform_driver_register(struct platform_driver *drv,
		const char *cellname)
{
	int err;

	err = add_shared_platform_device(cellname, drv->driver.name);
	if (err)
		printk(KERN_ERR "failed to add platform device %s\n",
				drv->driver.name);

	err = platform_driver_register(drv);
	if (err)
		printk(KERN_ERR "failed to add platform driver %s\n",
				drv->driver.name);

	return err;
}
EXPORT_SYMBOL(mfd_shared_platform_driver_register);

void mfd_shared_platform_driver_unregister(struct platform_driver *drv)
{
	struct device *dev;

	dev = bus_find_device_by_name(&platform_bus_type, NULL,
			drv->driver.name);
	if (dev)
		platform_device_unregister(to_platform_device(dev));

	platform_driver_unregister(drv);
}
EXPORT_SYMBOL(mfd_shared_platform_driver_unregister);

248 249
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov");