diff options
author | Niranjana Vishwanathapura <nvishwan@codeaurora.org> | 2011-03-23 19:42:55 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-03-23 22:46:38 -0400 |
commit | 73210a135b9dd53ba59beb4ced5a55633ae65b2f (patch) | |
tree | 23d1ffa9f18bbd0c26aabcd874af9b21788241b6 /drivers/char | |
parent | 0dcf334c44d99cd08515f4fc5cc9075abd92b2ff (diff) |
drivers/char: add MSM smd_pkt driver
Add smd_pkt driver which provides device interface to smd packet ports.
Signed-off-by: Niranjana Vishwanathapura <nvishwan@codeaurora.org>
Cc: Brian Swetland <swetland@google.com>
Cc: Greg KH <gregkh@suse.de>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: David Brown <davidb@codeaurora.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/Kconfig | 8 | ||||
-rw-r--r-- | drivers/char/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/msm_smd_pkt.c | 466 |
3 files changed, 475 insertions, 0 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 04f8b2d083c6..ad59b4e0a9b5 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig | |||
@@ -608,5 +608,13 @@ config RAMOOPS | |||
608 | This enables panic and oops messages to be logged to a circular | 608 | This enables panic and oops messages to be logged to a circular |
609 | buffer in RAM where it can be read back at some later point. | 609 | buffer in RAM where it can be read back at some later point. |
610 | 610 | ||
611 | config MSM_SMD_PKT | ||
612 | bool "Enable device interface for some SMD packet ports" | ||
613 | default n | ||
614 | depends on MSM_SMD | ||
615 | help | ||
616 | Enables userspace clients to read and write to some packet SMD | ||
617 | ports via device interface for MSM chipset. | ||
618 | |||
611 | endmenu | 619 | endmenu |
612 | 620 | ||
diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 057f65452e7b..7a00672bd85d 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile | |||
@@ -9,6 +9,7 @@ obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o | |||
9 | obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o | 9 | obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o |
10 | obj-$(CONFIG_RAW_DRIVER) += raw.o | 10 | obj-$(CONFIG_RAW_DRIVER) += raw.o |
11 | obj-$(CONFIG_SGI_SNSC) += snsc.o snsc_event.o | 11 | obj-$(CONFIG_SGI_SNSC) += snsc.o snsc_event.o |
12 | obj-$(CONFIG_MSM_SMD_PKT) += msm_smd_pkt.o | ||
12 | obj-$(CONFIG_MSPEC) += mspec.o | 13 | obj-$(CONFIG_MSPEC) += mspec.o |
13 | obj-$(CONFIG_MMTIMER) += mmtimer.o | 14 | obj-$(CONFIG_MMTIMER) += mmtimer.o |
14 | obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o | 15 | obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o |
diff --git a/drivers/char/msm_smd_pkt.c b/drivers/char/msm_smd_pkt.c new file mode 100644 index 000000000000..b6f8a65c9960 --- /dev/null +++ b/drivers/char/msm_smd_pkt.c | |||
@@ -0,0 +1,466 @@ | |||
1 | /* Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. | ||
2 | * | ||
3 | * This program is free software; you can redistribute it and/or modify | ||
4 | * it under the terms of the GNU General Public License version 2 and | ||
5 | * only version 2 as published by the Free Software Foundation. | ||
6 | * | ||
7 | * This program is distributed in the hope that it will be useful, | ||
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
10 | * GNU General Public License for more details. | ||
11 | * | ||
12 | * You should have received a copy of the GNU General Public License | ||
13 | * along with this program; if not, write to the Free Software | ||
14 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
15 | * 02110-1301, USA. | ||
16 | * | ||
17 | */ | ||
18 | /* | ||
19 | * SMD Packet Driver -- Provides userspace interface to SMD packet ports. | ||
20 | */ | ||
21 | |||
22 | #include <linux/slab.h> | ||
23 | #include <linux/cdev.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/fs.h> | ||
26 | #include <linux/device.h> | ||
27 | #include <linux/sched.h> | ||
28 | #include <linux/mutex.h> | ||
29 | #include <linux/delay.h> | ||
30 | #include <linux/uaccess.h> | ||
31 | #include <linux/workqueue.h> | ||
32 | #include <linux/poll.h> | ||
33 | |||
34 | #include <mach/msm_smd.h> | ||
35 | |||
36 | #define NUM_SMD_PKT_PORTS 9 | ||
37 | #define DEVICE_NAME "smdpkt" | ||
38 | #define MAX_BUF_SIZE 2048 | ||
39 | |||
40 | struct smd_pkt_dev { | ||
41 | struct cdev cdev; | ||
42 | struct device *devicep; | ||
43 | |||
44 | struct smd_channel *ch; | ||
45 | int open_count; | ||
46 | struct mutex ch_lock; | ||
47 | struct mutex rx_lock; | ||
48 | struct mutex tx_lock; | ||
49 | wait_queue_head_t ch_read_wait_queue; | ||
50 | wait_queue_head_t ch_opened_wait_queue; | ||
51 | |||
52 | int i; | ||
53 | |||
54 | unsigned char tx_buf[MAX_BUF_SIZE]; | ||
55 | unsigned char rx_buf[MAX_BUF_SIZE]; | ||
56 | int remote_open; | ||
57 | |||
58 | } *smd_pkt_devp[NUM_SMD_PKT_PORTS]; | ||
59 | |||
60 | struct class *smd_pkt_classp; | ||
61 | static dev_t smd_pkt_number; | ||
62 | |||
63 | static int msm_smd_pkt_debug_enable; | ||
64 | module_param_named(debug_enable, msm_smd_pkt_debug_enable, | ||
65 | int, S_IRUGO | S_IWUSR | S_IWGRP); | ||
66 | |||
67 | #ifdef DEBUG | ||
68 | #define D_DUMP_BUFFER(prestr, cnt, buf) do { \ | ||
69 | int i; \ | ||
70 | if (msm_smd_pkt_debug_enable) { \ | ||
71 | pr_debug("%s", prestr); \ | ||
72 | for (i = 0; i < cnt; i++) \ | ||
73 | pr_debug("%.2x", buf[i]); \ | ||
74 | pr_debug("\n"); \ | ||
75 | } \ | ||
76 | } while (0) | ||
77 | #else | ||
78 | #define D_DUMP_BUFFER(prestr, cnt, buf) do {} while (0) | ||
79 | #endif | ||
80 | |||
81 | #ifdef DEBUG | ||
82 | #define DBG(x...) do { \ | ||
83 | if (msm_smd_pkt_debug_enable) \ | ||
84 | pr_debug(x); \ | ||
85 | } while (0) | ||
86 | #else | ||
87 | #define DBG(x...) do {} while (0) | ||
88 | #endif | ||
89 | |||
90 | static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp) | ||
91 | { | ||
92 | int sz; | ||
93 | |||
94 | if (!smd_pkt_devp || !smd_pkt_devp->ch) | ||
95 | return; | ||
96 | |||
97 | sz = smd_cur_packet_size(smd_pkt_devp->ch); | ||
98 | if (sz == 0) { | ||
99 | DBG("no packet\n"); | ||
100 | return; | ||
101 | } | ||
102 | if (sz > smd_read_avail(smd_pkt_devp->ch)) { | ||
103 | DBG("incomplete packet\n"); | ||
104 | return; | ||
105 | } | ||
106 | |||
107 | DBG("waking up reader\n"); | ||
108 | wake_up_interruptible(&smd_pkt_devp->ch_read_wait_queue); | ||
109 | } | ||
110 | |||
111 | static int smd_pkt_read(struct file *file, char __user *buf, | ||
112 | size_t count, loff_t *ppos) | ||
113 | { | ||
114 | int r, bytes_read; | ||
115 | struct smd_pkt_dev *smd_pkt_devp; | ||
116 | struct smd_channel *chl; | ||
117 | |||
118 | DBG("read %d bytes\n", count); | ||
119 | if (count > MAX_BUF_SIZE) | ||
120 | return -EINVAL; | ||
121 | |||
122 | smd_pkt_devp = file->private_data; | ||
123 | if (!smd_pkt_devp || !smd_pkt_devp->ch) | ||
124 | return -EINVAL; | ||
125 | |||
126 | chl = smd_pkt_devp->ch; | ||
127 | wait_for_packet: | ||
128 | r = wait_event_interruptible(smd_pkt_devp->ch_read_wait_queue, | ||
129 | (smd_cur_packet_size(chl) > 0 && | ||
130 | smd_read_avail(chl) >= | ||
131 | smd_cur_packet_size(chl))); | ||
132 | |||
133 | if (r < 0) { | ||
134 | if (r != -ERESTARTSYS) | ||
135 | pr_err("wait returned %d\n", r); | ||
136 | return r; | ||
137 | } | ||
138 | |||
139 | mutex_lock(&smd_pkt_devp->rx_lock); | ||
140 | |||
141 | bytes_read = smd_cur_packet_size(smd_pkt_devp->ch); | ||
142 | if (bytes_read == 0 || | ||
143 | bytes_read < smd_read_avail(smd_pkt_devp->ch)) { | ||
144 | mutex_unlock(&smd_pkt_devp->rx_lock); | ||
145 | DBG("Nothing to read\n"); | ||
146 | goto wait_for_packet; | ||
147 | } | ||
148 | |||
149 | if (bytes_read > count) { | ||
150 | mutex_unlock(&smd_pkt_devp->rx_lock); | ||
151 | pr_info("packet size %d > buffer size %d", bytes_read, count); | ||
152 | return -EINVAL; | ||
153 | } | ||
154 | |||
155 | r = smd_read(smd_pkt_devp->ch, smd_pkt_devp->rx_buf, bytes_read); | ||
156 | if (r != bytes_read) { | ||
157 | mutex_unlock(&smd_pkt_devp->rx_lock); | ||
158 | pr_err("smd_read failed to read %d bytes: %d\n", bytes_read, r); | ||
159 | return -EIO; | ||
160 | } | ||
161 | |||
162 | D_DUMP_BUFFER("read: ", bytes_read, smd_pkt_devp->rx_buf); | ||
163 | r = copy_to_user(buf, smd_pkt_devp->rx_buf, bytes_read); | ||
164 | mutex_unlock(&smd_pkt_devp->rx_lock); | ||
165 | if (r) { | ||
166 | pr_err("copy_to_user failed %d\n", r); | ||
167 | return -EFAULT; | ||
168 | } | ||
169 | |||
170 | DBG("read complete %d bytes\n", bytes_read); | ||
171 | check_and_wakeup_reader(smd_pkt_devp); | ||
172 | |||
173 | return bytes_read; | ||
174 | } | ||
175 | |||
176 | static int smd_pkt_write(struct file *file, const char __user *buf, | ||
177 | size_t count, loff_t *ppos) | ||
178 | { | ||
179 | int r; | ||
180 | struct smd_pkt_dev *smd_pkt_devp; | ||
181 | |||
182 | if (count > MAX_BUF_SIZE) | ||
183 | return -EINVAL; | ||
184 | |||
185 | DBG("writting %d bytes\n", count); | ||
186 | |||
187 | smd_pkt_devp = file->private_data; | ||
188 | if (!smd_pkt_devp || !smd_pkt_devp->ch) | ||
189 | return -EINVAL; | ||
190 | |||
191 | mutex_lock(&smd_pkt_devp->tx_lock); | ||
192 | if (smd_write_avail(smd_pkt_devp->ch) < count) { | ||
193 | mutex_unlock(&smd_pkt_devp->tx_lock); | ||
194 | DBG("Not enough space to write\n"); | ||
195 | return -ENOMEM; | ||
196 | } | ||
197 | |||
198 | D_DUMP_BUFFER("write: ", count, buf); | ||
199 | r = copy_from_user(smd_pkt_devp->tx_buf, buf, count); | ||
200 | if (r) { | ||
201 | mutex_unlock(&smd_pkt_devp->tx_lock); | ||
202 | pr_err("copy_from_user failed %d\n", r); | ||
203 | return -EFAULT; | ||
204 | } | ||
205 | |||
206 | r = smd_write(smd_pkt_devp->ch, smd_pkt_devp->tx_buf, count); | ||
207 | if (r != count) { | ||
208 | mutex_unlock(&smd_pkt_devp->tx_lock); | ||
209 | pr_err("smd_write failed to write %d bytes: %d.\n", count, r); | ||
210 | return -EIO; | ||
211 | } | ||
212 | mutex_unlock(&smd_pkt_devp->tx_lock); | ||
213 | |||
214 | DBG("wrote %d bytes\n", count); | ||
215 | return count; | ||
216 | } | ||
217 | |||
218 | static unsigned int smd_pkt_poll(struct file *file, poll_table *wait) | ||
219 | { | ||
220 | struct smd_pkt_dev *smd_pkt_devp; | ||
221 | unsigned int mask = 0; | ||
222 | |||
223 | smd_pkt_devp = file->private_data; | ||
224 | if (!smd_pkt_devp) | ||
225 | return POLLERR; | ||
226 | |||
227 | DBG("poll waiting\n"); | ||
228 | poll_wait(file, &smd_pkt_devp->ch_read_wait_queue, wait); | ||
229 | if (smd_read_avail(smd_pkt_devp->ch)) | ||
230 | mask |= POLLIN | POLLRDNORM; | ||
231 | |||
232 | DBG("poll return\n"); | ||
233 | return mask; | ||
234 | } | ||
235 | |||
236 | static void smd_pkt_ch_notify(void *priv, unsigned event) | ||
237 | { | ||
238 | struct smd_pkt_dev *smd_pkt_devp = priv; | ||
239 | |||
240 | if (smd_pkt_devp->ch == 0) | ||
241 | return; | ||
242 | |||
243 | switch (event) { | ||
244 | case SMD_EVENT_DATA: | ||
245 | DBG("data\n"); | ||
246 | check_and_wakeup_reader(smd_pkt_devp); | ||
247 | break; | ||
248 | |||
249 | case SMD_EVENT_OPEN: | ||
250 | DBG("remote open\n"); | ||
251 | smd_pkt_devp->remote_open = 1; | ||
252 | wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue); | ||
253 | break; | ||
254 | |||
255 | case SMD_EVENT_CLOSE: | ||
256 | smd_pkt_devp->remote_open = 0; | ||
257 | pr_info("remote closed\n"); | ||
258 | break; | ||
259 | |||
260 | default: | ||
261 | pr_err("unknown event %d\n", event); | ||
262 | break; | ||
263 | } | ||
264 | } | ||
265 | |||
266 | static char *smd_pkt_dev_name[] = { | ||
267 | "smdcntl0", | ||
268 | "smdcntl1", | ||
269 | "smdcntl2", | ||
270 | "smdcntl3", | ||
271 | "smdcntl4", | ||
272 | "smdcntl5", | ||
273 | "smdcntl6", | ||
274 | "smdcntl7", | ||
275 | "smd22", | ||
276 | }; | ||
277 | |||
278 | static char *smd_ch_name[] = { | ||
279 | "DATA5_CNTL", | ||
280 | "DATA6_CNTL", | ||
281 | "DATA7_CNTL", | ||
282 | "DATA8_CNTL", | ||
283 | "DATA9_CNTL", | ||
284 | "DATA12_CNTL", | ||
285 | "DATA13_CNTL", | ||
286 | "DATA14_CNTL", | ||
287 | "DATA22", | ||
288 | }; | ||
289 | |||
290 | static int smd_pkt_open(struct inode *inode, struct file *file) | ||
291 | { | ||
292 | int r = 0; | ||
293 | struct smd_pkt_dev *smd_pkt_devp; | ||
294 | |||
295 | smd_pkt_devp = container_of(inode->i_cdev, struct smd_pkt_dev, cdev); | ||
296 | if (!smd_pkt_devp) | ||
297 | return -EINVAL; | ||
298 | |||
299 | file->private_data = smd_pkt_devp; | ||
300 | |||
301 | mutex_lock(&smd_pkt_devp->ch_lock); | ||
302 | if (smd_pkt_devp->open_count == 0) { | ||
303 | r = smd_open(smd_ch_name[smd_pkt_devp->i], | ||
304 | &smd_pkt_devp->ch, smd_pkt_devp, | ||
305 | smd_pkt_ch_notify); | ||
306 | if (r < 0) { | ||
307 | pr_err("smd_open failed for %s, %d\n", | ||
308 | smd_ch_name[smd_pkt_devp->i], r); | ||
309 | goto out; | ||
310 | } | ||
311 | |||
312 | r = wait_event_interruptible_timeout( | ||
313 | smd_pkt_devp->ch_opened_wait_queue, | ||
314 | smd_pkt_devp->remote_open, | ||
315 | msecs_to_jiffies(2 * HZ)); | ||
316 | if (r == 0) | ||
317 | r = -ETIMEDOUT; | ||
318 | |||
319 | if (r < 0) { | ||
320 | pr_err("wait returned %d\n", r); | ||
321 | smd_close(smd_pkt_devp->ch); | ||
322 | smd_pkt_devp->ch = 0; | ||
323 | } else { | ||
324 | smd_pkt_devp->open_count++; | ||
325 | r = 0; | ||
326 | } | ||
327 | } | ||
328 | out: | ||
329 | mutex_unlock(&smd_pkt_devp->ch_lock); | ||
330 | return r; | ||
331 | } | ||
332 | |||
333 | static int smd_pkt_release(struct inode *inode, struct file *file) | ||
334 | { | ||
335 | int r = 0; | ||
336 | struct smd_pkt_dev *smd_pkt_devp = file->private_data; | ||
337 | |||
338 | if (!smd_pkt_devp) | ||
339 | return -EINVAL; | ||
340 | |||
341 | mutex_lock(&smd_pkt_devp->ch_lock); | ||
342 | if (--smd_pkt_devp->open_count == 0) { | ||
343 | r = smd_close(smd_pkt_devp->ch); | ||
344 | smd_pkt_devp->ch = 0; | ||
345 | } | ||
346 | mutex_unlock(&smd_pkt_devp->ch_lock); | ||
347 | |||
348 | return r; | ||
349 | } | ||
350 | |||
351 | static const struct file_operations smd_pkt_fops = { | ||
352 | .owner = THIS_MODULE, | ||
353 | .open = smd_pkt_open, | ||
354 | .release = smd_pkt_release, | ||
355 | .read = smd_pkt_read, | ||
356 | .write = smd_pkt_write, | ||
357 | .poll = smd_pkt_poll, | ||
358 | }; | ||
359 | |||
360 | static int __init smd_pkt_init(void) | ||
361 | { | ||
362 | int i; | ||
363 | int r; | ||
364 | |||
365 | r = alloc_chrdev_region(&smd_pkt_number, 0, | ||
366 | NUM_SMD_PKT_PORTS, DEVICE_NAME); | ||
367 | if (r) { | ||
368 | pr_err("alloc_chrdev_region() failed %d\n", r); | ||
369 | return r; | ||
370 | } | ||
371 | |||
372 | smd_pkt_classp = class_create(THIS_MODULE, DEVICE_NAME); | ||
373 | if (IS_ERR(smd_pkt_classp)) { | ||
374 | r = PTR_ERR(smd_pkt_classp); | ||
375 | pr_err("class_create() failed %d\n", r); | ||
376 | goto unreg_chardev; | ||
377 | } | ||
378 | |||
379 | for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) { | ||
380 | smd_pkt_devp[i] = kzalloc(sizeof(struct smd_pkt_dev), | ||
381 | GFP_KERNEL); | ||
382 | if (IS_ERR(smd_pkt_devp[i])) { | ||
383 | r = PTR_ERR(smd_pkt_devp[i]); | ||
384 | pr_err("kmalloc() failed %d\n", r); | ||
385 | goto clean_cdevs; | ||
386 | } | ||
387 | |||
388 | smd_pkt_devp[i]->i = i; | ||
389 | |||
390 | init_waitqueue_head(&smd_pkt_devp[i]->ch_read_wait_queue); | ||
391 | smd_pkt_devp[i]->remote_open = 0; | ||
392 | init_waitqueue_head(&smd_pkt_devp[i]->ch_opened_wait_queue); | ||
393 | |||
394 | mutex_init(&smd_pkt_devp[i]->ch_lock); | ||
395 | mutex_init(&smd_pkt_devp[i]->rx_lock); | ||
396 | mutex_init(&smd_pkt_devp[i]->tx_lock); | ||
397 | |||
398 | cdev_init(&smd_pkt_devp[i]->cdev, &smd_pkt_fops); | ||
399 | smd_pkt_devp[i]->cdev.owner = THIS_MODULE; | ||
400 | |||
401 | r = cdev_add(&smd_pkt_devp[i]->cdev, | ||
402 | (smd_pkt_number + i), 1); | ||
403 | if (r) { | ||
404 | pr_err("cdev_add() failed %d\n", r); | ||
405 | kfree(smd_pkt_devp[i]); | ||
406 | goto clean_cdevs; | ||
407 | } | ||
408 | |||
409 | smd_pkt_devp[i]->devicep = | ||
410 | device_create(smd_pkt_classp, NULL, | ||
411 | (smd_pkt_number + i), NULL, | ||
412 | smd_pkt_dev_name[i]); | ||
413 | if (IS_ERR(smd_pkt_devp[i]->devicep)) { | ||
414 | r = PTR_ERR(smd_pkt_devp[i]->devicep); | ||
415 | pr_err("device_create() failed %d\n", r); | ||
416 | cdev_del(&smd_pkt_devp[i]->cdev); | ||
417 | kfree(smd_pkt_devp[i]); | ||
418 | goto clean_cdevs; | ||
419 | } | ||
420 | |||
421 | } | ||
422 | |||
423 | pr_info("SMD Packet Port Driver Initialized.\n"); | ||
424 | return 0; | ||
425 | |||
426 | clean_cdevs: | ||
427 | if (i > 0) { | ||
428 | while (--i >= 0) { | ||
429 | mutex_destroy(&smd_pkt_devp[i]->ch_lock); | ||
430 | mutex_destroy(&smd_pkt_devp[i]->rx_lock); | ||
431 | mutex_destroy(&smd_pkt_devp[i]->tx_lock); | ||
432 | cdev_del(&smd_pkt_devp[i]->cdev); | ||
433 | kfree(smd_pkt_devp[i]); | ||
434 | device_destroy(smd_pkt_classp, | ||
435 | MKDEV(MAJOR(smd_pkt_number), i)); | ||
436 | } | ||
437 | } | ||
438 | |||
439 | class_destroy(smd_pkt_classp); | ||
440 | unreg_chardev: | ||
441 | unregister_chrdev_region(MAJOR(smd_pkt_number), NUM_SMD_PKT_PORTS); | ||
442 | return r; | ||
443 | } | ||
444 | module_init(smd_pkt_init); | ||
445 | |||
446 | static void __exit smd_pkt_cleanup(void) | ||
447 | { | ||
448 | int i; | ||
449 | |||
450 | for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) { | ||
451 | mutex_destroy(&smd_pkt_devp[i]->ch_lock); | ||
452 | mutex_destroy(&smd_pkt_devp[i]->rx_lock); | ||
453 | mutex_destroy(&smd_pkt_devp[i]->tx_lock); | ||
454 | cdev_del(&smd_pkt_devp[i]->cdev); | ||
455 | kfree(smd_pkt_devp[i]); | ||
456 | device_destroy(smd_pkt_classp, | ||
457 | MKDEV(MAJOR(smd_pkt_number), i)); | ||
458 | } | ||
459 | |||
460 | class_destroy(smd_pkt_classp); | ||
461 | unregister_chrdev_region(MAJOR(smd_pkt_number), NUM_SMD_PKT_PORTS); | ||
462 | } | ||
463 | module_exit(smd_pkt_cleanup); | ||
464 | |||
465 | MODULE_DESCRIPTION("MSM Shared Memory Packet Port"); | ||
466 | MODULE_LICENSE("GPL v2"); | ||