Blame view

drivers/mfd/mfd-core.c 9.67 KB
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
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>
91fedede0   Samuel Ortiz   mfd: Check for AC...
16
  #include <linux/acpi.h>
4d215cabc   Andy Shevchenko   mfd: core: propag...
17
  #include <linux/property.h>
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
18
  #include <linux/mfd/core.h>
4c90aa94f   Mark Brown   mfd: Provide pm_r...
19
  #include <linux/pm_runtime.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
20
  #include <linux/slab.h>
4e36dd331   Paul Gortmaker   mfd: Add module.h...
21
  #include <linux/module.h>
c94bb233a   Lee Jones   mfd: Make MFD cor...
22
23
  #include <linux/irqdomain.h>
  #include <linux/of.h>
7fcd42746   Charles Keepax   mfd: Allow mappin...
24
  #include <linux/regulator/consumer.h>
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
25

b9fbb62eb   Charles Keepax   mfd: Only unregis...
26
27
28
  static struct device_type mfd_dev_type = {
  	.name	= "mfd_device",
  };
f77289ac2   Andres Salomon   mfd: Rename mfd_s...
29
  int mfd_cell_enable(struct platform_device *pdev)
1e29af62f   Andres Salomon   mfd: Add refcount...
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  {
  	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;
  }
f77289ac2   Andres Salomon   mfd: Rename mfd_s...
44
  EXPORT_SYMBOL(mfd_cell_enable);
1e29af62f   Andres Salomon   mfd: Add refcount...
45

f77289ac2   Andres Salomon   mfd: Rename mfd_s...
46
  int mfd_cell_disable(struct platform_device *pdev)
1e29af62f   Andres Salomon   mfd: Add refcount...
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
  {
  	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;
  }
f77289ac2   Andres Salomon   mfd: Rename mfd_s...
64
  EXPORT_SYMBOL(mfd_cell_disable);
1e29af62f   Andres Salomon   mfd: Add refcount...
65

e710d7d5a   Samuel Ortiz   mfd: Fetch cell p...
66
  static int mfd_platform_add_cell(struct platform_device *pdev,
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
67
68
  				 const struct mfd_cell *cell,
  				 atomic_t *usage_count)
e710d7d5a   Samuel Ortiz   mfd: Fetch cell p...
69
70
71
72
73
74
75
  {
  	if (!cell)
  		return 0;
  
  	pdev->mfd_cell = kmemdup(cell, sizeof(*cell), GFP_KERNEL);
  	if (!pdev->mfd_cell)
  		return -ENOMEM;
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
76
  	pdev->mfd_cell->usage_count = usage_count;
e710d7d5a   Samuel Ortiz   mfd: Fetch cell p...
77
78
  	return 0;
  }
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
79
80
81
82
  #if IS_ENABLED(CONFIG_ACPI)
  static void mfd_acpi_add_device(const struct mfd_cell *cell,
  				struct platform_device *pdev)
  {
98a3be44f   Andy Shevchenko   mfd: core: redo A...
83
84
  	const struct mfd_cell_acpi_match *match = cell->acpi_match;
  	struct acpi_device *parent, *child;
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
85
  	struct acpi_device *adev;
98a3be44f   Andy Shevchenko   mfd: core: redo A...
86
87
  	parent = ACPI_COMPANION(pdev->dev.parent);
  	if (!parent)
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
88
89
90
  		return;
  
  	/*
98a3be44f   Andy Shevchenko   mfd: core: redo A...
91
92
93
94
95
96
97
  	 * MFD child device gets its ACPI handle either from the ACPI device
  	 * directly under the parent that matches the either _HID or _CID, or
  	 * _ADR or it will use the parent handle if is no ID is given.
  	 *
  	 * Note that use of _ADR is a grey area in the ACPI specification,
  	 * though Intel Galileo Gen2 is using it to distinguish the children
  	 * devices.
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
98
  	 */
98a3be44f   Andy Shevchenko   mfd: core: redo A...
99
100
101
102
103
104
105
  	adev = parent;
  	if (match) {
  		if (match->pnpid) {
  			struct acpi_device_id ids[2] = {};
  
  			strlcpy(ids[0].id, match->pnpid, sizeof(ids[0].id));
  			list_for_each_entry(child, &parent->children, node) {
ee414de52   Irina Tirdea   mfd: core: Fix AC...
106
  				if (!acpi_match_device_ids(child, ids)) {
98a3be44f   Andy Shevchenko   mfd: core: redo A...
107
108
109
  					adev = child;
  					break;
  				}
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
110
  			}
98a3be44f   Andy Shevchenko   mfd: core: redo A...
111
112
113
114
115
116
117
118
119
120
121
122
123
124
  		} else {
  			unsigned long long adr;
  			acpi_status status;
  
  			list_for_each_entry(child, &parent->children, node) {
  				status = acpi_evaluate_integer(child->handle,
  							       "_ADR", NULL,
  							       &adr);
  				if (ACPI_SUCCESS(status) && match->adr == adr) {
  					adev = child;
  					break;
  				}
  			}
  		}
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
125
126
127
128
129
130
131
132
133
134
  	}
  
  	ACPI_COMPANION_SET(&pdev->dev, adev);
  }
  #else
  static inline void mfd_acpi_add_device(const struct mfd_cell *cell,
  				       struct platform_device *pdev)
  {
  }
  #endif
424f525a1   Dmitry Eremin-Solenikov   mfd: accept pure ...
135
  static int mfd_add_device(struct device *parent, int id,
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
136
  			  const struct mfd_cell *cell, atomic_t *usage_count,
7f71ac937   Ben Dooks   mfd: Coding style...
137
  			  struct resource *mem_base,
0848c94fb   Mark Brown   mfd: core: Push i...
138
  			  int irq_base, struct irq_domain *domain)
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
139
  {
a87903f3b   Ian Molton   mfd: reduce stack...
140
  	struct resource *res;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
141
  	struct platform_device *pdev;
c94bb233a   Lee Jones   mfd: Make MFD cor...
142
  	struct device_node *np = NULL;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
143
  	int ret = -ENOMEM;
6e3f62f07   Johan Hovold   mfd: core: Fix pl...
144
  	int platform_id;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
145
  	int r;
a77c50b44   Johan Hovold   mfd: core: Fix pl...
146
  	if (id == PLATFORM_DEVID_AUTO)
6e3f62f07   Johan Hovold   mfd: core: Fix pl...
147
148
149
150
151
  		platform_id = id;
  	else
  		platform_id = id + cell->id;
  
  	pdev = platform_device_alloc(cell->name, platform_id);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
152
153
  	if (!pdev)
  		goto fail_alloc;
a87903f3b   Ian Molton   mfd: reduce stack...
154
155
156
  	res = kzalloc(sizeof(*res) * cell->num_resources, GFP_KERNEL);
  	if (!res)
  		goto fail_device;
424f525a1   Dmitry Eremin-Solenikov   mfd: accept pure ...
157
  	pdev->dev.parent = parent;
b9fbb62eb   Charles Keepax   mfd: Only unregis...
158
  	pdev->dev.type = &mfd_dev_type;
b018e1361   Benedikt Spranger   mfd: core: Copy D...
159
160
  	pdev->dev.dma_mask = parent->dma_mask;
  	pdev->dev.dma_parms = parent->dma_parms;
4f08df1b0   Boris BREZILLON   mfd: Inherit cohe...
161
  	pdev->dev.coherent_dma_mask = parent->coherent_dma_mask;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
162

d137be00e   Charles Keepax   mfd: core: Don't ...
163
  	ret = regulator_bulk_register_supply_alias(
7fcd42746   Charles Keepax   mfd: Allow mappin...
164
165
166
167
168
  			&pdev->dev, cell->parent_supplies,
  			parent, cell->parent_supplies,
  			cell->num_parent_supplies);
  	if (ret < 0)
  		goto fail_res;
c94bb233a   Lee Jones   mfd: Make MFD cor...
169
170
171
172
  	if (parent->of_node && cell->of_compatible) {
  		for_each_child_of_node(parent->of_node, np) {
  			if (of_device_is_compatible(np, cell->of_compatible)) {
  				pdev->dev.of_node = np;
c94bb233a   Lee Jones   mfd: Make MFD cor...
173
174
175
176
  				break;
  			}
  		}
  	}
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
177
  	mfd_acpi_add_device(cell, pdev);
eb8956074   Samuel Ortiz   mfd: Add platform...
178
179
180
181
  	if (cell->pdata_size) {
  		ret = platform_device_add_data(pdev,
  					cell->platform_data, cell->pdata_size);
  		if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
182
  			goto fail_alias;
eb8956074   Samuel Ortiz   mfd: Add platform...
183
  	}
f4d052660   Heikki Krogerus   device property: ...
184
185
  	if (cell->properties) {
  		ret = platform_device_add_properties(pdev, cell->properties);
4d215cabc   Andy Shevchenko   mfd: core: propag...
186
187
188
  		if (ret)
  			goto fail_alias;
  	}
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
189
  	ret = mfd_platform_add_cell(pdev, cell, usage_count);
fe891a008   Andres Salomon   mfd-core: Uncondi...
190
  	if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
191
  		goto fail_alias;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
192

aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
193
194
195
196
197
  	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 */
f03cfcbc8   Samuel Ortiz   mfd: Check for me...
198
  		if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) {
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
199
200
201
202
203
204
  			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) {
c94bb233a   Lee Jones   mfd: Make MFD cor...
205
206
207
208
209
210
211
212
213
214
215
216
  			if (domain) {
  				/* Unable to create mappings for IRQ ranges. */
  				WARN_ON(cell->resources[r].start !=
  					cell->resources[r].end);
  				res[r].start = res[r].end = irq_create_mapping(
  					domain, cell->resources[r].start);
  			} else {
  				res[r].start = irq_base +
  					cell->resources[r].start;
  				res[r].end   = irq_base +
  					cell->resources[r].end;
  			}
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
217
218
219
220
221
  		} else {
  			res[r].parent = cell->resources[r].parent;
  			res[r].start = cell->resources[r].start;
  			res[r].end   = cell->resources[r].end;
  		}
