diff options
author | Hans Verkuil <hans.verkuil@cisco.com> | 2011-09-07 05:04:30 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2011-11-03 16:28:52 -0400 |
commit | 2dd7d29c783db1efa875e585770feb2cd7aaaf32 (patch) | |
tree | 5b8f487fa4cb5eb14df7ae422d6d8099a0741977 /drivers/media/video/mt9m001.c | |
parent | 34e181c5211f106f1d464e9bcb50bb88398126e2 (diff) |
[media] mt9m001: convert to the control framework
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
[g.liakhovetski@gmx.de: simplified pointer arithmetic]
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/video/mt9m001.c')
-rw-r--r-- | drivers/media/video/mt9m001.c | 218 |
1 files changed, 77 insertions, 141 deletions
diff --git a/drivers/media/video/mt9m001.c b/drivers/media/video/mt9m001.c index 3555e853e5c9..42bb3c89cfe6 100644 --- a/drivers/media/video/mt9m001.c +++ b/drivers/media/video/mt9m001.c | |||
@@ -17,6 +17,7 @@ | |||
17 | #include <media/soc_mediabus.h> | 17 | #include <media/soc_mediabus.h> |
18 | #include <media/v4l2-subdev.h> | 18 | #include <media/v4l2-subdev.h> |
19 | #include <media/v4l2-chip-ident.h> | 19 | #include <media/v4l2-chip-ident.h> |
20 | #include <media/v4l2-ctrls.h> | ||
20 | 21 | ||
21 | /* | 22 | /* |
22 | * mt9m001 i2c address 0x5d | 23 | * mt9m001 i2c address 0x5d |
@@ -85,15 +86,19 @@ static const struct mt9m001_datafmt mt9m001_monochrome_fmts[] = { | |||
85 | 86 | ||
86 | struct mt9m001 { | 87 | struct mt9m001 { |
87 | struct v4l2_subdev subdev; | 88 | struct v4l2_subdev subdev; |
89 | struct v4l2_ctrl_handler hdl; | ||
90 | struct { | ||
91 | /* exposure/auto-exposure cluster */ | ||
92 | struct v4l2_ctrl *autoexposure; | ||
93 | struct v4l2_ctrl *exposure; | ||
94 | }; | ||
88 | struct v4l2_rect rect; /* Sensor window */ | 95 | struct v4l2_rect rect; /* Sensor window */ |
89 | const struct mt9m001_datafmt *fmt; | 96 | const struct mt9m001_datafmt *fmt; |
90 | const struct mt9m001_datafmt *fmts; | 97 | const struct mt9m001_datafmt *fmts; |
91 | int num_fmts; | 98 | int num_fmts; |
92 | int model; /* V4L2_IDENT_MT9M001* codes from v4l2-chip-ident.h */ | 99 | int model; /* V4L2_IDENT_MT9M001* codes from v4l2-chip-ident.h */ |
93 | unsigned int gain; | 100 | unsigned int total_h; |
94 | unsigned int exposure; | ||
95 | unsigned short y_skip_top; /* Lines to skip at the top */ | 101 | unsigned short y_skip_top; /* Lines to skip at the top */ |
96 | unsigned char autoexposure; | ||
97 | }; | 102 | }; |
98 | 103 | ||
99 | static struct mt9m001 *to_mt9m001(const struct i2c_client *client) | 104 | static struct mt9m001 *to_mt9m001(const struct i2c_client *client) |
@@ -171,10 +176,8 @@ static int mt9m001_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) | |||
171 | struct i2c_client *client = v4l2_get_subdevdata(sd); | 176 | struct i2c_client *client = v4l2_get_subdevdata(sd); |
172 | struct mt9m001 *mt9m001 = to_mt9m001(client); | 177 | struct mt9m001 *mt9m001 = to_mt9m001(client); |
173 | struct v4l2_rect rect = a->c; | 178 | struct v4l2_rect rect = a->c; |
174 | struct soc_camera_device *icd = client->dev.platform_data; | ||
175 | int ret; | 179 | int ret; |
176 | const u16 hblank = 9, vblank = 25; | 180 | const u16 hblank = 9, vblank = 25; |
177 | unsigned int total_h; | ||
178 | 181 | ||
179 | if (mt9m001->fmts == mt9m001_colour_fmts) | 182 | if (mt9m001->fmts == mt9m001_colour_fmts) |
180 | /* | 183 | /* |
@@ -193,7 +196,7 @@ static int mt9m001_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) | |||
193 | soc_camera_limit_side(&rect.top, &rect.height, | 196 | soc_camera_limit_side(&rect.top, &rect.height, |
194 | MT9M001_ROW_SKIP, MT9M001_MIN_HEIGHT, MT9M001_MAX_HEIGHT); | 197 | MT9M001_ROW_SKIP, MT9M001_MIN_HEIGHT, MT9M001_MAX_HEIGHT); |
195 | 198 | ||
196 | total_h = rect.height + mt9m001->y_skip_top + vblank; | 199 | mt9m001->total_h = rect.height + mt9m001->y_skip_top + vblank; |
197 | 200 | ||
198 | /* Blanking and start values - default... */ | 201 | /* Blanking and start values - default... */ |
199 | ret = reg_write(client, MT9M001_HORIZONTAL_BLANKING, hblank); | 202 | ret = reg_write(client, MT9M001_HORIZONTAL_BLANKING, hblank); |
@@ -213,17 +216,8 @@ static int mt9m001_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) | |||
213 | if (!ret) | 216 | if (!ret) |
214 | ret = reg_write(client, MT9M001_WINDOW_HEIGHT, | 217 | ret = reg_write(client, MT9M001_WINDOW_HEIGHT, |
215 | rect.height + mt9m001->y_skip_top - 1); | 218 | rect.height + mt9m001->y_skip_top - 1); |
216 | if (!ret && mt9m001->autoexposure) { | 219 | if (!ret && v4l2_ctrl_g_ctrl(mt9m001->autoexposure) == V4L2_EXPOSURE_AUTO) |
217 | ret = reg_write(client, MT9M001_SHUTTER_WIDTH, total_h); | 220 | ret = reg_write(client, MT9M001_SHUTTER_WIDTH, mt9m001->total_h); |
218 | if (!ret) { | ||
219 | const struct v4l2_queryctrl *qctrl = | ||
220 | soc_camera_find_qctrl(icd->ops, | ||
221 | V4L2_CID_EXPOSURE); | ||
222 | mt9m001->exposure = (524 + (total_h - 1) * | ||
223 | (qctrl->maximum - qctrl->minimum)) / | ||
224 | 1048 + qctrl->minimum; | ||
225 | } | ||
226 | } | ||
227 | 221 | ||
228 | if (!ret) | 222 | if (!ret) |
229 | mt9m001->rect = rect; | 223 | mt9m001->rect = rect; |
@@ -383,105 +377,48 @@ static int mt9m001_s_register(struct v4l2_subdev *sd, | |||
383 | } | 377 | } |
384 | #endif | 378 | #endif |
385 | 379 | ||
386 | static const struct v4l2_queryctrl mt9m001_controls[] = { | 380 | static int mt9m001_g_volatile_ctrl(struct v4l2_ctrl *ctrl) |
387 | { | ||
388 | .id = V4L2_CID_VFLIP, | ||
389 | .type = V4L2_CTRL_TYPE_BOOLEAN, | ||
390 | .name = "Flip Vertically", | ||
391 | .minimum = 0, | ||
392 | .maximum = 1, | ||
393 | .step = 1, | ||
394 | .default_value = 0, | ||
395 | }, { | ||
396 | .id = V4L2_CID_GAIN, | ||
397 | .type = V4L2_CTRL_TYPE_INTEGER, | ||
398 | .name = "Gain", | ||
399 | .minimum = 0, | ||
400 | .maximum = 127, | ||
401 | .step = 1, | ||
402 | .default_value = 64, | ||
403 | .flags = V4L2_CTRL_FLAG_SLIDER, | ||
404 | }, { | ||
405 | .id = V4L2_CID_EXPOSURE, | ||
406 | .type = V4L2_CTRL_TYPE_INTEGER, | ||
407 | .name = "Exposure", | ||
408 | .minimum = 1, | ||
409 | .maximum = 255, | ||
410 | .step = 1, | ||
411 | .default_value = 255, | ||
412 | .flags = V4L2_CTRL_FLAG_SLIDER, | ||
413 | }, { | ||
414 | .id = V4L2_CID_EXPOSURE_AUTO, | ||
415 | .type = V4L2_CTRL_TYPE_BOOLEAN, | ||
416 | .name = "Automatic Exposure", | ||
417 | .minimum = 0, | ||
418 | .maximum = 1, | ||
419 | .step = 1, | ||
420 | .default_value = 1, | ||
421 | } | ||
422 | }; | ||
423 | |||
424 | static struct soc_camera_ops mt9m001_ops = { | ||
425 | .controls = mt9m001_controls, | ||
426 | .num_controls = ARRAY_SIZE(mt9m001_controls), | ||
427 | }; | ||
428 | |||
429 | static int mt9m001_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) | ||
430 | { | 381 | { |
431 | struct i2c_client *client = v4l2_get_subdevdata(sd); | 382 | struct mt9m001 *mt9m001 = container_of(ctrl->handler, |
432 | struct mt9m001 *mt9m001 = to_mt9m001(client); | 383 | struct mt9m001, hdl); |
433 | int data; | 384 | s32 min, max; |
434 | 385 | ||
435 | switch (ctrl->id) { | 386 | switch (ctrl->id) { |
436 | case V4L2_CID_VFLIP: | ||
437 | data = reg_read(client, MT9M001_READ_OPTIONS2); | ||
438 | if (data < 0) | ||
439 | return -EIO; | ||
440 | ctrl->value = !!(data & 0x8000); | ||
441 | break; | ||
442 | case V4L2_CID_EXPOSURE_AUTO: | 387 | case V4L2_CID_EXPOSURE_AUTO: |
443 | ctrl->value = mt9m001->autoexposure; | 388 | min = mt9m001->exposure->minimum; |
444 | break; | 389 | max = mt9m001->exposure->maximum; |
445 | case V4L2_CID_GAIN: | 390 | mt9m001->exposure->val = |
446 | ctrl->value = mt9m001->gain; | 391 | (524 + (mt9m001->total_h - 1) * (max - min)) / 1048 + min; |
447 | break; | ||
448 | case V4L2_CID_EXPOSURE: | ||
449 | ctrl->value = mt9m001->exposure; | ||
450 | break; | 392 | break; |
451 | } | 393 | } |
452 | return 0; | 394 | return 0; |
453 | } | 395 | } |
454 | 396 | ||
455 | static int mt9m001_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) | 397 | static int mt9m001_s_ctrl(struct v4l2_ctrl *ctrl) |
456 | { | 398 | { |
399 | struct mt9m001 *mt9m001 = container_of(ctrl->handler, | ||
400 | struct mt9m001, hdl); | ||
401 | struct v4l2_subdev *sd = &mt9m001->subdev; | ||
457 | struct i2c_client *client = v4l2_get_subdevdata(sd); | 402 | struct i2c_client *client = v4l2_get_subdevdata(sd); |
458 | struct mt9m001 *mt9m001 = to_mt9m001(client); | 403 | struct v4l2_ctrl *exp = mt9m001->exposure; |
459 | struct soc_camera_device *icd = client->dev.platform_data; | ||
460 | const struct v4l2_queryctrl *qctrl; | ||
461 | int data; | 404 | int data; |
462 | 405 | ||
463 | qctrl = soc_camera_find_qctrl(&mt9m001_ops, ctrl->id); | ||
464 | |||
465 | if (!qctrl) | ||
466 | return -EINVAL; | ||
467 | |||
468 | switch (ctrl->id) { | 406 | switch (ctrl->id) { |
469 | case V4L2_CID_VFLIP: | 407 | case V4L2_CID_VFLIP: |
470 | if (ctrl->value) | 408 | if (ctrl->val) |
471 | data = reg_set(client, MT9M001_READ_OPTIONS2, 0x8000); | 409 | data = reg_set(client, MT9M001_READ_OPTIONS2, 0x8000); |
472 | else | 410 | else |
473 | data = reg_clear(client, MT9M001_READ_OPTIONS2, 0x8000); | 411 | data = reg_clear(client, MT9M001_READ_OPTIONS2, 0x8000); |
474 | if (data < 0) | 412 | if (data < 0) |
475 | return -EIO; | 413 | return -EIO; |
476 | break; | 414 | return 0; |
415 | |||
477 | case V4L2_CID_GAIN: | 416 | case V4L2_CID_GAIN: |
478 | if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum) | ||
479 | return -EINVAL; | ||
480 | /* See Datasheet Table 7, Gain settings. */ | 417 | /* See Datasheet Table 7, Gain settings. */ |
481 | if (ctrl->value <= qctrl->default_value) { | 418 | if (ctrl->val <= ctrl->default_value) { |
482 | /* Pack it into 0..1 step 0.125, register values 0..8 */ | 419 | /* Pack it into 0..1 step 0.125, register values 0..8 */ |
483 | unsigned long range = qctrl->default_value - qctrl->minimum; | 420 | unsigned long range = ctrl->default_value - ctrl->minimum; |
484 | data = ((ctrl->value - qctrl->minimum) * 8 + range / 2) / range; | 421 | data = ((ctrl->val - ctrl->minimum) * 8 + range / 2) / range; |
485 | 422 | ||
486 | dev_dbg(&client->dev, "Setting gain %d\n", data); | 423 | dev_dbg(&client->dev, "Setting gain %d\n", data); |
487 | data = reg_write(client, MT9M001_GLOBAL_GAIN, data); | 424 | data = reg_write(client, MT9M001_GLOBAL_GAIN, data); |
@@ -490,8 +427,8 @@ static int mt9m001_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) | |||
490 | } else { | 427 | } else { |
491 | /* Pack it into 1.125..15 variable step, register values 9..67 */ | 428 | /* Pack it into 1.125..15 variable step, register values 9..67 */ |
492 | /* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */ | 429 | /* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */ |
493 | unsigned long range = qctrl->maximum - qctrl->default_value - 1; | 430 | unsigned long range = ctrl->maximum - ctrl->default_value - 1; |
494 | unsigned long gain = ((ctrl->value - qctrl->default_value - 1) * | 431 | unsigned long gain = ((ctrl->val - ctrl->default_value - 1) * |
495 | 111 + range / 2) / range + 9; | 432 | 111 + range / 2) / range + 9; |
496 | 433 | ||
497 | if (gain <= 32) | 434 | if (gain <= 32) |
@@ -507,47 +444,30 @@ static int mt9m001_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) | |||
507 | if (data < 0) | 444 | if (data < 0) |
508 | return -EIO; | 445 | return -EIO; |
509 | } | 446 | } |
447 | return 0; | ||
510 | 448 | ||
511 | /* Success */ | 449 | case V4L2_CID_EXPOSURE_AUTO: |
512 | mt9m001->gain = ctrl->value; | 450 | if (ctrl->val == V4L2_EXPOSURE_MANUAL) { |
513 | break; | 451 | unsigned long range = exp->maximum - exp->minimum; |
514 | case V4L2_CID_EXPOSURE: | 452 | unsigned long shutter = ((exp->val - exp->minimum) * 1048 + |
515 | /* mt9m001 has maximum == default */ | ||
516 | if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum) | ||
517 | return -EINVAL; | ||
518 | else { | ||
519 | unsigned long range = qctrl->maximum - qctrl->minimum; | ||
520 | unsigned long shutter = ((ctrl->value - qctrl->minimum) * 1048 + | ||
521 | range / 2) / range + 1; | 453 | range / 2) / range + 1; |
522 | 454 | ||
523 | dev_dbg(&client->dev, | 455 | dev_dbg(&client->dev, |
524 | "Setting shutter width from %d to %lu\n", | 456 | "Setting shutter width from %d to %lu\n", |
525 | reg_read(client, MT9M001_SHUTTER_WIDTH), | 457 | reg_read(client, MT9M001_SHUTTER_WIDTH), shutter); |
526 | shutter); | ||
527 | if (reg_write(client, MT9M001_SHUTTER_WIDTH, shutter) < 0) | 458 | if (reg_write(client, MT9M001_SHUTTER_WIDTH, shutter) < 0) |
528 | return -EIO; | 459 | return -EIO; |
529 | mt9m001->exposure = ctrl->value; | 460 | } else { |
530 | mt9m001->autoexposure = 0; | ||
531 | } | ||
532 | break; | ||
533 | case V4L2_CID_EXPOSURE_AUTO: | ||
534 | if (ctrl->value) { | ||
535 | const u16 vblank = 25; | 461 | const u16 vblank = 25; |
536 | unsigned int total_h = mt9m001->rect.height + | 462 | |
463 | mt9m001->total_h = mt9m001->rect.height + | ||
537 | mt9m001->y_skip_top + vblank; | 464 | mt9m001->y_skip_top + vblank; |
538 | if (reg_write(client, MT9M001_SHUTTER_WIDTH, | 465 | if (reg_write(client, MT9M001_SHUTTER_WIDTH, mt9m001->total_h) < 0) |
539 | total_h) < 0) | ||
540 | return -EIO; | 466 | return -EIO; |
541 | qctrl = soc_camera_find_qctrl(icd->ops, V4L2_CID_EXPOSURE); | 467 | } |
542 | mt9m001->exposure = (524 + (total_h - 1) * | 468 | return 0; |
543 | (qctrl->maximum - qctrl->minimum)) / | ||
544 | 1048 + qctrl->minimum; | ||
545 | mt9m001->autoexposure = 1; | ||
546 | } else | ||
547 | mt9m001->autoexposure = 0; | ||
548 | break; | ||
549 | } | 469 | } |
550 | return 0; | 470 | return -EINVAL; |
551 | } | 471 | } |
552 | 472 | ||
553 | /* | 473 | /* |
@@ -621,10 +541,7 @@ static int mt9m001_video_probe(struct soc_camera_device *icd, | |||
621 | dev_err(&client->dev, "Failed to initialise the camera\n"); | 541 | dev_err(&client->dev, "Failed to initialise the camera\n"); |
622 | 542 | ||
623 | /* mt9m001_init() has reset the chip, returning registers to defaults */ | 543 | /* mt9m001_init() has reset the chip, returning registers to defaults */ |
624 | mt9m001->gain = 64; | 544 | return v4l2_ctrl_handler_setup(&mt9m001->hdl); |
625 | mt9m001->exposure = 255; | ||
626 | |||
627 | return ret; | ||
628 | } | 545 | } |
629 | 546 | ||
630 | static void mt9m001_video_remove(struct soc_camera_device *icd) | 547 | static void mt9m001_video_remove(struct soc_camera_device *icd) |
@@ -647,9 +564,12 @@ static int mt9m001_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines) | |||
647 | return 0; | 564 | return 0; |
648 | } | 565 | } |
649 | 566 | ||
567 | static const struct v4l2_ctrl_ops mt9m001_ctrl_ops = { | ||
568 | .g_volatile_ctrl = mt9m001_g_volatile_ctrl, | ||
569 | .s_ctrl = mt9m001_s_ctrl, | ||
570 | }; | ||
571 | |||
650 | static struct v4l2_subdev_core_ops mt9m001_subdev_core_ops = { | 572 | static struct v4l2_subdev_core_ops mt9m001_subdev_core_ops = { |
651 | .g_ctrl = mt9m001_g_ctrl, | ||
652 | .s_ctrl = mt9m001_s_ctrl, | ||
653 | .g_chip_ident = mt9m001_g_chip_ident, | 573 | .g_chip_ident = mt9m001_g_chip_ident, |
654 | #ifdef CONFIG_VIDEO_ADV_DEBUG | 574 | #ifdef CONFIG_VIDEO_ADV_DEBUG |
655 | .g_register = mt9m001_g_register, | 575 | .g_register = mt9m001_g_register, |
@@ -765,25 +685,40 @@ static int mt9m001_probe(struct i2c_client *client, | |||
765 | return -ENOMEM; | 685 | return -ENOMEM; |
766 | 686 | ||
767 | v4l2_i2c_subdev_init(&mt9m001->subdev, client, &mt9m001_subdev_ops); | 687 | v4l2_i2c_subdev_init(&mt9m001->subdev, client, &mt9m001_subdev_ops); |
688 | v4l2_ctrl_handler_init(&mt9m001->hdl, 4); | ||
689 | v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, | ||
690 | V4L2_CID_VFLIP, 0, 1, 1, 0); | ||
691 | v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, | ||
692 | V4L2_CID_GAIN, 0, 127, 1, 64); | ||
693 | mt9m001->exposure = v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, | ||
694 | V4L2_CID_EXPOSURE, 1, 255, 1, 255); | ||
695 | /* | ||
696 | * Simulated autoexposure. If enabled, we calculate shutter width | ||
697 | * ourselves in the driver based on vertical blanking and frame width | ||
698 | */ | ||
699 | mt9m001->autoexposure = v4l2_ctrl_new_std_menu(&mt9m001->hdl, | ||
700 | &mt9m001_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0, | ||
701 | V4L2_EXPOSURE_AUTO); | ||
702 | mt9m001->subdev.ctrl_handler = &mt9m001->hdl; | ||
703 | if (mt9m001->hdl.error) { | ||
704 | int err = mt9m001->hdl.error; | ||
768 | 705 | ||
769 | /* Second stage probe - when a capture adapter is there */ | 706 | kfree(mt9m001); |
770 | icd->ops = &mt9m001_ops; | 707 | return err; |
708 | } | ||
709 | v4l2_ctrl_auto_cluster(2, &mt9m001->autoexposure, | ||
710 | V4L2_EXPOSURE_MANUAL, true); | ||
771 | 711 | ||
712 | /* Second stage probe - when a capture adapter is there */ | ||
772 | mt9m001->y_skip_top = 0; | 713 | mt9m001->y_skip_top = 0; |
773 | mt9m001->rect.left = MT9M001_COLUMN_SKIP; | 714 | mt9m001->rect.left = MT9M001_COLUMN_SKIP; |
774 | mt9m001->rect.top = MT9M001_ROW_SKIP; | 715 | mt9m001->rect.top = MT9M001_ROW_SKIP; |
775 | mt9m001->rect.width = MT9M001_MAX_WIDTH; | 716 | mt9m001->rect.width = MT9M001_MAX_WIDTH; |
776 | mt9m001->rect.height = MT9M001_MAX_HEIGHT; | 717 | mt9m001->rect.height = MT9M001_MAX_HEIGHT; |
777 | 718 | ||
778 | /* | ||
779 | * Simulated autoexposure. If enabled, we calculate shutter width | ||
780 | * ourselves in the driver based on vertical blanking and frame width | ||
781 | */ | ||
782 | mt9m001->autoexposure = 1; | ||
783 | |||
784 | ret = mt9m001_video_probe(icd, client); | 719 | ret = mt9m001_video_probe(icd, client); |
785 | if (ret) { | 720 | if (ret) { |
786 | icd->ops = NULL; | 721 | v4l2_ctrl_handler_free(&mt9m001->hdl); |
787 | kfree(mt9m001); | 722 | kfree(mt9m001); |
788 | } | 723 | } |
789 | 724 | ||
@@ -795,7 +730,8 @@ static int mt9m001_remove(struct i2c_client *client) | |||
795 | struct mt9m001 *mt9m001 = to_mt9m001(client); | 730 | struct mt9m001 *mt9m001 = to_mt9m001(client); |
796 | struct soc_camera_device *icd = client->dev.platform_data; | 731 | struct soc_camera_device *icd = client->dev.platform_data; |
797 | 732 | ||
798 | icd->ops = NULL; | 733 | v4l2_device_unregister_subdev(&mt9m001->subdev); |
734 | v4l2_ctrl_handler_free(&mt9m001->hdl); | ||
799 | mt9m001_video_remove(icd); | 735 | mt9m001_video_remove(icd); |
800 | kfree(mt9m001); | 736 | kfree(mt9m001); |
801 | 737 | ||