Merge branch 'core' of git://amd64.org/linux/rric into perf/core
[pandora-kernel.git] / drivers / media / video / ov9740.c
index 72c6ac1..edd1ffc 100644 (file)
 #define OV9740_MIPI_CTRL_3012          0x3012
 #define OV9740_SC_CMMM_MIPI_CTR                0x3014
 
-/* supported resolutions */
-enum {
-       OV9740_VGA,
-       OV9740_720P,
-};
-
-struct ov9740_resolution {
-       unsigned int width;
-       unsigned int height;
-};
-
-static struct ov9740_resolution ov9740_resolutions[] = {
-       [OV9740_VGA] = {
-               .width  = 640,
-               .height = 480,
-       },
-       [OV9740_720P] = {
-               .width  = 1280,
-               .height = 720,
-       },
-};
+#define OV9740_MAX_WIDTH               1280
+#define OV9740_MAX_HEIGHT              720
 
 /* Misc. structures */
 struct ov9740_reg {
@@ -220,6 +201,10 @@ struct ov9740_priv {
 
        bool                            flag_vflip;
        bool                            flag_hflip;
+
+       /* For suspend/resume. */
+       struct v4l2_mbus_framefmt       current_mf;
+       bool                            current_enable;
 };
 
 static const struct ov9740_reg ov9740_defaults[] = {
@@ -403,54 +388,6 @@ static const struct ov9740_reg ov9740_defaults[] = {
        { OV9740_ISP_CTRL19,            0x02 },
 };
 
-static const struct ov9740_reg ov9740_regs_vga[] = {
-       { OV9740_X_ADDR_START_HI,       0x00 },
-       { OV9740_X_ADDR_START_LO,       0xa0 },
-       { OV9740_Y_ADDR_START_HI,       0x00 },
-       { OV9740_Y_ADDR_START_LO,       0x00 },
-       { OV9740_X_ADDR_END_HI,         0x04 },
-       { OV9740_X_ADDR_END_LO,         0x63 },
-       { OV9740_Y_ADDR_END_HI,         0x02 },
-       { OV9740_Y_ADDR_END_LO,         0xd3 },
-       { OV9740_X_OUTPUT_SIZE_HI,      0x02 },
-       { OV9740_X_OUTPUT_SIZE_LO,      0x80 },
-       { OV9740_Y_OUTPUT_SIZE_HI,      0x01 },
-       { OV9740_Y_OUTPUT_SIZE_LO,      0xe0 },
-       { OV9740_ISP_CTRL1E,            0x03 },
-       { OV9740_ISP_CTRL1F,            0xc0 },
-       { OV9740_ISP_CTRL20,            0x02 },
-       { OV9740_ISP_CTRL21,            0xd0 },
-       { OV9740_VFIFO_READ_START_HI,   0x01 },
-       { OV9740_VFIFO_READ_START_LO,   0x40 },
-       { OV9740_ISP_CTRL00,            0xff },
-       { OV9740_ISP_CTRL01,            0xff },
-       { OV9740_ISP_CTRL03,            0xff },
-};
-
-static const struct ov9740_reg ov9740_regs_720p[] = {
-       { OV9740_X_ADDR_START_HI,       0x00 },
-       { OV9740_X_ADDR_START_LO,       0x00 },
-       { OV9740_Y_ADDR_START_HI,       0x00 },
-       { OV9740_Y_ADDR_START_LO,       0x00 },
-       { OV9740_X_ADDR_END_HI,         0x05 },
-       { OV9740_X_ADDR_END_LO,         0x03 },
-       { OV9740_Y_ADDR_END_HI,         0x02 },
-       { OV9740_Y_ADDR_END_LO,         0xd3 },
-       { OV9740_X_OUTPUT_SIZE_HI,      0x05 },
-       { OV9740_X_OUTPUT_SIZE_LO,      0x00 },
-       { OV9740_Y_OUTPUT_SIZE_HI,      0x02 },
-       { OV9740_Y_OUTPUT_SIZE_LO,      0xd0 },
-       { OV9740_ISP_CTRL1E,            0x05 },
-       { OV9740_ISP_CTRL1F,            0x00 },
-       { OV9740_ISP_CTRL20,            0x02 },
-       { OV9740_ISP_CTRL21,            0xd0 },
-       { OV9740_VFIFO_READ_START_HI,   0x02 },
-       { OV9740_VFIFO_READ_START_LO,   0x30 },
-       { OV9740_ISP_CTRL00,            0xff },
-       { OV9740_ISP_CTRL01,            0xef },
-       { OV9740_ISP_CTRL03,            0xff },
-};
-
 static enum v4l2_mbus_pixelcode ov9740_codes[] = {
        V4L2_MBUS_FMT_YUYV8_2X8,
 };
@@ -618,6 +555,8 @@ static int ov9740_s_stream(struct v4l2_subdev *sd, int enable)
                                               0x00);
        }
 
+       priv->current_enable = enable;
+
        return ret;
 }
 
@@ -640,126 +579,127 @@ static unsigned long ov9740_query_bus_param(struct soc_camera_device *icd)
        return soc_camera_apply_sensor_flags(icl, flags);
 }
 
-/* Get status of additional camera capabilities */
-static int ov9740_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
-{
-       struct ov9740_priv *priv = to_ov9740(sd);
-
-       switch (ctrl->id) {
-       case V4L2_CID_VFLIP:
-               ctrl->value = priv->flag_vflip;
-               break;
-       case V4L2_CID_HFLIP:
-               ctrl->value = priv->flag_hflip;
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       return 0;
-}
-
-/* Set status of additional camera capabilities */
-static int ov9740_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
-{
-       struct ov9740_priv *priv = to_ov9740(sd);
-
-       switch (ctrl->id) {
-       case V4L2_CID_VFLIP:
-               priv->flag_vflip = ctrl->value;
-               break;
-       case V4L2_CID_HFLIP:
-               priv->flag_hflip = ctrl->value;
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       return 0;
-}
-
-/* Get chip identification */
-static int ov9740_g_chip_ident(struct v4l2_subdev *sd,
-                              struct v4l2_dbg_chip_ident *id)
+/* select nearest higher resolution for capture */
+static void ov9740_res_roundup(u32 *width, u32 *height)
 {
-       struct ov9740_priv *priv = to_ov9740(sd);
+       /* Width must be a multiple of 4 pixels. */
+       *width = ALIGN(*width, 4);
 
-       id->ident = priv->ident;
-       id->revision = priv->revision;
+       /* Max resolution is 1280x720 (720p). */
+       if (*width > OV9740_MAX_WIDTH)
+               *width = OV9740_MAX_WIDTH;
 
-       return 0;
+       if (*height > OV9740_MAX_HEIGHT)
+               *height = OV9740_MAX_HEIGHT;
 }
 
-#ifdef CONFIG_VIDEO_ADV_DEBUG
-static int ov9740_get_register(struct v4l2_subdev *sd,
-                              struct v4l2_dbg_register *reg)
+/* Setup registers according to resolution and color encoding */
+static int ov9740_set_res(struct i2c_client *client, u32 width, u32 height)
 {
-       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       u32 x_start;
+       u32 y_start;
+       u32 x_end;
+       u32 y_end;
+       bool scaling = 0;
+       u32 scale_input_x;
+       u32 scale_input_y;
        int ret;
-       u8 val;
-
-       if (reg->reg & ~0xffff)
-               return -EINVAL;
-
-       reg->size = 2;
-
-       ret = ov9740_reg_read(client, reg->reg, &val);
-       if (ret)
-               return ret;
-
-       reg->val = (__u64)val;
 
-       return ret;
-}
+       if ((width != OV9740_MAX_WIDTH) || (height != OV9740_MAX_HEIGHT))
+               scaling = 1;
 
-static int ov9740_set_register(struct v4l2_subdev *sd,
-                              struct v4l2_dbg_register *reg)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       /*
+        * Try to use as much of the sensor area as possible when supporting
+        * smaller resolutions.  Depending on the aspect ratio of the
+        * chosen resolution, we can either use the full width of the sensor,
+        * or the full height of the sensor (or both if the aspect ratio is
+        * the same as 1280x720.
+        */
+       if ((OV9740_MAX_WIDTH * height) > (OV9740_MAX_HEIGHT * width)) {
+               scale_input_x = (OV9740_MAX_HEIGHT * width) / height;
+               scale_input_y = OV9740_MAX_HEIGHT;
+       } else {
+               scale_input_x = OV9740_MAX_WIDTH;
+               scale_input_y = (OV9740_MAX_WIDTH * height) / width;
+       }
 
-       if (reg->reg & ~0xffff || reg->val & ~0xff)
-               return -EINVAL;
+       /* These describe the area of the sensor to use. */
+       x_start = (OV9740_MAX_WIDTH - scale_input_x) / 2;
+       y_start = (OV9740_MAX_HEIGHT - scale_input_y) / 2;
+       x_end = x_start + scale_input_x - 1;
+       y_end = y_start + scale_input_y - 1;
 
-       return ov9740_reg_write(client, reg->reg, reg->val);
-}
-#endif
+       ret = ov9740_reg_write(client, OV9740_X_ADDR_START_HI, x_start >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_X_ADDR_START_LO, x_start & 0xff);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_Y_ADDR_START_HI, y_start >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_Y_ADDR_START_LO, y_start & 0xff);
+       if (ret)
+               goto done;
 
