1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Tegra host1x Channel |
4 | * |
5 | * Copyright (c) 2010-2013, NVIDIA Corporation. |
6 | */ |
7 | |
8 | #include <linux/slab.h> |
9 | #include <linux/module.h> |
10 | |
11 | #include "channel.h" |
12 | #include "dev.h" |
13 | #include "job.h" |
14 | |
15 | /* Constructor for the host1x device list */ |
16 | int host1x_channel_list_init(struct host1x_channel_list *chlist, |
17 | unsigned int num_channels) |
18 | { |
19 | chlist->channels = kcalloc(n: num_channels, size: sizeof(struct host1x_channel), |
20 | GFP_KERNEL); |
21 | if (!chlist->channels) |
22 | return -ENOMEM; |
23 | |
24 | chlist->allocated_channels = bitmap_zalloc(nbits: num_channels, GFP_KERNEL); |
25 | if (!chlist->allocated_channels) { |
26 | kfree(objp: chlist->channels); |
27 | return -ENOMEM; |
28 | } |
29 | |
30 | mutex_init(&chlist->lock); |
31 | |
32 | return 0; |
33 | } |
34 | |
35 | void host1x_channel_list_free(struct host1x_channel_list *chlist) |
36 | { |
37 | bitmap_free(bitmap: chlist->allocated_channels); |
38 | kfree(objp: chlist->channels); |
39 | } |
40 | |
41 | int host1x_job_submit(struct host1x_job *job) |
42 | { |
43 | struct host1x *host = dev_get_drvdata(dev: job->channel->dev->parent); |
44 | |
45 | return host1x_hw_channel_submit(host, job); |
46 | } |
47 | EXPORT_SYMBOL(host1x_job_submit); |
48 | |
49 | struct host1x_channel *host1x_channel_get(struct host1x_channel *channel) |
50 | { |
51 | kref_get(kref: &channel->refcount); |
52 | |
53 | return channel; |
54 | } |
55 | EXPORT_SYMBOL(host1x_channel_get); |
56 | |
57 | /** |
58 | * host1x_channel_get_index() - Attempt to get channel reference by index |
59 | * @host: Host1x device object |
60 | * @index: Index of channel |
61 | * |
62 | * If channel number @index is currently allocated, increase its refcount |
63 | * and return a pointer to it. Otherwise, return NULL. |
64 | */ |
65 | struct host1x_channel *host1x_channel_get_index(struct host1x *host, |
66 | unsigned int index) |
67 | { |
68 | struct host1x_channel *ch = &host->channel_list.channels[index]; |
69 | |
70 | if (!kref_get_unless_zero(kref: &ch->refcount)) |
71 | return NULL; |
72 | |
73 | return ch; |
74 | } |
75 | |
76 | void host1x_channel_stop(struct host1x_channel *channel) |
77 | { |
78 | struct host1x *host = dev_get_drvdata(dev: channel->dev->parent); |
79 | |
80 | host1x_hw_cdma_stop(host, cdma: &channel->cdma); |
81 | } |
82 | EXPORT_SYMBOL(host1x_channel_stop); |
83 | |
84 | /** |
85 | * host1x_channel_stop_all() - disable CDMA on allocated channels |
86 | * @host: host1x instance |
87 | * |
88 | * Stop CDMA on allocated channels |
89 | */ |
90 | void host1x_channel_stop_all(struct host1x *host) |
91 | { |
92 | struct host1x_channel_list *chlist = &host->channel_list; |
93 | int bit; |
94 | |
95 | mutex_lock(&chlist->lock); |
96 | |
97 | for_each_set_bit(bit, chlist->allocated_channels, host->info->nb_channels) |
98 | host1x_channel_stop(&chlist->channels[bit]); |
99 | |
100 | mutex_unlock(lock: &chlist->lock); |
101 | } |
102 | |
103 | static void release_channel(struct kref *kref) |
104 | { |
105 | struct host1x_channel *channel = |
106 | container_of(kref, struct host1x_channel, refcount); |
107 | struct host1x *host = dev_get_drvdata(dev: channel->dev->parent); |
108 | struct host1x_channel_list *chlist = &host->channel_list; |
109 | |
110 | host1x_hw_cdma_stop(host, cdma: &channel->cdma); |
111 | host1x_cdma_deinit(cdma: &channel->cdma); |
112 | |
113 | clear_bit(nr: channel->id, addr: chlist->allocated_channels); |
114 | } |
115 | |
116 | void host1x_channel_put(struct host1x_channel *channel) |
117 | { |
118 | kref_put(kref: &channel->refcount, release: release_channel); |
119 | } |
120 | EXPORT_SYMBOL(host1x_channel_put); |
121 | |
122 | static struct host1x_channel *acquire_unused_channel(struct host1x *host) |
123 | { |
124 | struct host1x_channel_list *chlist = &host->channel_list; |
125 | unsigned int max_channels = host->info->nb_channels; |
126 | unsigned int index; |
127 | |
128 | mutex_lock(&chlist->lock); |
129 | |
130 | index = find_first_zero_bit(addr: chlist->allocated_channels, size: max_channels); |
131 | if (index >= max_channels) { |
132 | mutex_unlock(lock: &chlist->lock); |
133 | dev_err(host->dev, "failed to find free channel\n" ); |
134 | return NULL; |
135 | } |
136 | |
137 | chlist->channels[index].id = index; |
138 | |
139 | set_bit(nr: index, addr: chlist->allocated_channels); |
140 | |
141 | mutex_unlock(lock: &chlist->lock); |
142 | |
143 | return &chlist->channels[index]; |
144 | } |
145 | |
146 | /** |
147 | * host1x_channel_request() - Allocate a channel |
148 | * @client: Host1x client this channel will be used to send commands to |
149 | * |
150 | * Allocates a new host1x channel for @client. May return NULL if CDMA |
151 | * initialization fails. |
152 | */ |
153 | struct host1x_channel *host1x_channel_request(struct host1x_client *client) |
154 | { |
155 | struct host1x *host = dev_get_drvdata(dev: client->dev->parent); |
156 | struct host1x_channel_list *chlist = &host->channel_list; |
157 | struct host1x_channel *channel; |
158 | int err; |
159 | |
160 | channel = acquire_unused_channel(host); |
161 | if (!channel) |
162 | return NULL; |
163 | |
164 | kref_init(kref: &channel->refcount); |
165 | mutex_init(&channel->submitlock); |
166 | channel->client = client; |
167 | channel->dev = client->dev; |
168 | |
169 | err = host1x_hw_channel_init(host, channel, id: channel->id); |
170 | if (err < 0) |
171 | goto fail; |
172 | |
173 | err = host1x_cdma_init(cdma: &channel->cdma); |
174 | if (err < 0) |
175 | goto fail; |
176 | |
177 | return channel; |
178 | |
179 | fail: |
180 | clear_bit(nr: channel->id, addr: chlist->allocated_channels); |
181 | |
182 | dev_err(client->dev, "failed to initialize channel\n" ); |
183 | |
184 | return NULL; |
185 | } |
186 | EXPORT_SYMBOL(host1x_channel_request); |
187 | |