From 61a96113de51e1f8f43ac98cbeadb54e60045905 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Sat, 30 Jun 2012 09:08:54 -0300 Subject: [media] tuner-xc2028: use request_firmware_nowait() Change the firmware logic to use request_firmware_nowait(), and to preserve the loaded firmwares in memory, to reduce the risk of troubles with buggy userspace apps. With this change, while the firmware is being loaded, the driver will return -EAGAIN to any calls. If, for some reason, firmware failed to be loaded from userspace, it will return -ENODEV. Signed-off-by: Mauro Carvalho Chehab --- drivers/media/common/tuners/tuner-xc2028.c | 178 +++++++++++++++++++++-------- 1 file changed, 129 insertions(+), 49 deletions(-) (limited to 'drivers/media/common/tuners/tuner-xc2028.c') diff --git a/drivers/media/common/tuners/tuner-xc2028.c b/drivers/media/common/tuners/tuner-xc2028.c index b5ee3ebfcfc..9638a69f36b 100644 --- a/drivers/media/common/tuners/tuner-xc2028.c +++ b/drivers/media/common/tuners/tuner-xc2028.c @@ -90,11 +90,22 @@ struct firmware_properties { int scode_nr; }; +enum xc2028_state { + XC2028_NO_FIRMWARE = 0, + XC2028_WAITING_FIRMWARE, + XC2028_ACTIVE, + XC2028_SLEEP, + XC2028_NODEV, +}; + struct xc2028_data { struct list_head hybrid_tuner_instance_list; struct tuner_i2c_props i2c_props; __u32 frequency; + enum xc2028_state state; + const char *fname; + struct firmware_description *firm; int firm_size; __u16 firm_version; @@ -255,6 +266,21 @@ static v4l2_std_id parse_audio_std_option(void) return 0; } +static int check_device_status(struct xc2028_data *priv) +{ + switch (priv->state) { + case XC2028_NO_FIRMWARE: + case XC2028_WAITING_FIRMWARE: + return -EAGAIN; + case XC2028_ACTIVE: + case XC2028_SLEEP: + return 0; + case XC2028_NODEV: + return -ENODEV; + } + return 0; +} + static void free_firmware(struct xc2028_data *priv) { int i; @@ -270,45 +296,28 @@ static void free_firmware(struct xc2028_data *priv) priv->firm = NULL; priv->firm_size = 0; + priv->state = XC2028_NO_FIRMWARE; memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); } -static int load_all_firmwares(struct dvb_frontend *fe) +static int load_all_firmwares(struct dvb_frontend *fe, + const struct firmware *fw) { struct xc2028_data *priv = fe->tuner_priv; - const struct firmware *fw = NULL; const unsigned char *p, *endp; int rc = 0; int n, n_array; char name[33]; - char *fname; tuner_dbg("%s called\n", __func__); - if (!firmware_name[0]) - fname = priv->ctrl.fname; - else - fname = firmware_name; - - tuner_dbg("Reading firmware %s\n", fname); - rc = request_firmware(&fw, fname, priv->i2c_props.adap->dev.parent); - if (rc < 0) { - if (rc == -ENOENT) - tuner_err("Error: firmware %s not found.\n", - fname); - else - tuner_err("Error %d while requesting firmware %s \n", - rc, fname); - - return rc; - } p = fw->data; endp = p + fw->size; if (fw->size < sizeof(name) - 1 + 2 + 2) { tuner_err("Error: firmware file %s has invalid size!\n", - fname); + priv->fname); goto corrupt; } @@ -323,7 +332,7 @@ static int load_all_firmwares(struct dvb_frontend *fe) p += 2; tuner_info("Loading %d firmware images from %s, type: %s, ver %d.%d\n", - n_array, fname, name, + n_array, priv->fname, name, priv->firm_version >> 8, priv->firm_version & 0xff); priv->firm = kcalloc(n_array, sizeof(*priv->firm), GFP_KERNEL); @@ -417,9 +426,10 @@ err: free_firmware(priv); done: - release_firmware(fw); if (rc == 0) tuner_dbg("Firmware files loaded.\n"); + else + priv->state = XC2028_NODEV; return rc; } @@ -707,22 +717,15 @@ static int check_firmware(struct dvb_frontend *fe, unsigned int type, { struct xc2028_data *priv = fe->tuner_priv; struct firmware_properties new_fw; - int rc = 0, retry_count = 0; + int rc, retry_count = 0; u16 version, hwmodel; v4l2_std_id std0; tuner_dbg("%s called\n", __func__); - if (!priv->firm) { - if (!priv->ctrl.fname) { - tuner_info("xc2028/3028 firmware name not set!\n"); - return -EINVAL; - } - - rc = load_all_firmwares(fe); - if (rc < 0) - return rc; - } + rc = check_device_status(priv); + if (rc < 0) + return rc; if (priv->ctrl.mts && !(type & FM)) type |= MTS; @@ -749,9 +752,13 @@ retry: printk("scode_nr %d\n", new_fw.scode_nr); } - /* No need to reload base firmware if it matches */ - if (((BASE | new_fw.type) & BASE_TYPES) == - (priv->cur_fw.type & BASE_TYPES)) { + /* + * No need to reload base firmware if it matches and if the tuner + * is not at sleep mode + */ + if ((priv->state = XC2028_ACTIVE) && + (((BASE | new_fw.type) & BASE_TYPES) == + (priv->cur_fw.type & BASE_TYPES))) { tuner_dbg("BASE firmware not changed.\n"); goto skip_base; } @@ -872,10 +879,13 @@ read_not_reliable: * 2. Tell whether BASE firmware was just changed the next time through. */ priv->cur_fw.type |= BASE; + priv->state = XC2028_ACTIVE; return 0; fail: + priv->state = XC2028_SLEEP; + memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); if (retry_count < 8) { msleep(50); @@ -897,6 +907,10 @@ static int xc2028_signal(struct dvb_frontend *fe, u16 *strength) tuner_dbg("%s called\n", __func__); + rc = check_device_status(priv); + if (rc < 0) + return rc; + mutex_lock(&priv->lock); /* Sync Lock Indicator */ @@ -1111,11 +1125,16 @@ static int xc2028_set_params(struct dvb_frontend *fe) u32 delsys = c->delivery_system; u32 bw = c->bandwidth_hz; struct xc2028_data *priv = fe->tuner_priv; - unsigned int type=0; + int rc; + unsigned int type = 0; u16 demod = 0; tuner_dbg("%s called\n", __func__); + rc = check_device_status(priv); + if (rc < 0) + return rc; + switch (delsys) { case SYS_DVBT: case SYS_DVBT2: @@ -1201,7 +1220,11 @@ static int xc2028_set_params(struct dvb_frontend *fe) static int xc2028_sleep(struct dvb_frontend *fe) { struct xc2028_data *priv = fe->tuner_priv; - int rc = 0; + int rc; + + rc = check_device_status(priv); + if (rc < 0) + return rc; /* Avoid firmware reload on slow devices or if PM disabled */ if (no_poweroff || priv->ctrl.disable_power_mgmt) @@ -1220,7 +1243,7 @@ static int xc2028_sleep(struct dvb_frontend *fe) else rc = send_seq(priv, {0x80, XREG_POWER_DOWN, 0x00, 0x00}); - priv->cur_fw.type = 0; /* need firmware reload */ + priv->state = XC2028_SLEEP; mutex_unlock(&priv->lock); @@ -1237,8 +1260,9 @@ static int xc2028_dvb_release(struct dvb_frontend *fe) /* only perform final cleanup if this is the last instance */ if (hybrid_tuner_report_instance_count(priv) == 1) { - kfree(priv->ctrl.fname); free_firmware(priv); + kfree(priv->ctrl.fname); + priv->ctrl.fname = NULL; } if (priv) @@ -1254,14 +1278,42 @@ static int xc2028_dvb_release(struct dvb_frontend *fe) static int xc2028_get_frequency(struct dvb_frontend *fe, u32 *frequency) { struct xc2028_data *priv = fe->tuner_priv; + int rc; tuner_dbg("%s called\n", __func__); + rc = check_device_status(priv); + if (rc < 0) + return rc; + *frequency = priv->frequency; return 0; } +static void load_firmware_cb(const struct firmware *fw, + void *context) +{ + struct dvb_frontend *fe = context; + struct xc2028_data *priv = fe->tuner_priv; + int rc; + + tuner_dbg("request_firmware_nowait(): %s\n", fw ? "OK" : "error"); + if (!fw) { + tuner_err("Could not load firmware %s.\n", priv->fname); + priv->state = XC2028_NODEV; + return; + } + + rc = load_all_firmwares(fe, fw); + + release_firmware(fw); + + if (rc < 0) + return; + priv->state = XC2028_SLEEP; +} + static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg) { struct xc2028_data *priv = fe->tuner_priv; @@ -1272,21 +1324,49 @@ static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg) mutex_lock(&priv->lock); + /* + * Copy the config data. + * For the firmware name, keep a local copy of the string, + * in order to avoid troubles during device release. + */ + if (priv->ctrl.fname) + kfree(priv->ctrl.fname); memcpy(&priv->ctrl, p, sizeof(priv->ctrl)); - if (priv->ctrl.max_len < 9) - priv->ctrl.max_len = 13; - if (p->fname) { - if (priv->ctrl.fname && strcmp(p->fname, priv->ctrl.fname)) { - kfree(priv->ctrl.fname); - free_firmware(priv); - } - priv->ctrl.fname = kstrdup(p->fname, GFP_KERNEL); if (priv->ctrl.fname == NULL) rc = -ENOMEM; } + /* + * If firmware name changed, frees firmware. As free_firmware will + * reset the status to NO_FIRMWARE, this forces a new request_firmware + */ + if (!firmware_name[0] && p->fname && + priv->fname && strcmp(p->fname, priv->fname)) + free_firmware(priv); + + if (priv->ctrl.max_len < 9) + priv->ctrl.max_len = 13; + + if (priv->state == XC2028_NO_FIRMWARE) { + if (!firmware_name[0]) + priv->fname = priv->ctrl.fname; + else + priv->fname = firmware_name; + + rc = request_firmware_nowait(THIS_MODULE, 1, + priv->fname, + priv->i2c_props.adap->dev.parent, + GFP_KERNEL, + fe, load_firmware_cb); + if (rc < 0) { + tuner_err("Failed to request firmware %s\n", + priv->fname); + priv->state = XC2028_NODEV; + } + priv->state = XC2028_WAITING_FIRMWARE; + } mutex_unlock(&priv->lock); return rc; -- cgit v1.2.3-70-g09d2 From 90acb85fb48372f30e0a2f6d516d2285faea6e6c Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Wed, 4 Jul 2012 02:00:00 -0300 Subject: [media] tuner-xc2028: Fix signal strength report There are several bugs at the signal strength algorithm: - It is using logical OR, instead of bit OR; - It doesn't wait up to 18 ms as it should; - the strength range is not ok. Rework on it, in order to make it work. Signed-off-by: Mauro Carvalho Chehab --- drivers/media/common/tuners/tuner-xc2028.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'drivers/media/common/tuners/tuner-xc2028.c') diff --git a/drivers/media/common/tuners/tuner-xc2028.c b/drivers/media/common/tuners/tuner-xc2028.c index 9638a69f36b..42fdf5c5709 100644 --- a/drivers/media/common/tuners/tuner-xc2028.c +++ b/drivers/media/common/tuners/tuner-xc2028.c @@ -903,7 +903,7 @@ static int xc2028_signal(struct dvb_frontend *fe, u16 *strength) { struct xc2028_data *priv = fe->tuner_priv; u16 frq_lock, signal = 0; - int rc; + int rc, i; tuner_dbg("%s called\n", __func__); @@ -914,21 +914,28 @@ static int xc2028_signal(struct dvb_frontend *fe, u16 *strength) mutex_lock(&priv->lock); /* Sync Lock Indicator */ - rc = xc2028_get_reg(priv, XREG_LOCK, &frq_lock); - if (rc < 0) - goto ret; + for (i = 0; i < 3; i++) { + rc = xc2028_get_reg(priv, XREG_LOCK, &frq_lock); + if (rc < 0) + goto ret; - /* Frequency is locked */ - if (frq_lock == 1) - signal = 1 << 11; + if (frq_lock) + break; + msleep(6); + } + + /* Frequency was not locked */ + if (frq_lock == 2) + goto ret; /* Get SNR of the video signal */ rc = xc2028_get_reg(priv, XREG_SNR, &signal); if (rc < 0) goto ret; - /* Use both frq_lock and signal to generate the result */ - signal = signal || ((signal & 0x07) << 12); + /* Signal level is 3 bits only */ + + signal = ((1 << 12) - 1) | ((signal & 0x07) << 12); ret: mutex_unlock(&priv->lock); -- cgit v1.2.3-70-g09d2 From 1d432a3d7783b0b86ff08a111b7a4bea550fc4a2 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Wed, 4 Jul 2012 02:33:55 -0300 Subject: [media] tuner, xc2028: add support for get_afc() Implement API support to return AFC frequency shift, as this device supports it. The only other driver that implements it is tda9887, and the frequency there is reported in Hz. So, use Hz also for this tuner. Signed-off-by: Mauro Carvalho Chehab --- drivers/media/common/tuners/tuner-xc2028.c | 46 +++++++++++++++++++++++++++++- drivers/media/dvb/dvb-core/dvb_frontend.h | 1 + drivers/media/video/tuner-core.c | 11 +++++++ 3 files changed, 57 insertions(+), 1 deletion(-) (limited to 'drivers/media/common/tuners/tuner-xc2028.c') diff --git a/drivers/media/common/tuners/tuner-xc2028.c b/drivers/media/common/tuners/tuner-xc2028.c index 42fdf5c5709..4857e86259a 100644 --- a/drivers/media/common/tuners/tuner-xc2028.c +++ b/drivers/media/common/tuners/tuner-xc2028.c @@ -924,7 +924,7 @@ static int xc2028_signal(struct dvb_frontend *fe, u16 *strength) msleep(6); } - /* Frequency was not locked */ + /* Frequency didn't lock */ if (frq_lock == 2) goto ret; @@ -947,6 +947,49 @@ ret: return rc; } +static int xc2028_get_afc(struct dvb_frontend *fe, s32 *afc) +{ + struct xc2028_data *priv = fe->tuner_priv; + int i, rc; + u16 frq_lock = 0; + s16 afc_reg = 0; + + rc = check_device_status(priv); + if (rc < 0) + return rc; + + mutex_lock(&priv->lock); + + /* Sync Lock Indicator */ + for (i = 0; i < 3; i++) { + rc = xc2028_get_reg(priv, XREG_LOCK, &frq_lock); + if (rc < 0) + goto ret; + + if (frq_lock) + break; + msleep(6); + } + + /* Frequency didn't lock */ + if (frq_lock == 2) + goto ret; + + /* Get AFC */ + rc = xc2028_get_reg(priv, XREG_FREQ_ERROR, &afc_reg); + if (rc < 0) + return rc; + + *afc = afc_reg * 15625; /* Hz */ + + tuner_dbg("AFC is %d Hz\n", *afc); + +ret: + mutex_unlock(&priv->lock); + + return rc; +} + #define DIV 15625 static int generic_set_freq(struct dvb_frontend *fe, u32 freq /* in HZ */, @@ -1392,6 +1435,7 @@ static const struct dvb_tuner_ops xc2028_dvb_tuner_ops = { .release = xc2028_dvb_release, .get_frequency = xc2028_get_frequency, .get_rf_strength = xc2028_signal, + .get_afc = xc2028_get_afc, .set_params = xc2028_set_params, .sleep = xc2028_sleep, }; diff --git a/drivers/media/dvb/dvb-core/dvb_frontend.h b/drivers/media/dvb/dvb-core/dvb_frontend.h index e929d5697b8..7c64c09103a 100644 --- a/drivers/media/dvb/dvb-core/dvb_frontend.h +++ b/drivers/media/dvb/dvb-core/dvb_frontend.h @@ -220,6 +220,7 @@ struct dvb_tuner_ops { #define TUNER_STATUS_STEREO 2 int (*get_status)(struct dvb_frontend *fe, u32 *status); int (*get_rf_strength)(struct dvb_frontend *fe, u16 *strength); + int (*get_afc)(struct dvb_frontend *fe, s32 *afc); /** These are provided separately from set_params in order to facilitate silicon * tuners which require sophisticated tuning loops, controlling each parameter separately. */ diff --git a/drivers/media/video/tuner-core.c b/drivers/media/video/tuner-core.c index 98adeeebb8b..b5a819af2b8 100644 --- a/drivers/media/video/tuner-core.c +++ b/drivers/media/video/tuner-core.c @@ -228,6 +228,16 @@ static int fe_has_signal(struct dvb_frontend *fe) return strength; } +static int fe_get_afc(struct dvb_frontend *fe) +{ + s32 afc = 0; + + if (fe->ops.tuner_ops.get_afc) + fe->ops.tuner_ops.get_afc(fe, &afc); + + return 0; +} + static int fe_set_config(struct dvb_frontend *fe, void *priv_cfg) { struct dvb_tuner_ops *fe_tuner_ops = &fe->ops.tuner_ops; @@ -247,6 +257,7 @@ static struct analog_demod_ops tuner_analog_ops = { .set_params = fe_set_params, .standby = fe_standby, .has_signal = fe_has_signal, + .get_afc = fe_get_afc, .set_config = fe_set_config, .tuner_status = tuner_status }; -- cgit v1.2.3-70-g09d2 From ab9cbcd36ce2f2d10de1610abeaa89ee0b619ae7 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 26 Jun 2012 15:34:22 -0300 Subject: [media] tuner-xc2028: tag the usual firmwares to help dracut When tuner-xc2028 is not compiled as a module, dracut will need to copy the firmware inside the initfs image. So, use MODULE_FIRMWARE() to indicate such need. Signed-off-by: Mauro Carvalho Chehab --- drivers/media/common/tuners/tuner-xc2028.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/media/common/tuners/tuner-xc2028.c') diff --git a/drivers/media/common/tuners/tuner-xc2028.c b/drivers/media/common/tuners/tuner-xc2028.c index 4857e86259a..f88f948efee 100644 --- a/drivers/media/common/tuners/tuner-xc2028.c +++ b/drivers/media/common/tuners/tuner-xc2028.c @@ -1506,3 +1506,5 @@ MODULE_DESCRIPTION("Xceive xc2028/xc3028 tuner driver"); MODULE_AUTHOR("Michel Ludwig "); MODULE_AUTHOR("Mauro Carvalho Chehab "); MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(XC2028_DEFAULT_FIRMWARE); +MODULE_FIRMWARE(XC3028L_DEFAULT_FIRMWARE); -- cgit v1.2.3-70-g09d2 From 3a495ed77a2f582d860472cdfca815c1dc4a5d02 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 21 Jul 2012 04:32:38 -0300 Subject: [media] tuner-xc2028: fix "=" vs "==" typo We intended to do a compare here, not an assignment. Signed-off-by: Dan Carpenter Signed-off-by: Mauro Carvalho Chehab --- drivers/media/common/tuners/tuner-xc2028.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/common/tuners/tuner-xc2028.c') diff --git a/drivers/media/common/tuners/tuner-xc2028.c b/drivers/media/common/tuners/tuner-xc2028.c index f88f948efee..9e602856b2f 100644 --- a/drivers/media/common/tuners/tuner-xc2028.c +++ b/drivers/media/common/tuners/tuner-xc2028.c @@ -756,7 +756,7 @@ retry: * No need to reload base firmware if it matches and if the tuner * is not at sleep mode */ - if ((priv->state = XC2028_ACTIVE) && + if ((priv->state == XC2028_ACTIVE) && (((BASE | new_fw.type) & BASE_TYPES) == (priv->cur_fw.type & BASE_TYPES))) { tuner_dbg("BASE firmware not changed.\n"); -- cgit v1.2.3-70-g09d2 From f088ccd6404ca5966104afcae319384f41a0b8cb Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 21 Jul 2012 04:32:59 -0300 Subject: [media] tuner-xc2028: unlock on error in xc2028_get_afc() We need to do a mutex_unlock(&priv->lock) before returning. Signed-off-by: Dan Carpenter Signed-off-by: Mauro Carvalho Chehab --- drivers/media/common/tuners/tuner-xc2028.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/common/tuners/tuner-xc2028.c') diff --git a/drivers/media/common/tuners/tuner-xc2028.c b/drivers/media/common/tuners/tuner-xc2028.c index 9e602856b2f..ea0550eafe7 100644 --- a/drivers/media/common/tuners/tuner-xc2028.c +++ b/drivers/media/common/tuners/tuner-xc2028.c @@ -978,7 +978,7 @@ static int xc2028_get_afc(struct dvb_frontend *fe, s32 *afc) /* Get AFC */ rc = xc2028_get_reg(priv, XREG_FREQ_ERROR, &afc_reg); if (rc < 0) - return rc; + goto ret; *afc = afc_reg * 15625; /* Hz */ -- cgit v1.2.3-70-g09d2