diff options
-rw-r--r-- | drivers/media/radio/radio-si470x.c | 57 |
1 files changed, 48 insertions, 9 deletions
diff --git a/drivers/media/radio/radio-si470x.c b/drivers/media/radio/radio-si470x.c index 649f14d2c013..4ad88ee616fd 100644 --- a/drivers/media/radio/radio-si470x.c +++ b/drivers/media/radio/radio-si470x.c | |||
@@ -85,6 +85,7 @@ | |||
85 | * Oliver Neukum <oliver@neukum.org> | 85 | * Oliver Neukum <oliver@neukum.org> |
86 | * Version 1.0.7 | 86 | * Version 1.0.7 |
87 | * - usb autosuspend support | 87 | * - usb autosuspend support |
88 | * - unplugging fixed | ||
88 | * | 89 | * |
89 | * ToDo: | 90 | * ToDo: |
90 | * - add seeking support | 91 | * - add seeking support |
@@ -97,10 +98,10 @@ | |||
97 | /* driver definitions */ | 98 | /* driver definitions */ |
98 | #define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>" | 99 | #define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>" |
99 | #define DRIVER_NAME "radio-si470x" | 100 | #define DRIVER_NAME "radio-si470x" |
100 | #define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 6) | 101 | #define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 7) |
101 | #define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver" | 102 | #define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver" |
102 | #define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers" | 103 | #define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers" |
103 | #define DRIVER_VERSION "1.0.6" | 104 | #define DRIVER_VERSION "1.0.7" |
104 | 105 | ||
105 | 106 | ||
106 | /* kernel includes */ | 107 | /* kernel includes */ |
@@ -424,6 +425,7 @@ struct si470x_device { | |||
424 | 425 | ||
425 | /* driver management */ | 426 | /* driver management */ |
426 | unsigned int users; | 427 | unsigned int users; |
428 | unsigned char disconnected; | ||
427 | 429 | ||
428 | /* Silabs internal registers (0..15) */ | 430 | /* Silabs internal registers (0..15) */ |
429 | unsigned short registers[RADIO_REGISTER_NUM]; | 431 | unsigned short registers[RADIO_REGISTER_NUM]; |
@@ -440,6 +442,12 @@ struct si470x_device { | |||
440 | 442 | ||
441 | 443 | ||
442 | /* | 444 | /* |
445 | * Lock to prevent kfree of data before all users have releases the device. | ||
446 | */ | ||
447 | static DEFINE_MUTEX(open_close_lock); | ||
448 | |||
449 | |||
450 | /* | ||
443 | * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW, | 451 | * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW, |
444 | * 62.5 kHz otherwise. | 452 | * 62.5 kHz otherwise. |
445 | * The tuner is able to have a channel spacing of 50, 100 or 200 kHz. | 453 | * The tuner is able to have a channel spacing of 50, 100 or 200 kHz. |
@@ -577,7 +585,7 @@ static int si470x_get_rds_registers(struct si470x_device *radio) | |||
577 | usb_rcvintpipe(radio->usbdev, 1), | 585 | usb_rcvintpipe(radio->usbdev, 1), |
578 | (void *) &buf, sizeof(buf), &size, usb_timeout); | 586 | (void *) &buf, sizeof(buf), &size, usb_timeout); |
579 | if (size != sizeof(buf)) | 587 | if (size != sizeof(buf)) |
580 | printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_register: " | 588 | printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: " |
581 | "return size differs: %d != %zu\n", size, sizeof(buf)); | 589 | "return size differs: %d != %zu\n", size, sizeof(buf)); |
582 | if (retval < 0) | 590 | if (retval < 0) |
583 | printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: " | 591 | printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: " |
@@ -875,6 +883,8 @@ static void si470x_work(struct work_struct *work) | |||
875 | struct si470x_device *radio = container_of(work, struct si470x_device, | 883 | struct si470x_device *radio = container_of(work, struct si470x_device, |
876 | work.work); | 884 | work.work); |
877 | 885 | ||
886 | if (radio->disconnected) | ||
887 | return; | ||
878 | if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) | 888 | if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) |
879 | return; | 889 | return; |
880 | 890 | ||
@@ -1001,13 +1011,21 @@ static int si470x_fops_open(struct inode *inode, struct file *file) | |||
1001 | static int si470x_fops_release(struct inode *inode, struct file *file) | 1011 | static int si470x_fops_release(struct inode *inode, struct file *file) |
1002 | { | 1012 | { |
1003 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); | 1013 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); |
1004 | int retval; | 1014 | int retval = 0; |
1005 | 1015 | ||
1006 | if (!radio) | 1016 | if (!radio) |
1007 | return -ENODEV; | 1017 | return -ENODEV; |
1008 | 1018 | ||
1019 | mutex_lock(&open_close_lock); | ||
1009 | radio->users--; | 1020 | radio->users--; |
1010 | if (radio->users == 0) { | 1021 | if (radio->users == 0) { |
1022 | if (radio->disconnected) { | ||
1023 | video_unregister_device(radio->videodev); | ||
1024 | kfree(radio->buffer); | ||
1025 | kfree(radio); | ||
1026 | goto done; | ||
1027 | } | ||
1028 | |||
1011 | /* stop rds reception */ | 1029 | /* stop rds reception */ |
1012 | cancel_delayed_work_sync(&radio->work); | 1030 | cancel_delayed_work_sync(&radio->work); |
1013 | 1031 | ||
@@ -1016,10 +1034,11 @@ static int si470x_fops_release(struct inode *inode, struct file *file) | |||
1016 | 1034 | ||
1017 | retval = si470x_stop(radio); | 1035 | retval = si470x_stop(radio); |
1018 | usb_autopm_put_interface(radio->intf); | 1036 | usb_autopm_put_interface(radio->intf); |
1019 | return retval; | ||
1020 | } | 1037 | } |
1021 | 1038 | ||
1022 | return 0; | 1039 | done: |
1040 | mutex_unlock(&open_close_lock); | ||
1041 | return retval; | ||
1023 | } | 1042 | } |
1024 | 1043 | ||
1025 | 1044 | ||
@@ -1157,6 +1176,9 @@ static int si470x_vidioc_g_ctrl(struct file *file, void *priv, | |||
1157 | { | 1176 | { |
1158 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); | 1177 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); |
1159 | 1178 | ||
1179 | if (radio->disconnected) | ||
1180 | return -EIO; | ||
1181 | |||
1160 | switch (ctrl->id) { | 1182 | switch (ctrl->id) { |
1161 | case V4L2_CID_AUDIO_VOLUME: | 1183 | case V4L2_CID_AUDIO_VOLUME: |
1162 | ctrl->value = radio->registers[SYSCONFIG2] & | 1184 | ctrl->value = radio->registers[SYSCONFIG2] & |
@@ -1181,6 +1203,9 @@ static int si470x_vidioc_s_ctrl(struct file *file, void *priv, | |||
1181 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); | 1203 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); |
1182 | int retval; | 1204 | int retval; |
1183 | 1205 | ||
1206 | if (radio->disconnected) | ||
1207 | return -EIO; | ||
1208 | |||
1184 | switch (ctrl->id) { | 1209 | switch (ctrl->id) { |
1185 | case V4L2_CID_AUDIO_VOLUME: | 1210 | case V4L2_CID_AUDIO_VOLUME: |
1186 | radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME; | 1211 | radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME; |
@@ -1243,6 +1268,8 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv, | |||
1243 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); | 1268 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); |
1244 | int retval; | 1269 | int retval; |
1245 | 1270 | ||
1271 | if (radio->disconnected) | ||
1272 | return -EIO; | ||
1246 | if (tuner->index > 0) | 1273 | if (tuner->index > 0) |
1247 | return -EINVAL; | 1274 | return -EINVAL; |
1248 | 1275 | ||
@@ -1299,6 +1326,8 @@ static int si470x_vidioc_s_tuner(struct file *file, void *priv, | |||
1299 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); | 1326 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); |
1300 | int retval; | 1327 | int retval; |
1301 | 1328 | ||
1329 | if (radio->disconnected) | ||
1330 | return -EIO; | ||
1302 | if (tuner->index > 0) | 1331 | if (tuner->index > 0) |
1303 | return -EINVAL; | 1332 | return -EINVAL; |
1304 | 1333 | ||
@@ -1324,6 +1353,9 @@ static int si470x_vidioc_g_frequency(struct file *file, void *priv, | |||
1324 | { | 1353 | { |
1325 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); | 1354 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); |
1326 | 1355 | ||
1356 | if (radio->disconnected) | ||
1357 | return -EIO; | ||
1358 | |||
1327 | freq->type = V4L2_TUNER_RADIO; | 1359 | freq->type = V4L2_TUNER_RADIO; |
1328 | freq->frequency = si470x_get_freq(radio); | 1360 | freq->frequency = si470x_get_freq(radio); |
1329 | 1361 | ||
@@ -1340,6 +1372,8 @@ static int si470x_vidioc_s_frequency(struct file *file, void *priv, | |||
1340 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); | 1372 | struct si470x_device *radio = video_get_drvdata(video_devdata(file)); |
1341 | int retval; | 1373 | int retval; |
1342 | 1374 | ||
1375 | if (radio->disconnected) | ||
1376 | return -EIO; | ||
1343 | if (freq->type != V4L2_TUNER_RADIO) | 1377 | if (freq->type != V4L2_TUNER_RADIO) |
1344 | return -EINVAL; | 1378 | return -EINVAL; |
1345 | 1379 | ||
@@ -1510,11 +1544,16 @@ static void si470x_usb_driver_disconnect(struct usb_interface *intf) | |||
1510 | { | 1544 | { |
1511 | struct si470x_device *radio = usb_get_intfdata(intf); | 1545 | struct si470x_device *radio = usb_get_intfdata(intf); |
1512 | 1546 | ||
1547 | mutex_lock(&open_close_lock); | ||
1548 | radio->disconnected = 1; | ||
1513 | cancel_delayed_work_sync(&radio->work); | 1549 | cancel_delayed_work_sync(&radio->work); |
1514 | usb_set_intfdata(intf, NULL); | 1550 | usb_set_intfdata(intf, NULL); |
1515 | video_unregister_device(radio->videodev); | 1551 | if (radio->users == 0) { |
1516 | kfree(radio->buffer); | 1552 | video_unregister_device(radio->videodev); |
1517 | kfree(radio); | 1553 | kfree(radio->buffer); |
1554 | kfree(radio); | ||
1555 | } | ||
1556 | mutex_unlock(&open_close_lock); | ||
1518 | } | 1557 | } |
1519 | 1558 | ||
1520 | 1559 | ||