[media] m5mols: Add support for the system initialization interrupt

The M-5MOLS internal controller's initialization time depends on the
hardware and firmware revision. Currently the driver just waits for
worst case time period, after applying the voltage supplies, for
the device to be ready. The M-5MOLS supports "System initialization"
interrupt which is triggered after the controller finished booting.
So use this interrupt to optimize the initialization sequence.

After the voltage supplies are applied the I2C communication will
fail, until the internal controller initializes to Flash Writer
state. For the period when the I2C is not accessible use the
isp_ready flag to suppress the error logs.

Signed-off-by: HeungJun Kim <riverful.kim@samsung.com>
Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/video/m5mols/m5mols.h b/drivers/media/video/m5mols/m5mols.h
index 5a8a858..0265ea6 100644
--- a/drivers/media/video/m5mols/m5mols.h
+++ b/drivers/media/video/m5mols/m5mols.h
@@ -174,8 +174,8 @@
  * @ver: information of the version
  * @cap: the capture mode attributes
  * @power: current sensor's power status
- * @ctrl_sync: true means all controls of the sensor are initialized
- * @int_capture: true means the capture interrupt is issued once
+ * @isp_ready: 1 when the ISP controller has completed booting
+ * @ctrl_sync: 1 when the control handler state is restored in H/W
  * @lock_ae: true means the Auto Exposure is locked
  * @lock_awb: true means the Aut WhiteBalance is locked
  * @resolution:	register value for current resolution
@@ -204,8 +204,11 @@
 
 	struct m5mols_version ver;
 	struct m5mols_capture cap;
-	bool power;
-	bool ctrl_sync;
+
+	unsigned int isp_ready:1;
+	unsigned int power:1;
+	unsigned int ctrl_sync:1;
+
 	bool lock_ae;
 	bool lock_awb;
 	u8 resolution;
@@ -213,7 +216,6 @@
 	int (*set_power)(struct device *dev, int on);
 };
 
-#define is_powered(__info) (__info->power)
 #define is_ctrl_synced(__info) (__info->ctrl_sync)
 #define is_available_af(__info)	(__info->ver.af)
 #define is_code(__code, __type) (__code == m5mols_default_ffmt[__type].code)
diff --git a/drivers/media/video/m5mols/m5mols_core.c b/drivers/media/video/m5mols/m5mols_core.c
index 070f04c..3c7556f 100644
--- a/drivers/media/video/m5mols/m5mols_core.c
+++ b/drivers/media/video/m5mols/m5mols_core.c
@@ -135,10 +135,13 @@
  * @reg: combination of size, category and command for the I2C packet
  * @size: desired size of I2C packet
  * @val: read value
