diff --git a/include/pcm_rate.h b/include/pcm_rate.h
index 4171fb9d19cb..48473ed4ea04 100644
--- a/include/pcm_rate.h
+++ b/include/pcm_rate.h
@@ -38,7 +38,7 @@ extern "C" {
 /**
  * Protocol version
  */
-#define SND_PCM_RATE_PLUGIN_VERSION	0x010002
+#define SND_PCM_RATE_PLUGIN_VERSION	0x010003
 
 /** hw_params information for a single side */
 typedef struct snd_pcm_rate_side_info {
@@ -55,6 +55,11 @@ typedef struct snd_pcm_rate_info {
 	unsigned int channels;
 } snd_pcm_rate_info_t;
 
+enum {
+	SND_PCM_RATE_FLAG_INTERLEAVED = (1U << 0),	/** only interleaved format */
+	SND_PCM_RATE_FLAG_SYNC_FORMATS = (1U << 1),	/** both input and output formats have to be identical */
+};
+
 /** Callback table of rate-converter */
 typedef struct snd_pcm_rate_ops {
 	/**
@@ -114,6 +119,13 @@ typedef struct snd_pcm_rate_ops {
 	 * new ops since version 0x010002
 	 */
 	void (*dump)(void *obj, snd_output_t *out);
+	/**
+	 * get the supported input and output formats (optional);
+	 * new ops since version 0x010003
+	 */
+	int (*get_supported_formats)(void *obj, uint64_t *in_formats,
+				     uint64_t *out_formats,
+				     unsigned int *flags);
 } snd_pcm_rate_ops_t;
 
 /** open function type */
@@ -147,6 +159,28 @@ typedef struct snd_pcm_rate_old_ops {
 	snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames);
 	snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames);
 } snd_pcm_rate_old_ops_t;
+
+/* old rate_ops for protocol version 0x010002 */
+typedef struct snd_pcm_rate_v2_ops {
+	void (*close)(void *obj);
+	int (*init)(void *obj, snd_pcm_rate_info_t *info);
+	void (*free)(void *obj);
+	void (*reset)(void *obj);
+	int (*adjust_pitch)(void *obj, snd_pcm_rate_info_t *info);
+	void (*convert)(void *obj,
+			const snd_pcm_channel_area_t *dst_areas,
+			snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+			const snd_pcm_channel_area_t *src_areas,
+			snd_pcm_uframes_t src_offset, unsigned int src_frames);
+	void (*convert_s16)(void *obj, int16_t *dst, unsigned int dst_frames,
+			    const int16_t *src, unsigned int src_frames);
+	snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames);
+	snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames);
+	unsigned int version;
+	int (*get_supported_rates)(void *obj, unsigned int *rate_min,
+				   unsigned int *rate_max);
+	void (*dump)(void *obj, snd_output_t *out);
+} snd_pcm_rate_v2_ops_t;
 #endif
 
 #ifdef __cplusplus
diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c
index 1e9961346a7c..13111d290c90 100644
--- a/src/pcm/pcm_rate.c
+++ b/src/pcm/pcm_rate.c
@@ -62,18 +62,22 @@ struct _snd_pcm_rate {
 	void *open_func;
 	void *obj;
 	snd_pcm_rate_ops_t ops;
-	unsigned int get_idx;
-	unsigned int put_idx;
-	int16_t *src_buf;
-	int16_t *dst_buf;
+	unsigned int src_conv_idx;
+	unsigned int dst_conv_idx;
+	snd_pcm_channel_area_t *src_buf;
+	snd_pcm_channel_area_t *dst_buf;
 	int start_pending; /* start is triggered but not commited to slave */
 	snd_htimestamp_t trigger_tstamp;
 	unsigned int plugin_version;
 	unsigned int rate_min, rate_max;
+	snd_pcm_format_t orig_in_format;
+	snd_pcm_format_t orig_out_format;
+	uint64_t in_formats;
+	uint64_t out_formats;
+	unsigned int format_flags;
 };
 
 #define SND_PCM_RATE_PLUGIN_VERSION_OLD	0x010001	/* old rate plugin */
-
 #endif /* DOC_HIDDEN */
 
 /* allocate a channel area and a temporary buffer for the given size */
@@ -274,12 +278,84 @@ static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm,
 				       snd_pcm_generic_hw_refine);
 }
 
+/* evaluate the best matching available format to the given format */
+static int get_best_format(uint64_t mask, snd_pcm_format_t orig)
+{
+	int pwidth = snd_pcm_format_physical_width(orig);
+	int width = snd_pcm_format_width(orig);
+	int signd = snd_pcm_format_signed(orig);
+	int best_score = -1;
+	int match = -1;
+	int f, score;
+
+	for (f = 0; f <= SND_PCM_FORMAT_LAST; f++) {
+		if (!(mask & (1ULL << f)))
+			continue;
+		score = 0;
+		if (snd_pcm_format_linear(f)) {
+			if (snd_pcm_format_physical_width(f) == pwidth)
+				score++;
+			if (snd_pcm_format_physical_width(f) >= pwidth)
+				score++;
+			if (snd_pcm_format_width(f) == width)
+				score++;
+			if (snd_pcm_format_signed(f) == signd)
+				score++;
+		}
+		if (score > best_score) {
+			match = f;
+			best_score = score;
+		}
+	}
+
+	return match;
+}
+
+/* set up the input and output formats from the available lists */
+static int choose_preferred_format(snd_pcm_rate_t *rate)
+{
+	uint64_t in_mask = rate->in_formats;
+	uint64_t out_mask = rate->out_formats;
+	int in, out;
+
+	if (!in_mask || !out_mask)
+		return 0;
+
+	if (rate->orig_in_format == rate->orig_out_format)
+		if (in_mask & out_mask & (1ULL << rate->orig_in_format))
+			return 0; /* nothing changed */
+
+ repeat:
+	in = get_best_format(in_mask, rate->orig_in_format);
+	out = get_best_format(out_mask, rate->orig_out_format);
+	if (in < 0 || out < 0)
+		return -ENOENT;
+
+	if ((rate->format_flags & SND_PCM_RATE_FLAG_SYNC_FORMATS) &&
+	    in != out) {
+		if (out_mask & (1ULL << in))
+			out = in;
+		else if (in_mask & (1ULL << out))
+			in = out;
+		else {
+			in_mask &= ~(1ULL << in);
+			out_mask &= ~(1ULL << out);
+			goto repeat;
+		}
+	}
+
+	rate->info.in.format = in;
+	rate->info.out.format = out;
+	return 0;
+}
+
 static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
 {
 	snd_pcm_rate_t *rate = pcm->private_data;
 	snd_pcm_t *slave = rate->gen.slave;
 	snd_pcm_rate_side_info_t *sinfo, *cinfo;
-	unsigned int channels, cwidth, swidth, chn;
+	unsigned int channels, cwidth, swidth, chn, acc;
+	int need_src_buf, need_dst_buf;
 	int err = snd_pcm_hw_params_slave(pcm, params,
 					  snd_pcm_rate_hw_refine_cchange,
 					  snd_pcm_rate_hw_refine_sprepare,
@@ -308,6 +384,9 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
 	if (err < 0)
 		return err;
 	err = INTERNAL(snd_pcm_hw_params_get_channels)(params, &channels);
+	if (err < 0)
+		return err;
+	err = INTERNAL(snd_pcm_hw_params_get_access)(params, &acc);
 	if (err < 0)
 		return err;
 
@@ -321,36 +400,80 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
 		SNDMSG("rate plugin already in use");
 		return -EBUSY;
 	}
-	err = rate->ops.init(rate->obj, &rate->info);
-	if (err < 0)
-		return err;
 
 	rate->pareas = rate_alloc_tmp_buf(rate, cinfo->format, channels,
 					  cinfo->period_size);
 	rate->sareas = rate_alloc_tmp_buf(rate, sinfo->format, channels,
 					  sinfo->period_size);
-	if (!rate->pareas || !rate->sareas)
-		goto error;
-
-	if (rate->ops.convert_s16) {
-		rate->get_idx = snd_pcm_linear_get_index(rate->info.in.format, SND_PCM_FORMAT_S16);
-		rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, rate->info.out.format);
-		free(rate->src_buf);
-		rate->src_buf = malloc(channels * rate->info.in.period_size * 2);
-		free(rate->dst_buf);
-		rate->dst_buf = malloc(channels * rate->info.out.period_size * 2);
-		if (! rate->src_buf || ! rate->dst_buf)
+	if (!rate->pareas || !rate->sareas) {
+		err = -ENOMEM;
+		goto error_pareas;
+	}
+
+	rate->orig_in_format = rate->info.in.format;
+	rate->orig_out_format = rate->info.out.format;
+	if (choose_preferred_format(rate) < 0) {
+		SNDERR("No matching format in rate plugin");
+		err = -EINVAL;
+		goto error_pareas;
+	}
+
+	err = rate->ops.init(rate->obj, &rate->info);
+	if (err < 0)
+		goto error_init;
+
+	rate_free_tmp_buf(&rate->src_buf);
+	rate_free_tmp_buf(&rate->dst_buf);
+
+	need_src_buf = need_dst_buf = 0;
+
+	if ((rate->format_flags & SND_PCM_RATE_FLAG_INTERLEAVED) &&
+	    !(acc == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
+	      acc == SND_PCM_ACCESS_RW_INTERLEAVED)) {
+		need_src_buf = need_dst_buf = 1;
+	} else {
+		if (rate->orig_in_format != rate->info.in.format)
+			need_src_buf = 1;
+		if (rate->orig_out_format != rate->info.out.format)
+			need_dst_buf = 1;
+	}
+
+	if (need_src_buf) {
+		rate->src_conv_idx =
+			snd_pcm_linear_convert_index(rate->orig_in_format,
+						     rate->info.in.format);
+		rate->src_buf = rate_alloc_tmp_buf(rate, rate->info.in.format,
+						   channels, rate->info.in.period_size);
+		if (!rate->src_buf) {
+			err = -ENOMEM;
 			goto error;
+		}
+	}
+
+	if (need_dst_buf) {
+		rate->dst_conv_idx =
+			snd_pcm_linear_convert_index(rate->info.out.format,
+						     rate->orig_out_format);
+		rate->dst_buf = rate_alloc_tmp_buf(rate, rate->info.out.format,
+						   channels, rate->info.out.period_size);
+		if (!rate->dst_buf) {
+			err = -ENOMEM;
+			goto error;
+		}
 	}
 
 	return 0;
 
  error:
-	rate_free_tmp_buf(&rate->pareas);
-	rate_free_tmp_buf(&rate->sareas);
+	rate_free_tmp_buf(&rate->src_buf);
+	rate_free_tmp_buf(&rate->dst_buf);
+ error_init:
 	if (rate->ops.free)
 		rate->ops.free(rate->obj);
-	return -ENOMEM;
+ error_pareas:
+	rate_free_tmp_buf(&rate->pareas);
+	rate_free_tmp_buf(&rate->sareas);
+	return err;
 }
 
 static int snd_pcm_rate_hw_free(snd_pcm_t *pcm)
