Commit | Line | Data |
---|---|---|
da389eac DV |
1 | /* |
2 | * Bus for UWB Multi-interface Controller capabilities. | |
3 | * | |
4 | * Copyright (C) 2007 Cambridge Silicon Radio Ltd. | |
5 | * | |
6 | * This file is released under the GNU GPL v2. | |
7 | */ | |
8 | #include <linux/kernel.h> | |
9 | #include <linux/sysfs.h> | |
10 | #include <linux/workqueue.h> | |
11 | #include <linux/uwb/umc.h> | |
12 | #include <linux/pci.h> | |
13 | ||
307ba6dd | 14 | static int umc_bus_pre_reset_helper(struct device *dev, void *data) |
da389eac | 15 | { |
307ba6dd | 16 | int ret = 0; |
da389eac | 17 | |
307ba6dd DV |
18 | if (dev->driver) { |
19 | struct umc_dev *umc = to_umc_dev(dev); | |
20 | struct umc_driver *umc_drv = to_umc_driver(dev->driver); | |
21 | ||
22 | if (umc_drv->pre_reset) | |
23 | ret = umc_drv->pre_reset(umc); | |
24 | else | |
25 | device_release_driver(dev); | |
26 | } | |
27 | return ret; | |
28 | } | |
29 | ||
30 | static int umc_bus_post_reset_helper(struct device *dev, void *data) | |
31 | { | |
32 | int ret = 0; | |
33 | ||
34 | if (dev->driver) { | |
35 | struct umc_dev *umc = to_umc_dev(dev); | |
36 | struct umc_driver *umc_drv = to_umc_driver(dev->driver); | |
37 | ||
38 | if (umc_drv->post_reset) | |
39 | ret = umc_drv->post_reset(umc); | |
40 | } else | |
41 | ret = device_attach(dev); | |
42 | ||
43 | return ret; | |
da389eac DV |
44 | } |
45 | ||
46 | /** | |
47 | * umc_controller_reset - reset the whole UMC controller | |
48 | * @umc: the UMC device for the radio controller. | |
49 | * | |
307ba6dd DV |
50 | * Drivers or all capabilities of the controller will have their |
51 | * pre_reset methods called or be unbound from their device. Then all | |
52 | * post_reset methods will be called or the drivers will be rebound. | |
53 | * | |
54 | * Radio controllers must provide pre_reset and post_reset methods and | |
55 | * reset the hardware in their start method. | |
da389eac DV |
56 | * |
57 | * If this is called while a probe() or remove() is in progress it | |
58 | * will return -EAGAIN and not perform the reset. | |
59 | */ | |
60 | int umc_controller_reset(struct umc_dev *umc) | |
61 | { | |
62 | struct device *parent = umc->dev.parent; | |
307ba6dd | 63 | int ret = 0; |
da389eac | 64 | |
8e9394ce | 65 | if (device_trylock(parent)) |
da389eac | 66 | return -EAGAIN; |
307ba6dd DV |
67 | ret = device_for_each_child(parent, parent, umc_bus_pre_reset_helper); |
68 | if (ret >= 0) | |
0396c215 | 69 | ret = device_for_each_child(parent, parent, umc_bus_post_reset_helper); |
8e9394ce | 70 | device_unlock(parent); |
da389eac DV |
71 | |
72 | return ret; | |
73 | } | |
74 | EXPORT_SYMBOL_GPL(umc_controller_reset); | |
75 | ||
76 | /** | |
77 | * umc_match_pci_id - match a UMC driver to a UMC device's parent PCI device. | |
78 | * @umc_drv: umc driver with match_data pointing to a zero-terminated | |
79 | * table of pci_device_id's. | |
80 | * @umc: umc device whose parent is to be matched. | |
81 | */ | |
82 | int umc_match_pci_id(struct umc_driver *umc_drv, struct umc_dev *umc) | |
83 | { | |
84 | const struct pci_device_id *id_table = umc_drv->match_data; | |
85 | struct pci_dev *pci; | |
86 | ||
87 | if (umc->dev.parent->bus != &pci_bus_type) | |
88 | return 0; | |
89 | ||
90 | pci = to_pci_dev(umc->dev.parent); | |
91 | return pci_match_id(id_table, pci) != NULL; | |
92 | } | |
93 | EXPORT_SYMBOL_GPL(umc_match_pci_id); | |
94 | ||
95 | static int umc_bus_rescan_helper(struct device *dev, void *data) | |
96 | { | |
97 | int ret = 0; | |
98 | ||
99 | if (!dev->driver) | |
100 | ret = device_attach(dev); | |
101 | ||
307ba6dd | 102 | return ret; |
da389eac DV |
103 | } |
104 | ||
307ba6dd | 105 | static void umc_bus_rescan(struct device *parent) |
da389eac DV |
106 | { |
107 | int err; | |
108 | ||
109 | /* | |
110 | * We can't use bus_rescan_devices() here as it deadlocks when | |
111 | * it tries to retake the dev->parent semaphore. | |
112 | */ | |
307ba6dd | 113 | err = device_for_each_child(parent, NULL, umc_bus_rescan_helper); |
da389eac DV |
114 | if (err < 0) |
115 | printk(KERN_WARNING "%s: rescan of bus failed: %d\n", | |
116 | KBUILD_MODNAME, err); | |
117 | } | |
118 | ||
119 | static int umc_bus_match(struct device *dev, struct device_driver *drv) | |
120 | { | |
121 | struct umc_dev *umc = to_umc_dev(dev); | |
122 | struct umc_driver *umc_driver = to_umc_driver(drv); | |
123 | ||
124 | if (umc->cap_id == umc_driver->cap_id) { | |
125 | if (umc_driver->match) | |
126 | return umc_driver->match(umc_driver, umc); | |
127 | else | |
128 | return 1; | |
129 | } | |
130 | return 0; | |
131 | } | |
132 | ||
133 | static int umc_device_probe(struct device *dev) | |
134 | { | |
135 | struct umc_dev *umc; | |
136 | struct umc_driver *umc_driver; | |
137 | int err; | |
138 | ||
139 | umc_driver = to_umc_driver(dev->driver); | |
140 | umc = to_umc_dev(dev); | |
141 | ||
142 | get_device(dev); | |
143 | err = umc_driver->probe(umc); | |
144 | if (err) | |
145 | put_device(dev); | |
146 | else | |
307ba6dd | 147 | umc_bus_rescan(dev->parent); |
da389eac DV |
148 | |
149 | return err; | |
150 | } | |
151 | ||
152 | static int umc_device_remove(struct device *dev) | |
153 | { | |
154 | struct umc_dev *umc; | |
155 | struct umc_driver *umc_driver; | |
156 | ||
157 | umc_driver = to_umc_driver(dev->driver); | |
158 | umc = to_umc_dev(dev); | |
159 | ||
160 | umc_driver->remove(umc); | |
161 | put_device(dev); | |
162 | return 0; | |
163 | } | |
164 | ||
165 | static int umc_device_suspend(struct device *dev, pm_message_t state) | |
166 | { | |
167 | struct umc_dev *umc; | |
168 | struct umc_driver *umc_driver; | |
169 | int err = 0; | |
170 | ||
171 | umc = to_umc_dev(dev); | |
172 | ||
173 | if (dev->driver) { | |
174 | umc_driver = to_umc_driver(dev->driver); | |
175 | if (umc_driver->suspend) | |
176 | err = umc_driver->suspend(umc, state); | |
177 | } | |
178 | return err; | |
179 | } | |
180 | ||
181 | static int umc_device_resume(struct device *dev) | |
182 | { | |
183 | struct umc_dev *umc; | |
184 | struct umc_driver *umc_driver; | |
185 | int err = 0; | |
186 | ||
187 | umc = to_umc_dev(dev); | |
188 | ||
189 | if (dev->driver) { | |
190 | umc_driver = to_umc_driver(dev->driver); | |
191 | if (umc_driver->resume) | |
192 | err = umc_driver->resume(umc); | |
193 | } | |
194 | return err; | |
195 | } | |
196 | ||
197 | static ssize_t capability_id_show(struct device *dev, struct device_attribute *attr, char *buf) | |
198 | { | |
199 | struct umc_dev *umc = to_umc_dev(dev); | |
200 | ||
201 | return sprintf(buf, "0x%02x\n", umc->cap_id); | |
202 | } | |
203 | ||
204 | static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) | |
205 | { | |
206 | struct umc_dev *umc = to_umc_dev(dev); | |
207 | ||
208 | return sprintf(buf, "0x%04x\n", umc->version); | |
209 | } | |
210 | ||
211 | static struct device_attribute umc_dev_attrs[] = { | |
212 | __ATTR_RO(capability_id), | |
213 | __ATTR_RO(version), | |
214 | __ATTR_NULL, | |
215 | }; | |
216 | ||
217 | struct bus_type umc_bus_type = { | |
218 | .name = "umc", | |
219 | .match = umc_bus_match, | |
220 | .probe = umc_device_probe, | |
221 | .remove = umc_device_remove, | |
222 | .suspend = umc_device_suspend, | |
223 | .resume = umc_device_resume, | |
224 | .dev_attrs = umc_dev_attrs, | |
225 | }; | |
226 | EXPORT_SYMBOL_GPL(umc_bus_type); | |
227 | ||
228 | static int __init umc_bus_init(void) | |
229 | { | |
230 | return bus_register(&umc_bus_type); | |
231 | } | |
232 | module_init(umc_bus_init); | |
233 | ||
234 | static void __exit umc_bus_exit(void) | |
235 | { | |
236 | bus_unregister(&umc_bus_type); | |
237 | } | |
238 | module_exit(umc_bus_exit); | |
239 | ||
240 | MODULE_DESCRIPTION("UWB Multi-interface Controller capability bus"); | |
241 | MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); | |
242 | MODULE_LICENSE("GPL"); |