[ALSA] opl3 - Use hwdep for patch loading

Use the hwdep device for loading OPL2/3 patch data instead of the
messy sequencer instrument layer.
Due to this change, the sbiload program should be updated, too.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
diff --git a/include/sound/asound_fm.h b/include/sound/asound_fm.h
index 8fbcab7..c2a4b96 100644
--- a/include/sound/asound_fm.h
+++ b/include/sound/asound_fm.h
@@ -104,6 +104,8 @@
 #define SNDRV_DM_FM_IOCTL_SET_MODE	_IOW('H', 0x25, int)
 /* for OPL3 only */
 #define SNDRV_DM_FM_IOCTL_SET_CONNECTION	_IOW('H', 0x26, int)
+/* SBI patch management */
+#define SNDRV_DM_FM_IOCTL_CLEAR_PATCHES	_IO ('H', 0x40)
 
 #define SNDRV_DM_FM_OSS_IOCTL_RESET		0x20
 #define SNDRV_DM_FM_OSS_IOCTL_PLAY_NOTE		0x21
@@ -112,4 +114,21 @@
 #define SNDRV_DM_FM_OSS_IOCTL_SET_MODE		0x24
 #define SNDRV_DM_FM_OSS_IOCTL_SET_OPL		0x25
 
+/*
+ * Patch Record - fixed size for write
+ */
+
+#define FM_KEY_SBI	"SBI\032"
+#define FM_KEY_2OP	"2OP\032"
+#define FM_KEY_4OP	"4OP\032"
+
+struct sbi_patch {
+	unsigned char prog;
+	unsigned char bank;
+	char key[4];
+	char name[25];
+	char extension[7];
+	unsigned char data[32];
+};
+
 #endif /* __SOUND_ASOUND_FM_H */
diff --git a/include/sound/opl3.h b/include/sound/opl3.h
index 1d14b3f..7ee865d 100644
--- a/include/sound/opl3.h
+++ b/include/sound/opl3.h
@@ -63,7 +63,7 @@
 #include "seq_oss_legacy.h"
 #endif
 #include "seq_device.h"
-#include "ainstr_fm.h"
+#include "asound_fm.h"
 
 /*
  *    Register numbers for the global registers
@@ -240,6 +240,47 @@
 struct snd_opl3;
 
 /*
+ * Instrument record, aka "Patch"
+ */
+
+/* FM operator */
+struct fm_operator {
+	unsigned char am_vib;
+	unsigned char ksl_level;
+	unsigned char attack_decay;
+	unsigned char sustain_release;
+	unsigned char wave_select;
+} __attribute__((packed));
+
+/* Instrument data */
+struct fm_instrument {
+	struct fm_operator op[4];
+	unsigned char feedback_connection[2];
+	unsigned char echo_delay;
+	unsigned char echo_atten;
+	unsigned char chorus_spread;
+	unsigned char trnsps;
+	unsigned char fix_dur;
+	unsigned char modes;
+	unsigned char fix_key;
+};
+
+/* type */
+#define FM_PATCH_OPL2	0x01		/* OPL2 2 operators FM instrument */
+#define FM_PATCH_OPL3	0x02		/* OPL3 4 operators FM instrument */
+
+/* Instrument record */
+struct fm_patch {
+	unsigned char prog;
+	unsigned char bank;
+	unsigned char type;
+	struct fm_instrument inst;
+	char name[24];
+	struct fm_patch *next;
+};
+
+
+/*
  * A structure to keep track of each hardware voice
  */
 struct snd_opl3_voice {
@@ -297,8 +338,8 @@
 	struct snd_midi_channel_set * oss_chset;
 #endif
  
-	struct snd_seq_kinstr_ops fm_ops;
-	struct snd_seq_kinstr_list *ilist;
+#define OPL3_PATCH_HASH_SIZE	32
+	struct fm_patch *patch_table[OPL3_PATCH_HASH_SIZE];
 
 	struct snd_opl3_voice voices[MAX_OPL3_VOICES]; /* Voices (OPL3 'channel') */
 	int use_time;			/* allocation counter */
@@ -333,8 +374,19 @@
 int snd_opl3_open(struct snd_hwdep * hw, struct file *file);
 int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file,
 		   unsigned int cmd, unsigned long arg);
