diff options
Diffstat (limited to 'drivers/media/v4l2-core/v4l2-subdev.c')
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-subdev.c | 393 | 
1 files changed, 232 insertions, 161 deletions
| diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 8470d6eda9a3..7c5812d55315 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -148,6 +148,23 @@ static int subdev_close(struct file *file)  }  #endif /* CONFIG_VIDEO_V4L2_SUBDEV_API */ +static void v4l2_subdev_enable_privacy_led(struct v4l2_subdev *sd) +{ +#if IS_REACHABLE(CONFIG_LEDS_CLASS) +	if (!IS_ERR_OR_NULL(sd->privacy_led)) +		led_set_brightness(sd->privacy_led, +				   sd->privacy_led->max_brightness); +#endif +} + +static void v4l2_subdev_disable_privacy_led(struct v4l2_subdev *sd) +{ +#if IS_REACHABLE(CONFIG_LEDS_CLASS) +	if (!IS_ERR_OR_NULL(sd->privacy_led)) +		led_set_brightness(sd->privacy_led, 0); +#endif +} +  static inline int check_which(u32 which)  {  	if (which != V4L2_SUBDEV_FORMAT_TRY && @@ -434,12 +451,8 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable)  	 * The .s_stream() operation must never be called to start or stop an  	 * already started or stopped subdev. Catch offenders but don't return  	 * an error yet to avoid regressions. -	 * -	 * As .s_stream() is mutually exclusive with the .enable_streams() and -	 * .disable_streams() operation, we can use the enabled_streams field -	 * to store the subdev streaming state.  	 */ -	if (WARN_ON(!!sd->enabled_streams == !!enable)) +	if (WARN_ON(sd->s_stream_enabled == !!enable))  		return 0;  	ret = sd->ops->video->s_stream(sd, enable); @@ -450,17 +463,12 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable)  	}  	if (!ret) { -		sd->enabled_streams = enable ? BIT(0) : 0; +		sd->s_stream_enabled = enable; -#if IS_REACHABLE(CONFIG_LEDS_CLASS) -		if (!IS_ERR_OR_NULL(sd->privacy_led)) { -			if (enable) -				led_set_brightness(sd->privacy_led, -						   sd->privacy_led->max_brightness); -			else -				led_set_brightness(sd->privacy_led, 0); -		} -#endif +		if (enable) +			v4l2_subdev_enable_privacy_led(sd); +		else +			v4l2_subdev_disable_privacy_led(sd);  	}  	return ret; @@ -1261,14 +1269,6 @@ v4l2_subdev_link_validate_get_format(struct media_pad *pad, u32 stream,  	struct v4l2_subdev *sd;  	int ret; -	if (!is_media_entity_v4l2_subdev(pad->entity)) { -		WARN(pad->entity->function != MEDIA_ENT_F_IO_V4L, -		     "Driver bug! Wrong media entity type 0x%08x, entity %s\n", -		     pad->entity->function, pad->entity->name); - -		return -EINVAL; -	} -  	sd = media_entity_to_v4l2_subdev(pad->entity);  	fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; @@ -1576,6 +1576,33 @@ int __v4l2_subdev_init_finalize(struct v4l2_subdev *sd, const char *name,  				struct lock_class_key *key)  {  	struct v4l2_subdev_state *state; +	struct device *dev = sd->dev; +	bool has_disable_streams; +	bool has_enable_streams; +	bool has_s_stream; + +	/* Check that the subdevice implements the required features */ + +	has_s_stream = v4l2_subdev_has_op(sd, video, s_stream); +	has_enable_streams = v4l2_subdev_has_op(sd, pad, enable_streams); +	has_disable_streams = v4l2_subdev_has_op(sd, pad, disable_streams); + +	if (has_enable_streams != has_disable_streams) { +		dev_err(dev, +			"subdev '%s' must implement both or neither of .enable_streams() and .disable_streams()\n", +			sd->name); +		return -EINVAL; +	} + +	if (sd->flags & V4L2_SUBDEV_FL_STREAMS) { +		if (has_s_stream && !has_enable_streams) { +			dev_err(dev, +				"subdev '%s' must implement .enable/disable_streams()\n", +				sd->name); + +			return -EINVAL; +		} +	}  	state = __v4l2_subdev_state_alloc(sd, name, key);  	if (IS_ERR(state)) @@ -2120,43 +2147,55 @@ out:  }  EXPORT_SYMBOL_GPL(v4l2_subdev_routing_validate); -static int v4l2_subdev_enable_streams_fallback(struct v4l2_subdev *sd, u32 pad, -					       u64 streams_mask) +static void v4l2_subdev_collect_streams(struct v4l2_subdev *sd, +					struct v4l2_subdev_state *state, +					u32 pad, u64 streams_mask, +					u64 *found_streams, +					u64 *enabled_streams)  { -	struct device *dev = sd->entity.graph_obj.mdev->dev; -	unsigned int i; -	int ret; +	if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) { +		*found_streams = BIT_ULL(0); +		*enabled_streams = +			(sd->enabled_pads & BIT_ULL(pad)) ? BIT_ULL(0) : 0; +		return; +	} -	/* -	 * The subdev doesn't implement pad-based stream enable, fall back -	 * on the .s_stream() operation. This can only be done for subdevs that -	 * have a single source pad, as sd->enabled_streams is global to the -	 * subdev. -	 */ -	if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) -		return -EOPNOTSUPP; +	*found_streams = 0; +	*enabled_streams = 0; -	for (i = 0; i < sd->entity.num_pads; ++i) { -		if (i != pad && sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) -			return -EOPNOTSUPP; -	} +	for (unsigned int i = 0; i < state->stream_configs.num_configs; ++i) { +		const struct v4l2_subdev_stream_config *cfg = +			&state->stream_configs.configs[i]; -	if (sd->enabled_streams & streams_mask) { -		dev_dbg(dev, "set of streams %#llx already enabled on %s:%u\n", -			streams_mask, sd->entity.name, pad); -		return -EALREADY; +		if (cfg->pad != pad || !(streams_mask & BIT_ULL(cfg->stream))) +			continue; + +		*found_streams |= BIT_ULL(cfg->stream); +		if (cfg->enabled) +			*enabled_streams |= BIT_ULL(cfg->stream);  	} +} -	/* Start streaming when the first streams are enabled. */ -	if (!sd->enabled_streams) { -		ret = v4l2_subdev_call(sd, video, s_stream, 1); -		if (ret) -			return ret; +static void v4l2_subdev_set_streams_enabled(struct v4l2_subdev *sd, +					    struct v4l2_subdev_state *state, +					    u32 pad, u64 streams_mask, +					    bool enabled) +{ +	if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) { +		if (enabled) +			sd->enabled_pads |= BIT_ULL(pad); +		else +			sd->enabled_pads &= ~BIT_ULL(pad); +		return;  	} -	sd->enabled_streams |= streams_mask; +	for (unsigned int i = 0; i < state->stream_configs.num_configs; ++i) { +		struct v4l2_subdev_stream_config *cfg = +			&state->stream_configs.configs[i]; -	return 0; +		if (cfg->pad == pad && (streams_mask & BIT_ULL(cfg->stream))) +			cfg->enabled = enabled; +	}  }  int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad, @@ -2164,44 +2203,44 @@ int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad,  {  	struct device *dev = sd->entity.graph_obj.mdev->dev;  	struct v4l2_subdev_state *state; -	u64 found_streams = 0; -	unsigned int i; +	bool already_streaming; +	u64 enabled_streams; +	u64 found_streams; +	bool use_s_stream;  	int ret;  	/* A few basic sanity checks first. */  	if (pad >= sd->entity.num_pads)  		return -EINVAL; +	if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) +		return -EOPNOTSUPP; + +	/* +	 * We use a 64-bit bitmask for tracking enabled pads, so only subdevices +	 * with 64 pads or less can be supported. +	 */ +	if (pad >= sizeof(sd->enabled_pads) * BITS_PER_BYTE) +		return -EOPNOTSUPP; +  	if (!streams_mask)  		return 0;  	/* Fallback on .s_stream() if .enable_streams() isn't available. */ -	if (!sd->ops->pad || !sd->ops->pad->enable_streams) -		return v4l2_subdev_enable_streams_fallback(sd, pad, -							   streams_mask); +	use_s_stream = !v4l2_subdev_has_op(sd, pad, enable_streams); -	state = v4l2_subdev_lock_and_get_active_state(sd); +	if (!use_s_stream) +		state = v4l2_subdev_lock_and_get_active_state(sd); +	else +		state = NULL;  	/*  	 * Verify that the requested streams exist and that they are not  	 * already enabled.  	 */ -	for (i = 0; i < state->stream_configs.num_configs; ++i) { -		struct v4l2_subdev_stream_config *cfg = -			&state->stream_configs.configs[i]; - -		if (cfg->pad != pad || !(streams_mask & BIT_ULL(cfg->stream))) -			continue; -		found_streams |= BIT_ULL(cfg->stream); - -		if (cfg->enabled) { -			dev_dbg(dev, "stream %u already enabled on %s:%u\n", -				cfg->stream, sd->entity.name, pad); -			ret = -EALREADY; -			goto done; -		} -	} +	v4l2_subdev_collect_streams(sd, state, pad, streams_mask, +				    &found_streams, &enabled_streams);  	if (found_streams != streams_mask) {  		dev_dbg(dev, "streams 0x%llx not found on %s:%u\n", @@ -2210,11 +2249,29 @@ int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad,  		goto done;  	} +	if (enabled_streams) { +		dev_dbg(dev, "streams 0x%llx already enabled on %s:%u\n", +			enabled_streams, sd->entity.name, pad); +		ret = -EALREADY; +		goto done; +	} +  	dev_dbg(dev, "enable streams %u:%#llx\n", pad, streams_mask); -	/* Call the .enable_streams() operation. */ -	ret = v4l2_subdev_call(sd, pad, enable_streams, state, pad, -			       streams_mask); +	already_streaming = v4l2_subdev_is_streaming(sd); + +	if (!use_s_stream) { +		/* Call the .enable_streams() operation. */ +		ret = v4l2_subdev_call(sd, pad, enable_streams, state, pad, +				       streams_mask); +	} else { +		/* Start streaming when the first pad is enabled. */ +		if (!already_streaming) +			ret = v4l2_subdev_call(sd, video, s_stream, 1); +		else +			ret = 0; +	} +  	if (ret) {  		dev_dbg(dev, "enable streams %u:%#llx failed: %d\n", pad,  			streams_mask, ret); @@ -2222,103 +2279,68 @@ int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad,  	}  	/* Mark the streams as enabled. */ -	for (i = 0; i < state->stream_configs.num_configs; ++i) { -		struct v4l2_subdev_stream_config *cfg = -			&state->stream_configs.configs[i]; +	v4l2_subdev_set_streams_enabled(sd, state, pad, streams_mask, true); -		if (cfg->pad == pad && (streams_mask & BIT_ULL(cfg->stream))) -			cfg->enabled = true; -	} +	/* +	 * TODO: When all the drivers have been changed to use +	 * v4l2_subdev_enable_streams() and v4l2_subdev_disable_streams(), +	 * instead of calling .s_stream() operation directly, we can remove +	 * the privacy LED handling from call_s_stream() and do it here +	 * for all cases. +	 */ +	if (!use_s_stream && !already_streaming) +		v4l2_subdev_enable_privacy_led(sd);  done: -	v4l2_subdev_unlock_state(state); +	if (!use_s_stream) +		v4l2_subdev_unlock_state(state);  	return ret;  }  EXPORT_SYMBOL_GPL(v4l2_subdev_enable_streams); -static int v4l2_subdev_disable_streams_fallback(struct v4l2_subdev *sd, u32 pad, -						u64 streams_mask) -{ -	struct device *dev = sd->entity.graph_obj.mdev->dev; -	unsigned int i; -	int ret; - -	/* -	 * If the subdev doesn't implement pad-based stream enable, fall  back -	 * on the .s_stream() operation. This can only be done for subdevs that -	 * have a single source pad, as sd->enabled_streams is global to the -	 * subdev. -	 */ -	if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) -		return -EOPNOTSUPP; - -	for (i = 0; i < sd->entity.num_pads; ++i) { -		if (i != pad && sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) -			return -EOPNOTSUPP; -	} - -	if ((sd->enabled_streams & streams_mask) != streams_mask) { -		dev_dbg(dev, "set of streams %#llx already disabled on %s:%u\n", -			streams_mask, sd->entity.name, pad); -		return -EALREADY; -	} - -	/* Stop streaming when the last streams are disabled. */ -	if (!(sd->enabled_streams & ~streams_mask)) { -		ret = v4l2_subdev_call(sd, video, s_stream, 0); -		if (ret) -			return ret; -	} - -	sd->enabled_streams &= ~streams_mask; - -	return 0; -} -  int v4l2_subdev_disable_streams(struct v4l2_subdev *sd, u32 pad,  				u64 streams_mask)  {  	struct device *dev = sd->entity.graph_obj.mdev->dev;  	struct v4l2_subdev_state *state; -	u64 found_streams = 0; -	unsigned int i; +	u64 enabled_streams; +	u64 found_streams; +	bool use_s_stream;  	int ret;  	/* A few basic sanity checks first. */  	if (pad >= sd->entity.num_pads)  		return -EINVAL; +	if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) +		return -EOPNOTSUPP; + +	/* +	 * We use a 64-bit bitmask for tracking enabled pads, so only subdevices +	 * with 64 pads or less can be supported. +	 */ +	if (pad >= sizeof(sd->enabled_pads) * BITS_PER_BYTE) +		return -EOPNOTSUPP; +  	if (!streams_mask)  		return 0;  	/* Fallback on .s_stream() if .disable_streams() isn't available. */ -	if (!sd->ops->pad || !sd->ops->pad->disable_streams) -		return v4l2_subdev_disable_streams_fallback(sd, pad, -							    streams_mask); +	use_s_stream = !v4l2_subdev_has_op(sd, pad, disable_streams); -	state = v4l2_subdev_lock_and_get_active_state(sd); +	if (!use_s_stream) +		state = v4l2_subdev_lock_and_get_active_state(sd); +	else +		state = NULL;  	/*  	 * Verify that the requested streams exist and that they are not  	 * already disabled.  	 */ -	for (i = 0; i < state->stream_configs.num_configs; ++i) { -		struct v4l2_subdev_stream_config *cfg = -			&state->stream_configs.configs[i]; -		if (cfg->pad != pad || !(streams_mask & BIT_ULL(cfg->stream))) -			continue; - -		found_streams |= BIT_ULL(cfg->stream); - -		if (!cfg->enabled) { -			dev_dbg(dev, "stream %u already disabled on %s:%u\n", -				cfg->stream, sd->entity.name, pad); -			ret = -EALREADY; -			goto done; -		} -	} +	v4l2_subdev_collect_streams(sd, state, pad, streams_mask, +				    &found_streams, &enabled_streams);  	if (found_streams != streams_mask) {  		dev_dbg(dev, "streams 0x%llx not found on %s:%u\n", @@ -2327,28 +2349,43 @@ int v4l2_subdev_disable_streams(struct v4l2_subdev *sd, u32 pad,  		goto done;  	} +	if (enabled_streams != streams_mask) { +		dev_dbg(dev, "streams 0x%llx already disabled on %s:%u\n", +			streams_mask & ~enabled_streams, sd->entity.name, pad); +		ret = -EALREADY; +		goto done; +	} +  	dev_dbg(dev, "disable streams %u:%#llx\n", pad, streams_mask); -	/* Call the .disable_streams() operation. */ -	ret = v4l2_subdev_call(sd, pad, disable_streams, state, pad, -			       streams_mask); +	if (!use_s_stream) { +		/* Call the .disable_streams() operation. */ +		ret = v4l2_subdev_call(sd, pad, disable_streams, state, pad, +				       streams_mask); +	} else { +		/* Stop streaming when the last streams are disabled. */ + +		if (!(sd->enabled_pads & ~BIT_ULL(pad))) +			ret = v4l2_subdev_call(sd, video, s_stream, 0); +		else +			ret = 0; +	} +  	if (ret) {  		dev_dbg(dev, "disable streams %u:%#llx failed: %d\n", pad,  			streams_mask, ret);  		goto done;  	} -	/* Mark the streams as disabled. */ -	for (i = 0; i < state->stream_configs.num_configs; ++i) { -		struct v4l2_subdev_stream_config *cfg = -			&state->stream_configs.configs[i]; - -		if (cfg->pad == pad && (streams_mask & BIT_ULL(cfg->stream))) -			cfg->enabled = false; -	} +	v4l2_subdev_set_streams_enabled(sd, state, pad, streams_mask, false);  done: -	v4l2_subdev_unlock_state(state); +	if (!use_s_stream) { +		if (!v4l2_subdev_is_streaming(sd)) +			v4l2_subdev_disable_privacy_led(sd); + +		v4l2_subdev_unlock_state(state); +	}  	return ret;  } @@ -2377,15 +2414,24 @@ int v4l2_subdev_s_stream_helper(struct v4l2_subdev *sd, int enable)  	if (WARN_ON(pad_index == -1))  		return -EINVAL; -	/* -	 * As there's a single source pad, just collect all the source streams. -	 */ -	state = v4l2_subdev_lock_and_get_active_state(sd); +	if (sd->flags & V4L2_SUBDEV_FL_STREAMS) { +		/* +		 * As there's a single source pad, just collect all the source +		 * streams. +		 */ +		state = v4l2_subdev_lock_and_get_active_state(sd); -	for_each_active_route(&state->routing, route) -		source_mask |= BIT_ULL(route->source_stream); +		for_each_active_route(&state->routing, route) +			source_mask |= BIT_ULL(route->source_stream); -	v4l2_subdev_unlock_state(state); +		v4l2_subdev_unlock_state(state); +	} else { +		/* +		 * For non-streams subdevices, there's a single implicit stream +		 * per pad. +		 */ +		source_mask = BIT_ULL(0); +	}  	if (enable)  		return v4l2_subdev_enable_streams(sd, pad_index, source_mask); @@ -2427,6 +2473,31 @@ void v4l2_subdev_notify_event(struct v4l2_subdev *sd,  }  EXPORT_SYMBOL_GPL(v4l2_subdev_notify_event); +bool v4l2_subdev_is_streaming(struct v4l2_subdev *sd) +{ +	struct v4l2_subdev_state *state; + +	if (!v4l2_subdev_has_op(sd, pad, enable_streams)) +		return sd->s_stream_enabled; + +	if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) +		return !!sd->enabled_pads; + +	state = v4l2_subdev_get_locked_active_state(sd); + +	for (unsigned int i = 0; i < state->stream_configs.num_configs; ++i) { +		const struct v4l2_subdev_stream_config *cfg; + +		cfg = &state->stream_configs.configs[i]; + +		if (cfg->enabled) +			return true; +	} + +	return false; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_is_streaming); +  int v4l2_subdev_get_privacy_led(struct v4l2_subdev *sd)  {  #if IS_REACHABLE(CONFIG_LEDS_CLASS) |