[ALSA] opl3 - Use hwdep for patch loading
authorTakashi Iwai <tiwai@suse.de>
Tue, 30 Oct 2007 10:49:22 +0000 (11:49 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 31 Jan 2008 16:29:13 +0000 (17:29 +0100)
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>
include/sound/asound_fm.h
include/sound/opl3.h
sound/drivers/opl3/opl3_lib.c
sound/drivers/opl3/opl3_midi.c
sound/drivers/opl3/opl3_oss.c
sound/drivers/opl3/opl3_seq.c
sound/drivers/opl3/opl3_synth.c

index 8fbcab7..c2a4b96 100644 (file)
@@ -104,6 +104,8 @@ struct snd_dm_fm_params {
 #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 @@ struct snd_dm_fm_params {
 #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 */
index 1d14b3f..7ee865d 100644 (file)
@@ -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
 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_opl3 {
        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_hwdep_new(struct snd_opl3 * opl3, int device, int seq_device,
 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 */
index a2b9ce0..a657da9 100644 (file)
@@ -327,6 +327,7 @@ static int snd_opl3_free(struct snd_opl3 *opl3)
        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 @@ int snd_opl3_hwdep_new(struct snd_opl3 * opl3,
        /* 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;
index 3557b6e..cebcb8b 100644 (file)
@@ -289,8 +289,6 @@ static int snd_opl3_oss_map[MAX_OPL3_VOICES] = {
 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 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
        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 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
        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 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
                        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 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
        }
 
  __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 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
                        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 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
        /* 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 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
        /* 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");
index 5fd3a4c..239347f 100644 (file)
@@ -195,17 +195,6 @@ static int snd_opl3_close_seq_oss(struct snd_seq_oss_arg *arg)
 
 /* 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 @@ static int snd_opl3_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
                                       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;
+       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;
 
-               struct snd_seq_event ev;
-               int i;
+       if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
+               snd_printk("FM Error: Invalid instrument number %d\n",
+                          sbi.channel);
+               return -EINVAL;
+       }
 
-               mm_segment_t fs;
+       memset(name, 0, sizeof(name));
+       sprintf(name, "Chan%d", sbi.channel);
 
-               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;
+       err = snd_opl3_load_patch(opl3, sbi.channel, 127, type, name, NULL,
+                                 sbi.operators);
+       if (err < 0)
+               return err;
 
-               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);
-       }
-       return err;
+       return sizeof(sbi);
 }
 
 /* ioctl */
index 96762c9..ff6da16 100644 (file)
@@ -152,15 +152,7 @@ static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct,
 {
        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 @@ static int snd_opl3_seq_new_device(struct snd_seq_device *dev)
                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 @@ static int snd_opl3_seq_delete_device(struct snd_seq_device *dev)
                snd_seq_delete_kernel_client(opl3->seq_client);
                opl3->seq_client = -1;
        }
-       if (opl3->ilist)
-               snd_seq_instr_list_free(&opl3->ilist);
        return 0;
 }
 
index a4b3543..d55eefc 100644 (file)
@@ -165,6 +165,10 @@ int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file,
 #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 @@ int snd_opl3_release(struct snd_hwdep * hw, struct file *file)
        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)