+long snd_opl3_write(struct snd_hwdep *hw, const char __user *buf, long count,
+		    loff_t *offset);
 int snd_opl3_release(struct snd_hwdep * hw, struct file *file);
 
 void snd_opl3_reset(struct snd_opl3 * opl3);
 
+int snd_opl3_load_patch(struct snd_opl3 *opl3,
+			int prog, int bank, int type,
+			const char *name,
+			const unsigned char *ext,
+			const unsigned char *data);
+struct fm_patch *snd_opl3_find_patch(struct snd_opl3 *opl3, int prog, int bank,
+				     int create_patch);
+void snd_opl3_clear_patches(struct snd_opl3 *opl3);
+
 #endif /* __SOUND_OPL3_H */
diff --git a/sound/drivers/opl3/opl3_lib.c b/sound/drivers/opl3/opl3_lib.c
index a2b9ce0..a657da9 100644
--- a/sound/drivers/opl3/opl3_lib.c
+++ b/sound/drivers/opl3/opl3_lib.c
@@ -327,6 +327,7 @@
 	snd_assert(opl3 != NULL, return -ENXIO);
 	if (opl3->private_free)
 		opl3->private_free(opl3);
+	snd_opl3_clear_patches(opl3);
 	release_and_free_resource(opl3->res_l_port);
 	release_and_free_resource(opl3->res_r_port);
 	kfree(opl3);
@@ -521,6 +522,7 @@
 	/* operators - only ioctl */
 	hw->ops.open = snd_opl3_open;
 	hw->ops.ioctl = snd_opl3_ioctl;
+	hw->ops.write = snd_opl3_write;
 	hw->ops.release = snd_opl3_release;
 
 	opl3->seq_dev_num = seq_device;
diff --git a/sound/drivers/opl3/opl3_midi.c b/sound/drivers/opl3/opl3_midi.c
index 3557b6e..cebcb8b 100644
--- a/sound/drivers/opl3/opl3_midi.c
+++ b/sound/drivers/opl3/opl3_midi.c
@@ -289,8 +289,6 @@
 void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
 {
 	struct snd_opl3 *opl3;
-	struct snd_seq_instr wanted;
-	struct snd_seq_kinstr *kinstr;
 	int instr_4op;
 
 	int voice;
@@ -306,11 +304,13 @@
 	unsigned char voice_offset;
 	unsigned short opl3_reg;
 	unsigned char reg_val;
+	unsigned char prg, bank;
 
 	int key = note;
 	unsigned char fnum, blocknum;
 	int i;
 
+	struct fm_patch *patch;
 	struct fm_instrument *fm;
 	unsigned long flags;
 
@@ -320,19 +320,17 @@
 	snd_printk("Note on, ch %i, inst %i, note %i, vel %i\n",
 		   chan->number, chan->midi_program, note, vel);
 #endif
-	wanted.cluster = 0;
-	wanted.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
 
 	/* in SYNTH mode, application takes care of voices */
 	/* in SEQ mode, drum voice numbers are notes on drum channel */
 	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
 		if (chan->drum_channel) {
 			/* percussion instruments are located in bank 128 */
-			wanted.bank = 128;
-			wanted.prg = note;
+			bank = 128;
+			prg = note;
 		} else {
-			wanted.bank = chan->gm_bank_select;
-			wanted.prg = chan->midi_program;
+			bank = chan->gm_bank_select;
+			prg = chan->midi_program;
 		}
 	} else {
 		/* Prepare for OSS mode */
@@ -340,8 +338,8 @@
 			return;
 
 		/* OSS instruments are located in bank 127 */
-		wanted.bank = 127;
-		wanted.prg = chan->midi_program;
+		bank = 127;
+		prg = chan->midi_program;
 	}
 
 	spin_lock_irqsave(&opl3->voice_lock, flags);
@@ -353,15 +351,14 @@
 	}
 
  __extra_prg:
