diff options
author | Hans Verkuil <hans.verkuil@cisco.com> | 2012-02-02 06:44:40 -0500 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-02-14 13:39:47 -0500 |
commit | 1bf20c3a0c616f44359c573b533d06bae960ee45 (patch) | |
tree | 4c8c81a1de7ddcfc06967b617896596c4970dc25 /drivers/media/radio | |
parent | 6d6604fa6643d15cd46f891b01084ecfd5d0fbec (diff) |
[media] radio-keene: add a driver for the Keene FM Transmitter
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/radio')
-rw-r--r-- | drivers/media/radio/Kconfig | 10 | ||||
-rw-r--r-- | drivers/media/radio/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/radio/radio-keene.c | 427 |
3 files changed, 438 insertions, 0 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index e954781c90bf..48747df59453 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig | |||
@@ -80,6 +80,16 @@ config RADIO_SI4713 | |||
80 | To compile this driver as a module, choose M here: the | 80 | To compile this driver as a module, choose M here: the |
81 | module will be called radio-si4713. | 81 | module will be called radio-si4713. |
82 | 82 | ||
83 | config USB_KEENE | ||
84 | tristate "Keene FM Transmitter USB support" | ||
85 | depends on USB && VIDEO_V4L2 | ||
86 | ---help--- | ||
87 | Say Y here if you want to connect this type of FM transmitter | ||
88 | to your computer's USB port. | ||
89 | |||
90 | To compile this driver as a module, choose M here: the | ||
91 | module will be called radio-keene. | ||
92 | |||
83 | config RADIO_TEA5764 | 93 | config RADIO_TEA5764 |
84 | tristate "TEA5764 I2C FM radio support" | 94 | tristate "TEA5764 I2C FM radio support" |
85 | depends on I2C && VIDEO_V4L2 | 95 | depends on I2C && VIDEO_V4L2 |
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 390daf94d847..aec5f6fa592f 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile | |||
@@ -20,6 +20,7 @@ obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o | |||
20 | obj-$(CONFIG_USB_DSBR) += dsbr100.o | 20 | obj-$(CONFIG_USB_DSBR) += dsbr100.o |
21 | obj-$(CONFIG_RADIO_SI470X) += si470x/ | 21 | obj-$(CONFIG_RADIO_SI470X) += si470x/ |
22 | obj-$(CONFIG_USB_MR800) += radio-mr800.o | 22 | obj-$(CONFIG_USB_MR800) += radio-mr800.o |
23 | obj-$(CONFIG_USB_KEENE) += radio-keene.o | ||
23 | obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o | 24 | obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o |
24 | obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o | 25 | obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o |
25 | obj-$(CONFIG_RADIO_TEF6862) += tef6862.o | 26 | obj-$(CONFIG_RADIO_TEF6862) += tef6862.o |
diff --git a/drivers/media/radio/radio-keene.c b/drivers/media/radio/radio-keene.c new file mode 100644 index 000000000000..55bd1d2937c8 --- /dev/null +++ b/drivers/media/radio/radio-keene.c | |||
@@ -0,0 +1,427 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2012 Hans Verkuil <hverkuil@xs4all.nl> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
17 | */ | ||
18 | |||
19 | /* kernel includes */ | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/input.h> | ||
25 | #include <linux/videodev2.h> | ||
26 | #include <media/v4l2-device.h> | ||
27 | #include <media/v4l2-ioctl.h> | ||
28 | #include <media/v4l2-ctrls.h> | ||
29 | #include <media/v4l2-event.h> | ||
30 | #include <linux/usb.h> | ||
31 | #include <linux/version.h> | ||
32 | #include <linux/mutex.h> | ||
33 | |||
34 | /* driver and module definitions */ | ||
35 | MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); | ||
36 | MODULE_DESCRIPTION("Keene FM Transmitter driver"); | ||
37 | MODULE_LICENSE("GPL"); | ||
38 | |||
39 | /* Actually, it advertises itself as a Logitech */ | ||
40 | #define USB_KEENE_VENDOR 0x046d | ||
41 | #define USB_KEENE_PRODUCT 0x0a0e | ||
42 | |||
43 | /* Probably USB_TIMEOUT should be modified in module parameter */ | ||
44 | #define BUFFER_LENGTH 8 | ||
45 | #define USB_TIMEOUT 500 | ||
46 | |||
47 | /* Frequency limits in MHz */ | ||
48 | #define FREQ_MIN 76U | ||
49 | #define FREQ_MAX 108U | ||
50 | #define FREQ_MUL 16000U | ||
51 | |||
52 | /* USB Device ID List */ | ||
53 | static struct usb_device_id usb_keene_device_table[] = { | ||
54 | {USB_DEVICE_AND_INTERFACE_INFO(USB_KEENE_VENDOR, USB_KEENE_PRODUCT, | ||
55 | USB_CLASS_HID, 0, 0) }, | ||
56 | { } /* Terminating entry */ | ||
57 | }; | ||
58 | |||
59 | MODULE_DEVICE_TABLE(usb, usb_keene_device_table); | ||
60 | |||
61 | struct keene_device { | ||
62 | struct usb_device *usbdev; | ||
63 | struct usb_interface *intf; | ||
64 | struct video_device vdev; | ||
65 | struct v4l2_device v4l2_dev; | ||
66 | struct v4l2_ctrl_handler hdl; | ||
67 | struct mutex lock; | ||
68 | |||
69 | u8 *buffer; | ||
70 | unsigned curfreq; | ||
71 | u8 tx; | ||
72 | u8 pa; | ||
73 | bool stereo; | ||
74 | bool muted; | ||
75 | bool preemph_75_us; | ||
76 | }; | ||
77 | |||
78 | static inline struct keene_device *to_keene_dev(struct v4l2_device *v4l2_dev) | ||
79 | { | ||
80 | return container_of(v4l2_dev, struct keene_device, v4l2_dev); | ||
81 | } | ||
82 | |||
83 | /* Set frequency (if non-0), PA, mute and turn on/off the FM transmitter. */ | ||
84 | static int keene_cmd_main(struct keene_device *radio, unsigned freq, bool play) | ||
85 | { | ||
86 | unsigned short freq_send = freq ? (freq - 76 * 16000) / 800 : 0; | ||
87 | int ret; | ||
88 | |||
89 | radio->buffer[0] = 0x00; | ||
90 | radio->buffer[1] = 0x50; | ||
91 | radio->buffer[2] = (freq_send >> 8) & 0xff; | ||
92 | radio->buffer[3] = freq_send & 0xff; | ||
93 | radio->buffer[4] = radio->pa; | ||
94 | /* If bit 4 is set, then tune to the frequency. | ||
95 | If bit 3 is set, then unmute; if bit 2 is set, then mute. | ||
96 | If bit 1 is set, then enter idle mode; if bit 0 is set, | ||
97 | then enter transit mode. | ||
98 | */ | ||
99 | radio->buffer[5] = (radio->muted ? 4 : 8) | (play ? 1 : 2) | | ||
100 | (freq ? 0x10 : 0); | ||
101 | radio->buffer[6] = 0x00; | ||
102 | radio->buffer[7] = 0x00; | ||
103 | |||
104 | ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), | ||
105 | 9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); | ||
106 | |||
107 | if (ret < 0) { | ||
108 | dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret); | ||
109 | return ret; | ||
110 | } | ||
111 | if (freq) | ||
112 | radio->curfreq = freq; | ||
113 | return 0; | ||
114 | } | ||
115 | |||
116 | /* Set TX, stereo and preemphasis mode (50 us vs 75 us). */ | ||
117 | static int keene_cmd_set(struct keene_device *radio) | ||
118 | { | ||
119 | int ret; | ||
120 | |||
121 | radio->buffer[0] = 0x00; | ||
122 | radio->buffer[1] = 0x51; | ||
123 | radio->buffer[2] = radio->tx; | ||
124 | /* If bit 0 is set, then transmit mono, otherwise stereo. | ||
125 | If bit 2 is set, then enable 75 us preemphasis, otherwise | ||
126 | it is 50 us. */ | ||
127 | radio->buffer[3] = (!radio->stereo) | (radio->preemph_75_us ? 4 : 0); | ||
128 | radio->buffer[4] = 0x00; | ||
129 | radio->buffer[5] = 0x00; | ||
130 | radio->buffer[6] = 0x00; | ||
131 | radio->buffer[7] = 0x00; | ||
132 | |||
133 | ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), | ||
134 | 9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); | ||
135 | |||
136 | if (ret < 0) { | ||
137 | dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret); | ||
138 | return ret; | ||
139 | } | ||
140 | return 0; | ||
141 | } | ||
142 | |||
143 | /* Handle unplugging the device. | ||
144 | * We call video_unregister_device in any case. | ||
145 | * The last function called in this procedure is | ||
146 | * usb_keene_device_release. | ||
147 | */ | ||
148 | static void usb_keene_disconnect(struct usb_interface *intf) | ||
149 | { | ||
150 | struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf)); | ||
151 | |||
152 | v4l2_device_get(&radio->v4l2_dev); | ||
153 | mutex_lock(&radio->lock); | ||
154 | usb_set_intfdata(intf, NULL); | ||
155 | video_unregister_device(&radio->vdev); | ||
156 | v4l2_device_disconnect(&radio->v4l2_dev); | ||
157 | mutex_unlock(&radio->lock); | ||
158 | v4l2_device_put(&radio->v4l2_dev); | ||
159 | } | ||
160 | |||
161 | static int vidioc_querycap(struct file *file, void *priv, | ||
162 | struct v4l2_capability *v) | ||
163 | { | ||
164 | struct keene_device *radio = video_drvdata(file); | ||
165 | |||
166 | strlcpy(v->driver, "radio-keene", sizeof(v->driver)); | ||
167 | strlcpy(v->card, "Keene FM Transmitter", sizeof(v->card)); | ||
168 | usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); | ||
169 | v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_MODULATOR; | ||
170 | v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; | ||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | static int vidioc_g_modulator(struct file *file, void *priv, | ||
175 | struct v4l2_modulator *v) | ||
176 | { | ||
177 | struct keene_device *radio = video_drvdata(file); | ||
178 | |||
179 | if (v->index > 0) | ||
180 | return -EINVAL; | ||
181 | |||
182 | strlcpy(v->name, "FM", sizeof(v->name)); | ||
183 | v->rangelow = FREQ_MIN * FREQ_MUL; | ||
184 | v->rangehigh = FREQ_MAX * FREQ_MUL; | ||
185 | v->txsubchans = radio->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; | ||
186 | v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; | ||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | static int vidioc_s_modulator(struct file *file, void *priv, | ||
191 | struct v4l2_modulator *v) | ||
192 | { | ||
193 | struct keene_device *radio = video_drvdata(file); | ||
194 | |||
195 | if (v->index > 0) | ||
196 | return -EINVAL; | ||
197 | |||
198 | radio->stereo = (v->txsubchans == V4L2_TUNER_SUB_STEREO); | ||
199 | return keene_cmd_set(radio); | ||
200 | } | ||
201 | |||
202 | static int vidioc_s_frequency(struct file *file, void *priv, | ||
203 | struct v4l2_frequency *f) | ||
204 | { | ||
205 | struct keene_device *radio = video_drvdata(file); | ||
206 | |||
207 | if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) | ||
208 | return -EINVAL; | ||
209 | f->frequency = clamp(f->frequency, | ||
210 | FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); | ||
211 | return keene_cmd_main(radio, f->frequency, true); | ||
212 | } | ||
213 | |||
214 | static int vidioc_g_frequency(struct file *file, void *priv, | ||
215 | struct v4l2_frequency *f) | ||
216 | { | ||
217 | struct keene_device *radio = video_drvdata(file); | ||
218 | |||
219 | if (f->tuner != 0) | ||
220 | return -EINVAL; | ||
221 | f->type = V4L2_TUNER_RADIO; | ||
222 | f->frequency = radio->curfreq; | ||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | static int keene_s_ctrl(struct v4l2_ctrl *ctrl) | ||
227 | { | ||
228 | static const u8 db2tx[] = { | ||
229 | /* -15, -12, -9, -6, -3, 0 dB */ | ||
230 | 0x03, 0x13, 0x02, 0x12, 0x22, 0x32, | ||
231 | /* 3, 6, 9, 12, 15, 18 dB */ | ||
232 | 0x21, 0x31, 0x20, 0x30, 0x40, 0x50 | ||
233 | }; | ||
234 | struct keene_device *radio = | ||
235 | container_of(ctrl->handler, struct keene_device, hdl); | ||
236 | |||
237 | switch (ctrl->id) { | ||
238 | case V4L2_CID_AUDIO_MUTE: | ||
239 | radio->muted = ctrl->val; | ||
240 | return keene_cmd_main(radio, 0, true); | ||
241 | |||
242 | case V4L2_CID_TUNE_POWER_LEVEL: | ||
243 | /* To go from dBuV to the register value we apply the | ||
244 | following formula: */ | ||
245 | radio->pa = (ctrl->val - 71) * 100 / 62; | ||
246 | return keene_cmd_main(radio, 0, true); | ||
247 | |||
248 | case V4L2_CID_TUNE_PREEMPHASIS: | ||
249 | radio->preemph_75_us = ctrl->val == V4L2_PREEMPHASIS_75_uS; | ||
250 | return keene_cmd_set(radio); | ||
251 | |||
252 | case V4L2_CID_AUDIO_COMPRESSION_GAIN: | ||
253 | radio->tx = db2tx[(ctrl->val - ctrl->minimum) / ctrl->step]; | ||
254 | return keene_cmd_set(radio); | ||
255 | } | ||
256 | return -EINVAL; | ||
257 | } | ||
258 | |||
259 | static int vidioc_subscribe_event(struct v4l2_fh *fh, | ||
260 | struct v4l2_event_subscription *sub) | ||
261 | { | ||
262 | switch (sub->type) { | ||
263 | case V4L2_EVENT_CTRL: | ||
264 | return v4l2_event_subscribe(fh, sub, 0); | ||
265 | default: | ||
266 | return -EINVAL; | ||
267 | } | ||
268 | } | ||
269 | |||
270 | |||
271 | /* File system interface */ | ||
272 | static const struct v4l2_file_operations usb_keene_fops = { | ||
273 | .owner = THIS_MODULE, | ||
274 | .open = v4l2_fh_open, | ||
275 | .release = v4l2_fh_release, | ||
276 | .poll = v4l2_ctrl_poll, | ||
277 | .unlocked_ioctl = video_ioctl2, | ||
278 | }; | ||
279 | |||
280 | static const struct v4l2_ctrl_ops keene_ctrl_ops = { | ||
281 | .s_ctrl = keene_s_ctrl, | ||
282 | }; | ||
283 | |||
284 | static const struct v4l2_ioctl_ops usb_keene_ioctl_ops = { | ||
285 | .vidioc_querycap = vidioc_querycap, | ||
286 | .vidioc_g_modulator = vidioc_g_modulator, | ||
287 | .vidioc_s_modulator = vidioc_s_modulator, | ||
288 | .vidioc_g_frequency = vidioc_g_frequency, | ||
289 | .vidioc_s_frequency = vidioc_s_frequency, | ||
290 | .vidioc_log_status = v4l2_ctrl_log_status, | ||
291 | .vidioc_subscribe_event = vidioc_subscribe_event, | ||
292 | .vidioc_unsubscribe_event = v4l2_event_unsubscribe, | ||
293 | }; | ||
294 | |||
295 | static void usb_keene_video_device_release(struct v4l2_device *v4l2_dev) | ||
296 | { | ||
297 | struct keene_device *radio = to_keene_dev(v4l2_dev); | ||
298 | |||
299 | /* free rest memory */ | ||
300 | v4l2_ctrl_handler_free(&radio->hdl); | ||
301 | kfree(radio->buffer); | ||
302 | kfree(radio); | ||
303 | } | ||
304 | |||
305 | /* check if the device is present and register with v4l and usb if it is */ | ||
306 | static int usb_keene_probe(struct usb_interface *intf, | ||
307 | const struct usb_device_id *id) | ||
308 | { | ||
309 | struct usb_device *dev = interface_to_usbdev(intf); | ||
310 | struct keene_device *radio; | ||
311 | struct v4l2_ctrl_handler *hdl; | ||
312 | int retval = 0; | ||
313 | |||
314 | /* | ||
315 | * The Keene FM transmitter USB device has the same USB ID as | ||
316 | * the Logitech AudioHub Speaker, but it should ignore the hid. | ||
317 | * Check if the name is that of the Keene device. | ||
318 | * If not, then someone connected the AudioHub and we shouldn't | ||
319 | * attempt to handle this driver. | ||
320 | * For reference: the product name of the AudioHub is | ||
321 | * "AudioHub Speaker". | ||
322 | */ | ||
323 | if (dev->product && strcmp(dev->product, "B-LINK USB Audio ")) | ||
324 | return -ENODEV; | ||
325 | |||
326 | radio = kzalloc(sizeof(struct keene_device), GFP_KERNEL); | ||
327 | if (radio) | ||
328 | radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); | ||
329 | |||
330 | if (!radio || !radio->buffer) { | ||
331 | dev_err(&intf->dev, "kmalloc for keene_device failed\n"); | ||
332 | kfree(radio); | ||
333 | retval = -ENOMEM; | ||
334 | goto err; | ||
335 | } | ||
336 | |||
337 | hdl = &radio->hdl; | ||
338 | v4l2_ctrl_handler_init(hdl, 4); | ||
339 | v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_MUTE, | ||
340 | 0, 1, 1, 0); | ||
341 | v4l2_ctrl_new_std_menu(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_PREEMPHASIS, | ||
342 | V4L2_PREEMPHASIS_75_uS, 1, V4L2_PREEMPHASIS_50_uS); | ||
343 | v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_POWER_LEVEL, | ||
344 | 84, 118, 1, 118); | ||
345 | v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_COMPRESSION_GAIN, | ||
346 | -15, 18, 3, 0); | ||
347 | radio->pa = 118; | ||
348 | radio->tx = 0x32; | ||
349 | radio->stereo = true; | ||
350 | radio->curfreq = 95.16 * FREQ_MUL; | ||
351 | if (hdl->error) { | ||
352 | retval = hdl->error; | ||
353 | |||
354 | v4l2_ctrl_handler_free(hdl); | ||
355 | goto err_v4l2; | ||
356 | } | ||
357 | retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); | ||
358 | if (retval < 0) { | ||
359 | dev_err(&intf->dev, "couldn't register v4l2_device\n"); | ||
360 | goto err_v4l2; | ||
361 | } | ||
362 | |||
363 | mutex_init(&radio->lock); | ||
364 | |||
365 | radio->v4l2_dev.ctrl_handler = hdl; | ||
366 | radio->v4l2_dev.release = usb_keene_video_device_release; | ||
367 | strlcpy(radio->vdev.name, radio->v4l2_dev.name, | ||
368 | sizeof(radio->vdev.name)); | ||
369 | radio->vdev.v4l2_dev = &radio->v4l2_dev; | ||
370 | radio->vdev.fops = &usb_keene_fops; | ||
371 | radio->vdev.ioctl_ops = &usb_keene_ioctl_ops; | ||
372 | radio->vdev.lock = &radio->lock; | ||
373 | radio->vdev.release = video_device_release_empty; | ||
374 | |||
375 | radio->usbdev = interface_to_usbdev(intf); | ||
376 | radio->intf = intf; | ||
377 | usb_set_intfdata(intf, &radio->v4l2_dev); | ||
378 | |||
379 | video_set_drvdata(&radio->vdev, radio); | ||
380 | set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); | ||
381 | |||
382 | retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); | ||
383 | if (retval < 0) { | ||
384 | dev_err(&intf->dev, "could not register video device\n"); | ||
385 | goto err_vdev; | ||
386 | } | ||
387 | v4l2_ctrl_handler_setup(hdl); | ||
388 | dev_info(&intf->dev, "V4L2 device registered as %s\n", | ||
389 | video_device_node_name(&radio->vdev)); | ||
390 | return 0; | ||
391 | |||
392 | err_vdev: | ||
393 | v4l2_device_unregister(&radio->v4l2_dev); | ||
394 | err_v4l2: | ||
395 | kfree(radio->buffer); | ||
396 | kfree(radio); | ||
397 | err: | ||
398 | return retval; | ||
399 | } | ||
400 | |||
401 | /* USB subsystem interface */ | ||
402 | static struct usb_driver usb_keene_driver = { | ||
403 | .name = "radio-keene", | ||
404 | .probe = usb_keene_probe, | ||
405 | .disconnect = usb_keene_disconnect, | ||
406 | .id_table = usb_keene_device_table, | ||
407 | }; | ||
408 | |||
409 | static int __init keene_init(void) | ||
410 | { | ||
411 | int retval = usb_register(&usb_keene_driver); | ||
412 | |||
413 | if (retval) | ||
414 | pr_err(KBUILD_MODNAME | ||
415 | ": usb_register failed. Error number %d\n", retval); | ||
416 | |||
417 | return retval; | ||
418 | } | ||
419 | |||
420 | static void __exit keene_exit(void) | ||
421 | { | ||
422 | usb_deregister(&usb_keene_driver); | ||
423 | } | ||
424 | |||
425 | module_init(keene_init); | ||
426 | module_exit(keene_exit); | ||
427 | |||