Message ID | 20210918150507.987294-2-jacopo+renesas@jmondi.org (mailing list archive) |
---|---|
State | New |
Delegated to: | Kieran Bingham |
Headers | show |
Series | media: Add multiplexed support to R-Car and GMSL | expand |
Hi Jacopo, Thank you for the patch. On Sat, Sep 18, 2021 at 05:05:03PM +0200, Jacopo Mondi wrote: > Implement support for multiplexed streams to the max9286 driver. > > Add support for the init_cfg, get_frame_desc and set_routing operations > and move format handling to the subdevice state. It may make sense to split part of this out (namely the switch to .init_cfg() and the move to subdevice state, either as separate patches or grouped together). > Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org> > --- > drivers/media/i2c/max9286.c | 235 ++++++++++++++++++++++++------------ > 1 file changed, 160 insertions(+), 75 deletions(-) > > diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c > index 1b92d18a1f94..baff86a4dcec 100644 > --- a/drivers/media/i2c/max9286.c > +++ b/drivers/media/i2c/max9286.c > @@ -476,6 +476,18 @@ static int max9286_check_config_link(struct max9286_priv *priv, > return 0; > } > > +static void max9286_init_format(struct v4l2_mbus_framefmt *fmt) > +{ > + fmt->width = 1280; > + fmt->height = 800; > + fmt->code = MEDIA_BUS_FMT_UYVY8_1X16; > + fmt->colorspace = V4L2_COLORSPACE_SRGB; > + fmt->field = V4L2_FIELD_NONE; > + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; > + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; > + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; > +} > + > /* ----------------------------------------------------------------------------- > * V4L2 Subdev > */ > @@ -745,28 +757,18 @@ static int max9286_enum_mbus_code(struct v4l2_subdev *sd, > return 0; > } > > -static struct v4l2_mbus_framefmt * > -max9286_get_pad_format(struct max9286_priv *priv, > - struct v4l2_subdev_state *sd_state, > - unsigned int pad, u32 which) > -{ > - switch (which) { > - case V4L2_SUBDEV_FORMAT_TRY: > - return v4l2_subdev_get_try_format(&priv->sd, sd_state, pad); > - case V4L2_SUBDEV_FORMAT_ACTIVE: > - return &priv->fmt[pad]; > - default: > - return NULL; > - } > -} > - > static int max9286_set_fmt(struct v4l2_subdev *sd, > struct v4l2_subdev_state *sd_state, > struct v4l2_subdev_format *format) > { > - struct max9286_priv *priv = sd_to_max9286(sd); > - struct v4l2_mbus_framefmt *cfg_fmt; > + struct v4l2_mbus_framefmt *fmt; > + struct v4l2_subdev_state *state; > + int ret = 0; > > + /* > + * Refuse to set format on the multiplexed source pad. > + * Format is propagated from sinks streams to source streams. > + */ > if (format->pad == MAX9286_SRC_PAD) > return -EINVAL; > > @@ -782,44 +784,143 @@ static int max9286_set_fmt(struct v4l2_subdev *sd, > break; > } > > - cfg_fmt = max9286_get_pad_format(priv, sd_state, format->pad, > - format->which); > - if (!cfg_fmt) > + state = v4l2_subdev_validate_and_lock_state(sd, sd_state); I really want RAII semantics... __attribute__((__cleanup__)) would give us that. It's used in two places in the kernel (lib/locking-selftest.c and include/linux/kcsan-checks.h), but I'm not sure it's supported by all compilers (namely ICC) :-S > + fmt = v4l2_state_get_stream_format(state, format->pad, > + format->stream); > + if (!fmt) { > + ret = -EINVAL; > + goto out; > + } > + *fmt = format->format; > + > + /* Propagate format to the other end of the route. */ > + fmt = v4l2_state_get_opposite_stream_format(state, format->pad, > + format->stream); > + if (!fmt) { > + ret = -EINVAL; > + goto out; > + } > + *fmt = format->format; > + > +out: > + v4l2_subdev_unlock_state(state); > + > + return ret; > +} > + > +static int max9286_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, > + struct v4l2_mbus_frame_desc *fd) > +{ > + const struct v4l2_subdev_krouting *routing; > + struct v4l2_subdev_state *state; > + unsigned int i; > + int ret = 0; > + > + if (pad != MAX9286_SRC_PAD) > return -EINVAL; > > - mutex_lock(&priv->mutex); > - *cfg_fmt = format->format; > - mutex_unlock(&priv->mutex); > + state = v4l2_subdev_lock_active_state(sd); > + routing = &state->routing; > > - return 0; > + /* One stream per each connected route. */ > + memset(fd, 0, sizeof(*fd)); Should the memset() be done by the caller ? > + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; > + > + for (i = 0; i < routing->num_routes; ++i) { > + const struct v4l2_subdev_route *route = &routing->routes[i]; > + unsigned int idx = fd->num_entries; > + struct v4l2_mbus_framefmt *fmt; > + > + if (!(route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) > + continue; Do we need a for_each_active_route(routing, route) macro ? > + > + if (route->source_pad != pad) > + continue; As we have a single source pad, is this possible ? > + > + fmt = v4l2_state_get_stream_format(state, pad, > + route->source_stream); > + if (!fmt) { > + ret = -EINVAL; > + goto out; > + } > + > + /* > + * Assume a YUYV format (0x1e DT) and 16 bpp: we only support > + * these formats at the moment. > + */ I would have defined a struct v4l2_mbus_frame_desc_entry *entry; with entry = &fd->entry[fd->num_entries]; here. > + fd->entry[idx].stream = idx; > + fd->entry[idx].flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; > + fd->entry[idx].length = fmt->width * fmt->height * 16 / 8; > + fd->entry[idx].pixelcode = fmt->code; > + > + fd->entry[idx].bus.csi2.vc = route->source_stream; A comment to explain that we hardcode the VC of each input to the input number would be useful. > + fd->entry[idx].bus.csi2.dt = 0x1e; We should define macros for the CSI-2 data types in a common header in include/media/. > + > + fd->num_entries++; > + } > + > +out: > + v4l2_subdev_unlock_state(state); > + > + return ret; > } > > -static int max9286_get_fmt(struct v4l2_subdev *sd, > - struct v4l2_subdev_state *sd_state, > - struct v4l2_subdev_format *format) > +static int max9286_set_routing(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, > + struct v4l2_subdev_krouting *routing) > { > - struct max9286_priv *priv = sd_to_max9286(sd); > - struct v4l2_mbus_framefmt *cfg_fmt; > - unsigned int pad = format->pad; > + struct v4l2_mbus_framefmt format; > + int ret; > > /* > - * Multiplexed Stream Support: Support link validation by returning the > - * format of the first bound link. All links must have the same format, > - * as we do not support mixing and matching of cameras connected to the > - * max9286. > + * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until > + * frame desc is made dynamically allocated. > */ > - if (pad == MAX9286_SRC_PAD) > - pad = __ffs(priv->bound_sources); > > - cfg_fmt = max9286_get_pad_format(priv, sd_state, pad, format->which); > - if (!cfg_fmt) > + if (routing->num_routes >= V4L2_FRAME_DESC_ENTRY_MAX) > return -EINVAL; Where does this limitation come from ? It doesn't seem to originate from the max9286 driver itself. If it's related to one of the helpers below, maybe that's where it should be checked ? > > - mutex_lock(&priv->mutex); > - format->format = *cfg_fmt; > - mutex_unlock(&priv->mutex); > + ret = v4l2_routing_simple_verify(routing); > + if (ret) > + return ret; > > - return 0; > + state = v4l2_subdev_validate_and_lock_state(sd, state); > + > + max9286_init_format(&format); You could replace this with a static const v4l2_mbus_framefmt variable that contains the default format. > + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); > + > + v4l2_subdev_unlock_state(state); > + > + return ret; > +} > + > +static int max9286_init_cfg(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state) > +{ > + struct v4l2_subdev_route routes[MAX9286_NUM_GMSL]; > + struct max9286_priv *priv = sd_to_max9286(sd); > + struct v4l2_subdev_krouting routing; > + struct max9286_source *source; > + unsigned int num_routes = 0; > + u32 which = state->which; > + > + /* Create a route for each enable source. */ > + for_each_source(priv, source) { > + struct v4l2_subdev_route *route = &routes[num_routes++]; > + unsigned int idx = to_index(priv, source); > + > + route->sink_pad = idx; > + route->sink_stream = 0; > + route->source_pad = MAX9286_SRC_PAD; > + route->source_stream = idx; > + route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; > + } > + > + routing.which = which; > + routing.num_routes = num_routes; > + routing.routes = routes; > + > + return max9286_set_routing(sd, state, &routing); > } > > static const struct v4l2_subdev_video_ops max9286_video_ops = { > @@ -827,9 +928,12 @@ static const struct v4l2_subdev_video_ops max9286_video_ops = { > }; > > static const struct v4l2_subdev_pad_ops max9286_pad_ops = { > + .init_cfg = max9286_init_cfg, > .enum_mbus_code = max9286_enum_mbus_code, > - .get_fmt = max9286_get_fmt, > + .get_fmt = v4l2_subdev_get_fmt, That's so nice :-) > .set_fmt = max9286_set_fmt, > + .get_frame_desc = max9286_get_frame_desc, > + .set_routing = max9286_set_routing, > }; > > static const struct v4l2_subdev_ops max9286_subdev_ops = { > @@ -837,35 +941,6 @@ static const struct v4l2_subdev_ops max9286_subdev_ops = { > .pad = &max9286_pad_ops, > }; > > -static void max9286_init_format(struct v4l2_mbus_framefmt *fmt) > -{ > - fmt->width = 1280; > - fmt->height = 800; > - fmt->code = MEDIA_BUS_FMT_UYVY8_1X16; > - fmt->colorspace = V4L2_COLORSPACE_SRGB; > - fmt->field = V4L2_FIELD_NONE; > - fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; > - fmt->quantization = V4L2_QUANTIZATION_DEFAULT; > - fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; > -} > - > -static int max9286_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) > -{ > - struct v4l2_mbus_framefmt *format; > - unsigned int i; > - > - for (i = 0; i < MAX9286_N_SINKS; i++) { > - format = v4l2_subdev_get_try_format(subdev, fh->state, i); > - max9286_init_format(format); > - } > - > - return 0; > -} > - > -static const struct v4l2_subdev_internal_ops max9286_subdev_internal_ops = { > - .open = max9286_open, > -}; > - > static int max9286_s_ctrl(struct v4l2_ctrl *ctrl) > { > switch (ctrl->id) { > @@ -900,8 +975,8 @@ static int max9286_v4l2_register(struct max9286_priv *priv) > max9286_init_format(&priv->fmt[i]); As far as I can tell, priv->fmt isn't used anymore, so you can drop it, as well as the code above. The MAX9286_N_SINKS macro can also be dropped. > > v4l2_i2c_subdev_init(&priv->sd, priv->client, &max9286_subdev_ops); > - priv->sd.internal_ops = &max9286_subdev_internal_ops; > - priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | > + V4L2_SUBDEV_FL_MULTIPLEXED; > > v4l2_ctrl_handler_init(&priv->ctrls, 1); > priv->pixelrate = v4l2_ctrl_new_std(&priv->ctrls, > @@ -933,14 +1008,23 @@ static int max9286_v4l2_register(struct max9286_priv *priv) > } > priv->sd.fwnode = ep; > > + ret = v4l2_subdev_alloc_state(&priv->sd); > + if (ret) { > + dev_err(dev, "Unable to allocate subdevice state/\n"); > + ret = -ENOENT; > + goto err_put_node; > + } > + > ret = v4l2_async_register_subdev(&priv->sd); > if (ret < 0) { > dev_err(dev, "Unable to register subdevice\n"); > - goto err_put_node; > + goto err_free_state; > } > > return 0; > > +err_free_state: > + v4l2_subdev_free_state(&priv->sd); > err_put_node: > fwnode_handle_put(ep); > err_async: > @@ -952,6 +1036,7 @@ static int max9286_v4l2_register(struct max9286_priv *priv) > static void max9286_v4l2_unregister(struct max9286_priv *priv) > { > fwnode_handle_put(priv->sd.fwnode); > + v4l2_subdev_free_state(&priv->sd); > v4l2_async_unregister_subdev(&priv->sd); > max9286_v4l2_notifier_unregister(priv); > }
diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c index 1b92d18a1f94..baff86a4dcec 100644 --- a/drivers/media/i2c/max9286.c +++ b/drivers/media/i2c/max9286.c @@ -476,6 +476,18 @@ static int max9286_check_config_link(struct max9286_priv *priv, return 0; } +static void max9286_init_format(struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = 1280; + fmt->height = 800; + fmt->code = MEDIA_BUS_FMT_UYVY8_1X16; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->field = V4L2_FIELD_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + /* ----------------------------------------------------------------------------- * V4L2 Subdev */ @@ -745,28 +757,18 @@ static int max9286_enum_mbus_code(struct v4l2_subdev *sd, return 0; } -static struct v4l2_mbus_framefmt * -max9286_get_pad_format(struct max9286_priv *priv, - struct v4l2_subdev_state *sd_state, - unsigned int pad, u32 which) -{ - switch (which) { - case V4L2_SUBDEV_FORMAT_TRY: - return v4l2_subdev_get_try_format(&priv->sd, sd_state, pad); - case V4L2_SUBDEV_FORMAT_ACTIVE: - return &priv->fmt[pad]; - default: - return NULL; - } -} - static int max9286_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { - struct max9286_priv *priv = sd_to_max9286(sd); - struct v4l2_mbus_framefmt *cfg_fmt; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_state *state; + int ret = 0; + /* + * Refuse to set format on the multiplexed source pad. + * Format is propagated from sinks streams to source streams. + */ if (format->pad == MAX9286_SRC_PAD) return -EINVAL; @@ -782,44 +784,143 @@ static int max9286_set_fmt(struct v4l2_subdev *sd, break; } - cfg_fmt = max9286_get_pad_format(priv, sd_state, format->pad, - format->which); - if (!cfg_fmt) + state = v4l2_subdev_validate_and_lock_state(sd, sd_state); + fmt = v4l2_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) { + ret = -EINVAL; + goto out; + } + *fmt = format->format; + + /* Propagate format to the other end of the route. */ + fmt = v4l2_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) { + ret = -EINVAL; + goto out; + } + *fmt = format->format; + +out: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int max9286_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + const struct v4l2_subdev_krouting *routing; + struct v4l2_subdev_state *state; + unsigned int i; + int ret = 0; + + if (pad != MAX9286_SRC_PAD) return -EINVAL; - mutex_lock(&priv->mutex); - *cfg_fmt = format->format; - mutex_unlock(&priv->mutex); + state = v4l2_subdev_lock_active_state(sd); + routing = &state->routing; - return 0; + /* One stream per each connected route. */ + memset(fd, 0, sizeof(*fd)); + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + for (i = 0; i < routing->num_routes; ++i) { + const struct v4l2_subdev_route *route = &routing->routes[i]; + unsigned int idx = fd->num_entries; + struct v4l2_mbus_framefmt *fmt; + + if (!(route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) + continue; + + if (route->source_pad != pad) + continue; + + fmt = v4l2_state_get_stream_format(state, pad, + route->source_stream); + if (!fmt) { + ret = -EINVAL; + goto out; + } + + /* + * Assume a YUYV format (0x1e DT) and 16 bpp: we only support + * these formats at the moment. + */ + fd->entry[idx].stream = idx; + fd->entry[idx].flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd->entry[idx].length = fmt->width * fmt->height * 16 / 8; + fd->entry[idx].pixelcode = fmt->code; + + fd->entry[idx].bus.csi2.vc = route->source_stream; + fd->entry[idx].bus.csi2.dt = 0x1e; + + fd->num_entries++; + } + +out: + v4l2_subdev_unlock_state(state); + + return ret; } -static int max9286_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *format) +static int max9286_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) { - struct max9286_priv *priv = sd_to_max9286(sd); - struct v4l2_mbus_framefmt *cfg_fmt; - unsigned int pad = format->pad; + struct v4l2_mbus_framefmt format; + int ret; /* - * Multiplexed Stream Support: Support link validation by returning the - * format of the first bound link. All links must have the same format, - * as we do not support mixing and matching of cameras connected to the - * max9286. + * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until + * frame desc is made dynamically allocated. */ - if (pad == MAX9286_SRC_PAD) - pad = __ffs(priv->bound_sources); - cfg_fmt = max9286_get_pad_format(priv, sd_state, pad, format->which); - if (!cfg_fmt) + if (routing->num_routes >= V4L2_FRAME_DESC_ENTRY_MAX) return -EINVAL; - mutex_lock(&priv->mutex); - format->format = *cfg_fmt; - mutex_unlock(&priv->mutex); + ret = v4l2_routing_simple_verify(routing); + if (ret) + return ret; - return 0; + state = v4l2_subdev_validate_and_lock_state(sd, state); + + max9286_init_format(&format); + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int max9286_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[MAX9286_NUM_GMSL]; + struct max9286_priv *priv = sd_to_max9286(sd); + struct v4l2_subdev_krouting routing; + struct max9286_source *source; + unsigned int num_routes = 0; + u32 which = state->which; + + /* Create a route for each enable source. */ + for_each_source(priv, source) { + struct v4l2_subdev_route *route = &routes[num_routes++]; + unsigned int idx = to_index(priv, source); + + route->sink_pad = idx; + route->sink_stream = 0; + route->source_pad = MAX9286_SRC_PAD; + route->source_stream = idx; + route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + } + + routing.which = which; + routing.num_routes = num_routes; + routing.routes = routes; + + return max9286_set_routing(sd, state, &routing); } static const struct v4l2_subdev_video_ops max9286_video_ops = { @@ -827,9 +928,12 @@ static const struct v4l2_subdev_video_ops max9286_video_ops = { }; static const struct v4l2_subdev_pad_ops max9286_pad_ops = { + .init_cfg = max9286_init_cfg, .enum_mbus_code = max9286_enum_mbus_code, - .get_fmt = max9286_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = max9286_set_fmt, + .get_frame_desc = max9286_get_frame_desc, + .set_routing = max9286_set_routing, }; static const struct v4l2_subdev_ops max9286_subdev_ops = { @@ -837,35 +941,6 @@ static const struct v4l2_subdev_ops max9286_subdev_ops = { .pad = &max9286_pad_ops, }; -static void max9286_init_format(struct v4l2_mbus_framefmt *fmt) -{ - fmt->width = 1280; - fmt->height = 800; - fmt->code = MEDIA_BUS_FMT_UYVY8_1X16; - fmt->colorspace = V4L2_COLORSPACE_SRGB; - fmt->field = V4L2_FIELD_NONE; - fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; - fmt->quantization = V4L2_QUANTIZATION_DEFAULT; - fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; -} - -static int max9286_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) -{ - struct v4l2_mbus_framefmt *format; - unsigned int i; - - for (i = 0; i < MAX9286_N_SINKS; i++) { - format = v4l2_subdev_get_try_format(subdev, fh->state, i); - max9286_init_format(format); - } - - return 0; -} - -static const struct v4l2_subdev_internal_ops max9286_subdev_internal_ops = { - .open = max9286_open, -}; - static int max9286_s_ctrl(struct v4l2_ctrl *ctrl) { switch (ctrl->id) { @@ -900,8 +975,8 @@ static int max9286_v4l2_register(struct max9286_priv *priv) max9286_init_format(&priv->fmt[i]); v4l2_i2c_subdev_init(&priv->sd, priv->client, &max9286_subdev_ops); - priv->sd.internal_ops = &max9286_subdev_internal_ops; - priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_MULTIPLEXED; v4l2_ctrl_handler_init(&priv->ctrls, 1); priv->pixelrate = v4l2_ctrl_new_std(&priv->ctrls, @@ -933,14 +1008,23 @@ static int max9286_v4l2_register(struct max9286_priv *priv) } priv->sd.fwnode = ep; + ret = v4l2_subdev_alloc_state(&priv->sd); + if (ret) { + dev_err(dev, "Unable to allocate subdevice state/\n"); + ret = -ENOENT; + goto err_put_node; + } + ret = v4l2_async_register_subdev(&priv->sd); if (ret < 0) { dev_err(dev, "Unable to register subdevice\n"); - goto err_put_node; + goto err_free_state; } return 0; +err_free_state: + v4l2_subdev_free_state(&priv->sd); err_put_node: fwnode_handle_put(ep); err_async: @@ -952,6 +1036,7 @@ static int max9286_v4l2_register(struct max9286_priv *priv) static void max9286_v4l2_unregister(struct max9286_priv *priv) { fwnode_handle_put(priv->sd.fwnode); + v4l2_subdev_free_state(&priv->sd); v4l2_async_unregister_subdev(&priv->sd); max9286_v4l2_notifier_unregister(priv); }
Implement support for multiplexed streams to the max9286 driver. Add support for the init_cfg, get_frame_desc and set_routing operations and move format handling to the subdevice state. Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org> --- drivers/media/i2c/max9286.c | 235 ++++++++++++++++++++++++------------ 1 file changed, 160 insertions(+), 75 deletions(-)