-	kinstr = snd_seq_instr_find(opl3->ilist, &wanted, 1, 0);
-	if (kinstr == NULL) {
+	patch = snd_opl3_find_patch(opl3, prg, bank, 0);
+	if (!patch) {
 		spin_unlock_irqrestore(&opl3->voice_lock, flags);
 		return;
 	}
 
-	fm = KINSTR_DATA(kinstr);
-
-	switch (fm->type) {
+	fm = &patch->inst;
+	switch (patch->type) {
 	case FM_PATCH_OPL2:
 		instr_4op = 0;
 		break;
@@ -371,14 +368,12 @@
 			break;
 		}
 	default:
-		snd_seq_instr_free_use(opl3->ilist, kinstr);
 		spin_unlock_irqrestore(&opl3->voice_lock, flags);
 		return;
 	}
-
 #ifdef DEBUG_MIDI
 	snd_printk("  --> OPL%i instrument: %s\n",
-		   instr_4op ? 3 : 2, kinstr->name);
+		   instr_4op ? 3 : 2, patch->name);
 #endif
 	/* in SYNTH mode, application takes care of voices */
 	/* in SEQ mode, allocate voice on free OPL3 channel */
@@ -569,8 +564,6 @@
 	/* get extra pgm, but avoid possible loops */
 	extra_prg = (extra_prg) ? 0 : fm->modes;
 
-	snd_seq_instr_free_use(opl3->ilist, kinstr);
-
 	/* do the bookkeeping */
 	vp->time = opl3->use_time++;
 	vp->note = key;
