diff options
Diffstat (limited to 'drivers/mfd/mfd-core.c')
-rw-r--r-- | drivers/mfd/mfd-core.c | 135 |
1 files changed, 124 insertions, 11 deletions
diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index d83ad0f141a..79eda0264fb 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c | |||
@@ -18,6 +18,43 @@ | |||
18 | #include <linux/pm_runtime.h> | 18 | #include <linux/pm_runtime.h> |
19 | #include <linux/slab.h> | 19 | #include <linux/slab.h> |
20 | 20 | ||
21 | int mfd_cell_enable(struct platform_device *pdev) | ||
22 | { | ||
23 | const struct mfd_cell *cell = mfd_get_cell(pdev); | ||
24 | int err = 0; | ||
25 | |||
26 | /* only call enable hook if the cell wasn't previously enabled */ | ||
27 | if (atomic_inc_return(cell->usage_count) == 1) | ||
28 | err = cell->enable(pdev); | ||
29 | |||
30 | /* if the enable hook failed, decrement counter to allow retries */ | ||
31 | if (err) | ||
32 | atomic_dec(cell->usage_count); | ||
33 | |||
34 | return err; | ||
35 | } | ||
36 | EXPORT_SYMBOL(mfd_cell_enable); | ||
37 | |||
38 | int mfd_cell_disable(struct platform_device *pdev) | ||
39 | { | ||
40 | const struct mfd_cell *cell = mfd_get_cell(pdev); | ||
41 | int err = 0; | ||
42 | |||
43 | /* only disable if no other clients are using it */ | ||
44 | if (atomic_dec_return(cell->usage_count) == 0) | ||
45 | err = cell->disable(pdev); | ||
46 | |||
47 | /* if the disable hook failed, increment to allow retries */ | ||
48 | if (err) | ||
49 | atomic_inc(cell->usage_count); | ||
50 | |||
51 | /* sanity check; did someone call disable too many times? */ | ||
52 | WARN_ON(atomic_read(cell->usage_count) < 0); | ||
53 | |||
54 | return err; | ||
55 | } | ||
56 | EXPORT_SYMBOL(mfd_cell_disable); | ||
57 | |||
21 | static int mfd_add_device(struct device *parent, int id, | 58 | static int mfd_add_device(struct device *parent, int id, |
22 | const struct mfd_cell *cell, | 59 | const struct mfd_cell *cell, |
23 | struct resource *mem_base, | 60 | struct resource *mem_base, |
@@ -37,14 +74,10 @@ static int mfd_add_device(struct device *parent, int id, | |||
37 | goto fail_device; | 74 | goto fail_device; |
38 | 75 | ||
39 | pdev->dev.parent = parent; | 76 | pdev->dev.parent = parent; |
40 | platform_set_drvdata(pdev, cell->driver_data); | ||
41 | 77 | ||
42 | if (cell->data_size) { | 78 | ret = platform_device_add_data(pdev, cell, sizeof(*cell)); |
43 | ret = platform_device_add_data(pdev, | 79 | if (ret) |
44 | cell->platform_data, cell->data_size); | 80 | goto fail_res; |
45 | if (ret) | ||
46 | goto fail_res; | ||
47 | } | ||
48 | 81 | ||
49 | for (r = 0; r < cell->num_resources; r++) { | 82 | for (r = 0; r < cell->num_resources; r++) { |
50 | res[r].name = cell->resources[r].name; | 83 | res[r].name = cell->resources[r].name; |
@@ -100,14 +133,22 @@ fail_alloc: | |||
100 | } | 133 | } |
101 | 134 | ||
102 | int mfd_add_devices(struct device *parent, int id, | 135 | int mfd_add_devices(struct device *parent, int id, |
103 | const struct mfd_cell *cells, int n_devs, | 136 | struct mfd_cell *cells, int n_devs, |
104 | struct resource *mem_base, | 137 | struct resource *mem_base, |
105 | int irq_base) | 138 | int irq_base) |
106 | { | 139 | { |
107 | int i; | 140 | int i; |
108 | int ret = 0; | 141 | int ret = 0; |
142 | atomic_t *cnts; | ||
143 | |||
144 | /* initialize reference counting for all cells */ | ||
145 | cnts = kcalloc(sizeof(*cnts), n_devs, GFP_KERNEL); | ||
146 | if (!cnts) | ||
147 | return -ENOMEM; | ||
109 | 148 | ||
110 | for (i = 0; i < n_devs; i++) { | 149 | for (i = 0; i < n_devs; i++) { |
150 | atomic_set(&cnts[i], 0); | ||
151 | cells[i].usage_count = &cnts[i]; | ||
111 | ret = mfd_add_device(parent, id, cells + i, mem_base, irq_base); | 152 | ret = mfd_add_device(parent, id, cells + i, mem_base, irq_base); |
112 | if (ret) | 153 | if (ret) |
113 | break; | 154 | break; |
@@ -120,17 +161,89 @@ int mfd_add_devices(struct device *parent, int id, | |||
120 | } | 161 | } |
121 | EXPORT_SYMBOL(mfd_add_devices); | 162 | EXPORT_SYMBOL(mfd_add_devices); |
122 | 163 | ||
123 | static int mfd_remove_devices_fn(struct device *dev, void *unused) | 164 | static int mfd_remove_devices_fn(struct device *dev, void *c) |
124 | { | 165 | { |
125 | platform_device_unregister(to_platform_device(dev)); | 166 | struct platform_device *pdev = to_platform_device(dev); |
167 | const struct mfd_cell *cell = mfd_get_cell(pdev); | ||
168 | atomic_t **usage_count = c; | ||
169 | |||
170 | /* find the base address of usage_count pointers (for freeing) */ | ||
171 | if (!*usage_count || (cell->usage_count < *usage_count)) | ||
172 | *usage_count = cell->usage_count; | ||
173 | |||
174 | platform_device_unregister(pdev); | ||
126 | return 0; | 175 | return 0; |
127 | } | 176 | } |
128 | 177 | ||
129 | void mfd_remove_devices(struct device *parent) | 178 | void mfd_remove_devices(struct device *parent) |
130 | { | 179 | { |
131 | device_for_each_child(parent, NULL, mfd_remove_devices_fn); | 180 | atomic_t *cnts = NULL; |
181 | |||
182 | device_for_each_child(parent, &cnts, mfd_remove_devices_fn); | ||
183 | kfree(cnts); | ||
132 | } | 184 | } |
133 | EXPORT_SYMBOL(mfd_remove_devices); | 185 | EXPORT_SYMBOL(mfd_remove_devices); |
134 | 186 | ||
187 | static int add_shared_platform_device(const char *cell, const char *name) | ||
188 | { | ||
189 | struct mfd_cell cell_entry; | ||
190 | struct device *dev; | ||
191 | struct platform_device *pdev; | ||
192 | int err; | ||
193 | |||
194 | /* check if we've already registered a device (don't fail if we have) */ | ||
195 | if (bus_find_device_by_name(&platform_bus_type, NULL, name)) | ||
196 | return 0; | ||
197 | |||
198 | /* fetch the parent cell's device (should already be registered!) */ | ||
199 | dev = bus_find_device_by_name(&platform_bus_type, NULL, cell); | ||
200 | if (!dev) { | ||
201 | printk(KERN_ERR "failed to find device for cell %s\n", cell); | ||
202 | return -ENODEV; | ||
203 | } | ||
204 | pdev = to_platform_device(dev); | ||
205 | memcpy(&cell_entry, mfd_get_cell(pdev), sizeof(cell_entry)); | ||
206 | |||
207 | WARN_ON(!cell_entry.enable); | ||
208 | |||
209 | cell_entry.name = name; | ||
210 | err = mfd_add_device(pdev->dev.parent, -1, &cell_entry, NULL, 0); | ||
211 | if (err) | ||
212 | dev_err(dev, "MFD add devices failed: %d\n", err); | ||
213 | return err; | ||
214 | } | ||
215 | |||
216 | int mfd_shared_platform_driver_register(struct platform_driver *drv, | ||
217 | const char *cellname) | ||
218 | { | ||
219 | int err; | ||
220 | |||
221 | err = add_shared_platform_device(cellname, drv->driver.name); | ||
222 | if (err) | ||
223 | printk(KERN_ERR "failed to add platform device %s\n", | ||
224 | drv->driver.name); | ||
225 | |||
226 | err = platform_driver_register(drv); | ||
227 | if (err) | ||
228 | printk(KERN_ERR "failed to add platform driver %s\n", | ||
229 | drv->driver.name); | ||
230 | |||
231 | return err; | ||
232 | } | ||
233 | EXPORT_SYMBOL(mfd_shared_platform_driver_register); | ||
234 | |||
235 | void mfd_shared_platform_driver_unregister(struct platform_driver *drv) | ||
236 | { | ||
237 | struct device *dev; | ||
238 | |||
239 | dev = bus_find_device_by_name(&platform_bus_type, NULL, | ||
240 | drv->driver.name); | ||
241 | if (dev) | ||
242 | platform_device_unregister(to_platform_device(dev)); | ||
243 | |||
244 | platform_driver_unregister(drv); | ||
245 | } | ||
246 | EXPORT_SYMBOL(mfd_shared_platform_driver_unregister); | ||
247 | |||
135 | MODULE_LICENSE("GPL"); | 248 | MODULE_LICENSE("GPL"); |
136 | MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov"); | 249 | MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov"); |