[media] radio-si4713: convert to the control framework

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Acked-by: Eduardo Valentin <edubezval@gmail.com>
Tested-by: Eduardo Valentin <edubezval@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c
index 633c545..f8c6137 100644
--- a/drivers/media/radio/radio-si4713.c
+++ b/drivers/media/radio/radio-si4713.c
@@ -76,61 +76,6 @@
 	return 0;
 }
 
-/* radio_si4713_queryctrl - enumerate control items */
-static int radio_si4713_queryctrl(struct file *file, void *priv,
-				  struct v4l2_queryctrl *qc)
-{
-	/* Must be sorted from low to high control ID! */
-	static const u32 user_ctrls[] = {
-		V4L2_CID_USER_CLASS,
-		V4L2_CID_AUDIO_MUTE,
-		0
-	};
-
-	/* Must be sorted from low to high control ID! */
-	static const u32 fmtx_ctrls[] = {
-		V4L2_CID_FM_TX_CLASS,
-		V4L2_CID_RDS_TX_DEVIATION,
-		V4L2_CID_RDS_TX_PI,
-		V4L2_CID_RDS_TX_PTY,
-		V4L2_CID_RDS_TX_PS_NAME,
-		V4L2_CID_RDS_TX_RADIO_TEXT,
-		V4L2_CID_AUDIO_LIMITER_ENABLED,
-		V4L2_CID_AUDIO_LIMITER_RELEASE_TIME,
-		V4L2_CID_AUDIO_LIMITER_DEVIATION,
-		V4L2_CID_AUDIO_COMPRESSION_ENABLED,
-		V4L2_CID_AUDIO_COMPRESSION_GAIN,
-		V4L2_CID_AUDIO_COMPRESSION_THRESHOLD,
-		V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME,
-		V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME,
-		V4L2_CID_PILOT_TONE_ENABLED,
-		V4L2_CID_PILOT_TONE_DEVIATION,
-		V4L2_CID_PILOT_TONE_FREQUENCY,
-		V4L2_CID_TUNE_PREEMPHASIS,
-		V4L2_CID_TUNE_POWER_LEVEL,
-		V4L2_CID_TUNE_ANTENNA_CAPACITOR,
-		0
-	};
-	static const u32 *ctrl_classes[] = {
-		user_ctrls,
-		fmtx_ctrls,
-		NULL
-	};
-	struct radio_si4713_device *rsdev;
-
-	rsdev = video_get_drvdata(video_devdata(file));
-
-	qc->id = v4l2_ctrl_next(ctrl_classes, qc->id);
-	if (qc->id == 0)
-		return -EINVAL;
-
-	if (qc->id == V4L2_CID_USER_CLASS || qc->id == V4L2_CID_FM_TX_CLASS)
-		return v4l2_ctrl_query_fill(qc, 0, 0, 0, 0);
-
-	return v4l2_device_call_until_err(&rsdev->v4l2_dev, 0, core,
-					  queryctrl, qc);
-}
-
 /*
  * v4l2 ioctl call backs.
  * we are just a wrapper for v4l2_sub_devs.
@@ -140,34 +85,6 @@
 	return &((struct radio_si4713_device *)video_drvdata(file))->v4l2_dev;
 }
 
-static int radio_si4713_g_ext_ctrls(struct file *file, void *p,
-				    struct v4l2_ext_controls *vecs)
-{
-	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
-					  g_ext_ctrls, vecs);
-}
-
-static int radio_si4713_s_ext_ctrls(struct file *file, void *p,
-				    struct v4l2_ext_controls *vecs)
-{
-	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
-					  s_ext_ctrls, vecs);
-}
-
-static int radio_si4713_g_ctrl(struct file *file, void *p,
-			       struct v4l2_control *vc)
-{
-	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
-					  g_ctrl, vc);
-}
-
-static int radio_si4713_s_ctrl(struct file *file, void *p,
-			       struct v4l2_control *vc)
-{
-	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
-					  s_ctrl, vc);
-}
-
 static int radio_si4713_g_modulator(struct file *file, void *p,
 				    struct v4l2_modulator *vm)
 {
@@ -205,11 +122,6 @@
 
 static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = {
 	.vidioc_querycap	= radio_si4713_querycap,
-	.vidioc_queryctrl	= radio_si4713_queryctrl,
-	.vidioc_g_ext_ctrls	= radio_si4713_g_ext_ctrls,
-	.vidioc_s_ext_ctrls	= radio_si4713_s_ext_ctrls,
-	.vidioc_g_ctrl		= radio_si4713_g_ctrl,
-	.vidioc_s_ctrl		= radio_si4713_s_ctrl,
 	.vidioc_g_modulator	= radio_si4713_g_modulator,
 	.vidioc_s_modulator	= radio_si4713_s_modulator,
 	.vidioc_g_frequency	= radio_si4713_g_frequency,
@@ -274,6 +186,7 @@
 
 	rsdev->radio_dev = radio_si4713_vdev_template;
 	rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev;
+	rsdev->radio_dev.ctrl_handler = sd->ctrl_handler;
 	/* Serialize all access to the si4713 */
 	rsdev->radio_dev.lock = &rsdev->lock;
 	video_set_drvdata(&rsdev->radio_dev, rsdev);
diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713-i2c.c
index 56c9241..fe16088 100644
--- a/drivers/media/radio/si4713-i2c.c
+++ b/drivers/media/radio/si4713-i2c.c
@@ -52,8 +52,6 @@
 
 #define DEFAULT_RDS_PI			0x00
 #define DEFAULT_RDS_PTY			0x00
-#define DEFAULT_RDS_PS_NAME		""
-#define DEFAULT_RDS_RADIO_TEXT		DEFAULT_RDS_PS_NAME
 #define DEFAULT_RDS_DEVIATION		0x00C8
 #define DEFAULT_RDS_PS_REPEAT_COUNT	0x0003
 #define DEFAULT_LIMITER_RTIME		0x1392
@@ -107,7 +105,6 @@
 					(status & SI4713_ERR))
 /* mute definition */
 #define set_mute(p)	((p & 1) | ((p & 1) << 1));
-#define get_mute(p)	(p & 0x01)
 
 #ifdef DEBUG
 #define DBG_BUFFER(device, message, buffer, size)			\
@@ -189,21 +186,6 @@
 	return rval;
 }
 