@@ -361,9 +484,8 @@ static int snd_pcm_rate_hw_free(snd_pcm_t *pcm)
 	rate_free_tmp_buf(&rate->sareas);
 	if (rate->ops.free)
 		rate->ops.free(rate->obj);
-	free(rate->src_buf);
-	free(rate->dst_buf);
-	rate->src_buf = rate->dst_buf = NULL;
+	rate_free_tmp_buf(&rate->src_buf);
+	rate_free_tmp_buf(&rate->dst_buf);
 	return snd_pcm_hw_free(rate->gen.slave);
 }
 
@@ -444,82 +566,6 @@ static int snd_pcm_rate_init(snd_pcm_t *pcm)
 	return 0;
 }
 
-static void convert_to_s16(snd_pcm_rate_t *rate, int16_t *buf,
-			   const snd_pcm_channel_area_t *areas,
-			   snd_pcm_uframes_t offset, unsigned int frames,
-			   unsigned int channels)
-{
-#ifndef DOC_HIDDEN
-#define GET16_LABELS
-#include "plugin_ops.h"
-#undef GET16_LABELS
-#endif /* DOC_HIDDEN */
-	void *get = get16_labels[rate->get_idx];
-	const char *src;
-	int16_t sample;
-	const char *srcs[channels];
-	int src_step[channels];
-	unsigned int c;
-
-	for (c = 0; c < channels; c++) {
-		srcs[c] = snd_pcm_channel_area_addr(areas + c, offset);
-		src_step[c] = snd_pcm_channel_area_step(areas + c);
-	}
-
-	while (frames--) {
-		for (c = 0; c < channels; c++) {
-			src = srcs[c];
-			goto *get;
-#ifndef DOC_HIDDEN
-#define GET16_END after_get
-#include "plugin_ops.h"
-#undef GET16_END
-#endif /* DOC_HIDDEN */
-		after_get:
-			*buf++ = sample;
-			srcs[c] += src_step[c];
-		}
-	}
-}
-
-static void convert_from_s16(snd_pcm_rate_t *rate, const int16_t *buf,
-			     const snd_pcm_channel_area_t *areas,
-			     snd_pcm_uframes_t offset, unsigned int frames,
-			     unsigned int channels)
-{
-#ifndef DOC_HIDDEN
-#define PUT16_LABELS
-#include "plugin_ops.h"
-#undef PUT16_LABELS
-#endif /* DOC_HIDDEN */
-	void *put = put16_labels[rate->put_idx];
-	char *dst;
-	int16_t sample;
-	char *dsts[channels];
-	int dst_step[channels];
-	unsigned int c;
-
-	for (c = 0; c < channels; c++) {
-		dsts[c] = snd_pcm_channel_area_addr(areas + c, offset);
-		dst_step[c] = snd_pcm_channel_area_step(areas + c);
-	}
-
-	while (frames--) {
-		for (c = 0; c < channels; c++) {
-			dst = dsts[c];
-			sample = *buf++;
-			goto *put;
-#ifndef DOC_HIDDEN
-#define PUT16_END after_put
-#include "plugin_ops.h"
-#undef PUT16_END
-#endif /* DOC_HIDDEN */
-		after_put:
-			dsts[c] += dst_step[c];
-		}
-	}
-}
-
 static void do_convert(const snd_pcm_channel_area_t *dst_areas,
 		       snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
 		       const snd_pcm_channel_area_t *src_areas,
@@ -527,28 +573,40 @@ static void do_convert(const snd_pcm_channel_area_t *dst_areas,
 		       unsigned int channels,
 		       snd_pcm_rate_t *rate)
 {
-	if (rate->ops.convert_s16) {
-		const int16_t *src;
-		int16_t *dst;
-		if (! rate->src_buf)
-			src = (int16_t *)src_areas->addr + src_offset * channels;
-		else {
-			convert_to_s16(rate, rate->src_buf, src_areas, src_offset,
-				       src_frames, channels);
-			src = rate->src_buf;
-		}
-		if (! rate->dst_buf)
-			dst = (int16_t *)dst_areas->addr + dst_offset * channels;
-		else
-			dst = rate->dst_buf;
-		rate->ops.convert_s16(rate->obj, dst, dst_frames, src, src_frames);
-		if (dst == rate->dst_buf)
-			convert_from_s16(rate, rate->dst_buf, dst_areas, dst_offset,
-					 dst_frames, channels);
+	const snd_pcm_channel_area_t *out_areas;
+	snd_pcm_uframes_t out_offset;
+
+	if (rate->dst_buf) {
+		out_areas = rate->dst_buf;
+		out_offset = 0;
 	} else {
-		rate->ops.convert(rate->obj, dst_areas, dst_offset, dst_frames,
-				   src_areas, src_offset, src_frames);
+		out_areas = dst_areas;
+		out_offset = dst_offset;
+	}
+
+	if (rate->src_buf) {
+		snd_pcm_linear_convert(rate->src_buf, 0,
+				       src_areas, src_offset,
+				       channels, src_frames,
+				       rate->src_conv_idx);
+		src_areas = rate->src_buf;
+		src_offset = 0;
 	}
+
+	if (rate->ops.convert)
+		rate->ops.convert(rate->obj, out_areas, out_offset, dst_frames,
+				   src_areas, src_offset, src_frames);
+	else
+		rate->ops.convert_s16(rate->obj,
+				      snd_pcm_channel_area_addr(out_areas, out_offset),
+				      dst_frames,
+				      snd_pcm_channel_area_addr(src_areas, src_offset),
+				      src_frames);
+	if (rate->dst_buf)
+		snd_pcm_linear_convert(dst_areas, dst_offset,
+				       rate->dst_buf, 0,
+				       channels, dst_frames,
+				       rate->dst_conv_idx);
 }
 
 static inline void
@@ -1276,6 +1334,30 @@ const snd_config_t *snd_pcm_rate_get_default_converter(snd_config_t *root)
 	return NULL;
 }
 
+static void rate_initial_setup(snd_pcm_rate_t *rate)
+{
+	if (rate->plugin_version == SND_PCM_RATE_PLUGIN_VERSION)
+		rate->plugin_version = rate->ops.version;
+
+	if (rate->plugin_version >= 0x010002 &&
+	    rate->ops.get_supported_rates)
+		rate->ops.get_supported_rates(rate->obj,
+					      &rate->rate_min,
+					      &rate->rate_max);
+
+	if (rate->plugin_version >= 0x010003 &&
+	    rate->ops.get_supported_formats) {
+		rate->ops.get_supported_formats(rate->obj,
+						&rate->in_formats,
+						&rate->out_formats,
+						&rate->format_flags);
+	} else if (!rate->ops.convert && rate->ops.convert_s16) {
+		rate->in_formats = rate->out_formats =
+			1ULL << SND_PCM_FORMAT_S16;
+		rate->format_flags = SND_PCM_RATE_FLAG_INTERLEAVED;
+	}
+}
+
 #ifdef PIC
 static int is_builtin_plugin(const char *type)
 {
@@ -1301,20 +1383,11 @@ static int rate_open_func(snd_pcm_rate_t *rate, const char *type, const snd_conf
 		lib = lib_name;
 	}
 
-	rate->rate_min = SND_PCM_PLUGIN_RATE_MIN;
-	rate->rate_max = SND_PCM_PLUGIN_RATE_MAX;
-	rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION;
-
 	open_conf_func = snd_dlobj_cache_get(lib, open_conf_name, NULL, verbose && converter_conf != NULL);
 	if (open_conf_func) {
 		err = open_conf_func(SND_PCM_RATE_PLUGIN_VERSION,
 				     &rate->obj, &rate->ops, converter_conf);
 		if (!err) {
-			rate->plugin_version = rate->ops.version;
-			if (rate->ops.get_supported_rates)
-				rate->ops.get_supported_rates(rate->obj,
-							      &rate->rate_min,
-							      &rate->rate_max);
 			rate->open_func = open_conf_func;
 			return 0;
 		} else {
@@ -1330,23 +1403,18 @@ static int rate_open_func(snd_pcm_rate_t *rate, const char *type, const snd_conf
 	rate->open_func = open_func;
 
 	err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops);
-	if (!err) {
-		rate->plugin_version = rate->ops.version;
-		if (rate->ops.get_supported_rates)
-			rate->ops.get_supported_rates(rate->obj,
-						      &rate->rate_min,
-						      &rate->rate_max);
+	if (!err)
 		return 0;
-	}
 
 	/* try to open with the old protocol version */
 	rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION_OLD;
 	err = open_func(SND_PCM_RATE_PLUGIN_VERSION_OLD,
 			&rate->obj, &rate->ops);
-	if (err) {
-		snd_dlobj_cache_put(open_func);
-		rate->open_func = NULL;
-	}
+	if (!err)
+		return 0;
+
+	snd_dlobj_cache_put(open_func);
+	rate->open_func = NULL;
 	return err;
 }
 #endif
@@ -1417,6 +1485,10 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
 	rate->srate = srate;
 	rate->sformat = sformat;
 
+	rate->rate_min = SND_PCM_PLUGIN_RATE_MIN;
+	rate->rate_max = SND_PCM_PLUGIN_RATE_MAX;
+	rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION;
+
 	err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode);
 	if (err < 0) {
 		free(rate);
@@ -1496,6 +1568,8 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
 		return err;
 	}
 
+	rate_initial_setup(rate);
+
 	pcm->ops = &snd_pcm_rate_ops;
 	pcm->fast_ops = &snd_pcm_rate_fast_ops;
 	pcm->private_data = rate;
