[patch] Fix AF9015 Dual tuner i2c write failures

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi, this has been annoying me for some time, so this evening I fixed
it. If you use one of the above dual tuner devices (e.g. KWorld 399U),
you get random tuning failures and i2c errors reported in dmesg such
as:

af9013: I2C read failed reg:d607
af9015: command failed:1
mxl5005s I2C write failed
af9015: command failed:1
mxl5005s I2C write failed
af9015: command failed:2
af9013: I2C read failed reg:d607
af9015: command failed:2
af9013: I2C read failed reg:d607

Looking at the large comment in dvb-usb/af9015.c/af9015_i2c_xfer() it
seems the dual demods/tuners have the same i2c addresses and are
switched by an i2c gate. Since the two show up as separate DVB
devices, and there is no synchronisation of i2c accesses, its fairly
obvious the problem is because both tuners are being written to at the
same time, which is mucking up the i2c gate. I tested this with two
scripts on my development machine:

while [ 1 ];
do
        scan -a 0 /usr/share/dvb/dvb-t/uk-Craigkelly
done

AND

while [ 1 ];
do
        scan -a 1 /usr/share/dvb/dvb-t/uk-Craigkelly
done


If I run only one at a time, it works perfectly. Run two, and I start
to see errors.

Adding a "bus lock" to af9015_i2c_xfer() will not work as demod/tuner
accesses will take multiple i2c transactions.

Therefore, the following patch overrides the dvb_frontend_ops
functions to add a per-device lock around them: only one frontend can
now use the i2c bus at a time. Testing with the scripts above shows
this has eliminated the errors.

Signed-off-by: Andrew de Quincey <adq_dvb@xxxxxxxxxxxxx>
diff --git a/drivers/media/dvb/dvb-usb/af9015.c b/drivers/media/dvb/dvb-usb/af9015.c
index 31c0a0e..740d969 100644
--- a/drivers/media/dvb/dvb-usb/af9015.c
+++ b/drivers/media/dvb/dvb-usb/af9015.c
@@ -1083,6 +1083,104 @@ static int af9015_i2c_init(struct dvb_usb_device *d)
 	return ret;
 }
 
+static int af9015_lock_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters* params)
+{
+	int result;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct af9015_state *state = adap->dev->priv;
+
+	if (mutex_lock_interruptible(&adap->dev->usb_mutex))
+		return -EAGAIN;
+
+	result = state->fe_ops[adap->id].set_frontend(fe, params);
+	mutex_unlock(&adap->dev->usb_mutex);
+	return result;
+}
+                
+static int af9015_lock_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters* params)
+{
+	int result;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct af9015_state *state = adap->dev->priv;
+
+	if (mutex_lock_interruptible(&adap->dev->usb_mutex))
+		return -EAGAIN;
+
+	result = state->fe_ops[adap->id].get_frontend(fe, params);
+	mutex_unlock(&adap->dev->usb_mutex);
+	return result;
+}
+                        
+static int af9015_lock_read_status(struct dvb_frontend* fe, fe_status_t* status)
+{
+	int result;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct af9015_state *state = adap->dev->priv;
+
+	if (mutex_lock_interruptible(&adap->dev->usb_mutex))
+		return -EAGAIN;
+
+	result = state->fe_ops[adap->id].read_status(fe, status);
+	mutex_unlock(&adap->dev->usb_mutex);
+	return result;
+}
+
+static int af9015_lock_read_ber(struct dvb_frontend* fe, u32* ber)
+{
+	int result;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct af9015_state *state = adap->dev->priv;
+
+	if (mutex_lock_interruptible(&adap->dev->usb_mutex))
+		return -EAGAIN;
+
+	result = state->fe_ops[adap->id].read_ber(fe, ber);
+	mutex_unlock(&adap->dev->usb_mutex);
+	return result;
+}
+
+static int af9015_lock_read_signal_strength(struct dvb_frontend* fe, u16* strength)
+{
+	int result;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct af9015_state *state = adap->dev->priv;
+
+	if (mutex_lock_interruptible(&adap->dev->usb_mutex))
+		return -EAGAIN;
+
+	result = state->fe_ops[adap->id].read_signal_strength(fe, strength);
+	mutex_unlock(&adap->dev->usb_mutex);
+	return result;
+}
+
+static int af9015_lock_read_snr(struct dvb_frontend* fe, u16* snr)
+{
+	int result;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct af9015_state *state = adap->dev->priv;
+
+	if (mutex_lock_interruptible(&adap->dev->usb_mutex))
+		return -EAGAIN;
+
+	result = state->fe_ops[adap->id].read_snr(fe, snr);
+	mutex_unlock(&adap->dev->usb_mutex);
+	return result;
+}
+
+static int af9015_lock_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks)
+{
+	int result;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct af9015_state *state = adap->dev->priv;
+
+	if (mutex_lock_interruptible(&adap->dev->usb_mutex))
+		return -EAGAIN;
+
+	result = state->fe_ops[adap->id].read_ucblocks(fe, ucblocks);
+	mutex_unlock(&adap->dev->usb_mutex);
+	return result;
+}
+
 static int af9015_af9013_frontend_attach(struct dvb_usb_adapter *adap)
 {
 	int ret;
@@ -1116,6 +1214,22 @@ static int af9015_af9013_frontend_attach(struct dvb_usb_adapter *adap)
 	/* attach demodulator */
 	adap->fe = dvb_attach(af9013_attach, &af9015_af9013_config[adap->id],
 		i2c_adap);
+		
+	memcpy(&state->fe_ops[adap->id], &adap->fe->ops, sizeof(struct dvb_frontend_ops));
+	if (adap->fe->ops.set_frontend)
+		adap->fe->ops.set_frontend = af9015_lock_set_frontend;
+	if (adap->fe->ops.get_frontend)
+		adap->fe->ops.get_frontend = af9015_lock_get_frontend;
+	if (adap->fe->ops.read_status)
+		adap->fe->ops.read_status = af9015_lock_read_status;
+	if (adap->fe->ops.read_ber)
+		adap->fe->ops.read_ber = af9015_lock_read_ber;
+	if (adap->fe->ops.read_signal_strength)
+		adap->fe->ops.read_signal_strength = af9015_lock_read_signal_strength;
+	if (adap->fe->ops.read_snr)
+		adap->fe->ops.read_snr = af9015_lock_read_snr;
+	if (adap->fe->ops.read_ucblocks)
+		adap->fe->ops.read_ucblocks = af9015_lock_read_ucblocks;
 
 	return adap->fe == NULL ? -ENODEV : 0;
 }
diff --git a/drivers/media/dvb/dvb-usb/af9015.h b/drivers/media/dvb/dvb-usb/af9015.h
index f20cfa6..759bb3f 100644
--- a/drivers/media/dvb/dvb-usb/af9015.h
+++ b/drivers/media/dvb/dvb-usb/af9015.h
@@ -102,6 +102,7 @@ struct af9015_state {
 	struct i2c_adapter i2c_adap; /* I2C adapter for 2nd FE */
 	u8 rc_repeat;
 	u32 rc_keycode;
+	struct dvb_frontend_ops fe_ops[2];
 };
 
 struct af9015_config {

[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux