Blame view

drivers/mfd/mfd-core.c 8.1 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>
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
17
  #include <linux/mfd/core.h>
4c90aa94f   Mark Brown   mfd: Provide pm_r...
18
  #include <linux/pm_runtime.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
19
  #include <linux/slab.h>
4e36dd331   Paul Gortmaker   mfd: Add module.h...
20
  #include <linux/module.h>
c94bb233a   Lee Jones   mfd: Make MFD cor...
21
22
  #include <linux/irqdomain.h>
  #include <linux/of.h>
7fcd42746   Charles Keepax   mfd: Allow mappin...
23
  #include <linux/regulator/consumer.h>
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
24

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

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

e710d7d5a   Samuel Ortiz   mfd: Fetch cell p...
65
  static int mfd_platform_add_cell(struct platform_device *pdev,
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
66
67
  				 const struct mfd_cell *cell,
  				 atomic_t *usage_count)
e710d7d5a   Samuel Ortiz   mfd: Fetch cell p...
68
69
70
71
72
73
74
  {
  	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...
75
  	pdev->mfd_cell->usage_count = usage_count;
e710d7d5a   Samuel Ortiz   mfd: Fetch cell p...
76
77
  	return 0;
  }
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
  #if IS_ENABLED(CONFIG_ACPI)
  static void mfd_acpi_add_device(const struct mfd_cell *cell,
  				struct platform_device *pdev)
  {
  	struct acpi_device *parent_adev;
  	struct acpi_device *adev;
  
  	parent_adev = ACPI_COMPANION(pdev->dev.parent);
  	if (!parent_adev)
  		return;
  
  	/*
  	 * MFD child device gets its ACPI handle either from the ACPI
  	 * device directly under the parent that matches the acpi_pnpid or
  	 * it will use the parent handle if is no acpi_pnpid is given.
  	 */
  	adev = parent_adev;
  	if (cell->acpi_pnpid) {
  		struct acpi_device_id ids[2] = {};
  		struct acpi_device *child_adev;
  
  		strlcpy(ids[0].id, cell->acpi_pnpid, sizeof(ids[0].id));
  		list_for_each_entry(child_adev, &parent_adev->children, node)
  			if (acpi_match_device_ids(child_adev, ids)) {
  				adev = child_adev;
  				break;
  			}
  	}
  
  	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 ...
115
  static int mfd_add_device(struct device *parent, int id,
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
116
  			  const struct mfd_cell *cell, atomic_t *usage_count,
7f71ac937   Ben Dooks   mfd: Coding style...
117
  			  struct resource *mem_base,
0848c94fb   Mark Brown   mfd: core: Push i...
118
  			  int irq_base, struct irq_domain *domain)
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
119
  {
a87903f3b   Ian Molton   mfd: reduce stack...
120
  	struct resource *res;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
121
  	struct platform_device *pdev;
c94bb233a   Lee Jones   mfd: Make MFD cor...
122
  	struct device_node *np = NULL;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
123
  	int ret = -ENOMEM;
6e3f62f07   Johan Hovold   mfd: core: Fix pl...
124
  	int platform_id;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
125
  	int r;
6e3f62f07   Johan Hovold   mfd: core: Fix pl...
126
127
128
129
130
131
  	if (id < 0)
  		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...
132
133
  	if (!pdev)
  		goto fail_alloc;
a87903f3b   Ian Molton   mfd: reduce stack...
134
135
136
  	res = kzalloc(sizeof(*res) * cell->num_resources, GFP_KERNEL);
  	if (!res)
  		goto fail_device;
424f525a1   Dmitry Eremin-Solenikov   mfd: accept pure ...
137
  	pdev->dev.parent = parent;
b9fbb62eb   Charles Keepax   mfd: Only unregis...
138
  	pdev->dev.type = &mfd_dev_type;
b018e1361   Benedikt Spranger   mfd: core: Copy D...
139
140
  	pdev->dev.dma_mask = parent->dma_mask;
  	pdev->dev.dma_parms = parent->dma_parms;
4f08df1b0   Boris BREZILLON   mfd: Inherit cohe...
141
  	pdev->dev.coherent_dma_mask = parent->coherent_dma_mask;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
142

d137be00e   Charles Keepax   mfd: core: Don't ...
143
  	ret = regulator_bulk_register_supply_alias(
7fcd42746   Charles Keepax   mfd: Allow mappin...
144
145
146
147
148
  			&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...
149
150
151
152
  	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...
153
154
155
156
  				break;
  			}
  		}
  	}
6ab343012   Mika Westerberg   mfd: Add ACPI sup...
157
  	mfd_acpi_add_device(cell, pdev);
eb8956074   Samuel Ortiz   mfd: Add platform...
158
159
160
161
  	if (cell->pdata_size) {
  		ret = platform_device_add_data(pdev,
  					cell->platform_data, cell->pdata_size);
  		if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
162
  			goto fail_alias;
eb8956074   Samuel Ortiz   mfd: Add platform...
163
  	}
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
164
  	ret = mfd_platform_add_cell(pdev, cell, usage_count);
fe891a008   Andres Salomon   mfd-core: Uncondi...
165
  	if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
166
  		goto fail_alias;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
167

aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
168
169
170
171
172
  	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...
173
  		if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) {
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
174
175
176
177
178
179
  			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...
180
181
182
183
184
185
186
187
188
189
190
191
  			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...
192
193
194
195
196
  		} 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...
197

5f2545fa1   Daniel Drake   mfd: Allow for by...
198
  		if (!cell->ignore_resource_conflicts) {
855cc4543   Jean Delvare   mfd: Fix ACPI con...
199
  			ret = acpi_check_resource_conflict(&res[r]);
5f2545fa1   Daniel Drake   mfd: Allow for by...
200
  			if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
201
  				goto fail_alias;
5f2545fa1   Daniel Drake   mfd: Allow for by...
202
  		}
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
203
  	}
8af5fe3bc   Axel Lin   mfd: properly han...
204
205
  	ret = platform_device_add_resources(pdev, res, cell->num_resources);
  	if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
206
  		goto fail_alias;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
207
208
209
  
  	ret = platform_device_add(pdev);
  	if (ret)
7fcd42746   Charles Keepax   mfd: Allow mappin...
210
  		goto fail_alias;
a87903f3b   Ian Molton   mfd: reduce stack...
211

4c90aa94f   Mark Brown   mfd: Provide pm_r...
212
213
  	if (cell->pm_runtime_no_callbacks)
  		pm_runtime_no_callbacks(&pdev->dev);
a87903f3b   Ian Molton   mfd: reduce stack...
214
  	kfree(res);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
215
216
  
  	return 0;
7fcd42746   Charles Keepax   mfd: Allow mappin...
217
  fail_alias:
d137be00e   Charles Keepax   mfd: core: Don't ...
218
219
220
  	regulator_bulk_unregister_supply_alias(&pdev->dev,
  					       cell->parent_supplies,
  					       cell->num_parent_supplies);
a87903f3b   Ian Molton   mfd: reduce stack...
221
222
  fail_res:
  	kfree(res);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
223
224
225
226
227
  fail_device:
  	platform_device_put(pdev);
  fail_alloc:
  	return ret;
  }
