]>
Commit | Line | Data |
---|---|---|
9d3cce0b MW |
1 | /* |
2 | * Thunderbolt bus support | |
3 | * | |
4 | * Copyright (C) 2017, Intel Corporation | |
5 | * Author: Mika Westerberg <mika.westerberg@linux.intel.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | ||
12 | #include <linux/device.h> | |
13 | #include <linux/idr.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/slab.h> | |
16 | ||
17 | #include "tb.h" | |
18 | ||
19 | static DEFINE_IDA(tb_domain_ida); | |
20 | ||
21 | struct bus_type tb_bus_type = { | |
22 | .name = "thunderbolt", | |
23 | }; | |
24 | ||
25 | static void tb_domain_release(struct device *dev) | |
26 | { | |
27 | struct tb *tb = container_of(dev, struct tb, dev); | |
28 | ||
29 | tb_ctl_free(tb->ctl); | |
30 | destroy_workqueue(tb->wq); | |
31 | ida_simple_remove(&tb_domain_ida, tb->index); | |
32 | mutex_destroy(&tb->lock); | |
33 | kfree(tb); | |
34 | } | |
35 | ||
36 | struct device_type tb_domain_type = { | |
37 | .name = "thunderbolt_domain", | |
38 | .release = tb_domain_release, | |
39 | }; | |
40 | ||
41 | /** | |
42 | * tb_domain_alloc() - Allocate a domain | |
43 | * @nhi: Pointer to the host controller | |
44 | * @privsize: Size of the connection manager private data | |
45 | * | |
46 | * Allocates and initializes a new Thunderbolt domain. Connection | |
47 | * managers are expected to call this and then fill in @cm_ops | |
48 | * accordingly. | |
49 | * | |
50 | * Call tb_domain_put() to release the domain before it has been added | |
51 | * to the system. | |
52 | * | |
53 | * Return: allocated domain structure on %NULL in case of error | |
54 | */ | |
55 | struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize) | |
56 | { | |
57 | struct tb *tb; | |
58 | ||
59 | /* | |
60 | * Make sure the structure sizes map with that the hardware | |
61 | * expects because bit-fields are being used. | |
62 | */ | |
63 | BUILD_BUG_ON(sizeof(struct tb_regs_switch_header) != 5 * 4); | |
64 | BUILD_BUG_ON(sizeof(struct tb_regs_port_header) != 8 * 4); | |
65 | BUILD_BUG_ON(sizeof(struct tb_regs_hop) != 2 * 4); | |
66 | ||
67 | tb = kzalloc(sizeof(*tb) + privsize, GFP_KERNEL); | |
68 | if (!tb) | |
69 | return NULL; | |
70 | ||
71 | tb->nhi = nhi; | |
72 | mutex_init(&tb->lock); | |
73 | ||
74 | tb->index = ida_simple_get(&tb_domain_ida, 0, 0, GFP_KERNEL); | |
75 | if (tb->index < 0) | |
76 | goto err_free; | |
77 | ||
78 | tb->wq = alloc_ordered_workqueue("thunderbolt%d", 0, tb->index); | |
79 | if (!tb->wq) | |
80 | goto err_remove_ida; | |
81 | ||
82 | tb->dev.parent = &nhi->pdev->dev; | |
83 | tb->dev.bus = &tb_bus_type; | |
84 | tb->dev.type = &tb_domain_type; | |
85 | dev_set_name(&tb->dev, "domain%d", tb->index); | |
86 | device_initialize(&tb->dev); | |
87 | ||
88 | return tb; | |
89 | ||
90 | err_remove_ida: | |
91 | ida_simple_remove(&tb_domain_ida, tb->index); | |
92 | err_free: | |
93 | kfree(tb); | |
94 | ||
95 | return NULL; | |
96 | } | |
97 | ||
98 | /** | |
99 | * tb_domain_add() - Add domain to the system | |
100 | * @tb: Domain to add | |
101 | * | |
102 | * Starts the domain and adds it to the system. Hotplugging devices will | |
103 | * work after this has been returned successfully. In order to remove | |
104 | * and release the domain after this function has been called, call | |
105 | * tb_domain_remove(). | |
106 | * | |
107 | * Return: %0 in case of success and negative errno in case of error | |
108 | */ | |
109 | int tb_domain_add(struct tb *tb) | |
110 | { | |
111 | int ret; | |
112 | ||
113 | if (WARN_ON(!tb->cm_ops)) | |
114 | return -EINVAL; | |
115 | ||
116 | mutex_lock(&tb->lock); | |
117 | ||
118 | tb->ctl = tb_ctl_alloc(tb->nhi, tb->cm_ops->hotplug, tb); | |
119 | if (!tb->ctl) { | |
120 | ret = -ENOMEM; | |
121 | goto err_unlock; | |
122 | } | |
123 | ||
124 | /* | |
125 | * tb_schedule_hotplug_handler may be called as soon as the config | |
126 | * channel is started. Thats why we have to hold the lock here. | |
127 | */ | |
128 | tb_ctl_start(tb->ctl); | |
129 | ||
130 | ret = device_add(&tb->dev); | |
131 | if (ret) | |
132 | goto err_ctl_stop; | |
133 | ||
134 | /* Start the domain */ | |
135 | if (tb->cm_ops->start) { | |
136 | ret = tb->cm_ops->start(tb); | |
137 | if (ret) | |
138 | goto err_domain_del; | |
139 | } | |
140 | ||
141 | /* This starts event processing */ | |
142 | mutex_unlock(&tb->lock); | |
143 | ||
144 | return 0; | |
145 | ||
146 | err_domain_del: | |
147 | device_del(&tb->dev); | |
148 | err_ctl_stop: | |
149 | tb_ctl_stop(tb->ctl); | |
150 | err_unlock: | |
151 | mutex_unlock(&tb->lock); | |
152 | ||
153 | return ret; | |
154 | } | |
155 | ||
156 | /** | |
157 | * tb_domain_remove() - Removes and releases a domain | |
158 | * @tb: Domain to remove | |
159 | * | |
160 | * Stops the domain, removes it from the system and releases all | |
161 | * resources once the last reference has been released. | |
162 | */ | |
163 | void tb_domain_remove(struct tb *tb) | |
164 | { | |
165 | mutex_lock(&tb->lock); | |
166 | if (tb->cm_ops->stop) | |
167 | tb->cm_ops->stop(tb); | |
168 | /* Stop the domain control traffic */ | |
169 | tb_ctl_stop(tb->ctl); | |
170 | mutex_unlock(&tb->lock); | |
171 | ||
172 | flush_workqueue(tb->wq); | |
173 | device_unregister(&tb->dev); | |
174 | } | |
175 | ||
176 | /** | |
177 | * tb_domain_suspend_noirq() - Suspend a domain | |
178 | * @tb: Domain to suspend | |
179 | * | |
180 | * Suspends all devices in the domain and stops the control channel. | |
181 | */ | |
182 | int tb_domain_suspend_noirq(struct tb *tb) | |
183 | { | |
184 | int ret = 0; | |
185 | ||
186 | /* | |
187 | * The control channel interrupt is left enabled during suspend | |
188 | * and taking the lock here prevents any events happening before | |
189 | * we actually have stopped the domain and the control channel. | |
190 | */ | |
191 | mutex_lock(&tb->lock); | |
192 | if (tb->cm_ops->suspend_noirq) | |
193 | ret = tb->cm_ops->suspend_noirq(tb); | |
194 | if (!ret) | |
195 | tb_ctl_stop(tb->ctl); | |
196 | mutex_unlock(&tb->lock); | |
197 | ||
198 | return ret; | |
199 | } | |
200 | ||
201 | /** | |
202 | * tb_domain_resume_noirq() - Resume a domain | |
203 | * @tb: Domain to resume | |
204 | * | |
205 | * Re-starts the control channel, and resumes all devices connected to | |
206 | * the domain. | |
207 | */ | |
208 | int tb_domain_resume_noirq(struct tb *tb) | |
209 | { | |
210 | int ret = 0; | |
211 | ||
212 | mutex_lock(&tb->lock); | |
213 | tb_ctl_start(tb->ctl); | |
214 | if (tb->cm_ops->resume_noirq) | |
215 | ret = tb->cm_ops->resume_noirq(tb); | |
216 | mutex_unlock(&tb->lock); | |
217 | ||
218 | return ret; | |
219 | } | |
220 | ||
221 | int tb_domain_init(void) | |
222 | { | |
223 | return bus_register(&tb_bus_type); | |
224 | } | |
225 | ||
226 | void tb_domain_exit(void) | |
227 | { | |
228 | bus_unregister(&tb_bus_type); | |
229 | ida_destroy(&tb_domain_ida); | |
230 | } |