91fedede0   Samuel Ortiz   mfd: Check for AC...
222

5f2545fa1   Daniel Drake   mfd: Allow for by...
223
  		if (!cell->ignore_resource_conflicts) {
ec40c606c   Lorenzo Pieralisi   mfd: Check ACPI d...
224
225
226
227
228
  			if (has_acpi_companion(&pdev->dev)) {
  				ret = acpi_check_resource_conflict(&res[r]);
  				if (ret)
  					goto fail_alias;
  			}
5f2545fa1   Daniel Drake   mfd: Allow for by...
229
  		}
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
230
  	}
8af5fe3bc   Axel Lin   mfd: properly han...
231
232
  	ret = platform_device_add_resources(pdev, res, cell->num_resources);
  	if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
233
  		goto fail_alias;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
234
235
236
  
  	ret = platform_device_add(pdev);
  	if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
237
  		goto fail_alias;
a87903f3b   Ian Molton   mfd: reduce stack...
238

4c90aa94f   Mark Brown   mfd: Provide pm_r...
239
240
  	if (cell->pm_runtime_no_callbacks)
  		pm_runtime_no_callbacks(&pdev->dev);
a87903f3b   Ian Molton   mfd: reduce stack...
241
  	kfree(res);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
242
243
  
  	return 0;
7fcd42746   Charles Keepax   mfd: Allow mappin...
244
  fail_alias:
d137be00e   Charles Keepax   mfd: core: Don't ...
245
246
247
  	regulator_bulk_unregister_supply_alias(&pdev->dev,
  					       cell->parent_supplies,
  					       cell->num_parent_supplies);
a87903f3b   Ian Molton   mfd: reduce stack...
248
249
  fail_res:
  	kfree(res);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
250
251
252
253
254
  fail_device:
  	platform_device_put(pdev);
  fail_alloc:
  	return ret;
  }
424f525a1   Dmitry Eremin-Solenikov   mfd: accept pure ...
255
  int mfd_add_devices(struct device *parent, int id,
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
256
  		    const struct mfd_cell *cells, int n_devs,
7f71ac937   Ben Dooks   mfd: Coding style...
257
  		    struct resource *mem_base,
0848c94fb   Mark Brown   mfd: core: Push i...
258
  		    int irq_base, struct irq_domain *domain)
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
259
260
  {
  	int i;
0b208e41a   Geert Uytterhoeven   mfd: Fix memory l...
261
  	int ret;
1e29af62f   Andres Salomon   mfd: Add refcount...
262
263
264
  	atomic_t *cnts;
  
  	/* initialize reference counting for all cells */
d1b5c5e23   Axel Lin   mfd: Fix kcalloc ...
265
  	cnts = kcalloc(n_devs, sizeof(*cnts), GFP_KERNEL);
1e29af62f   Andres Salomon   mfd: Add refcount...
266
267
  	if (!cnts)
  		return -ENOMEM;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
268
269
  
  	for (i = 0; i < n_devs; i++) {
1e29af62f   Andres Salomon   mfd: Add refcount...
270
  		atomic_set(&cnts[i], 0);
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
271
  		ret = mfd_add_device(parent, id, cells + i, cnts + i, mem_base,
0848c94fb   Mark Brown   mfd: core: Push i...
272
  				     irq_base, domain);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
273
  		if (ret)
0b208e41a   Geert Uytterhoeven   mfd: Fix memory l...
274
  			goto fail;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
275
  	}
0b208e41a   Geert Uytterhoeven   mfd: Fix memory l...
276
  	return 0;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
277

0b208e41a   Geert Uytterhoeven   mfd: Fix memory l...
278
279
280
281
282
  fail:
  	if (i)
  		mfd_remove_devices(parent);
  	else
  		kfree(cnts);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
283
284
285
  	return ret;
  }
  EXPORT_SYMBOL(mfd_add_devices);