@@ -601,12 +594,12 @@
 	/* allocate extra program if specified in patch library */
 	if (extra_prg) {
 		if (extra_prg > 128) {
-			wanted.bank = 128;
+			bank = 128;
 			/* percussions start at 35 */
-			wanted.prg = extra_prg - 128 + 35 - 1;
+			prg = extra_prg - 128 + 35 - 1;
 		} else {
-			wanted.bank = 0;
-			wanted.prg = extra_prg - 1;
+			bank = 0;
+			prg = extra_prg - 1;
 		}
 #ifdef DEBUG_MIDI
 		snd_printk(" *** allocating extra program\n");
diff --git a/sound/drivers/opl3/opl3_oss.c b/sound/drivers/opl3/opl3_oss.c
index 5fd3a4c..239347f 100644
--- a/sound/drivers/opl3/opl3_oss.c
+++ b/sound/drivers/opl3/opl3_oss.c
@@ -195,17 +195,6 @@
 
 /* load patch */
 
-/* offsets for SBI params */
-#define AM_VIB		0
-#define KSL_LEVEL	2
-#define ATTACK_DECAY	4
-#define SUSTAIN_RELEASE	6
-#define WAVE_SELECT	8
-
-/* offset for SBI instrument */
-#define CONNECTION	10
-#define OFFSET_4OP	11
-
 /* from sound_config.h */
 #define SBFM_MAXINSTR	256
 
@@ -213,112 +202,42 @@
 				       const char __user *buf, int offs, int count)
 {
 	struct snd_opl3 *opl3;
-	int err = -EINVAL;
+	struct sbi_instrument sbi;
+	char name[32];
+	int err, type;
 
 	snd_assert(arg != NULL, return -ENXIO);
 	opl3 = arg->private_data;
 
-	if ((format == FM_PATCH) || (format == OPL3_PATCH)) {
-		struct sbi_instrument sbi;
+	if (format == FM_PATCH)
+		type = FM_PATCH_OPL2;
+	else if (format == OPL3_PATCH)
+		type = FM_PATCH_OPL3;
+	else
+		return -EINVAL;
 
-		size_t size;
-		struct snd_seq_instr_header *put;
-		struct snd_seq_instr_data *data;
-		struct fm_xinstrument *xinstr;
-
-		struct snd_seq_event ev;
-		int i;
-
-		mm_segment_t fs;
-
-		if (count < (int)sizeof(sbi)) {
-			snd_printk("FM Error: Patch record too short\n");
-			return -EINVAL;
-		}
-		if (copy_from_user(&sbi, buf, sizeof(sbi)))
-			return -EFAULT;
-
-		if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
-			snd_printk("FM Error: Invalid instrument number %d\n", sbi.channel);
-			return -EINVAL;
-		}
-
-		size = sizeof(*put) + sizeof(struct fm_xinstrument);
-		put = kzalloc(size, GFP_KERNEL);
-		if (put == NULL)
-			return -ENOMEM;
-		/* build header */
-		data = &put->data;
-		data->type = SNDRV_SEQ_INSTR_ATYPE_DATA;
-		strcpy(data->data.format, SNDRV_SEQ_INSTR_ID_OPL2_3);
-		/* build data section */
-		xinstr = (struct fm_xinstrument *)(data + 1);
-		xinstr->stype = FM_STRU_INSTR;
-        
-		for (i = 0; i < 2; i++) {
-			xinstr->op[i].am_vib = sbi.operators[AM_VIB + i];
-			xinstr->op[i].ksl_level = sbi.operators[KSL_LEVEL + i];
-			xinstr->op[i].attack_decay = sbi.operators[ATTACK_DECAY + i];
-			xinstr->op[i].sustain_release = sbi.operators[SUSTAIN_RELEASE + i];
-			xinstr->op[i].wave_select = sbi.operators[WAVE_SELECT + i];
-		}
-		xinstr->feedback_connection[0] = sbi.operators[CONNECTION];
-
-		if (format == OPL3_PATCH) {
-			xinstr->type = FM_PATCH_OPL3;
-			for (i = 0; i < 2; i++) {
-				xinstr->op[i+2].am_vib = sbi.operators[OFFSET_4OP + AM_VIB + i];
-				xinstr->op[i+2].ksl_level = sbi.operators[OFFSET_4OP + KSL_LEVEL + i];
-				xinstr->op[i+2].attack_decay = sbi.operators[OFFSET_4OP + ATTACK_DECAY + i];
-				xinstr->op[i+2].sustain_release = sbi.operators[OFFSET_4OP + SUSTAIN_RELEASE + i];
-				xinstr->op[i+2].wave_select = sbi.operators[OFFSET_4OP + WAVE_SELECT + i];
-			}
-			xinstr->feedback_connection[1] = sbi.operators[OFFSET_4OP + CONNECTION];
-		} else {
-			xinstr->type = FM_PATCH_OPL2;
-		}
-
-		put->id.instr.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
-		put->id.instr.bank = 127;
-		put->id.instr.prg = sbi.channel;
-		put->cmd = SNDRV_SEQ_INSTR_PUT_CMD_CREATE;
-
-		memset (&ev, 0, sizeof(ev));
-		ev.source.client = SNDRV_SEQ_CLIENT_OSS;
-		ev.dest = arg->addr; 
-
-		ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARUSR;
-		ev.queue = SNDRV_SEQ_QUEUE_DIRECT;
-
-		fs = snd_enter_user();
-	__again:
-		ev.type = SNDRV_SEQ_EVENT_INSTR_PUT;
-		ev.data.ext.len = size;
-		ev.data.ext.ptr = put;
-
-		err = snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, &ev,
-				    opl3->seq_client, 0, 0);
-		if (err == -EBUSY) {
-			struct snd_seq_instr_header remove;
-
-			memset (&remove, 0, sizeof(remove));
-			remove.cmd = SNDRV_SEQ_INSTR_FREE_CMD_SINGLE;
-			remove.id.instr = put->id.instr;
-
-			/* remove instrument */
-			ev.type = SNDRV_SEQ_EVENT_INSTR_FREE;
-			ev.data.ext.len = sizeof(remove);
-			ev.data.ext.ptr = &remove;
-
-			snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, &ev,
-					    opl3->seq_client, 0, 0);
-			goto __again;
-		}
-		snd_leave_user(fs);
-
-		kfree(put);
+	if (count < (int)sizeof(sbi)) {
+		snd_printk("FM Error: Patch record too short\n");
+		return -EINVAL;
 	}
-	return err;
+	if (copy_from_user(&sbi, buf, sizeof(sbi)))
+		return -EFAULT;
+
+	if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
+		snd_printk("FM Error: Invalid instrument number %d\n",
+			   sbi.channel);
+		return -EINVAL;
+	}
+
+	memset(name, 0, sizeof(name));
+	sprintf(name, "Chan%d", sbi.channel);
+
+	err = snd_opl3_load_patch(opl3, sbi.channel, 127, type, name, NULL,
+				  sbi.operators);
+	if (err < 0)
+		return err;
+
+	return sizeof(sbi);
 }
 
 /* ioctl */
diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c
index 96762c9..ff6da16 100644
--- a/sound/drivers/opl3/opl3_seq.c
+++ b/sound/drivers/opl3/opl3_seq.c
@@ -152,15 +152,7 @@
 {
 	struct snd_opl3 *opl3 = private_data;
 
-	if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN &&
-	    ev->type <= SNDRV_SEQ_EVENT_INSTR_CHANGE) {
-		if (direct) {
-			snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, ev,
-					    opl3->seq_client, atomic, hop);
-		}
-	} else {
-		snd_midi_process_event(&opl3_ops, ev, opl3->chset);
-	}
+	snd_midi_process_event(&opl3_ops, ev, opl3->chset);
 	return 0;
 }
 
@@ -249,16 +241,6 @@
 		return err;
 	}
 
-	/* initialize instrument list */
-	opl3->ilist = snd_seq_instr_list_new();
-	if (opl3->ilist == NULL) {
-		snd_seq_delete_kernel_client(client);
-		opl3->seq_client = -1;
-		return -ENOMEM;
-	}
-	opl3->ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT;
-	snd_seq_fm_init(&opl3->fm_ops, NULL);
-
 	/* setup system timer */
 	init_timer(&opl3->tlist);
 	opl3->tlist.function = snd_opl3_timer_func;
@@ -287,8 +269,6 @@
 		snd_seq_delete_kernel_client(opl3->seq_client);
 		opl3->seq_client = -1;
 	}
-	if (opl3->ilist)
-		snd_seq_instr_list_free(&opl3->ilist);
 	return 0;
 }
 
diff --git a/sound/drivers/opl3/opl3_synth.c b/sound/drivers/opl3/opl3_synth.c
index a4b3543..d55eefc 100644
--- a/sound/drivers/opl3/opl3_synth.c
+++ b/sound/drivers/opl3/opl3_synth.c
@@ -165,6 +165,10 @@
 #endif
 		return snd_opl3_set_connection(opl3, (int) arg);
 
+	case SNDRV_DM_FM_IOCTL_CLEAR_PATCHES:
+		snd_opl3_clear_patches(opl3);
+		return 0;
+
 #ifdef CONFIG_SND_DEBUG
 	default:
 		snd_printk("unknown IOCTL: 0x%x\n", cmd);
@@ -188,6 +192,170 @@
 	return 0;
 }
 