+ *
+ * Returns 0 on success, or else negative errno.
  */
 static int m5mols_read(struct v4l2_subdev *sd, u32 size, u32 reg, u32 *val)
 {
 	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct m5mols_info *info = to_m5mols(sd);
 	u8 rbuf[M5MOLS_I2C_MAX_SIZE + 1];
 	u8 category = I2C_CATEGORY(reg);
 	u8 cmd = I2C_COMMAND(reg);
@@ -168,15 +171,17 @@
 	usleep_range(200, 200);
 
 	ret = i2c_transfer(client->adapter, msg, 2);
-	if (ret < 0) {
-		v4l2_err(sd, "read failed: size:%d cat:%02x cmd:%02x. %d\n",
-			 size, category, cmd, ret);
-		return ret;
+
+	if (ret == 2) {
+		*val = m5mols_swap_byte(&rbuf[1], size);
+		return 0;
 	}
 
-	*val = m5mols_swap_byte(&rbuf[1], size);
+	if (info->isp_ready)
+		v4l2_err(sd, "read failed: size:%d cat:%02x cmd:%02x. %d\n",
+			 size, category, cmd, ret);
 
-	return 0;
+	return ret < 0 ? ret : -EIO;
 }
 
 int m5mols_read_u8(struct v4l2_subdev *sd, u32 reg, u8 *val)
@@ -229,10 +234,13 @@
  * m5mols_write - I2C command write function
  * @reg: combination of size, category and command for the I2C packet
  * @val: value to write
+ *
+ * Returns 0 on success, or else negative errno.
  */
 int m5mols_write(struct v4l2_subdev *sd, u32 reg, u32 val)
 {
 	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct m5mols_info *info = to_m5mols(sd);
 	u8 wbuf[M5MOLS_I2C_MAX_SIZE + 4];
 	u8 category = I2C_CATEGORY(reg);
 	u8 cmd = I2C_COMMAND(reg);
@@ -263,13 +271,14 @@
 	usleep_range(200, 200);
 
 	ret = i2c_transfer(client->adapter, msg, 1);
-	if (ret < 0) {
-		v4l2_err(sd, "write failed: size:%d cat:%02x cmd:%02x. %d\n",
-			size, category, cmd, ret);
-		return ret;
-	}
+	if (ret == 1)
+		return 0;
 
-	return 0;
+	if (info->isp_ready)
+		v4l2_err(sd, "write failed: cat:%02x cmd:%02x ret:%d\n",
+			 category, cmd, ret);
+
+	return ret < 0 ? ret : -EIO;
 }
 
 /**
@@ -620,7 +629,7 @@
 			return ret;
 
 		v4l2_ctrl_handler_setup(&info->handle);
-		info->ctrl_sync = true;
+		info->ctrl_sync = 1;
 	}
 
 	return ret;
@@ -700,10 +709,10 @@
 	const struct m5mols_platform_data *pdata = info->pdata;
 	int ret;
 
-	if (enable) {
-		if (is_powered(info))
-			return 0;
+	if (info->power == enable)
+		return 0;
 
+	if (enable) {
 		if (info->set_power) {
 			ret = info->set_power(&client->dev, 1);
 			if (ret)
@@ -717,15 +726,11 @@
 		}
 
 		gpio_set_value(pdata->gpio_reset, !pdata->reset_polarity);
-		usleep_range(1000, 1000);
-		info->power = true;
+		info->power = 1;
 
 		return ret;
 	}
 
-	if (!is_powered(info))
-		return 0;
-
 	ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies);
 	if (ret)
 		return ret;
@@ -734,8 +739,9 @@
 		info->set_power(&client->dev, 0);
 
 	gpio_set_value(pdata->gpio_reset, pdata->reset_polarity);
-	usleep_range(1000, 1000);
-	info->power = false;
+
+	info->isp_ready = 0;
+	info->power = 0;
 
 	return ret;
 }
@@ -748,21 +754,29 @@
 }
 
 /**
- * m5mols_sensor_armboot - Booting M-5MOLS internal ARM core.
+ * m5mols_fw_start - M-5MOLS internal ARM controller initialization
  *
- * Booting internal ARM core makes the M-5MOLS is ready for getting commands
- * with I2C. It's the first thing to be done after it powered up. It must wait
- * at least 520ms recommended by M-5MOLS datasheet, after executing arm booting.
+ * Execute the M-5MOLS internal ARM controller initialization sequence.
+ * This function should be called after the supply voltage has been
+ * applied and before any requests to the device are made.
  */
-static int m5mols_sensor_armboot(struct v4l2_subdev *sd)
+static int m5mols_fw_start(struct v4l2_subdev *sd)
 {
+	struct m5mols_info *info = to_m5mols(sd);
 	int ret;
 
-	ret = m5mols_write(sd, FLASH_CAM_START, REG_START_ARM_BOOT);
+	atomic_set(&info->irq_done, 0);
+	/* Wait until I2C slave is initialized in Flash Writer mode */
+	ret = m5mols_busy_wait(sd, FLASH_CAM_START, REG_IN_FLASH_MODE,
+			       M5MOLS_I2C_RDY_WAIT_FL | 0xff, -1);
+	if (!ret)
+		ret = m5mols_write(sd, FLASH_CAM_START, REG_START_ARM_BOOT);
+	if (!ret)
+		ret = m5mols_wait_interrupt(sd, REG_INT_MODE, 2000);
 	if (ret < 0)
 		return ret;
 
-	msleep(520);
+	info->isp_ready = 1;
 
 	ret = m5mols_get_version(sd);
 	if (!ret)
@@ -840,7 +854,7 @@
 	if (on) {
 		ret = m5mols_sensor_power(info, true);
 		if (!ret)
-			ret = m5mols_sensor_armboot(sd);
+			ret = m5mols_fw_start(sd);
 		if (!ret)
 			ret = m5mols_init_controls(info);
 		if (ret)
@@ -869,7 +883,7 @@
 	ret = m5mols_sensor_power(info, false);
 	if (!ret) {
 		v4l2_ctrl_handler_free(&info->handle);
-		info->ctrl_sync = false;
+		info->ctrl_sync = 0;
 	}
 
 	return ret;
diff --git a/drivers/media/video/m5mols/m5mols_reg.h b/drivers/media/video/m5mols/m5mols_reg.h
index d488add..ae4aced 100644
--- a/drivers/media/video/m5mols/m5mols_reg.h
+++ b/drivers/media/video/m5mols/m5mols_reg.h
@@ -355,6 +355,7 @@
 
 /* Starts internal ARM core booting after power-up */
 #define FLASH_CAM_START		I2C_REG(CAT_FLASH, 0x12, 1)
-#define REG_START_ARM_BOOT	0x01
+#define REG_START_ARM_BOOT	0x01	/* write value */
+#define REG_IN_FLASH_MODE	0x00	/* read value */
 
 #endif	/* M5MOLS_REG_H */