| /* |
| * soc-camera generic scaling-cropping manipulation functions |
| * |
| * Copyright (C) 2013 Guennadi Liakhovetski <g.liakhovetski@gmx.de> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/module.h> |
| |
| #include <media/soc_camera.h> |
| #include <media/v4l2-common.h> |
| |
| #include "soc_scale_crop.h" |
| |
| #ifdef DEBUG_GEOMETRY |
| #define dev_geo dev_info |
| #else |
| #define dev_geo dev_dbg |
| #endif |
| |
| /* Check if any dimension of r1 is smaller than respective one of r2 */ |
| static bool is_smaller(const struct v4l2_rect *r1, const struct v4l2_rect *r2) |
| { |
| return r1->width < r2->width || r1->height < r2->height; |
| } |
| |
| /* Check if r1 fails to cover r2 */ |
| static bool is_inside(const struct v4l2_rect *r1, const struct v4l2_rect *r2) |
| { |
| return r1->left > r2->left || r1->top > r2->top || |
| r1->left + r1->width < r2->left + r2->width || |
| r1->top + r1->height < r2->top + r2->height; |
| } |
| |
| /* Get and store current client crop */ |
| int soc_camera_client_g_rect(struct v4l2_subdev *sd, struct v4l2_rect *rect) |
| { |
| struct v4l2_subdev_selection sdsel = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| .target = V4L2_SEL_TGT_CROP, |
| }; |
| int ret; |
| |
| ret = v4l2_subdev_call(sd, pad, get_selection, NULL, &sdsel); |
| if (!ret) { |
| *rect = sdsel.r; |
| return ret; |
| } |
| |
| sdsel.target = V4L2_SEL_TGT_CROP_DEFAULT; |
| ret = v4l2_subdev_call(sd, pad, get_selection, NULL, &sdsel); |
| if (!ret) |
| *rect = sdsel.r; |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(soc_camera_client_g_rect); |
| |
| /* Client crop has changed, update our sub-rectangle to remain within the area */ |
| static void move_and_crop_subrect(struct v4l2_rect *rect, |
| struct v4l2_rect *subrect) |
| { |
| if (rect->width < subrect->width) |
| subrect->width = rect->width; |
| |
| if (rect->height < subrect->height) |
| subrect->height = rect->height; |
| |
| if (rect->left > subrect->left) |
| subrect->left = rect->left; |
| else if (rect->left + rect->width < |
| subrect->left + subrect->width) |
| subrect->left = rect->left + rect->width - |
| subrect->width; |
| |
| if (rect->top > subrect->top) |
| subrect->top = rect->top; |
| else if (rect->top + rect->height < |
| subrect->top + subrect->height) |
| subrect->top = rect->top + rect->height - |
| subrect->height; |
| } |
| |
| /* |
| * The common for both scaling and cropping iterative approach is: |
| * 1. try if the client can produce exactly what requested by the user |
| * 2. if (1) failed, try to double the client image until we get one big enough |
| * 3. if (2) failed, try to request the maximum image |
| */ |
| int soc_camera_client_s_selection(struct v4l2_subdev *sd, |
| struct v4l2_selection *sel, struct v4l2_selection *cam_sel, |
| struct v4l2_rect *target_rect, struct v4l2_rect *subrect) |
| { |
| struct v4l2_subdev_selection sdsel = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| .target = sel->target, |
| .flags = sel->flags, |
| .r = sel->r, |
| }; |
| struct v4l2_subdev_selection bounds = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| .target = V4L2_SEL_TGT_CROP_BOUNDS, |
| }; |
| struct v4l2_rect *rect = &sel->r, *cam_rect = &cam_sel->r; |
| struct device *dev = sd->v4l2_dev->dev; |
| int ret; |
| unsigned int width, height; |
| |
| v4l2_subdev_call(sd, pad, set_selection, NULL, &sdsel); |
| sel->r = sdsel.r; |
| ret = soc_camera_client_g_rect(sd, cam_rect); |
| if (ret < 0) |
| return ret; |
| |
| /* |
| * Now cam_crop contains the current camera input rectangle, and it must |
| * be within camera cropcap bounds |
| */ |
| if (!memcmp(rect, cam_rect, sizeof(*rect))) { |
| /* Even if camera S_SELECTION failed, but camera rectangle matches */ |
| dev_dbg(dev, "Camera S_SELECTION successful for %dx%d@%d:%d\n", |
| rect->width, rect->height, rect->left, rect->top); |
| *target_rect = *cam_rect; |
| return 0; |
| } |
| |
| /* Try to fix cropping, that camera hasn't managed to set */ |
| dev_geo(dev, "Fix camera S_SELECTION for %dx%d@%d:%d to %dx%d@%d:%d\n", |
| cam_rect->width, cam_rect->height, |
| cam_rect->left, cam_rect->top, |
| rect->width, rect->height, rect->left, rect->top); |
| |
| /* We need sensor maximum rectangle */ |
| ret = v4l2_subdev_call(sd, pad, get_selection, NULL, &bounds); |
| if (ret < 0) |
| return ret; |
| |
| /* Put user requested rectangle within sensor bounds */ |
| soc_camera_limit_side(&rect->left, &rect->width, sdsel.r.left, 2, |
| bounds.r.width); |
| soc_camera_limit_side(&rect->top, &rect->height, sdsel.r.top, 4, |
| bounds.r.height); |
| |
| /* |
| * Popular special case - some cameras can only handle fixed sizes like |
| * QVGA, VGA,... Take care to avoid infinite loop. |
| */ |
| width = max_t(unsigned int, cam_rect->width, 2); |
| height = max_t(unsigned int, cam_rect->height, 2); |
| |
| /* |
| * Loop as long as sensor is not covering the requested rectangle and |
| * is still within its bounds |
| */ |
| while (!ret && (is_smaller(cam_rect, rect) || |
| is_inside(cam_rect, rect)) && |
| (bounds.r.width > width || bounds.r.height > height)) { |
| |
| width *= 2; |
| height *= 2; |
| |
| cam_rect->width = width; |
| cam_rect->height = height; |
| |
| /* |
| * We do not know what capabilities the camera has to set up |
| * left and top borders. We could try to be smarter in iterating |
| * them, e.g., if camera current left is to the right of the |
| * target left, set it to the middle point between the current |
| * left and minimum left. But that would add too much |
| * complexity: we would have to iterate each border separately. |
| * Instead we just drop to the left and top bounds. |
| */ |
| if (cam_rect->left > rect->left) |
| cam_rect->left = bounds.r.left; |
| |
| if (cam_rect->left + cam_rect->width < rect->left + rect->width) |
| cam_rect->width = rect->left + rect->width - |
| cam_rect->left; |
| |
| if (cam_rect->top > rect->top) |
| cam_rect->top = bounds.r.top; |
| |
| if (cam_rect->top + cam_rect->height < rect->top + rect->height) |
| cam_rect->height = rect->top + rect->height - |
| cam_rect->top; |
| |
| sdsel.r = *cam_rect; |
| v4l2_subdev_call(sd, pad, set_selection, NULL, &sdsel); |
| *cam_rect = sdsel.r; |
| ret = soc_camera_client_g_rect(sd, cam_rect); |
| dev_geo(dev, "Camera S_SELECTION %d for %dx%d@%d:%d\n", ret, |
| cam_rect->width, cam_rect->height, |
| cam_rect->left, cam_rect->top); |
| } |
| |
| /* S_SELECTION must not modify the rectangle */ |
| if (is_smaller(cam_rect, rect) || is_inside(cam_rect, rect)) { |
| /* |
| * The camera failed to configure a suitable cropping, |
| * we cannot use the current rectangle, set to max |
| */ |
| sdsel.r = bounds.r; |
| v4l2_subdev_call(sd, pad, set_selection, NULL, &sdsel); |
| *cam_rect = sdsel.r; |
| |
| ret = soc_camera_client_g_rect(sd, cam_rect); |
| dev_geo(dev, "Camera S_SELECTION %d for max %dx%d@%d:%d\n", ret, |
| cam_rect->width, cam_rect->height, |
| cam_rect->left, cam_rect->top); |
| } |
| |
| if (!ret) { |
| *target_rect = *cam_rect; |
| move_and_crop_subrect(target_rect, subrect); |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(soc_camera_client_s_selection); |
| |
| /* Iterative set_fmt, also updates cached client crop on success */ |
| static int client_set_fmt(struct soc_camera_device *icd, |
| struct v4l2_rect *rect, struct v4l2_rect *subrect, |
| unsigned int max_width, unsigned int max_height, |
| struct v4l2_subdev_format *format, bool host_can_scale) |
| { |
| struct v4l2_subdev *sd = soc_camera_to_subdev(icd); |
| struct device *dev = icd->parent; |
| struct v4l2_mbus_framefmt *mf = &format->format; |
| unsigned int width = mf->width, height = mf->height, tmp_w, tmp_h; |
| struct v4l2_subdev_selection sdsel = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| .target = V4L2_SEL_TGT_CROP_BOUNDS, |
| }; |
| bool host_1to1; |
| int ret; |
| |
| ret = v4l2_device_call_until_err(sd->v4l2_dev, |
| soc_camera_grp_id(icd), pad, |
| set_fmt, NULL, format); |
| if (ret < 0) |
| return ret; |
| |
| dev_geo(dev, "camera scaled to %ux%u\n", mf->width, mf->height); |
| |
| if (width == mf->width && height == mf->height) { |
| /* Perfect! The client has done it all. */ |
| host_1to1 = true; |
| goto update_cache; |
| } |
| |
| host_1to1 = false; |
| if (!host_can_scale) |
| goto update_cache; |
| |
| ret = v4l2_subdev_call(sd, pad, get_selection, NULL, &sdsel); |
| if (ret < 0) |
| return ret; |
| |
| if (max_width > sdsel.r.width) |
| max_width = sdsel.r.width; |
| if (max_height > sdsel.r.height) |
| max_height = sdsel.r.height; |
| |
| /* Camera set a format, but geometry is not precise, try to improve */ |
| tmp_w = mf->width; |
| tmp_h = mf->height; |
| |
| /* width <= max_width && height <= max_height - guaranteed by try_fmt */ |
| while ((width > tmp_w || height > tmp_h) && |
| tmp_w < max_width && tmp_h < max_height) { |
| tmp_w = min(2 * tmp_w, max_width); |
| tmp_h = min(2 * tmp_h, max_height); |
| mf->width = tmp_w; |
| mf->height = tmp_h; |
| ret = v4l2_device_call_until_err(sd->v4l2_dev, |
| soc_camera_grp_id(icd), pad, |
| set_fmt, NULL, format); |
| dev_geo(dev, "Camera scaled to %ux%u\n", |
| mf->width, mf->height); |
| if (ret < 0) { |
| /* This shouldn't happen */ |
| dev_err(dev, "Client failed to set format: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| update_cache: |
| /* Update cache */ |
| ret = soc_camera_client_g_rect(sd, rect); |
| if (ret < 0) |
| return ret; |
| |
| if (host_1to1) |
| *subrect = *rect; |
| else |
| move_and_crop_subrect(rect, subrect); |
| |
| return 0; |
| } |
| |
| /** |
| * soc_camera_client_scale |
| * @icd: soc-camera device |
| * @rect: camera cropping window |
| * @subrect: part of rect, sent to the user |
| * @mf: in- / output camera output window |
| * @width: on input: max host input width; |
| * on output: user width, mapped back to input |
| * @height: on input: max host input height; |
| * on output: user height, mapped back to input |
| * @host_can_scale: host can scale this pixel format |
| * @shift: shift, used for scaling |
| */ |
| int soc_camera_client_scale(struct soc_camera_device *icd, |
| struct v4l2_rect *rect, struct v4l2_rect *subrect, |
| struct v4l2_mbus_framefmt *mf, |
| unsigned int *width, unsigned int *height, |
| bool host_can_scale, unsigned int shift) |
| { |
| struct device *dev = icd->parent; |
| struct v4l2_subdev_format fmt_tmp = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| .format = *mf, |
| }; |
| struct v4l2_mbus_framefmt *mf_tmp = &fmt_tmp.format; |
| unsigned int scale_h, scale_v; |
| int ret; |
| |
| /* |
| * 5. Apply iterative camera S_FMT for camera user window (also updates |
| * client crop cache and the imaginary sub-rectangle). |
| */ |
| ret = client_set_fmt(icd, rect, subrect, *width, *height, |
| &fmt_tmp, host_can_scale); |
| if (ret < 0) |
| return ret; |
| |
| dev_geo(dev, "5: camera scaled to %ux%u\n", |
| mf_tmp->width, mf_tmp->height); |
| |
| /* 6. Retrieve camera output window (g_fmt) */ |
| |
| /* unneeded - it is already in "mf_tmp" */ |
| |
| /* 7. Calculate new client scales. */ |
| scale_h = soc_camera_calc_scale(rect->width, shift, mf_tmp->width); |
| scale_v = soc_camera_calc_scale(rect->height, shift, mf_tmp->height); |
| |
| mf->width = mf_tmp->width; |
| mf->height = mf_tmp->height; |
| mf->colorspace = mf_tmp->colorspace; |
| |
| /* |
| * 8. Calculate new host crop - apply camera scales to previously |
| * updated "effective" crop. |
| */ |
| *width = soc_camera_shift_scale(subrect->width, shift, scale_h); |
| *height = soc_camera_shift_scale(subrect->height, shift, scale_v); |
| |
| dev_geo(dev, "8: new client sub-window %ux%u\n", *width, *height); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(soc_camera_client_scale); |
| |
| /* |
| * Calculate real client output window by applying new scales to the current |
| * client crop. New scales are calculated from the requested output format and |
| * host crop, mapped backed onto the client input (subrect). |
| */ |
| void soc_camera_calc_client_output(struct soc_camera_device *icd, |
| struct v4l2_rect *rect, struct v4l2_rect *subrect, |
| const struct v4l2_pix_format *pix, struct v4l2_mbus_framefmt *mf, |
| unsigned int shift) |
| { |
| struct device *dev = icd->parent; |
| unsigned int scale_v, scale_h; |
| |
| if (subrect->width == rect->width && |
| subrect->height == rect->height) { |
| /* No sub-cropping */ |
| mf->width = pix->width; |
| mf->height = pix->height; |
| return; |
| } |
| |
| /* 1.-2. Current camera scales and subwin - cached. */ |
| |
| dev_geo(dev, "2: subwin %ux%u@%u:%u\n", |
| subrect->width, subrect->height, |
| subrect->left, subrect->top); |
| |
| /* |
| * 3. Calculate new combined scales from input sub-window to requested |
| * user window. |
| */ |
| |
| /* |
| * TODO: CEU cannot scale images larger than VGA to smaller than SubQCIF |
| * (128x96) or larger than VGA. This and similar limitations have to be |
| * taken into account here. |
| */ |
| scale_h = soc_camera_calc_scale(subrect->width, shift, pix->width); |
| scale_v = soc_camera_calc_scale(subrect->height, shift, pix->height); |
| |
| dev_geo(dev, "3: scales %u:%u\n", scale_h, scale_v); |
| |
| /* |
| * 4. Calculate desired client output window by applying combined scales |
| * to client (real) input window. |
| */ |
| mf->width = soc_camera_shift_scale(rect->width, shift, scale_h); |
| mf->height = soc_camera_shift_scale(rect->height, shift, scale_v); |
| } |
| EXPORT_SYMBOL(soc_camera_calc_client_output); |
| |
| MODULE_DESCRIPTION("soc-camera scaling-cropping functions"); |
| MODULE_AUTHOR("Guennadi Liakhovetski <kernel@pengutronix.de>"); |
| MODULE_LICENSE("GPL"); |