1e29af62f   Andres Salomon   mfd: Add refcount...
286
  static int mfd_remove_devices_fn(struct device *dev, void *c)
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
287
  {
b9fbb62eb   Charles Keepax   mfd: Only unregis...
288
289
  	struct platform_device *pdev;
  	const struct mfd_cell *cell;
1e29af62f   Andres Salomon   mfd: Add refcount...
290
  	atomic_t **usage_count = c;
b9fbb62eb   Charles Keepax   mfd: Only unregis...
291
292
293
294
295
  	if (dev->type != &mfd_dev_type)
  		return 0;
  
  	pdev = to_platform_device(dev);
  	cell = mfd_get_cell(pdev);
d137be00e   Charles Keepax   mfd: core: Don't ...
296
297
  	regulator_bulk_unregister_supply_alias(dev, cell->parent_supplies,
  					       cell->num_parent_supplies);
1e29af62f   Andres Salomon   mfd: Add refcount...
298
299
300
301
302
  	/* 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);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
303
304
  	return 0;
  }
424f525a1   Dmitry Eremin-Solenikov   mfd: accept pure ...
305
  void mfd_remove_devices(struct device *parent)
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
306
  {
1e29af62f   Andres Salomon   mfd: Add refcount...
307
  	atomic_t *cnts = NULL;
b9a8a271c   Andy Shevchenko   mfd: make mfd_rem...
308
  	device_for_each_child_reverse(parent, &cnts, mfd_remove_devices_fn);
1e29af62f   Andres Salomon   mfd: Add refcount...
309
  	kfree(cnts);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
310
311
  }
  EXPORT_SYMBOL(mfd_remove_devices);
a8f447be8   Laxman Dewangan   mfd: Add resource...
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
  static void devm_mfd_dev_release(struct device *dev, void *res)
  {
  	mfd_remove_devices(dev);
  }
  
  /**
   * devm_mfd_add_devices - Resource managed version of mfd_add_devices()
   *
   * Returns 0 on success or an appropriate negative error number on failure.
   * All child-devices of the MFD will automatically be removed when it gets
   * unbinded.
   */
  int devm_mfd_add_devices(struct device *dev, int id,
  			 const struct mfd_cell *cells, int n_devs,
  			 struct resource *mem_base,
  			 int irq_base, struct irq_domain *domain)
  {
  	struct device **ptr;
  	int ret;
  
  	ptr = devres_alloc(devm_mfd_dev_release, sizeof(*ptr), GFP_KERNEL);
  	if (!ptr)
  		return -ENOMEM;
  
  	ret = mfd_add_devices(dev, id, cells, n_devs, mem_base,
  			      irq_base, domain);
  	if (ret < 0) {
  		devres_free(ptr);
  		return ret;
  	}
  
  	*ptr = dev;
  	devres_add(dev, ptr);
  
  	return ret;
  }
  EXPORT_SYMBOL(devm_mfd_add_devices);
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
349
  int mfd_clone_cell(const char *cell, const char **clones, size_t n_clones)
a9bbba996   Andres Salomon   mfd: add platform...
350
351
352
353
  {
  	struct mfd_cell cell_entry;
  	struct device *dev;
  	struct platform_device *pdev;
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
354
  	int i;
a9bbba996   Andres Salomon   mfd: add platform...
355
356
357
358
359
360
361
362
363
364
365
366
  
  	/* 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
  ", cell);
  		return -ENODEV;
  	}
  	pdev = to_platform_device(dev);
  	memcpy(&cell_entry, mfd_get_cell(pdev), sizeof(cell_entry));
  
  	WARN_ON(!cell_entry.enable);
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
367
368
369
  	for (i = 0; i < n_clones; i++) {
  		cell_entry.name = clones[i];
  		/* don't give up if a single call fails; just report error */
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
370
371
  		if (mfd_add_device(pdev->dev.parent, -1, &cell_entry,
  				   cell_entry.usage_count, NULL, 0, NULL))
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
372
373
374
375
  			dev_err(dev, "failed to create platform device '%s'
  ",
  					clones[i]);
  	}
a9bbba996   Andres Salomon   mfd: add platform...
376

fa1df6916   Andres Salomon   mfd: Add mfd_clon...
377
  	return 0;
a9bbba996   Andres Salomon   mfd: add platform...
378
  }
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
379
  EXPORT_SYMBOL(mfd_clone_cell);
a9bbba996   Andres Salomon   mfd: add platform...
380

aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
381
382
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov");