V4L/DVB (7347): tuner-simple: add basic support for digital tuning of hybrid devices

Add entry points used for digital tuning via the dvb_frontend.

Share state data between multiple instances of the driver for hybrid
tuners.

Signed-off-by: Michael Krufky <mkrufky@linuxtv.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
diff --git a/drivers/media/video/tuner-simple.c b/drivers/media/video/tuner-simple.c
index ee5ef86..d6d922c 100644
--- a/drivers/media/video/tuner-simple.c
+++ b/drivers/media/video/tuner-simple.c
@@ -88,14 +88,20 @@
 #define TUNER_PLL_LOCKED   0x40
 #define TUNER_STEREO_MK3   0x04
 
+static DEFINE_MUTEX(tuner_simple_list_mutex);
+static LIST_HEAD(hybrid_tuner_instance_list);
+
 struct tuner_simple_priv {
 	u16 last_div;
+
 	struct tuner_i2c_props i2c_props;
+	struct list_head hybrid_tuner_instance_list;
 
 	unsigned int type;
 	struct tunertype *tun;
 
 	u32 frequency;
+	u32 bandwidth;
 };
 
 /* ---------------------------------------------------------------------- */
@@ -188,6 +194,9 @@
 	case TUNER_PARAM_TYPE_NTSC:
 		name = "ntsc";
 		break;
+	case TUNER_PARAM_TYPE_DIGITAL:
+		name = "digital";
+		break;
 	default:
 		name = "unknown";
 		break;
@@ -687,14 +696,118 @@
 		priv->frequency = params->frequency * 62500;
 		break;
 	}
+	priv->bandwidth = 0;
 
 	return ret;
 }
 
+static int simple_dvb_configure(struct dvb_frontend *fe, u8 *buf,
+				const struct dvb_frontend_parameters *params)
+{
+	struct tuner_simple_priv *priv = fe->tuner_priv;
+	struct tunertype *tun = priv->tun;
+	static struct tuner_params *t_params;
+	u8 config, cb;
+	u32 div;
+	int ret, frequency = params->frequency / 62500;
+
+	t_params = simple_tuner_params(fe, TUNER_PARAM_TYPE_DIGITAL);
+	ret = simple_config_lookup(fe, t_params, &frequency, &config, &cb);
+	if (ret < 0)
+		return ret;
+
+	div = ((frequency + t_params->iffreq) * 62500 + offset +
+	       tun->stepsize/2) / tun->stepsize;
+
+	buf[0] = div >> 8;
+	buf[1] = div & 0xff;
+	buf[2] = config;
+	buf[3] = cb;
+
+	tuner_dbg("%s: div=%d | buf=0x%02x,0x%02x,0x%02x,0x%02x\n",
+		  tun->name, div, buf[0], buf[1], buf[2], buf[3]);
+
+	/* calculate the frequency we set it to */
+	return (div * tun->stepsize) - t_params->iffreq;
+}
+
+static int simple_dvb_calc_regs(struct dvb_frontend *fe,
+				struct dvb_frontend_parameters *params,
+				u8 *buf, int buf_len)
+{
+	struct tuner_simple_priv *priv = fe->tuner_priv;
+	int ret;
+	u32 frequency;
+
+	if (buf_len < 5)
+		return -EINVAL;
+
+	ret = simple_dvb_configure(fe, buf+1, params);
+	if (ret < 0)
+		return ret;
+	else
+		frequency = ret;
+
+	buf[0] = priv->i2c_props.addr;
+
+	priv->frequency = frequency;
+	priv->bandwidth = (fe->ops.info.type == FE_OFDM) ?
+		params->u.ofdm.bandwidth : 0;
+
+	return 5;
+}
+
+static int simple_dvb_set_params(struct dvb_frontend *fe,
+				 struct dvb_frontend_parameters *params)
+{
+	struct tuner_simple_priv *priv = fe->tuner_priv;
+	u32 prev_freq, prev_bw;
+	int ret;
+	u8 buf[5];
+
+	if (priv->i2c_props.adap == NULL)
+		return -EINVAL;
+
+	prev_freq = priv->frequency;
+	prev_bw   = priv->bandwidth;
+
+	ret = simple_dvb_calc_regs(fe, params, buf, 5);
+	if (ret != 5)
+		goto fail;
+
+	/* put analog demod in standby when tuning digital */
+	if (fe->ops.analog_ops.standby)
+		fe->ops.analog_ops.standby(fe);
+
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+
+	/* buf[0] contains the i2c address, but *
+	 * we already have it in i2c_props.addr */
+	ret = tuner_i2c_xfer_send(&priv->i2c_props, buf+1, 4);
+	if (ret != 4)
+		goto fail;
+
+	return 0;
+fail:
+	/* calc_regs sets frequency and bandwidth. if we failed, unset them */
+	priv->frequency = prev_freq;
+	priv->bandwidth = prev_bw;
+
+	return ret;
+}
 
 static int simple_release(struct dvb_frontend *fe)
 {
-	kfree(fe->tuner_priv);
+	struct tuner_simple_priv *priv = fe->tuner_priv;
+
+	mutex_lock(&tuner_simple_list_mutex);
+
+	if (priv)
+		hybrid_tuner_release_state(priv);
+
+	mutex_unlock(&tuner_simple_list_mutex);
+
 	fe->tuner_priv = NULL;
 
 	return 0;
@@ -707,10 +820,20 @@
 	return 0;
 }
 
