1 /* 2 * drivers/mfd/mfd-core.c 3 * 4 * core MFD support 5 * Copyright (c) 2006 Ian Molton 6 * Copyright (c) 2007,2008 Dmitry Baryshkov 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11 * 12 */ 13 14 #include <linux/kernel.h> 15 #include <linux/platform_device.h> 16 #include <linux/acpi.h> 17 #include <linux/mfd/core.h> 18 #include <linux/pm_runtime.h> 19 #include <linux/slab.h> 20 #include <linux/module.h> 21 #include <linux/irqdomain.h> 22 #include <linux/of.h> 23 24 int mfd_cell_enable(struct platform_device *pdev) 25 { 26 const struct mfd_cell *cell = mfd_get_cell(pdev); 27 int err = 0; 28 29 /* only call enable hook if the cell wasn't previously enabled */ 30 if (atomic_inc_return(cell->usage_count) == 1) 31 err = cell->enable(pdev); 32 33 /* if the enable hook failed, decrement counter to allow retries */ 34 if (err) 35 atomic_dec(cell->usage_count); 36 37 return err; 38 } 39 EXPORT_SYMBOL(mfd_cell_enable); 40 41 int mfd_cell_disable(struct platform_device *pdev) 42 { 43 const struct mfd_cell *cell = mfd_get_cell(pdev); 44 int err = 0; 45 46 /* only disable if no other clients are using it */ 47 if (atomic_dec_return(cell->usage_count) == 0) 48 err = cell->disable(pdev); 49 50 /* if the disable hook failed, increment to allow retries */ 51 if (err) 52 atomic_inc(cell->usage_count); 53 54 /* sanity check; did someone call disable too many times? */ 55 WARN_ON(atomic_read(cell->usage_count) < 0); 56 57 return err; 58 } 59 EXPORT_SYMBOL(mfd_cell_disable); 60 61 static int mfd_platform_add_cell(struct platform_device *pdev, 62 const struct mfd_cell *cell) 63 { 64 if (!cell) 65 return 0; 66 67 pdev->mfd_cell = kmemdup(cell, sizeof(*cell), GFP_KERNEL); 68 if (!pdev->mfd_cell) 69 return -ENOMEM; 70 71 return 0; 72 } 73 74 static int mfd_add_device(struct device *parent, int id, 75 const struct mfd_cell *cell, 76 struct resource *mem_base, 77 int irq_base) 78 { 79 struct resource *res; 80 struct platform_device *pdev; 81 struct device_node *np = NULL; 82 struct irq_domain *domain = NULL; 83 int ret = -ENOMEM; 84 int r; 85 86 pdev = platform_device_alloc(cell->name, id + cell->id); 87 if (!pdev) 88 goto fail_alloc; 89 90 res = kzalloc(sizeof(*res) * cell->num_resources, GFP_KERNEL); 91 if (!res) 92 goto fail_device; 93 94 pdev->dev.parent = parent; 95 96 if (parent->of_node && cell->of_compatible) { 97 for_each_child_of_node(parent->of_node, np) { 98 if (of_device_is_compatible(np, cell->of_compatible)) { 99 pdev->dev.of_node = np; 100 domain = irq_find_host(parent->of_node); 101 break; 102 } 103 } 104 } 105 106 if (cell->pdata_size) { 107 ret = platform_device_add_data(pdev, 108 cell->platform_data, cell->pdata_size); 109 if (ret) 110 goto fail_res; 111 } 112 113 ret = mfd_platform_add_cell(pdev, cell); 114 if (ret) 115 goto fail_res; 116 117 for (r = 0; r < cell->num_resources; r++) { 118 res[r].name = cell->resources[r].name; 119 res[r].flags = cell->resources[r].flags; 120 121 /* Find out base to use */ 122 if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) { 123 res[r].parent = mem_base; 124 res[r].start = mem_base->start + 125 cell->resources[r].start; 126 res[r].end = mem_base->start + 127 cell->resources[r].end; 128 } else if (cell->resources[r].flags & IORESOURCE_IRQ) { 129 if (domain) { 130 /* Unable to create mappings for IRQ ranges. */ 131 WARN_ON(cell->resources[r].start != 132 cell->resources[r].end); 133 res[r].start = res[r].end = irq_create_mapping( 134 domain, cell->resources[r].start); 135 } else { 136 res[r].start = irq_base + 137 cell->resources[r].start; 138 res[r].end = irq_base + 139 cell->resources[r].end; 140 } 141 } else { 142 res[r].parent = cell->resources[r].parent; 143 res[r].start = cell->resources[r].start; 144 res[r].end = cell->resources[r].end; 145 } 146 147 if (!cell->ignore_resource_conflicts) { 148 ret = acpi_check_resource_conflict(&res[r]); 149 if (ret) 150 goto fail_res; 151 } 152 } 153 154 ret = platform_device_add_resources(pdev, res, cell->num_resources); 155 if (ret) 156 goto fail_res; 157 158 ret = platform_device_add(pdev); 159 if (ret) 160 goto fail_res; 161 162 if (cell->pm_runtime_no_callbacks) 163 pm_runtime_no_callbacks(&pdev->dev); 164 165 kfree(res); 166 167 return 0; 168 169 fail_res: 170 kfree(res); 171 fail_device: 172 platform_device_put(pdev); 173 fail_alloc: 174 return ret; 175 } 176 177 int mfd_add_devices(struct device *parent, int id, 178 struct mfd_cell *cells, int n_devs, 179 struct resource *mem_base, 180 int irq_base) 181 { 182 int i; 183 int ret = 0; 184 atomic_t *cnts; 185 186 /* initialize reference counting for all cells */ 187 cnts = kcalloc(n_devs, sizeof(*cnts), GFP_KERNEL); 188 if (!cnts) 189 return -ENOMEM; 190 191 for (i = 0; i < n_devs; i++) { 192 atomic_set(&cnts[i], 0); 193 cells[i].usage_count = &cnts[i]; 194 ret = mfd_add_device(parent, id, cells + i, mem_base, irq_base); 195 if (ret) 196 break; 197 } 198 199 if (ret) 200 mfd_remove_devices(parent); 201 202 return ret; 203 } 204 EXPORT_SYMBOL(mfd_add_devices); 205 206 static int mfd_remove_devices_fn(struct device *dev, void *c) 207 { 208 struct platform_device *pdev = to_platform_device(dev); 209 const struct mfd_cell *cell = mfd_get_cell(pdev); 210 atomic_t **usage_count = c; 211 212 /* find the base address of usage_count pointers (for freeing) */ 213 if (!*usage_count || (cell->usage_count < *usage_count)) 214 *usage_count = cell->usage_count; 215 216 platform_device_unregister(pdev); 217 return 0; 218 } 219 220 void mfd_remove_devices(struct device *parent) 221 { 222 atomic_t *cnts = NULL; 223 224 device_for_each_child(parent, &cnts, mfd_remove_devices_fn); 225 kfree(cnts); 226 } 227 EXPORT_SYMBOL(mfd_remove_devices); 228 229 int mfd_clone_cell(const char *cell, const char **clones, size_t n_clones) 230 { 231 struct mfd_cell cell_entry; 232 struct device *dev; 233 struct platform_device *pdev; 234 int i; 235 236 /* fetch the parent cell's device (should already be registered!) */ 237 dev = bus_find_device_by_name(&platform_bus_type, NULL, cell); 238 if (!dev) { 239 printk(KERN_ERR "failed to find device for cell %s\n", cell); 240 return -ENODEV; 241 } 242 pdev = to_platform_device(dev); 243 memcpy(&cell_entry, mfd_get_cell(pdev), sizeof(cell_entry)); 244 245 WARN_ON(!cell_entry.enable); 246 247 for (i = 0; i < n_clones; i++) { 248 cell_entry.name = clones[i]; 249 /* don't give up if a single call fails; just report error */ 250 if (mfd_add_device(pdev->dev.parent, -1, &cell_entry, NULL, 0)) 251 dev_err(dev, "failed to create platform device '%s'\n", 252 clones[i]); 253 } 254 255 return 0; 256 } 257 EXPORT_SYMBOL(mfd_clone_cell); 258 259 MODULE_LICENSE("GPL"); 260 MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov"); 261