-static unsigned long dev_to_usecs(int value, unsigned long const array[],
-			int size)
-{
-	int i;
-	int rval = -EINVAL;
-
-	for (i = 0; i < size / 2; i++)
-		if (array[i * 2] == value) {
-			rval = array[(i * 2) + 1];
-			break;
-		}
-
-	return rval;
-}
-
 /* si4713_handler: IRQ handler, just complete work */
 static irqreturn_t si4713_handler(int irq, void *dev)
 {
@@ -787,9 +769,6 @@
 		rval = si4713_write_property(sdev,
 				SI4713_TX_LINE_INPUT_MUTE, mute);
 
-	if (rval >= 0)
-		sdev->mute = get_mute(mute);
-
 	return rval;
 }
 
@@ -830,7 +809,6 @@
 			return rval;
 	}
 
-	strncpy(sdev->rds_info.ps_name, ps_name, MAX_RDS_PS_NAME);
 	return rval;
 }
 
@@ -842,24 +820,23 @@
 	s8 left;
 
 	if (!sdev->power_state)
-		goto copy;
+		return rval;
 
 	rval = si4713_tx_rds_buff(sdev, RDS_BLOCK_CLEAR, 0, 0, 0, &left);
 	if (rval < 0)
 		return rval;
 
 	if (!strlen(rt))
-		goto copy;
+		return rval;
 
 	do {
 		/* RDS spec says that if the last block isn't used,
 		 * then apply a carriage return
 		 */
-		if (t_index < (RDS_RADIOTEXT_INDEX_MAX *
-			RDS_RADIOTEXT_BLK_SIZE)) {
+		if (t_index < (RDS_RADIOTEXT_INDEX_MAX * RDS_RADIOTEXT_BLK_SIZE)) {
 			for (i = 0; i < RDS_RADIOTEXT_BLK_SIZE; i++) {
-				if (!rt[t_index + i] || rt[t_index + i] ==
-					RDS_CARRIAGE_RETURN) {
+				if (!rt[t_index + i] ||
+				    rt[t_index + i] == RDS_CARRIAGE_RETURN) {
 					rt[t_index + i] = RDS_CARRIAGE_RETURN;
 					cr_inserted = 1;
 					break;
@@ -881,13 +858,38 @@
 			break;
 	} while (left > 0);
 
-copy:
-	strncpy(sdev->rds_info.radio_text, rt, MAX_RDS_RADIO_TEXT);
+	return rval;
+}
+
+/*
+ * si4713_update_tune_status - update properties from tx_tune_status
+ * command. Must be called with sdev->mutex held.
+ * @sdev: si4713_device structure for the device we are communicating
+ */
+static int si4713_update_tune_status(struct si4713_device *sdev)
+{
+	int rval;
+	u16 f = 0;
+	u8 p = 0, a = 0, n = 0;
+
+	rval = si4713_tx_tune_status(sdev, 0x00, &f, &p, &a, &n);
+
+	if (rval < 0)
+		goto exit;
+
+/*	TODO: check that power_level and antenna_capacitor really are not
+	changed by the hardware. If they are, then these controls should become
+	volatiles.
+	sdev->power_level = p;
+	sdev->antenna_capacitor = a;*/
+	sdev->tune_rnl = n;
+
+exit:
 	return rval;
 }
 
 static int si4713_choose_econtrol_action(struct si4713_device *sdev, u32 id,
-		u32 **shadow, s32 *bit, s32 *mask, u16 *property, int *mul,
+		s32 *bit, s32 *mask, u16 *property, int *mul,
 		unsigned long **table, int *size)
 {
 	s32 rval = 0;
@@ -897,157 +899,71 @@
 	case V4L2_CID_RDS_TX_PI:
 		*property = SI4713_TX_RDS_PI;
 		*mul = 1;
-		*shadow = &sdev->rds_info.pi;
 		break;
 	case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD:
 		*property = SI4713_TX_ACOMP_THRESHOLD;
 		*mul = 1;
-		*shadow = &sdev->acomp_info.threshold;
 		break;
 	case V4L2_CID_AUDIO_COMPRESSION_GAIN:
 		*property = SI4713_TX_ACOMP_GAIN;
 		*mul = 1;
-		*shadow = &sdev->acomp_info.gain;
 		break;
 	case V4L2_CID_PILOT_TONE_FREQUENCY:
 		*property = SI4713_TX_PILOT_FREQUENCY;
 		*mul = 1;
-		*shadow = &sdev->pilot_info.frequency;
 		break;
 	case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME:
 		*property = SI4713_TX_ACOMP_ATTACK_TIME;
 		*mul = ATTACK_TIME_UNIT;
-		*shadow = &sdev->acomp_info.attack_time;
 		break;
 	case V4L2_CID_PILOT_TONE_DEVIATION:
 		*property = SI4713_TX_PILOT_DEVIATION;
 		*mul = 10;
-		*shadow = &sdev->pilot_info.deviation;
 		break;
 	case V4L2_CID_AUDIO_LIMITER_DEVIATION:
 		*property = SI4713_TX_AUDIO_DEVIATION;
 		*mul = 10;
-		*shadow = &sdev->limiter_info.deviation;
 		break;
 	case V4L2_CID_RDS_TX_DEVIATION:
 		*property = SI4713_TX_RDS_DEVIATION;
 		*mul = 1;
-		*shadow = &sdev->rds_info.deviation;
 		break;
 
 	case V4L2_CID_RDS_TX_PTY:
 		*property = SI4713_TX_RDS_PS_MISC;
 		*bit = 5;
 		*mask = 0x1F << 5;
-		*shadow = &sdev->rds_info.pty;
 		break;
 	case V4L2_CID_AUDIO_LIMITER_ENABLED:
 		*property = SI4713_TX_ACOMP_ENABLE;
 		*bit = 1;
 		*mask = 1 << 1;
-		*shadow = &sdev->limiter_info.enabled;
 		break;
 	case V4L2_CID_AUDIO_COMPRESSION_ENABLED:
 		*property = SI4713_TX_ACOMP_ENABLE;
 		*bit = 0;
 		*mask = 1 << 0;
-		*shadow = &sdev->acomp_info.enabled;
 		break;
 	case V4L2_CID_PILOT_TONE_ENABLED:
 		*property = SI4713_TX_COMPONENT_ENABLE;
 		*bit = 0;
 		*mask = 1 << 0;
-		*shadow = &sdev->pilot_info.enabled;
 		break;
 
 	case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME:
 		*property = SI4713_TX_LIMITER_RELEASE_TIME;
 		*table = limiter_times;
 		*size = ARRAY_SIZE(limiter_times);
-		*shadow = &sdev->limiter_info.release_time;
 		break;
 	case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME:
 		*property = SI4713_TX_ACOMP_RELEASE_TIME;
 		*table = acomp_rtimes;
 		*size = ARRAY_SIZE(acomp_rtimes);
-		*shadow = &sdev->acomp_info.release_time;
 		break;
 	case V4L2_CID_TUNE_PREEMPHASIS:
 		*property = SI4713_TX_PREEMPHASIS;
 		*table = preemphasis_values;
 		*size = ARRAY_SIZE(preemphasis_values);
-		*shadow = &sdev->preemphasis;
-		break;
-
-	default:
-		rval = -EINVAL;
-	}
-
-	return rval;
-}
-
-static int si4713_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);
-
-/* write string property */
-static int si4713_write_econtrol_string(struct si4713_device *sdev,
-				struct v4l2_ext_control *control)
-{
-	struct v4l2_queryctrl vqc;
-	int len;
-	s32 rval = 0;
-
-	vqc.id = control->id;
-	rval = si4713_queryctrl(&sdev->sd, &vqc);
-	if (rval < 0)
-		goto exit;
-
-	switch (control->id) {
-	case V4L2_CID_RDS_TX_PS_NAME: {
-		char ps_name[MAX_RDS_PS_NAME + 1];
-
-		len = control->size - 1;
-		if (len < 0 || len > MAX_RDS_PS_NAME) {
-			rval = -ERANGE;
-			goto exit;
-		}
-		rval = copy_from_user(ps_name, control->string, len);
-		if (rval) {
-			rval = -EFAULT;
-			goto exit;
-		}
-		ps_name[len] = '\0';
-
-		if (strlen(ps_name) % vqc.step) {
-			rval = -ERANGE;
-			goto exit;
-		}
-
-		rval = si4713_set_rds_ps_name(sdev, ps_name);
-	}
-		break;
-
-	case V4L2_CID_RDS_TX_RADIO_TEXT: {
-		char radio_text[MAX_RDS_RADIO_TEXT + 1];
-
-		len = control->size - 1;
-		if (len < 0 || len > MAX_RDS_RADIO_TEXT) {
-			rval = -ERANGE;
-			goto exit;
-		}
-		rval = copy_from_user(radio_text, control->string, len);
-		if (rval) {
-			rval = -EFAULT;
-			goto exit;
-		}
-		radio_text[len] = '\0';
-
-		if (strlen(radio_text) % vqc.step) {
-			rval = -ERANGE;
-			goto exit;
-		}
-
-		rval = si4713_set_rds_radio_text(sdev, radio_text);
-	}
 		break;
 
 	default:
@@ -1055,121 +971,6 @@
 		break;
 	}
 
-exit:
-	return rval;
-}
-
-static int validate_range(struct v4l2_subdev *sd,
-					struct v4l2_ext_control *control)
-{
-	struct v4l2_queryctrl vqc;
-	int rval;
-
-	vqc.id = control->id;
-	rval = si4713_queryctrl(sd, &vqc);
-	if (rval < 0)
-		goto exit;
-
-	if (control->value < vqc.minimum || control->value > vqc.maximum)
-		rval = -ERANGE;
-
-exit:
-	return rval;
-}
-
-/* properties which use tx_tune_power*/
-static int si4713_write_econtrol_tune(struct si4713_device *sdev,
-				struct v4l2_ext_control *control)
-{
-	s32 rval = 0;
-	u8 power, antcap;
-
-	rval = validate_range(&sdev->sd, control);
-	if (rval < 0)
-		return rval;
-
-	switch (control->id) {
-	case V4L2_CID_TUNE_POWER_LEVEL:
-		power = control->value;
-		antcap = sdev->antenna_capacitor;
-		break;
-	case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
-		power = sdev->power_level;
-		antcap = control->value;
-		break;
-	default:
-		return -EINVAL;
-	}
-
-	if (sdev->power_state)
-		rval = si4713_tx_tune_power(sdev, power, antcap);
-
-	if (rval == 0) {
-		sdev->power_level = power;
-		sdev->antenna_capacitor = antcap;
-	}
-
-	return rval;
-}
-
-static int si4713_write_econtrol_integers(struct si4713_device *sdev,
-					struct v4l2_ext_control *control)
-{
-	s32 rval;
-	u32 *shadow = NULL, val = 0;
-	s32 bit = 0, mask = 0;
-	u16 property = 0;
-	int mul = 0;
-	unsigned long *table = NULL;
-	int size = 0;
-
-	rval = validate_range(&sdev->sd, control);
-	if (rval < 0)
-		return rval;
-
-	rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit,
-			&mask, &property, &mul, &table, &size);
-	if (rval < 0)
-		return rval;
-
-	val = control->value;
-	if (mul) {
-		val = control->value / mul;
-	} else if (table) {
-		rval = usecs_to_dev(control->value, table, size);
-		if (rval < 0)
-			return rval;
-		val = rval;
-		rval = 0;
-	}
-
-	if (sdev->power_state) {
-		if (mask) {
-			rval = si4713_read_property(sdev, property, &val);
-			if (rval < 0)
-				return rval;
-			val = set_bits(val, control->value, bit, mask);
-		}
-
-		rval = si4713_write_property(sdev, property, val);
-		if (rval < 0)
-			return rval;
-		if (mask)
-			val = control->value;
-	}
-
-	if (mul) {
-		*shadow = val * mul;
-	} else if (table) {
-		rval = dev_to_usecs(val, table, size);
-		if (rval < 0)
-			return rval;
-		*shadow = rval;
-		rval = 0;
-	} else {
-		*shadow = val;
-	}
-
 	return rval;
 }
 
@@ -1181,110 +982,25 @@
  */
 static int si4713_setup(struct si4713_device *sdev)
 {
-	struct v4l2_ext_control ctrl;
 	struct v4l2_frequency f;
 	struct v4l2_modulator vm;
-	struct si4713_device *tmp;
-	int rval = 0;
-
-	tmp = kmalloc(sizeof(*tmp), GFP_KERNEL);
-	if (!tmp)
-		return -ENOMEM;
-
-	/* Get a local copy to avoid race */
-	memcpy(tmp, sdev, sizeof(*sdev));
-
-	ctrl.id = V4L2_CID_RDS_TX_PI;
-	ctrl.value = tmp->rds_info.pi;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_AUDIO_COMPRESSION_THRESHOLD;
-	ctrl.value = tmp->acomp_info.threshold;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_AUDIO_COMPRESSION_GAIN;
-	ctrl.value = tmp->acomp_info.gain;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_PILOT_TONE_FREQUENCY;
-	ctrl.value = tmp->pilot_info.frequency;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME;
-	ctrl.value = tmp->acomp_info.attack_time;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_PILOT_TONE_DEVIATION;
-	ctrl.value = tmp->pilot_info.deviation;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_AUDIO_LIMITER_DEVIATION;
-	ctrl.value = tmp->limiter_info.deviation;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_RDS_TX_DEVIATION;
-	ctrl.value = tmp->rds_info.deviation;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_RDS_TX_PTY;
-	ctrl.value = tmp->rds_info.pty;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_AUDIO_LIMITER_ENABLED;
-	ctrl.value = tmp->limiter_info.enabled;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_AUDIO_COMPRESSION_ENABLED;
-	ctrl.value = tmp->acomp_info.enabled;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_PILOT_TONE_ENABLED;
-	ctrl.value = tmp->pilot_info.enabled;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_AUDIO_LIMITER_RELEASE_TIME;
-	ctrl.value = tmp->limiter_info.release_time;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME;
-	ctrl.value = tmp->acomp_info.release_time;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_TUNE_PREEMPHASIS;
-	ctrl.value = tmp->preemphasis;
-	rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_RDS_TX_PS_NAME;
-	rval |= si4713_set_rds_ps_name(sdev, tmp->rds_info.ps_name);
-
-	ctrl.id = V4L2_CID_RDS_TX_RADIO_TEXT;
-	rval |= si4713_set_rds_radio_text(sdev, tmp->rds_info.radio_text);
+	int rval;
 
 	/* Device procedure needs to set frequency first */
 	f.tuner = 0;
-	f.frequency = tmp->frequency ? tmp->frequency : DEFAULT_FREQUENCY;
+	f.frequency = sdev->frequency ? sdev->frequency : DEFAULT_FREQUENCY;
 	f.frequency = si4713_to_v4l2(f.frequency);
-	rval |= si4713_s_frequency(&sdev->sd, &f);
-
-	ctrl.id = V4L2_CID_TUNE_POWER_LEVEL;
-	ctrl.value = tmp->power_level;
-	rval |= si4713_write_econtrol_tune(sdev, &ctrl);
-
-	ctrl.id = V4L2_CID_TUNE_ANTENNA_CAPACITOR;
-	ctrl.value = tmp->antenna_capacitor;
-	rval |= si4713_write_econtrol_tune(sdev, &ctrl);
+	rval = si4713_s_frequency(&sdev->sd, &f);
 
 	vm.index = 0;
-	if (tmp->stereo)
+	if (sdev->stereo)
 		vm.txsubchans = V4L2_TUNER_SUB_STEREO;
 	else
 		vm.txsubchans = V4L2_TUNER_SUB_MONO;
-	if (tmp->rds_info.enabled)
+	if (sdev->rds_enabled)
 		vm.txsubchans |= V4L2_TUNER_SUB_RDS;
 	si4713_s_modulator(&sdev->sd, &vm);
 
-	kfree(tmp);
-
 	return rval;
 }
 
@@ -1308,406 +1024,116 @@
 	if (rval < 0)
 		return rval;
 
-	sdev->rds_info.pi = DEFAULT_RDS_PI;
-	sdev->rds_info.pty = DEFAULT_RDS_PTY;
-	sdev->rds_info.deviation = DEFAULT_RDS_DEVIATION;
-	strlcpy(sdev->rds_info.ps_name, DEFAULT_RDS_PS_NAME, MAX_RDS_PS_NAME);
-	strlcpy(sdev->rds_info.radio_text, DEFAULT_RDS_RADIO_TEXT,
-							MAX_RDS_RADIO_TEXT);
-	sdev->rds_info.enabled = 1;
-
-	sdev->limiter_info.release_time = DEFAULT_LIMITER_RTIME;
-	sdev->limiter_info.deviation = DEFAULT_LIMITER_DEV;
-	sdev->limiter_info.enabled = 1;
-
-	sdev->pilot_info.deviation = DEFAULT_PILOT_DEVIATION;
-	sdev->pilot_info.frequency = DEFAULT_PILOT_FREQUENCY;
-	sdev->pilot_info.enabled = 1;
-
-	sdev->acomp_info.release_time = DEFAULT_ACOMP_RTIME;
-	sdev->acomp_info.attack_time = DEFAULT_ACOMP_ATIME;
-	sdev->acomp_info.threshold = DEFAULT_ACOMP_THRESHOLD;
-	sdev->acomp_info.gain = DEFAULT_ACOMP_GAIN;
-	sdev->acomp_info.enabled = 1;
 
 	sdev->frequency = DEFAULT_FREQUENCY;
-	sdev->preemphasis = DEFAULT_PREEMPHASIS;
-	sdev->mute = DEFAULT_MUTE;
-	sdev->power_level = DEFAULT_POWER_LEVEL;
-	sdev->antenna_capacitor = 0;
 	sdev->stereo = 1;
 	sdev->tune_rnl = DEFAULT_TUNE_RNL;
-
-	return rval;
+	return 0;
 }
 
-/* read string property */
-static int si4713_read_econtrol_string(struct si4713_device *sdev,
-				struct v4l2_ext_control *control)
+/* si4713_s_ctrl - set the value of a control */
+static int si4713_s_ctrl(struct v4l2_ctrl *ctrl)
 {
-	s32 rval = 0;
-
-	switch (control->id) {
-	case V4L2_CID_RDS_TX_PS_NAME:
-		if (strlen(sdev->rds_info.ps_name) + 1 > control->size) {
-			control->size = MAX_RDS_PS_NAME + 1;
-			rval = -ENOSPC;
-			goto exit;
-		}
-		rval = copy_to_user(control->string, sdev->rds_info.ps_name,
-					strlen(sdev->rds_info.ps_name) + 1);
-		if (rval)
-			rval = -EFAULT;
-		break;
-
-	case V4L2_CID_RDS_TX_RADIO_TEXT:
-		if (strlen(sdev->rds_info.radio_text) + 1 > control->size) {
-			control->size = MAX_RDS_RADIO_TEXT + 1;
-			rval = -ENOSPC;
-			goto exit;
-		}
-		rval = copy_to_user(control->string, sdev->rds_info.radio_text,
-					strlen(sdev->rds_info.radio_text) + 1);
-		if (rval)
-			rval = -EFAULT;
-		break;
-
-	default:
-		rval = -EINVAL;
-		break;
-	}
-
-exit:
-	return rval;
-}
-
-/*
- * si4713_update_tune_status - update properties from tx_tune_status
- * command.
- * @sdev: si4713_device structure for the device we are communicating
- */
-static int si4713_update_tune_status(struct si4713_device *sdev)
-{
-	int rval;
-	u16 f = 0;
-	u8 p = 0, a = 0, n = 0;
-
-	rval = si4713_tx_tune_status(sdev, 0x00, &f, &p, &a, &n);
-
-	if (rval < 0)
-		goto exit;
-
-	sdev->power_level = p;
-	sdev->antenna_capacitor = a;
-	sdev->tune_rnl = n;
-
-exit:
-	return rval;
-}
-
-/* properties which use tx_tune_status */
-static int si4713_read_econtrol_tune(struct si4713_device *sdev,
-				struct v4l2_ext_control *control)
-{
-	s32 rval = 0;
-
-	if (sdev->power_state) {
-		rval = si4713_update_tune_status(sdev);
-		if (rval < 0)
-			return rval;
-	}
-
-	switch (control->id) {
-	case V4L2_CID_TUNE_POWER_LEVEL:
-		control->value = sdev->power_level;
-		break;
-	case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
-		control->value = sdev->antenna_capacitor;
-		break;
-	default:
-		return -EINVAL;
-	}
-
-	return rval;
-}
-
-static int si4713_read_econtrol_integers(struct si4713_device *sdev,
-				struct v4l2_ext_control *control)
-{
-	s32 rval;
-	u32 *shadow = NULL, val = 0;
+	struct si4713_device *sdev =
+		container_of(ctrl->handler, struct si4713_device, ctrl_handler);
+	u32 val = 0;
 	s32 bit = 0, mask = 0;
 	u16 property = 0;
 	int mul = 0;
 	unsigned long *table = NULL;
 	int size = 0;
+	bool force = false;
+	int c;
+	int ret = 0;
 
-	rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit,
-			&mask, &property, &mul, &table, &size);
-	if (rval < 0)
-		return rval;
-
-	if (sdev->power_state) {
-		rval = si4713_read_property(sdev, property, &val);
-		if (rval < 0)
-			return rval;
-
-		/* Keep negative values for threshold */
-		if (control->id == V4L2_CID_AUDIO_COMPRESSION_THRESHOLD)
-			*shadow = (s16)val;
-		else if (mask)
-			*shadow = get_status_bit(val, bit, mask);
-		else if (mul)
-			*shadow = val * mul;
-		else
-			*shadow = dev_to_usecs(val, table, size);
-	}
-
-	control->value = *shadow;
-
-	return rval;
-}
-
-/*
- * Video4Linux Subdev Interface
- */
-/* si4713_s_ext_ctrls - set extended controls value */
-static int si4713_s_ext_ctrls(struct v4l2_subdev *sd,
-				struct v4l2_ext_controls *ctrls)
-{
-	struct si4713_device *sdev = to_si4713_device(sd);
-	int i;
-
-	if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX)
+	if (ctrl->id != V4L2_CID_AUDIO_MUTE)
 		return -EINVAL;
+	if (ctrl->is_new) {
+		if (ctrl->val) {
+			ret = si4713_set_mute(sdev, ctrl->val);
+			if (!ret)
+				ret = si4713_set_power_state(sdev, POWER_DOWN);
+			return ret;
+		}
+		ret = si4713_set_power_state(sdev, POWER_UP);
+		if (!ret)
+			ret = si4713_set_mute(sdev, ctrl->val);
+		if (!ret)
+			ret = si4713_setup(sdev);
+		if (ret)
+			return ret;
+		force = true;
+	}
 
-	for (i = 0; i < ctrls->count; i++) {
-		int err;
+	if (!sdev->power_state)
+		return 0;
 
-		switch ((ctrls->controls + i)->id) {
+	for (c = 1; !ret && c < ctrl->ncontrols; c++) {
+		ctrl = ctrl->cluster[c];
+
+		if (!force && !ctrl->is_new)
+			continue;
+
+		switch (ctrl->id) {
 		case V4L2_CID_RDS_TX_PS_NAME:
+			ret = si4713_set_rds_ps_name(sdev, ctrl->string);
+			break;
+
 		case V4L2_CID_RDS_TX_RADIO_TEXT:
-			err = si4713_write_econtrol_string(sdev,
-							ctrls->controls + i);
+			ret = si4713_set_rds_radio_text(sdev, ctrl->string);
 			break;
+
 		case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
+			/* don't handle this control if we force setting all
+			 * controls since in that case it will be handled by
+			 * V4L2_CID_TUNE_POWER_LEVEL. */
+			if (force)
+				break;
+			/* fall through */
 		case V4L2_CID_TUNE_POWER_LEVEL:
-			err = si4713_write_econtrol_tune(sdev,
-							ctrls->controls + i);
+			ret = si4713_tx_tune_power(sdev,
+				sdev->tune_pwr_level->val, sdev->tune_ant_cap->val);
+			if (!ret) {
+				/* Make sure we don't set this twice */
+				sdev->tune_ant_cap->is_new = false;
+				sdev->tune_pwr_level->is_new = false;
+			}
 			break;
+
 		default:
-			err = si4713_write_econtrol_integers(sdev,
-							ctrls->controls + i);
-		}
+			ret = si4713_choose_econtrol_action(sdev, ctrl->id, &bit,
+					&mask, &property, &mul, &table, &size);
+			if (ret < 0)
+				break;
 
-		if (err < 0) {
-			ctrls->error_idx = i;
-			return err;
-		}
-	}
+			val = ctrl->val;
+			if (mul) {
+				val = val / mul;
+			} else if (table) {
+				ret = usecs_to_dev(val, table, size);
+				if (ret < 0)
+					break;
+				val = ret;
+				ret = 0;
+			}
 
-	return 0;
-}
+			if (mask) {
+				ret = si4713_read_property(sdev, property, &val);
+				if (ret < 0)
+					break;
+				val = set_bits(val, ctrl->val, bit, mask);
+			}
 
-/* si4713_g_ext_ctrls - get extended controls value */
-static int si4713_g_ext_ctrls(struct v4l2_subdev *sd,
-				struct v4l2_ext_controls *ctrls)
-{
-	struct si4713_device *sdev = to_si4713_device(sd);
-	int i;
-
-	if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX)
-		return -EINVAL;
-
-	for (i = 0; i < ctrls->count; i++) {
-		int err;
-
-		switch ((ctrls->controls + i)->id) {
-		case V4L2_CID_RDS_TX_PS_NAME:
-		case V4L2_CID_RDS_TX_RADIO_TEXT:
-			err = si4713_read_econtrol_string(sdev,
-							ctrls->controls + i);
+			ret = si4713_write_property(sdev, property, val);
+			if (ret < 0)
+				break;
+			if (mask)
+				val = ctrl->val;
 			break;
-		case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
-		case V4L2_CID_TUNE_POWER_LEVEL:
-			err = si4713_read_econtrol_tune(sdev,
-							ctrls->controls + i);
-			break;
-		default:
-			err = si4713_read_econtrol_integers(sdev,
-							ctrls->controls + i);
-		}
-
-		if (err < 0) {
-			ctrls->error_idx = i;
-			return err;
 		}
 	}
 
-	return 0;
-}
-
-/* si4713_queryctrl - enumerate control items */
-static int si4713_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
-{
-	int rval = 0;
-
-	switch (qc->id) {
-	/* User class controls */
-	case V4L2_CID_AUDIO_MUTE:
-		rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, DEFAULT_MUTE);
-		break;
-	/* FM_TX class controls */
-	case V4L2_CID_RDS_TX_PI:
-		rval = v4l2_ctrl_query_fill(qc, 0, 0xFFFF, 1, DEFAULT_RDS_PI);
-		break;
-	case V4L2_CID_RDS_TX_PTY:
-		rval = v4l2_ctrl_query_fill(qc, 0, 31, 1, DEFAULT_RDS_PTY);
-		break;
-	case V4L2_CID_RDS_TX_DEVIATION:
-		rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_DEVIATION,
-						10, DEFAULT_RDS_DEVIATION);
-		break;
-	case V4L2_CID_RDS_TX_PS_NAME:
-		/*
-		 * Report step as 8. From RDS spec, psname
-		 * should be 8. But there are receivers which scroll strings
-		 * sized as 8xN.
-		 */
-		rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_PS_NAME, 8, 0);
-		break;
-	case V4L2_CID_RDS_TX_RADIO_TEXT:
-		/*
-		 * Report step as 32 (2A block). From RDS spec,
-		 * radio text should be 32 for 2A block. But there are receivers
-		 * which scroll strings sized as 32xN. Setting default to 32.
-		 */
-		rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_RADIO_TEXT, 32, 0);
-		break;
-
-	case V4L2_CID_AUDIO_LIMITER_ENABLED:
-		rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
-		break;
-	case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME:
-		rval = v4l2_ctrl_query_fill(qc, 250, MAX_LIMITER_RELEASE_TIME,
-						50, DEFAULT_LIMITER_RTIME);
-		break;
-	case V4L2_CID_AUDIO_LIMITER_DEVIATION:
-		rval = v4l2_ctrl_query_fill(qc, 0, MAX_LIMITER_DEVIATION,
-						10, DEFAULT_LIMITER_DEV);
-		break;
-
-	case V4L2_CID_AUDIO_COMPRESSION_ENABLED:
-		rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
-		break;
-	case V4L2_CID_AUDIO_COMPRESSION_GAIN:
-		rval = v4l2_ctrl_query_fill(qc, 0, MAX_ACOMP_GAIN, 1,
-						DEFAULT_ACOMP_GAIN);
-		break;
-	case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD:
-		rval = v4l2_ctrl_query_fill(qc, MIN_ACOMP_THRESHOLD,
-						MAX_ACOMP_THRESHOLD, 1,
-						DEFAULT_ACOMP_THRESHOLD);
-		break;
-	case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME:
-		rval = v4l2_ctrl_query_fill(qc, 0, MAX_ACOMP_ATTACK_TIME,
-						500, DEFAULT_ACOMP_ATIME);
-		break;
-	case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME:
-		rval = v4l2_ctrl_query_fill(qc, 100000, MAX_ACOMP_RELEASE_TIME,
-						100000, DEFAULT_ACOMP_RTIME);
-		break;
-
-	case V4L2_CID_PILOT_TONE_ENABLED:
-		rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
-		break;
-	case V4L2_CID_PILOT_TONE_DEVIATION:
-		rval = v4l2_ctrl_query_fill(qc, 0, MAX_PILOT_DEVIATION,
-						10, DEFAULT_PILOT_DEVIATION);
-		break;
-	case V4L2_CID_PILOT_TONE_FREQUENCY:
-		rval = v4l2_ctrl_query_fill(qc, 0, MAX_PILOT_FREQUENCY,
-						1, DEFAULT_PILOT_FREQUENCY);
-		break;
-
-	case V4L2_CID_TUNE_PREEMPHASIS:
-		rval = v4l2_ctrl_query_fill(qc, V4L2_PREEMPHASIS_DISABLED,
-						V4L2_PREEMPHASIS_75_uS, 1,
-						V4L2_PREEMPHASIS_50_uS);
-		break;
-	case V4L2_CID_TUNE_POWER_LEVEL:
-		rval = v4l2_ctrl_query_fill(qc, 0, 120, 1, DEFAULT_POWER_LEVEL);
-		break;
-	case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
-		rval = v4l2_ctrl_query_fill(qc, 0, 191, 1, 0);
-		break;
-	default:
-		rval = -EINVAL;
-		break;
-	}
-
-	return rval;
-}
-
-/* si4713_g_ctrl - get the value of a control */
-static int si4713_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
-{
-	struct si4713_device *sdev = to_si4713_device(sd);
-	int rval = 0;
-
-	if (!sdev)
-		return -ENODEV;
-
-	if (sdev->power_state) {
-		rval = si4713_read_property(sdev, SI4713_TX_LINE_INPUT_MUTE,
-						&sdev->mute);
-
-		if (rval < 0)
-			return rval;
-	}
-
-	switch (ctrl->id) {
-	case V4L2_CID_AUDIO_MUTE:
-		ctrl->value = get_mute(sdev->mute);
-		break;
-	}
-
-	return rval;
-}
-
-/* si4713_s_ctrl - set the value of a control */
-static int si4713_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
-{
-	struct si4713_device *sdev = to_si4713_device(sd);
-	int rval = 0;
-
-	if (!sdev)
-		return -ENODEV;
-
-	switch (ctrl->id) {
-	case V4L2_CID_AUDIO_MUTE:
-		if (ctrl->value) {
-			rval = si4713_set_mute(sdev, ctrl->value);
-			if (rval < 0)
-				goto exit;
-
-			rval = si4713_set_power_state(sdev, POWER_DOWN);
-		} else {
-			rval = si4713_set_power_state(sdev, POWER_UP);
-			if (rval < 0)
-				goto exit;
-
-			rval = si4713_setup(sdev);
-			if (rval < 0)
-				goto exit;
-
-			rval = si4713_set_mute(sdev, ctrl->value);
-		}
-		break;
-	}
-
-exit:
-	return rval;
+	return ret;
 }
 
 /* si4713_ioctl - deal with private ioctls (only rnl for now) */
@@ -1746,15 +1172,6 @@
 	return rval;
 }
 
-static const struct v4l2_subdev_core_ops si4713_subdev_core_ops = {
-	.queryctrl	= si4713_queryctrl,
-	.g_ext_ctrls	= si4713_g_ext_ctrls,
-	.s_ext_ctrls	= si4713_s_ext_ctrls,
-	.g_ctrl		= si4713_g_ctrl,
-	.s_ctrl		= si4713_s_ctrl,
-	.ioctl		= si4713_ioctl,
-};
-
 /* si4713_g_modulator - get modulator attributes */
 static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm)
 {
@@ -1784,7 +1201,6 @@
 			return rval;
 
 		sdev->stereo = get_status_bit(comp_en, 1, 1 << 1);
-		sdev->rds_info.enabled = get_status_bit(comp_en, 2, 1 << 2);
 	}
 
 	/* Report current audio mode: mono or stereo */
@@ -1794,7 +1210,7 @@
 		vm->txsubchans = V4L2_TUNER_SUB_MONO;
 
 	/* Report rds feature status */
-	if (sdev->rds_info.enabled)
+	if (sdev->rds_enabled)
 		vm->txsubchans |= V4L2_TUNER_SUB_RDS;
 	else
 		vm->txsubchans &= ~V4L2_TUNER_SUB_RDS;
@@ -1842,7 +1258,7 @@
 	}
 
 	sdev->stereo = stereo;
-	sdev->rds_info.enabled = rds;
+	sdev->rds_enabled = rds;
 
 	return rval;
 }
@@ -1897,6 +1313,14 @@
 	return rval;
 }
 
+static const struct v4l2_ctrl_ops si4713_ctrl_ops = {
+	.s_ctrl = si4713_s_ctrl,
+};
+
+static const struct v4l2_subdev_core_ops si4713_subdev_core_ops = {
+	.ioctl		= si4713_ioctl,
+};
+
 static const struct v4l2_subdev_tuner_ops si4713_subdev_tuner_ops = {
 	.g_frequency	= si4713_g_frequency,
 	.s_frequency	= si4713_s_frequency,
@@ -1918,6 +1342,7 @@
 {
 	struct si4713_device *sdev;
 	struct si4713_platform_data *pdata = client->dev.platform_data;
+	struct v4l2_ctrl_handler *hdl;
 	int rval, i;
 
 	sdev = kzalloc(sizeof *sdev, GFP_KERNEL);
@@ -1953,6 +1378,82 @@
 
 	init_completion(&sdev->work);
 
+	hdl = &sdev->ctrl_handler;
+	v4l2_ctrl_handler_init(hdl, 20);
+	sdev->mute = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_MUTE, 0, 1, 1, DEFAULT_MUTE);
+
+	sdev->rds_pi = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_RDS_TX_PI, 0, 0xffff, 1, DEFAULT_RDS_PI);
+	sdev->rds_pty = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_RDS_TX_PTY, 0, 31, 1, DEFAULT_RDS_PTY);
+	sdev->rds_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_RDS_TX_DEVIATION, 0, MAX_RDS_DEVIATION,
+			10, DEFAULT_RDS_DEVIATION);
+	/*
+	 * Report step as 8. From RDS spec, psname
+	 * should be 8. But there are receivers which scroll strings
+	 * sized as 8xN.
+	 */
+	sdev->rds_ps_name = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_RDS_TX_PS_NAME, 0, MAX_RDS_PS_NAME, 8, 0);
+	/*
+	 * Report step as 32 (2A block). From RDS spec,
+	 * radio text should be 32 for 2A block. But there are receivers
+	 * which scroll strings sized as 32xN. Setting default to 32.
+	 */
+	sdev->rds_radio_text = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_RDS_TX_RADIO_TEXT, 0, MAX_RDS_RADIO_TEXT, 32, 0);
+
+	sdev->limiter_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_LIMITER_ENABLED, 0, 1, 1, 1);
+	sdev->limiter_release_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_LIMITER_RELEASE_TIME, 250,
+			MAX_LIMITER_RELEASE_TIME, 10, DEFAULT_LIMITER_RTIME);
+	sdev->limiter_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_LIMITER_DEVIATION, 0,
+			MAX_LIMITER_DEVIATION, 10, DEFAULT_LIMITER_DEV);
+
+	sdev->compression_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_COMPRESSION_ENABLED, 0, 1, 1, 1);
+	sdev->compression_gain = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_COMPRESSION_GAIN, 0, MAX_ACOMP_GAIN, 1,
+			DEFAULT_ACOMP_GAIN);
+	sdev->compression_threshold = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_COMPRESSION_THRESHOLD, MIN_ACOMP_THRESHOLD,
+			MAX_ACOMP_THRESHOLD, 1,
+			DEFAULT_ACOMP_THRESHOLD);
+	sdev->compression_attack_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME, 0,
+			MAX_ACOMP_ATTACK_TIME, 500, DEFAULT_ACOMP_ATIME);
+	sdev->compression_release_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME, 100000,
+			MAX_ACOMP_RELEASE_TIME, 100000, DEFAULT_ACOMP_RTIME);
+
+	sdev->pilot_tone_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_PILOT_TONE_ENABLED, 0, 1, 1, 1);
+	sdev->pilot_tone_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_PILOT_TONE_DEVIATION, 0, MAX_PILOT_DEVIATION,
+			10, DEFAULT_PILOT_DEVIATION);
+	sdev->pilot_tone_freq = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_PILOT_TONE_FREQUENCY, 0, MAX_PILOT_FREQUENCY,
+			1, DEFAULT_PILOT_FREQUENCY);
+
+	sdev->tune_preemphasis = v4l2_ctrl_new_std_menu(hdl, &si4713_ctrl_ops,
+			V4L2_CID_TUNE_PREEMPHASIS,
+			V4L2_PREEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_50_uS);
+	sdev->tune_pwr_level = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_TUNE_POWER_LEVEL, 0, 120, 1, DEFAULT_POWER_LEVEL);
+	sdev->tune_ant_cap = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+			V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0, 191, 1, 0);
+
+	if (hdl->error) {
+		rval = hdl->error;
+		goto free_ctrls;
+	}
+	v4l2_ctrl_cluster(20, &sdev->mute);
+	sdev->sd.ctrl_handler = hdl;
+
 	if (client->irq) {
 		rval = request_irq(client->irq,
 			si4713_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED,
@@ -1977,6 +1478,8 @@
 free_irq:
 	if (client->irq)
 		free_irq(client->irq, sdev);
+free_ctrls:
+	v4l2_ctrl_handler_free(hdl);
 put_reg:
 	regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies);
 free_gpio:
@@ -2001,6 +1504,7 @@
 		free_irq(client->irq, sdev);
 
 	v4l2_device_unregister_subdev(sd);
+	v4l2_ctrl_handler_free(sd->ctrl_handler);
 	regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies);
 	if (gpio_is_valid(sdev->gpio_reset))
 		gpio_free(sdev->gpio_reset);
diff --git a/drivers/media/radio/si4713-i2c.h b/drivers/media/radio/si4713-i2c.h
index 979828d..25cdea2 100644
--- a/drivers/media/radio/si4713-i2c.h
+++ b/drivers/media/radio/si4713-i2c.h
@@ -16,6 +16,7 @@
 #define SI4713_I2C_H
 
 #include <media/v4l2-subdev.h>
+#include <media/v4l2-ctrls.h>
 #include <media/si4713.h>
 
 #define SI4713_PRODUCT_NUMBER		0x0D
@@ -160,56 +161,33 @@
 #define POWER_UP			0x01
 #define POWER_DOWN			0x00
 
-struct rds_info {
-	u32 pi;
 #define MAX_RDS_PTY			31
-	u32 pty;
 #define MAX_RDS_DEVIATION		90000
-	u32 deviation;
+
 /*
  * PSNAME is known to be defined as 8 character sized (RDS Spec).
  * However, there is receivers which scroll PSNAME 8xN sized.
  */
 #define MAX_RDS_PS_NAME			96
-	u8 ps_name[MAX_RDS_PS_NAME + 1];
+
 /*
  * MAX_RDS_RADIO_TEXT is known to be defined as 32 (2A group) or 64 (2B group)
  * character sized (RDS Spec).
  * However, there is receivers which scroll them as well.
  */
 #define MAX_RDS_RADIO_TEXT		384
-	u8 radio_text[MAX_RDS_RADIO_TEXT + 1];
-	u32 enabled;
-};
 
-struct limiter_info {
 #define MAX_LIMITER_RELEASE_TIME	102390
-	u32 release_time;
 #define MAX_LIMITER_DEVIATION		90000
-	u32 deviation;
-	u32 enabled;
-};
 
-struct pilot_info {
 #define MAX_PILOT_DEVIATION		90000
-	u32 deviation;
 #define MAX_PILOT_FREQUENCY		19000
-	u32 frequency;
-	u32 enabled;
-};
 
-struct acomp_info {
 #define MAX_ACOMP_RELEASE_TIME		1000000
-	u32 release_time;
 #define MAX_ACOMP_ATTACK_TIME		5000
-	u32 attack_time;
 #define MAX_ACOMP_THRESHOLD		0
 #define MIN_ACOMP_THRESHOLD		(-40)
-	s32 threshold;
 #define MAX_ACOMP_GAIN			20
-	u32 gain;
-	u32 enabled;
-};
 
 #define SI4713_NUM_SUPPLIES		2
 
@@ -219,20 +197,41 @@
 struct si4713_device {
 	/* v4l2_subdev and i2c reference (v4l2_subdev priv data) */
 	struct v4l2_subdev sd;
+	struct v4l2_ctrl_handler ctrl_handler;
 	/* private data structures */
+	struct { /* si4713 control cluster */
+		/* This is one big cluster since the mute control
+		 * powers off the device and after unmuting again all
+		 * controls need to be set at once. The only way of doing
+		 * that is by making it one big cluster. */
+		struct v4l2_ctrl *mute;
+		struct v4l2_ctrl *rds_ps_name;
+		struct v4l2_ctrl *rds_radio_text;
+		struct v4l2_ctrl *rds_pi;
+		struct v4l2_ctrl *rds_deviation;
+		struct v4l2_ctrl *rds_pty;
+		struct v4l2_ctrl *compression_enabled;
+		struct v4l2_ctrl *compression_threshold;
+		struct v4l2_ctrl *compression_gain;
+		struct v4l2_ctrl *compression_attack_time;
+		struct v4l2_ctrl *compression_release_time;
+		struct v4l2_ctrl *pilot_tone_enabled;
+		struct v4l2_ctrl *pilot_tone_freq;
+		struct v4l2_ctrl *pilot_tone_deviation;
+		struct v4l2_ctrl *limiter_enabled;
+		struct v4l2_ctrl *limiter_deviation;
+		struct v4l2_ctrl *limiter_release_time;
+		struct v4l2_ctrl *tune_preemphasis;
+		struct v4l2_ctrl *tune_pwr_level;
+		struct v4l2_ctrl *tune_ant_cap;
+	};
 	struct completion work;
-	struct rds_info rds_info;
-	struct limiter_info limiter_info;
-	struct pilot_info pilot_info;
-	struct acomp_info acomp_info;
 	struct regulator_bulk_data supplies[SI4713_NUM_SUPPLIES];
 	int gpio_reset;
+	u32 power_state;
+	u32 rds_enabled;
 	u32 frequency;
 	u32 preemphasis;
-	u32 mute;
-	u32 power_level;
-	u32 power_state;
-	u32 antenna_capacitor;
 	u32 stereo;
 	u32 tune_rnl;
 };