]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blame - drivers/thunderbolt/domain.c
thunderbolt: Introduce thunderbolt bus and connection manager
[mirror_ubuntu-artful-kernel.git] / drivers / thunderbolt / domain.c
CommitLineData
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
19static DEFINE_IDA(tb_domain_ida);
20
21struct bus_type tb_bus_type = {
22 .name = "thunderbolt",
23};
24
25static 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
36struct 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 */
55struct 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
90err_remove_ida:
91 ida_simple_remove(&tb_domain_ida, tb->index);
92err_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 */
109int 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
146err_domain_del:
147 device_del(&tb->dev);
148err_ctl_stop:
149 tb_ctl_stop(tb->ctl);
150err_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 */
163void 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 */
182int 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 */
208int 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
221int tb_domain_init(void)
222{
223 return bus_register(&tb_bus_type);
224}
225
226void tb_domain_exit(void)
227{
228 bus_unregister(&tb_bus_type);
229 ida_destroy(&tb_domain_ida);
230}