424f525a1   Dmitry Eremin-Solenikov   mfd: accept pure ...
228
  int mfd_add_devices(struct device *parent, int id,
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
229
  		    const struct mfd_cell *cells, int n_devs,
7f71ac937   Ben Dooks   mfd: Coding style...
230
  		    struct resource *mem_base,
0848c94fb   Mark Brown   mfd: core: Push i...
231
  		    int irq_base, struct irq_domain *domain)
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
232
233
  {
  	int i;
0b208e41a   Geert Uytterhoeven   mfd: Fix memory l...
234
  	int ret;
1e29af62f   Andres Salomon   mfd: Add refcount...
235
236
237
  	atomic_t *cnts;
  
  	/* initialize reference counting for all cells */
d1b5c5e23   Axel Lin   mfd: Fix kcalloc ...
238
  	cnts = kcalloc(n_devs, sizeof(*cnts), GFP_KERNEL);
1e29af62f   Andres Salomon   mfd: Add refcount...
239
240
  	if (!cnts)
  		return -ENOMEM;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
241
242
  
  	for (i = 0; i < n_devs; i++) {
1e29af62f   Andres Salomon   mfd: Add refcount...
243
  		atomic_set(&cnts[i], 0);
03e361b25   Geert Uytterhoeven   mfd: Stop setting...
244
  		ret = mfd_add_device(parent, id, cells + i, cnts + i, mem_base,
0848c94fb   Mark Brown   mfd: core: Push i...
245
  				     irq_base, domain);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
246
  		if (ret)
0b208e41a   Geert Uytterhoeven   mfd: Fix memory l...
247
  			goto fail;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
248
  	}
0b208e41a   Geert Uytterhoeven   mfd: Fix memory l...
249
  	return 0;
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
250

0b208e41a   Geert Uytterhoeven   mfd: Fix memory l...
251
252
253
254
255
  fail:
  	if (i)
  		mfd_remove_devices(parent);
  	else
  		kfree(cnts);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
256
257
258
  	return ret;
  }
  EXPORT_SYMBOL(mfd_add_devices);
1e29af62f   Andres Salomon   mfd: Add refcount...
259
  static int mfd_remove_devices_fn(struct device *dev, void *c)
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
260
  {
b9fbb62eb   Charles Keepax   mfd: Only unregis...
261
262
  	struct platform_device *pdev;
  	const struct mfd_cell *cell;
1e29af62f   Andres Salomon   mfd: Add refcount...
263
  	atomic_t **usage_count = c;
b9fbb62eb   Charles Keepax   mfd: Only unregis...
264
265
266
267
268
  	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 ...
269
270
  	regulator_bulk_unregister_supply_alias(dev, cell->parent_supplies,
  					       cell->num_parent_supplies);
1e29af62f   Andres Salomon   mfd: Add refcount...
271
272
273
274
275
  	/* 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...
276
277
  	return 0;
  }
424f525a1   Dmitry Eremin-Solenikov   mfd: accept pure ...
278
  void mfd_remove_devices(struct device *parent)
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
279
  {
1e29af62f   Andres Salomon   mfd: Add refcount...
280
281
282
283
  	atomic_t *cnts = NULL;
  
  	device_for_each_child(parent, &cnts, mfd_remove_devices_fn);
  	kfree(cnts);
aa613de67   Dmitry Eremin-Solenikov   [ARM] 5127/1: Cor...
284
285
  }
  EXPORT_SYMBOL(mfd_remove_devices);
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
286
  int mfd_clone_cell(const char *cell, const char **clones, size_t n_clones)
a9bbba996   Andres Salomon   mfd: add platform...
287
288
289
290
  {
  	struct mfd_cell cell_entry;
  	struct device *dev;
  	struct platform_device *pdev;
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
291
  	int i;
a9bbba996   Andres Salomon   mfd: add platform...
292
293
294
295
296
297
298
299
300
301
302
303
  
  	/* 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...
304
305
306
  	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...
307
308
  		if (mfd_add_device(pdev->dev.parent, -1, &cell_entry,
  				   cell_entry.usage_count, NULL, 0, NULL))
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
309
310
311
312
  			dev_err(dev, "failed to create platform device '%s'
  ",
  					clones[i]);
  	}
a9bbba996   Andres Salomon   mfd: add platform...
313

fa1df6916   Andres Salomon   mfd: Add mfd_clon...
314
  	return 0;
a9bbba996   Andres Salomon   mfd: add platform...
315
  }
fa1df6916   Andres Salomon   mfd: Add mfd_clon...
316
  EXPORT_SYMBOL(mfd_clone_cell);
a9bbba996   Andres Salomon   mfd: add platform...
317

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