+/*
+ * write the device - load patches
+ */
+long snd_opl3_write(struct snd_hwdep *hw, const char __user *buf, long count,
+		    loff_t *offset)
+{
+	struct snd_opl3 *opl3 = hw->private_data;
+	long result = 0;
+	int err = 0;
+	struct sbi_patch inst;
+
+	while (count >= sizeof(inst)) {
+		unsigned char type;
+		if (copy_from_user(&inst, buf, sizeof(inst)))
+			return -EFAULT;
+		if (!memcmp(inst.key, FM_KEY_SBI, 4) ||
+		    !memcmp(inst.key, FM_KEY_2OP, 4))
+			type = FM_PATCH_OPL2;
+		else if (!memcmp(inst.key, FM_KEY_4OP, 4))
+			type = FM_PATCH_OPL3;
+		else /* invalid type */
+			break;
+		err = snd_opl3_load_patch(opl3, inst.prog, inst.bank, type,
+					  inst.name, inst.extension,
+					  inst.data);
+		if (err < 0)
+			break;
+		result += sizeof(inst);
+		count -= sizeof(inst);
+	}
+	return result > 0 ? result : err;
+}
+
+
+/*
+ * Patch management
+ */
+
+/* offsets for SBI params */
+#define AM_VIB		0
+#define KSL_LEVEL	2
+#define ATTACK_DECAY	4
+#define SUSTAIN_RELEASE	6
+#define WAVE_SELECT	8
+
+/* offset for SBI instrument */
+#define CONNECTION	10
+#define OFFSET_4OP	11
+
+/*
+ * load a patch, obviously.
+ *
+ * loaded on the given program and bank numbers with the given type
+ * (FM_PATCH_OPLx).
+ * data is the pointer of SBI record _without_ header (key and name).
+ * name is the name string of the patch.
+ * ext is the extension data of 7 bytes long (stored in name of SBI
+ * data up to offset 25), or NULL to skip.
+ * return 0 if successful or a negative error code.
+ */
+int snd_opl3_load_patch(struct snd_opl3 *opl3,
+			int prog, int bank, int type,
+			const char *name,
+			const unsigned char *ext,
+			const unsigned char *data)
+{
+	struct fm_patch *patch;
+	int i;
+
+	patch = snd_opl3_find_patch(opl3, prog, bank, 1);
+	if (!patch)
+		return -ENOMEM;
+
+	patch->type = type;
+
+	for (i = 0; i < 2; i++) {
+		patch->inst.op[i].am_vib = data[AM_VIB + i];
+		patch->inst.op[i].ksl_level = data[KSL_LEVEL + i];
+		patch->inst.op[i].attack_decay = data[ATTACK_DECAY + i];
+		patch->inst.op[i].sustain_release = data[SUSTAIN_RELEASE + i];
+		patch->inst.op[i].wave_select = data[WAVE_SELECT + i];
+	}
+	patch->inst.feedback_connection[0] = data[CONNECTION];
+
+	if (type == FM_PATCH_OPL3) {
+		for (i = 0; i < 2; i++) {
+			patch->inst.op[i+2].am_vib =
+				data[OFFSET_4OP + AM_VIB + i];
+			patch->inst.op[i+2].ksl_level =
+				data[OFFSET_4OP + KSL_LEVEL + i];
+			patch->inst.op[i+2].attack_decay =
+				data[OFFSET_4OP + ATTACK_DECAY + i];
+			patch->inst.op[i+2].sustain_release =
+				data[OFFSET_4OP + SUSTAIN_RELEASE + i];
+			patch->inst.op[i+2].wave_select =
+				data[OFFSET_4OP + WAVE_SELECT + i];
+		}
+		patch->inst.feedback_connection[1] =
+			data[OFFSET_4OP + CONNECTION];
+	}
+
+	if (ext) {
+		patch->inst.echo_delay = ext[0];
+		patch->inst.echo_atten = ext[1];
+		patch->inst.chorus_spread = ext[2];
+		patch->inst.trnsps = ext[3];
+		patch->inst.fix_dur = ext[4];
+		patch->inst.modes = ext[5];
+		patch->inst.fix_key = ext[6];
+	}
+
+	if (name)
+		strlcpy(patch->name, name, sizeof(patch->name));
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_opl3_load_patch);
+
+/*
+ * find a patch with the given program and bank numbers, returns its pointer
+ * if no matching patch is found and create_patch is set, it creates a
+ * new patch object.
+ */
+struct fm_patch *snd_opl3_find_patch(struct snd_opl3 *opl3, int prog, int bank,
+				     int create_patch)
+{
+	/* pretty dumb hash key */
+	unsigned int key = (prog + bank) % OPL3_PATCH_HASH_SIZE;
+	struct fm_patch *patch;
+
+	for (patch = opl3->patch_table[key]; patch; patch = patch->next) {
+		if (patch->prog == prog && patch->bank == bank)
+			return patch;
+	}
+	if (!create_patch)
+		return NULL;
+
+	patch = kzalloc(sizeof(*patch), GFP_KERNEL);
+	if (!patch)
+		return NULL;
+	patch->prog = prog;
+	patch->bank = bank;
+	patch->next = opl3->patch_table[key];
+	opl3->patch_table[key] = patch;
+	return patch;
+}
+EXPORT_SYMBOL(snd_opl3_find_patch);
+
+/*
+ * Clear all patches of the given OPL3 instance
+ */
+void snd_opl3_clear_patches(struct snd_opl3 *opl3)
+{
+	int i;
+	for (i = 0; i <  OPL3_PATCH_HASH_SIZE; i++) {
+		struct fm_patch *patch, *next;
+		for (patch = opl3->patch_table[i]; patch; patch = next) {
+			next = patch->next;
+			kfree(patch);
+		}
+	}
+	memset(opl3->patch_table, 0, sizeof(opl3->patch_table));
+}
+
 /* ------------------------------ */
 
 void snd_opl3_reset(struct snd_opl3 * opl3)