V4L/DVB: au8522: fix race condition in switching from digital to analog mode

With applications like MythTV, switching inputs results in closing the digital
side and then immediately opening the analog side.  This exposes a race
condition where the dvb_frontend kernel thread powers down the chip and closes
the i2c gate even though we're in the middle of bringing up the analog part
of the chip (since the shutdown of the dvb_frontend kernel thread occurs
asychronously).

Introduce a construct to keep track of what mode we're in, and drop requests
to power down or management the gate if we've already switched to analog mode.

Thanks to Zaphod Beeblebrox for reporting this issue.

Signed-off-by: Devin Heitmueller <dheitmueller@kernellabs.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/dvb/frontends/au8522_dig.c b/drivers/media/dvb/frontends/au8522_dig.c
index a1fed0f..e2efb25 100644
--- a/drivers/media/dvb/frontends/au8522_dig.c
+++ b/drivers/media/dvb/frontends/au8522_dig.c
@@ -84,6 +84,14 @@
 
 	dprintk("%s(%d)\n", __func__, enable);
 
+	if (state->operational_mode == AU8522_ANALOG_MODE) {
+		/* We're being asked to manage the gate even though we're
+		   not in digital mode.  This can occur if we get switched
+		   over to analog mode before the dvb_frontend kernel thread
+		   has completely shutdown */
+		return 0;
+	}
+
 	if (enable)
 		return au8522_writereg(state, 0x106, 1);
 	else
@@ -608,6 +616,8 @@
 	struct au8522_state *state = fe->demodulator_priv;
 	dprintk("%s()\n", __func__);
 
+	state->operational_mode = AU8522_DIGITAL_MODE;
+
 	au8522_writereg(state, 0xa4, 1 << 5);
 
 	au8522_i2c_gate_ctrl(fe, 1);
@@ -704,6 +714,15 @@
 	struct au8522_state *state = fe->demodulator_priv;
 	dprintk("%s()\n", __func__);
 
+	/* Only power down if the digital side is currently using the chip */
+	if (state->operational_mode == AU8522_ANALOG_MODE) {
+		/* We're not in one of the expected power modes, which means
+		   that the DVB thread is probably telling us to go to sleep
+		   even though the analog frontend has already started using
+		   the chip.  So ignore the request */
+		return 0;
+	}
+
 	/* turn off led */
 	au8522_led_ctrl(state, 0);
 
@@ -932,6 +951,8 @@
 	/* setup the state */
 	state->config = config;
 	state->i2c = i2c;
+	state->operational_mode = AU8522_DIGITAL_MODE;
+
 	/* create dvb_frontend */
 	memcpy(&state->frontend.ops, &au8522_ops,
 	       sizeof(struct dvb_frontend_ops));