+static int simple_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
+{
+	struct tuner_simple_priv *priv = fe->tuner_priv;
+	*bandwidth = priv->bandwidth;
+	return 0;
+}
+
 static struct dvb_tuner_ops simple_tuner_ops = {
 	.set_analog_params = simple_set_params,
+	.set_params        = simple_dvb_set_params,
+	.calc_regs         = simple_dvb_calc_regs,
 	.release           = simple_release,
 	.get_frequency     = simple_get_frequency,
+	.get_bandwidth     = simple_get_bandwidth,
 	.get_status        = simple_get_status,
 	.get_rf_strength   = simple_get_rf_strength,
 };
@@ -721,6 +844,7 @@
 					 unsigned int type)
 {
 	struct tuner_simple_priv *priv = NULL;
+	int instance;
 
 	if (type >= tuner_count) {
 		printk(KERN_WARNING "%s: invalid tuner type: %d (max: %d)\n",
@@ -728,22 +852,34 @@
 		return NULL;
 	}
 
-	priv = kzalloc(sizeof(struct tuner_simple_priv), GFP_KERNEL);
-	if (priv == NULL)
+	mutex_lock(&tuner_simple_list_mutex);
+
+	instance = hybrid_tuner_request_state(struct tuner_simple_priv, priv,
+					      hybrid_tuner_instance_list,
+					      i2c_adap, i2c_addr,
+					      "tuner-simple");
+	switch (instance) {
+	case 0:
+		mutex_unlock(&tuner_simple_list_mutex);
 		return NULL;
-	fe->tuner_priv = priv;
+		break;
+	case 1:
+		fe->tuner_priv = priv;
 
-	priv->i2c_props.addr = i2c_addr;
-	priv->i2c_props.adap = i2c_adap;
-	priv->i2c_props.name = "tuner-simple";
+		priv->type = type;
+		priv->tun  = &tuners[type];
+		break;
+	default:
+		fe->tuner_priv = priv;
+		break;
+	}
 
-	priv->type = type;
-	priv->tun  = &tuners[type];
+	mutex_unlock(&tuner_simple_list_mutex);
 
 	memcpy(&fe->ops.tuner_ops, &simple_tuner_ops,
 	       sizeof(struct dvb_tuner_ops));
 
-	tuner_info("type set to %d (%s)\n", priv->type, priv->tun->name);
+	tuner_info("type set to %d (%s)\n", type, priv->tun->name);
 
 	strlcpy(fe->ops.tuner_ops.info.name, priv->tun->name,
 		sizeof(fe->ops.tuner_ops.info.name));
diff --git a/include/media/tuner-types.h b/include/media/tuner-types.h
index b201371..4b5e5cf 100644
--- a/include/media/tuner-types.h
+++ b/include/media/tuner-types.h
@@ -6,10 +6,11 @@
 #define __TUNER_TYPES_H__
 
 enum param_type {
-	TUNER_PARAM_TYPE_RADIO, \
-	TUNER_PARAM_TYPE_PAL, \
-	TUNER_PARAM_TYPE_SECAM, \
-	TUNER_PARAM_TYPE_NTSC
+	TUNER_PARAM_TYPE_RADIO,
+	TUNER_PARAM_TYPE_PAL,
+	TUNER_PARAM_TYPE_SECAM,
+	TUNER_PARAM_TYPE_NTSC,
+	TUNER_PARAM_TYPE_DIGITAL,
 };
 
 struct tuner_range {
@@ -105,6 +106,7 @@
 	   the SECAM-L/L' standards. Range: -16:+15 */
 	signed int default_top_secam_high:5;
 
+	u16 iffreq;
 
 	unsigned int count;
 	struct tuner_range *ranges;
@@ -114,6 +116,10 @@
 	char *name;
 	unsigned int count;
 	struct tuner_params *params;
+
+	u16 min;
+	u16 max;
+	u16 stepsize;
 };
 
 extern struct tunertype tuners[];