-/* select nearest higher resolution for capture */
-static void ov9740_res_roundup(u32 *width, u32 *height)
-{
-       int i;
+       ret = ov9740_reg_write(client, OV9740_X_ADDR_END_HI, x_end >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_X_ADDR_END_LO, x_end & 0xff);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_Y_ADDR_END_HI, y_end >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_Y_ADDR_END_LO, y_end & 0xff);
+       if (ret)
+               goto done;
 
-       for (i = 0; i < ARRAY_SIZE(ov9740_resolutions); i++)
-               if ((ov9740_resolutions[i].width >= *width) &&
-                   (ov9740_resolutions[i].height >= *height)) {
-                       *width = ov9740_resolutions[i].width;
-                       *height = ov9740_resolutions[i].height;
-                       return;
-               }
+       ret = ov9740_reg_write(client, OV9740_X_OUTPUT_SIZE_HI, width >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_X_OUTPUT_SIZE_LO, width & 0xff);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_Y_OUTPUT_SIZE_HI, height >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_Y_OUTPUT_SIZE_LO, height & 0xff);
+       if (ret)
+               goto done;
 
-       *width = ov9740_resolutions[OV9740_720P].width;
-       *height = ov9740_resolutions[OV9740_720P].height;
-}
+       ret = ov9740_reg_write(client, OV9740_ISP_CTRL1E, scale_input_x >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_ISP_CTRL1F, scale_input_x & 0xff);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_ISP_CTRL20, scale_input_y >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_ISP_CTRL21, scale_input_y & 0xff);
+       if (ret)
+               goto done;
 
-/* Setup registers according to resolution and color encoding */
-static int ov9740_set_res(struct i2c_client *client, u32 width)
-{
-       int ret;
+       ret = ov9740_reg_write(client, OV9740_VFIFO_READ_START_HI,
+                              (scale_input_x - width) >> 8);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_VFIFO_READ_START_LO,
+                              (scale_input_x - width) & 0xff);
+       if (ret)
+               goto done;
 
