Commit 7f2c983c authored by Devin Heitmueller's avatar Devin Heitmueller Committed by Mauro Carvalho Chehab

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: default avatarDevin Heitmueller <dheitmueller@kernellabs.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 535653b1
...@@ -664,6 +664,8 @@ static int au8522_reset(struct v4l2_subdev *sd, u32 val) ...@@ -664,6 +664,8 @@ static int au8522_reset(struct v4l2_subdev *sd, u32 val)
{ {
struct au8522_state *state = to_state(sd); struct au8522_state *state = to_state(sd);
state->operational_mode = AU8522_ANALOG_MODE;
au8522_writereg(state, 0xa4, 1 << 5); au8522_writereg(state, 0xa4, 1 << 5);
return 0; return 0;
......
...@@ -84,6 +84,14 @@ static int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) ...@@ -84,6 +84,14 @@ static int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
dprintk("%s(%d)\n", __func__, enable); 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) if (enable)
return au8522_writereg(state, 0x106, 1); return au8522_writereg(state, 0x106, 1);
else else
...@@ -608,6 +616,8 @@ int au8522_init(struct dvb_frontend *fe) ...@@ -608,6 +616,8 @@ int au8522_init(struct dvb_frontend *fe)
struct au8522_state *state = fe->demodulator_priv; struct au8522_state *state = fe->demodulator_priv;
dprintk("%s()\n", __func__); dprintk("%s()\n", __func__);
state->operational_mode = AU8522_DIGITAL_MODE;
au8522_writereg(state, 0xa4, 1 << 5); au8522_writereg(state, 0xa4, 1 << 5);
au8522_i2c_gate_ctrl(fe, 1); au8522_i2c_gate_ctrl(fe, 1);
...@@ -704,6 +714,15 @@ int au8522_sleep(struct dvb_frontend *fe) ...@@ -704,6 +714,15 @@ int au8522_sleep(struct dvb_frontend *fe)
struct au8522_state *state = fe->demodulator_priv; struct au8522_state *state = fe->demodulator_priv;
dprintk("%s()\n", __func__); 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 */ /* turn off led */
au8522_led_ctrl(state, 0); au8522_led_ctrl(state, 0);
...@@ -932,6 +951,8 @@ struct dvb_frontend *au8522_attach(const struct au8522_config *config, ...@@ -932,6 +951,8 @@ struct dvb_frontend *au8522_attach(const struct au8522_config *config,
/* setup the state */ /* setup the state */
state->config = config; state->config = config;
state->i2c = i2c; state->i2c = i2c;
state->operational_mode = AU8522_DIGITAL_MODE;
/* create dvb_frontend */ /* create dvb_frontend */
memcpy(&state->frontend.ops, &au8522_ops, memcpy(&state->frontend.ops, &au8522_ops,
sizeof(struct dvb_frontend_ops)); sizeof(struct dvb_frontend_ops));
......
...@@ -34,10 +34,15 @@ ...@@ -34,10 +34,15 @@
#include "au8522.h" #include "au8522.h"
#include "tuner-i2c.h" #include "tuner-i2c.h"
#define AU8522_ANALOG_MODE 0
#define AU8522_DIGITAL_MODE 1
struct au8522_state { struct au8522_state {
struct i2c_client *c; struct i2c_client *c;
struct i2c_adapter *i2c; struct i2c_adapter *i2c;
u8 operational_mode;
/* Used for sharing of the state between analog and digital mode */ /* Used for sharing of the state between analog and digital mode */
struct tuner_i2c_props i2c_props; struct tuner_i2c_props i2c_props;
struct list_head hybrid_tuner_instance_list; struct list_head hybrid_tuner_instance_list;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment