/* * V4L2 flash LED sub-device registration helpers. * * Copyright (C) 2015 Samsung Electronics Co., Ltd * Author: Jacek Anaszewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #define has_flash_op(v4l2_flash, op) \ (v4l2_flash && v4l2_flash->ops && v4l2_flash->ops->op) #define call_flash_op(v4l2_flash, op, arg) \ (has_flash_op(v4l2_flash, op) ? \ v4l2_flash->ops->op(v4l2_flash, arg) : \ -EINVAL) enum ctrl_init_data_id { LED_MODE, TORCH_INTENSITY, FLASH_INTENSITY, INDICATOR_INTENSITY, FLASH_TIMEOUT, STROBE_SOURCE, /* * Only above values are applicable to * the 'ctrls' array in the struct v4l2_flash. */ FLASH_STROBE, STROBE_STOP, STROBE_STATUS, FLASH_FAULT, NUM_FLASH_CTRLS, }; static enum led_brightness __intensity_to_led_brightness( struct v4l2_ctrl *ctrl, s32 intensity) { intensity -= ctrl->minimum; intensity /= (u32) ctrl->step; /* * Indicator LEDs, unlike torch LEDs, are turned on/off basing on * the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only. * Therefore it must be possible to set it to 0 level which in * the LED subsystem reflects LED_OFF state. */ if (ctrl->minimum) ++intensity; return intensity; } static s32 __led_brightness_to_intensity(struct v4l2_ctrl *ctrl, enum led_brightness brightness) { /* * Indicator LEDs, unlike torch LEDs, are turned on/off basing on * the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only. * Do not decrement brightness read from the LED subsystem for * indicator LED as it may equal 0. For torch LEDs this function * is called only when V4L2_FLASH_LED_MODE_TORCH is set and the * brightness read is guaranteed to be greater than 0. In the mode * V4L2_FLASH_LED_MODE_NONE the cached torch intensity value is used. */ if (ctrl->id != V4L2_CID_FLASH_INDICATOR_INTENSITY) --brightness; return (brightness * ctrl->step) + ctrl->minimum; } static void v4l2_flash_set_led_brightness(struct v4l2_flash *v4l2_flash, struct v4l2_ctrl *ctrl) { struct v4l2_ctrl **ctrls = v4l2_flash->ctrls; enum led_brightness brightness; if (has_flash_op(v4l2_flash, intensity_to_led_brightness)) brightness = call_flash_op(v4l2_flash, intensity_to_led_brightness, ctrl->val); else brightness = __intensity_to_led_brightness(ctrl, ctrl->val); /* * In case a LED Flash class driver provides ops for custom * brightness <-> intensity conversion, it also must have defined * related v4l2 control step == 1. In such a case a backward conversion * from led brightness to v4l2 intensity is required to find out the * the aligned intensity value. */ if (has_flash_op(v4l2_flash, led_brightness_to_intensity)) ctrl->val = call_flash_op(v4l2_flash, led_brightness_to_intensity, brightness); if (ctrl == ctrls[TORCH_INTENSITY]) { if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH) return; led_set_brightness_sync(&v4l2_flash->fled_cdev->led_cdev, brightness); } else { led_set_brightness_sync(v4l2_flash->iled_cdev, brightness); } } static int v4l2_flash_update_led_brightness(struct v4l2_flash *v4l2_flash, struct v4l2_ctrl *ctrl) { struct v4l2_ctrl **ctrls = v4l2_flash->ctrls; struct led_classdev *led_cdev; int ret; if (ctrl == ctrls[TORCH_INTENSITY]) { /* * Update torch brightness only if in TORCH_MODE. In other modes * torch led is turned off, which would spuriously inform the * user space that V4L2_CID_FLASH_TORCH_INTENSITY control value * has changed to 0. */ if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH) return 0; led_cdev = &v4l2_flash->fled_cdev->led_cdev; } else { led_cdev = v4l2_flash->iled_cdev; } ret = led_update_brightness(led_cdev); if (ret < 0) return ret; if (has_flash_op(v4l2_flash, led_brightness_to_intensity)) ctrl->val = call_flash_op(v4l2_flash, led_brightness_to_intensity, led_cdev->brightness); else ctrl->val = __led_brightness_to_intensity(ctrl, led_cdev->brightness); return 0; } static int v4l2_flash_g_volatile_ctrl(struct v4l2_ctrl *c) { struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c); struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; bool is_strobing; int ret; switch (c->id) { case V4L2_CID_FLASH_TORCH_INTENSITY: case V4L2_CID_FLASH_INDICATOR_INTENSITY: return v4l2_flash_update_led_brightness(v4l2_flash, c); case V4L2_CID_FLASH_INTENSITY: ret = led_update_flash_brightness(fled_cdev); if (ret < 0) return ret; /* * No conversion is needed as LED Flash class also uses * microamperes for flash intensity units. */ c->val = fled_cdev->brightness.val; return 0; case V4L2_CID_FLASH_STROBE_STATUS: ret = led_get_flash_strobe(fled_cdev, &is_strobing); if (ret < 0) return ret; c->val = is_strobing; return 0; case V4L2_CID_FLASH_FAULT: /* LED faults map directly to V4L2 flash faults */ return led_get_flash_fault(fled_cdev, &c->val); default: return -EINVAL; } } static bool __software_strobe_mode_inactive(struct v4l2_ctrl **ctrls) { return ((ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH) || (ctrls[STROBE_SOURCE] && (ctrls[STROBE_SOURCE]->val != V4L2_FLASH_STROBE_SOURCE_SOFTWARE))); } static int v4l2_flash_s_ctrl(struct v4l2_ctrl *c) { struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c); struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL; struct v4l2_ctrl **ctrls = v4l2_flash->ctrls; bool external_strobe; int ret = 0; switch (c->id) { case V4L2_CID_FLASH_LED_MODE: switch (c->val) { case V4L2_FLASH_LED_MODE_NONE: led_set_brightness_sync(led_cdev, LED_OFF); return led_set_flash_strobe(fled_cdev, false); case V4L2_FLASH_LED_MODE_FLASH: /* Turn the torch LED off */ led_set_brightness_sync(led_cdev, LED_OFF); if (ctrls[STROBE_SOURCE]) { external_strobe = (ctrls[STROBE_SOURCE]->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL); ret = call_flash_op(v4l2_flash, external_strobe_set, external_strobe); } return ret; case V4L2_FLASH_LED_MODE_TORCH: if (ctrls[STROBE_SOURCE]) { ret = call_flash_op(v4l2_flash, external_strobe_set, false); if (ret < 0) return ret; } /* Stop flash strobing */ ret = led_set_flash_strobe(fled_cdev, false); if (ret < 0) return ret; v4l2_flash_set_led_brightness(v4l2_flash, ctrls[TORCH_INTENSITY]); return 0; } break; case V4L2_CID_FLASH_STROBE_SOURCE: external_strobe = (c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL); /* * For some hardware arrangements setting strobe source may * affect torch mode. Therefore, if not in the flash mode, * cache only this setting. It will be applied upon switching * to flash mode. */ if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH) return 0; return call_flash_op(v4l2_flash, external_strobe_set, external_strobe); case V4L2_CID_FLASH_STROBE: if (__software_strobe_mode_inactive(ctrls)) return -EBUSY; return led_set_flash_strobe(fled_cdev, true); case V4L2_CID_FLASH_STROBE_STOP: if (__software_strobe_mode_inactive(ctrls)) return -EBUSY; return led_set_flash_strobe(fled_cdev, false); case V4L2_CID_FLASH_TIMEOUT: /* * No conversion is needed as LED Flash class also uses * microseconds for flash timeout units. */ return led_set_flash_timeout(fled_cdev, c->val); case V4L2_CID_FLASH_INTENSITY: /* * No conversion is needed as LED Flash class also uses * microamperes for flash intensity units. */ return led_set_flash_brightness(fled_cdev, c->val); case V4L2_CID_FLASH_TORCH_INTENSITY: case V4L2_CID_FLASH_INDICATOR_INTENSITY: v4l2_flash_set_led_brightness(v4l2_flash, c); return 0; } return -EINVAL; } static const struct v4l2_ctrl_ops v4l2_flash_ctrl_ops = { .g_volatile_ctrl = v4l2_flash_g_volatile_ctrl, .s_ctrl = v4l2_flash_s_ctrl, }; static void __lfs_to_v4l2_ctrl_config(struct led_flash_setting *s, struct v4l2_ctrl_config *c) { c->min = s->min; c->max = s->max; c->step = s->step; c->def = s->val; } static void __fill_ctrl_init_data(struct v4l2_flash *v4l2_flash, struct v4l2_flash_config *flash_cfg, struct v4l2_flash_ctrl_data *ctrl_init_data) { struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL; struct v4l2_ctrl_config *ctrl_cfg; u32 mask; /* Init INDICATOR_INTENSITY ctrl data */ if (v4l2_flash->iled_cdev) { ctrl_init_data[INDICATOR_INTENSITY].cid = V4L2_CID_FLASH_INDICATOR_INTENSITY; ctrl_cfg = &ctrl_init_data[INDICATOR_INTENSITY].config; __lfs_to_v4l2_ctrl_config(&flash_cfg->intensity, ctrl_cfg); ctrl_cfg->id = V4L2_CID_FLASH_INDICATOR_INTENSITY; ctrl_cfg->min = 0; ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; } if (!led_cdev || WARN_ON(!(led_cdev->flags & LED_DEV_CAP_FLASH))) return; /* Init FLASH_FAULT ctrl data */ if (flash_cfg->flash_faults) { ctrl_init_data[FLASH_FAULT].cid = V4L2_CID_FLASH_FAULT; ctrl_cfg = &ctrl_init_data[FLASH_FAULT].config; ctrl_cfg->id = V4L2_CID_FLASH_FAULT; ctrl_cfg->max = flash_cfg->flash_faults; ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY; } /* Init FLASH_LED_MODE ctrl data */ mask = 1 << V4L2_FLASH_LED_MODE_NONE | 1 << V4L2_FLASH_LED_MODE_TORCH; if (led_cdev->flags & LED_DEV_CAP_FLASH) mask |= 1 << V4L2_FLASH_LED_MODE_FLASH; ctrl_init_data[LED_MODE].cid = V4L2_CID_FLASH_LED_MODE; ctrl_cfg = &ctrl_init_data[LED_MODE].config; ctrl_cfg->id = V4L2_CID_FLASH_LED_MODE; ctrl_cfg->max = V4L2_FLASH_LED_MODE_TORCH; ctrl_cfg->menu_skip_mask = ~mask; ctrl_cfg->def = V4L2_FLASH_LED_MODE_NONE; ctrl_cfg->flags = 0; /* Init TORCH_INTENSITY ctrl data */ ctrl_init_data[TORCH_INTENSITY].cid = V4L2_CID_FLASH_TORCH_INTENSITY; ctrl_cfg = &ctrl_init_data[TORCH_INTENSITY].config; __lfs_to_v4l2_ctrl_config(&flash_cfg->intensity, ctrl_cfg); ctrl_cfg->id = V4L2_CID_FLASH_TORCH_INTENSITY; ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; /* Init FLASH_STROBE ctrl data */ ctrl_init_data[FLASH_STROBE].cid = V4L2_CID_FLASH_STROBE; ctrl_cfg = &ctrl_init_data[FLASH_STROBE].config; ctrl_cfg->id = V4L2_CID_FLASH_STROBE; /* Init STROBE_STOP ctrl data */ ctrl_init_data[STROBE_STOP].cid = V4L2_CID_FLASH_STROBE_STOP; ctrl_cfg = &ctrl_init_data[STROBE_STOP].config; ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STOP; /* Init FLASH_STROBE_SOURCE ctrl data */ if (flash_cfg->has_external_strobe) { mask = (1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE) | (1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL); ctrl_init_data[STROBE_SOURCE].cid = V4L2_CID_FLASH_STROBE_SOURCE; ctrl_cfg = &ctrl_init_data[STROBE_SOURCE].config; ctrl_cfg->id = V4L2_CID_FLASH_STROBE_SOURCE; ctrl_cfg->max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL; ctrl_cfg->menu_skip_mask = ~mask; ctrl_cfg->def = V4L2_FLASH_STROBE_SOURCE_SOFTWARE; } /* Init STROBE_STATUS ctrl data */ if (has_flash_op(fled_cdev, strobe_get)) { ctrl_init_data[STROBE_STATUS].cid = V4L2_CID_FLASH_STROBE_STATUS; ctrl_cfg = &ctrl_init_data[STROBE_STATUS].config; ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STATUS; ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY; } /* Init FLASH_TIMEOUT ctrl data */ if (has_flash_op(fled_cdev, timeout_set)) { ctrl_init_data[FLASH_TIMEOUT].cid = V4L2_CID_FLASH_TIMEOUT; ctrl_cfg = &ctrl_init_data[FLASH_TIMEOUT].config; __lfs_to_v4l2_ctrl_config(&fled_cdev->timeout, ctrl_cfg); ctrl_cfg->id = V4L2_CID_FLASH_TIMEOUT; } /* Init FLASH_INTENSITY ctrl data */ if (has_flash_op(fled_cdev, flash_brightness_set)) { ctrl_init_data[FLASH_INTENSITY].cid = V4L2_CID_FLASH_INTENSITY; ctrl_cfg = &ctrl_init_data[FLASH_INTENSITY].config; __lfs_to_v4l2_ctrl_config(&fled_cdev->brightness, ctrl_cfg); ctrl_cfg->id = V4L2_CID_FLASH_INTENSITY; ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; } } static int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash, struct v4l2_flash_config *flash_cfg) { struct v4l2_flash_ctrl_data *ctrl_init_data; struct v4l2_ctrl *ctrl; struct v4l2_ctrl_config *ctrl_cfg; int i, ret, num_ctrls = 0; v4l2_flash->ctrls = devm_kcalloc(v4l2_flash->sd.dev, STROBE_SOURCE + 1, sizeof(*v4l2_flash->ctrls), GFP_KERNEL); if (!v4l2_flash->ctrls) return -ENOMEM; /* allocate memory dynamically so as not to exceed stack frame size */ ctrl_init_data = kcalloc(NUM_FLASH_CTRLS, sizeof(*ctrl_init_data), GFP_KERNEL); if (!ctrl_init_data) return -ENOMEM; __fill_ctrl_init_data(v4l2_flash, flash_cfg, ctrl_init_data); for (i = 0; i < NUM_FLASH_CTRLS; ++i) if (ctrl_init_data[i].cid) ++num_ctrls; v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls); for (i = 0; i < NUM_FLASH_CTRLS; ++i) { ctrl_cfg = &ctrl_init_data[i].config; if (!ctrl_init_data[i].cid) continue; if (ctrl_cfg->id == V4L2_CID_FLASH_LED_MODE || ctrl_cfg->id == V4L2_CID_FLASH_STROBE_SOURCE) ctrl = v4l2_ctrl_new_std_menu(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops, ctrl_cfg->id, ctrl_cfg->max, ctrl_cfg->menu_skip_mask, ctrl_cfg->def); else ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops, ctrl_cfg->id, ctrl_cfg->min, ctrl_cfg->max, ctrl_cfg->step, ctrl_cfg->def); if (ctrl) ctrl->flags |= ctrl_cfg->flags; if (i <= STROBE_SOURCE) v4l2_flash->ctrls[i] = ctrl; } kfree(ctrl_init_data); if (v4l2_flash->hdl.error) { ret = v4l2_flash->hdl.error; goto error_free_handler; } v4l2_ctrl_handler_setup(&v4l2_flash->hdl); v4l2_flash->sd.ctrl_handler = &v4l2_flash->hdl; return 0; error_free_handler: v4l2_ctrl_handler_free(&v4l2_flash->hdl); return ret; } static int __sync_device_with_v4l2_controls(struct v4l2_flash *v4l2_flash) { struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; struct v4l2_ctrl **ctrls = v4l2_flash->ctrls; int ret = 0; if (ctrls[TORCH_INTENSITY]) v4l2_flash_set_led_brightness(v4l2_flash, ctrls[TORCH_INTENSITY]); if (ctrls[INDICATOR_INTENSITY]) v4l2_flash_set_led_brightness(v4l2_flash, ctrls[INDICATOR_INTENSITY]); if (ctrls[FLASH_TIMEOUT]) { ret = led_set_flash_timeout(fled_cdev, ctrls[FLASH_TIMEOUT]->val); if (ret < 0) return ret; } if (ctrls[FLASH_INTENSITY]) { ret = led_set_flash_brightness(fled_cdev, ctrls[FLASH_INTENSITY]->val); if (ret < 0) return ret; } /* * For some hardware arrangements setting strobe source may affect * torch mode. Synchronize strobe source setting only if not in torch * mode. For torch mode case it will get synchronized upon switching * to flash mode. */ if (ctrls[STROBE_SOURCE] && ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH) ret = call_flash_op(v4l2_flash, external_strobe_set, ctrls[STROBE_SOURCE]->val); return ret; } /* * V4L2 subdev internal operations */ static int v4l2_flash_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) { struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd); struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL; struct led_classdev *led_cdev_ind = v4l2_flash->iled_cdev; int ret = 0; if (!v4l2_fh_is_singular(&fh->vfh)) return 0; if (led_cdev) { mutex_lock(&led_cdev->led_access); led_sysfs_disable(led_cdev); led_trigger_remove(led_cdev); mutex_unlock(&led_cdev->led_access); } if (led_cdev_ind) { mutex_lock(&led_cdev_ind->led_access); led_sysfs_disable(led_cdev_ind); led_trigger_remove(led_cdev_ind); mutex_unlock(&led_cdev_ind->led_access); } ret = __sync_device_with_v4l2_controls(v4l2_flash); if (ret < 0) goto out_sync_device; return 0; out_sync_device: if (led_cdev) { mutex_lock(&led_cdev->led_access); led_sysfs_enable(led_cdev); mutex_unlock(&led_cdev->led_access); } if (led_cdev_ind) { mutex_lock(&led_cdev_ind->led_access); led_sysfs_enable(led_cdev_ind); mutex_unlock(&led_cdev_ind->led_access); } return ret; } static int v4l2_flash_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) { struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd); struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL; struct led_classdev *led_cdev_ind = v4l2_flash->iled_cdev; int ret = 0; if (!v4l2_fh_is_singular(&fh->vfh)) return 0; if (led_cdev) { mutex_lock(&led_cdev->led_access); if (v4l2_flash->ctrls[STROBE_SOURCE]) ret = v4l2_ctrl_s_ctrl( v4l2_flash->ctrls[STROBE_SOURCE], V4L2_FLASH_STROBE_SOURCE_SOFTWARE); led_sysfs_enable(led_cdev); mutex_unlock(&led_cdev->led_access); } if (led_cdev_ind) { mutex_lock(&led_cdev_ind->led_access); led_sysfs_enable(led_cdev_ind); mutex_unlock(&led_cdev_ind->led_access); } return ret; } static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = { .open = v4l2_flash_open, .close = v4l2_flash_close, }; static const struct v4l2_subdev_ops v4l2_flash_subdev_ops; static struct v4l2_flash *__v4l2_flash_init( struct device *dev, struct fwnode_handle *fwn, struct led_classdev_flash *fled_cdev, struct led_classdev *iled_cdev, const struct v4l2_flash_ops *ops, struct v4l2_flash_config *config) { struct v4l2_flash *v4l2_flash; struct v4l2_subdev *sd; int ret; if (!config) return ERR_PTR(-EINVAL); v4l2_flash = devm_kzalloc(dev, sizeof(*v4l2_flash), GFP_KERNEL); if (!v4l2_flash) return ERR_PTR(-ENOMEM); sd = &v4l2_flash->sd; v4l2_flash->fled_cdev = fled_cdev; v4l2_flash->iled_cdev = iled_cdev; v4l2_flash->ops = ops; sd->dev = dev; sd->fwnode = fwn ? fwn : dev_fwnode(dev); v4l2_subdev_init(sd, &v4l2_flash_subdev_ops); sd->internal_ops = &v4l2_flash_subdev_internal_ops; sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; strscpy(sd->name, config->dev_name, sizeof(sd->name)); ret = media_entity_pads_init(&sd->entity, 0, NULL); if (ret < 0) return ERR_PTR(ret); sd->entity.function = MEDIA_ENT_F_FLASH; ret = v4l2_flash_init_controls(v4l2_flash, config); if (ret < 0) goto err_init_controls; fwnode_handle_get(sd->fwnode); ret = v4l2_async_register_subdev(sd); if (ret < 0) goto err_async_register_sd; return v4l2_flash; err_async_register_sd: fwnode_handle_put(sd->fwnode); v4l2_ctrl_handler_free(sd->ctrl_handler); err_init_controls: media_entity_cleanup(&sd->entity); return ERR_PTR(ret); } struct v4l2_flash *v4l2_flash_init( struct device *dev, struct fwnode_handle *fwn, struct led_classdev_flash *fled_cdev, const struct v4l2_flash_ops *ops, struct v4l2_flash_config *config) { return __v4l2_flash_init(dev, fwn, fled_cdev, NULL, ops, config); } EXPORT_SYMBOL_GPL(v4l2_flash_init); struct v4l2_flash *v4l2_flash_indicator_init( struct device *dev, struct fwnode_handle *fwn, struct led_classdev *iled_cdev, struct v4l2_flash_config *config) { return __v4l2_flash_init(dev, fwn, NULL, iled_cdev, NULL, config); } EXPORT_SYMBOL_GPL(v4l2_flash_indicator_init); void v4l2_flash_release(struct v4l2_flash *v4l2_flash) { struct v4l2_subdev *sd; if (IS_ERR_OR_NULL(v4l2_flash)) return; sd = &v4l2_flash->sd; v4l2_async_unregister_subdev(sd); fwnode_handle_put(sd->fwnode); v4l2_ctrl_handler_free(sd->ctrl_handler); media_entity_cleanup(&sd->entity); } EXPORT_SYMBOL_GPL(v4l2_flash_release); MODULE_AUTHOR("Jacek Anaszewski "); MODULE_DESCRIPTION("V4L2 Flash sub-device helpers"); MODULE_LICENSE("GPL v2");