-       /* select register configuration for given resolution */
-       if (width == ov9740_resolutions[OV9740_VGA].width) {
-               dev_dbg(&client->dev, "Setting image size to 640x480\n");
-               ret = ov9740_reg_write_array(client, ov9740_regs_vga,
-                                            ARRAY_SIZE(ov9740_regs_vga));
-       } else if (width == ov9740_resolutions[OV9740_720P].width) {
-               dev_dbg(&client->dev, "Setting image size to 1280x720\n");
-               ret = ov9740_reg_write_array(client, ov9740_regs_720p,
-                                            ARRAY_SIZE(ov9740_regs_720p));
-       } else {
-               dev_err(&client->dev, "Failed to select resolution!\n");
-               return -EINVAL;
-       }
+       ret = ov9740_reg_write(client, OV9740_ISP_CTRL00, 0xff);
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_ISP_CTRL01, 0xef |
+                                                         (scaling << 4));
+       if (ret)
+               goto done;
+       ret = ov9740_reg_write(client, OV9740_ISP_CTRL03, 0xff);
 
+done:
        return ret;
 }
 
@@ -768,6 +708,7 @@ static int ov9740_s_fmt(struct v4l2_subdev *sd,
                        struct v4l2_mbus_framefmt *mf)
 {
        struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct ov9740_priv *priv = to_ov9740(sd);
        enum v4l2_colorspace cspace;
        enum v4l2_mbus_pixelcode code = mf->code;
        int ret;
@@ -787,13 +728,15 @@ static int ov9740_s_fmt(struct v4l2_subdev *sd,
        if (ret < 0)
                return ret;
 
-       ret = ov9740_set_res(client, mf->width);
+       ret = ov9740_set_res(client, mf->width, mf->height);
        if (ret < 0)
                return ret;
 
        mf->code        = code;
        mf->colorspace  = cspace;
 
+       memcpy(&priv->current_mf, mf, sizeof(struct v4l2_mbus_framefmt));
+
        return ret;
 }
 
@@ -824,8 +767,8 @@ static int ov9740_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
 {
        a->bounds.left          = 0;
        a->bounds.top           = 0;
-       a->bounds.width         = ov9740_resolutions[OV9740_720P].width;
-       a->bounds.height        = ov9740_resolutions[OV9740_720P].height;
+       a->bounds.width         = OV9740_MAX_WIDTH;
+       a->bounds.height        = OV9740_MAX_HEIGHT;
        a->defrect              = a->bounds;
        a->type                 = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        a->pixelaspect.numerator        = 1;
@@ -838,13 +781,115 @@ static int ov9740_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
 {
        a->c.left               = 0;
        a->c.top                = 0;
-       a->c.width              = ov9740_resolutions[OV9740_720P].width;
-       a->c.height             = ov9740_resolutions[OV9740_720P].height;
+       a->c.width              = OV9740_MAX_WIDTH;
+       a->c.height             = OV9740_MAX_HEIGHT;
        a->type                 = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
        return 0;
 }
 
+/* Get status of additional camera capabilities */
+static int ov9740_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
+{
+       struct ov9740_priv *priv = to_ov9740(sd);
+
+       switch (ctrl->id) {
+       case V4L2_CID_VFLIP:
+               ctrl->value = priv->flag_vflip;
+               break;
+       case V4L2_CID_HFLIP:
+               ctrl->value = priv->flag_hflip;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* Set status of additional camera capabilities */
+static int ov9740_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
+{
+       struct ov9740_priv *priv = to_ov9740(sd);
+
+       switch (ctrl->id) {
+       case V4L2_CID_VFLIP:
+               priv->flag_vflip = ctrl->value;
+               break;
+       case V4L2_CID_HFLIP:
+               priv->flag_hflip = ctrl->value;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* Get chip identification */
+static int ov9740_g_chip_ident(struct v4l2_subdev *sd,
+                              struct v4l2_dbg_chip_ident *id)
+{
+       struct ov9740_priv *priv = to_ov9740(sd);
+
+       id->ident = priv->ident;
+       id->revision = priv->revision;
+
+       return 0;
+}
+
+static int ov9740_s_power(struct v4l2_subdev *sd, int on)
+{
+       struct ov9740_priv *priv = to_ov9740(sd);
+
+       if (!priv->current_enable)
+               return 0;
+
+       if (on) {
+               ov9740_s_fmt(sd, &priv->current_mf);
+               ov9740_s_stream(sd, priv->current_enable);
+       } else {
+               ov9740_s_stream(sd, 0);
+               priv->current_enable = true;
+       }
+
+       return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int ov9740_get_register(struct v4l2_subdev *sd,
+                              struct v4l2_dbg_register *reg)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       int ret;
+       u8 val;
+
+       if (reg->reg & ~0xffff)
+               return -EINVAL;
+
+       reg->size = 2;
+
+       ret = ov9740_reg_read(client, reg->reg, &val);
+       if (ret)
+               return ret;
+
+       reg->val = (__u64)val;
+
+       return ret;
+}
+
+static int ov9740_set_register(struct v4l2_subdev *sd,
+                              struct v4l2_dbg_register *reg)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+       if (reg->reg & ~0xffff || reg->val & ~0xff)
+               return -EINVAL;
+
+       return ov9740_reg_write(client, reg->reg, reg->val);
+}
+#endif
+
 static int ov9740_video_probe(struct soc_camera_device *icd,
                              struct i2c_client *client)
 {
@@ -853,16 +898,9 @@ static int ov9740_video_probe(struct soc_camera_device *icd,
        u8 modelhi, modello;
        int ret;
 
-       /*
-        * We must have a parent by now. And it cannot be a wrong one.
-        * So this entire test is completely redundant.
-        */
-       if (!icd->dev.parent ||
-           to_soc_camera_host(icd->dev.parent)->nr != icd->iface) {
-               dev_err(&client->dev, "Parent missing or invalid!\n");
-               ret = -ENODEV;
-               goto err;
-       }
+       /* We must have a parent by now. And it cannot be a wrong one. */
+       BUG_ON(!icd->parent ||
+              to_soc_camera_host(icd->parent)->nr != icd->iface);
 
        /*
         * check and show product ID and manufacturer ID
@@ -911,25 +949,26 @@ static struct soc_camera_ops ov9740_ops = {
        .num_controls           = ARRAY_SIZE(ov9740_controls),
 };
 
+static struct v4l2_subdev_video_ops ov9740_video_ops = {
+       .s_stream               = ov9740_s_stream,
+       .s_mbus_fmt             = ov9740_s_fmt,
+       .try_mbus_fmt           = ov9740_try_fmt,
+       .enum_mbus_fmt          = ov9740_enum_fmt,
+       .cropcap                = ov9740_cropcap,
+       .g_crop                 = ov9740_g_crop,
+};
+
 static struct v4l2_subdev_core_ops ov9740_core_ops = {
        .g_ctrl                 = ov9740_g_ctrl,
        .s_ctrl                 = ov9740_s_ctrl,
        .g_chip_ident           = ov9740_g_chip_ident,
+       .s_power                = ov9740_s_power,
 #ifdef CONFIG_VIDEO_ADV_DEBUG
        .g_register             = ov9740_get_register,
        .s_register             = ov9740_set_register,
 #endif
 };
 
-static struct v4l2_subdev_video_ops ov9740_video_ops = {
-       .s_stream               = ov9740_s_stream,
-       .s_mbus_fmt             = ov9740_s_fmt,
-       .try_mbus_fmt           = ov9740_try_fmt,
-       .enum_mbus_fmt          = ov9740_enum_fmt,
-       .cropcap                = ov9740_cropcap,
-       .g_crop                 = ov9740_g_crop,
-};
-
 static struct v4l2_subdev_ops ov9740_subdev_ops = {
        .core                   = &ov9740_core_ops,
        .video                  = &ov9740_video_ops,