diff options
-rw-r--r-- | drivers/mfd/mfd-core.c | 64 | ||||
-rw-r--r-- | include/linux/mfd/core.h | 14 |
2 files changed, 73 insertions, 5 deletions
diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 01115f686dfa..ca789f882305 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_shared_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_shared_cell_enable); | ||
37 | |||
38 | int mfd_shared_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_shared_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, |
@@ -96,14 +133,22 @@ fail_alloc: | |||
96 | } | 133 | } |
97 | 134 | ||
98 | int mfd_add_devices(struct device *parent, int id, | 135 | int mfd_add_devices(struct device *parent, int id, |
99 | const struct mfd_cell *cells, int n_devs, | 136 | struct mfd_cell *cells, int n_devs, |
100 | struct resource *mem_base, | 137 | struct resource *mem_base, |
101 | int irq_base) | 138 | int irq_base) |
102 | { | 139 | { |
103 | int i; | 140 | int i; |
104 | 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; | ||
105 | 148 | ||
106 | 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]; | ||
107 | 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); |
108 | if (ret) | 153 | if (ret) |
109 | break; | 154 | break; |
@@ -116,15 +161,26 @@ int mfd_add_devices(struct device *parent, int id, | |||
116 | } | 161 | } |
117 | EXPORT_SYMBOL(mfd_add_devices); | 162 | EXPORT_SYMBOL(mfd_add_devices); |
118 | 163 | ||
119 | static int mfd_remove_devices_fn(struct device *dev, void *unused) | 164 | static int mfd_remove_devices_fn(struct device *dev, void *c) |
120 | { | 165 | { |
121 | 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); | ||
122 | return 0; | 175 | return 0; |
123 | } | 176 | } |
124 | 177 | ||
125 | void mfd_remove_devices(struct device *parent) | 178 | void mfd_remove_devices(struct device *parent) |
126 | { | 179 | { |
127 | 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); | ||
128 | } | 184 | } |
129 | EXPORT_SYMBOL(mfd_remove_devices); | 185 | EXPORT_SYMBOL(mfd_remove_devices); |
130 | 186 | ||
diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 71cd1f983cce..22a2f5ebd9db 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h | |||
@@ -25,8 +25,11 @@ struct mfd_cell { | |||
25 | const char *name; | 25 | const char *name; |
26 | int id; | 26 | int id; |
27 | 27 | ||
28 | /* refcounting for multiple drivers to use a single cell */ | ||
29 | atomic_t *usage_count; | ||
28 | int (*enable)(struct platform_device *dev); | 30 | int (*enable)(struct platform_device *dev); |
29 | int (*disable)(struct platform_device *dev); | 31 | int (*disable)(struct platform_device *dev); |
32 | |||
30 | int (*suspend)(struct platform_device *dev); | 33 | int (*suspend)(struct platform_device *dev); |
31 | int (*resume)(struct platform_device *dev); | 34 | int (*resume)(struct platform_device *dev); |
32 | 35 | ||
@@ -51,6 +54,15 @@ struct mfd_cell { | |||
51 | }; | 54 | }; |
52 | 55 | ||
53 | /* | 56 | /* |
57 | * Convenience functions for clients using shared cells. Refcounting | ||
58 | * happens automatically, with the cell's enable/disable callbacks | ||
59 | * being called only when a device is first being enabled or no other | ||
60 | * clients are making use of it. | ||
61 | */ | ||
62 | extern int mfd_shared_cell_enable(struct platform_device *pdev); | ||
63 | extern int mfd_shared_cell_disable(struct platform_device *pdev); | ||
64 | |||
65 | /* | ||
54 | * Given a platform device that's been created by mfd_add_devices(), fetch | 66 | * Given a platform device that's been created by mfd_add_devices(), fetch |
55 | * the mfd_cell that created it. | 67 | * the mfd_cell that created it. |
56 | */ | 68 | */ |
@@ -69,7 +81,7 @@ static inline void *mfd_get_data(struct platform_device *pdev) | |||
69 | } | 81 | } |
70 | 82 | ||
71 | extern int mfd_add_devices(struct device *parent, int id, | 83 | extern int mfd_add_devices(struct device *parent, int id, |
72 | const struct mfd_cell *cells, int n_devs, | 84 | struct mfd_cell *cells, int n_devs, |
73 | struct resource *mem_base, | 85 | struct resource *mem_base, |
74 | int irq_base); | 86 | int irq_base); |
75 | 87 | ||