757 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * MOTU Midi Timepiece ALSA Main routines4 * Copyright by Michael T. Mayers (c) Jan 09, 20005 * mail: michael@tweakoz.com6 * Thanks to John Galbraith7 *8 * This driver is for the 'Mark Of The Unicorn' (MOTU)9 * MidiTimePiece AV multiport MIDI interface 10 *11 * IOPORTS12 * -------13 * 8 MIDI Ins and 8 MIDI outs14 * Video Sync In (BNC), Word Sync Out (BNC), 15 * ADAT Sync Out (DB9)16 * SMPTE in/out (1/4")17 * 2 programmable pedal/footswitch inputs and 4 programmable MIDI controller knobs.18 * Macintosh RS422 serial port19 * RS422 "network" port for ganging multiple MTP's20 * PC Parallel Port ( which this driver currently uses )21 *22 * MISC FEATURES23 * -------------24 * Hardware MIDI routing, merging, and filtering 25 * MIDI Synchronization to Video, ADAT, SMPTE and other Clock sources26 * 128 'scene' memories, recallable from MIDI program change27 *28 * ChangeLog29 * Jun 11 2001 Takashi Iwai <tiwai@suse.de>30 * - Recoded & debugged31 * - Added timer interrupt for midi outputs32 * - hwports is between 1 and 8, which specifies the number of hardware ports.33 * The three global ports, computer, adat and broadcast ports, are created34 * always after h/w and remote ports.35 */36 37#include <linux/init.h>38#include <linux/interrupt.h>39#include <linux/module.h>40#include <linux/err.h>41#include <linux/platform_device.h>42#include <linux/ioport.h>43#include <linux/io.h>44#include <linux/moduleparam.h>45#include <sound/core.h>46#include <sound/initval.h>47#include <sound/rawmidi.h>48#include <linux/delay.h>49 50/*51 * globals52 */53MODULE_AUTHOR("Michael T. Mayers");54MODULE_DESCRIPTION("MOTU MidiTimePiece AV multiport MIDI");55MODULE_LICENSE("GPL");56 57// io resources58#define MTPAV_IOBASE 0x37859#define MTPAV_IRQ 760#define MTPAV_MAX_PORTS 861 62static int index = SNDRV_DEFAULT_IDX1;63static char *id = SNDRV_DEFAULT_STR1;64static long port = MTPAV_IOBASE; /* 0x378, 0x278 */65static int irq = MTPAV_IRQ; /* 7, 5 */66static int hwports = MTPAV_MAX_PORTS; /* use hardware ports 1-8 */67 68module_param(index, int, 0444);69MODULE_PARM_DESC(index, "Index value for MotuMTPAV MIDI.");70module_param(id, charp, 0444);71MODULE_PARM_DESC(id, "ID string for MotuMTPAV MIDI.");72module_param_hw(port, long, ioport, 0444);73MODULE_PARM_DESC(port, "Parallel port # for MotuMTPAV MIDI.");74module_param_hw(irq, int, irq, 0444);75MODULE_PARM_DESC(irq, "Parallel IRQ # for MotuMTPAV MIDI.");76module_param(hwports, int, 0444);77MODULE_PARM_DESC(hwports, "Hardware ports # for MotuMTPAV MIDI.");78 79static struct platform_device *device;80 81/*82 * defines83 */84//#define USE_FAKE_MTP // don't actually read/write to MTP device (for debugging without an actual unit) (does not work yet)85 86// parallel port usage masks87#define SIGS_BYTE 0x0888#define SIGS_RFD 0x8089#define SIGS_IRQ 0x4090#define SIGS_IN0 0x1091#define SIGS_IN1 0x2092 93#define SIGC_WRITE 0x0494#define SIGC_READ 0x0895#define SIGC_INTEN 0x1096 97#define DREG 098#define SREG 199#define CREG 2100 101//102#define MTPAV_MODE_INPUT_OPENED 0x01103#define MTPAV_MODE_OUTPUT_OPENED 0x02104#define MTPAV_MODE_INPUT_TRIGGERED 0x04105#define MTPAV_MODE_OUTPUT_TRIGGERED 0x08106 107#define NUMPORTS (0x12+1)108 109 110/*111 */112 113struct mtpav_port {114 u8 number;115 u8 hwport;116 u8 mode;117 u8 running_status;118 struct snd_rawmidi_substream *input;119 struct snd_rawmidi_substream *output;120};121 122struct mtpav {123 struct snd_card *card;124 unsigned long port;125 struct resource *res_port;126 int irq; /* interrupt (for inputs) */127 spinlock_t spinlock;128 int share_irq; /* number of accesses to input interrupts */129 int istimer; /* number of accesses to timer interrupts */130 struct timer_list timer; /* timer interrupts for outputs */131 struct snd_rawmidi *rmidi;132 int num_ports; /* number of hw ports (1-8) */133 struct mtpav_port ports[NUMPORTS]; /* all ports including computer, adat and bc */134 135 u32 inmidiport; /* selected input midi port */136 u32 inmidistate; /* during midi command 0xf5 */137 138 u32 outmidihwport; /* selected output midi hw port */139};140 141 142/*143 * possible hardware ports (selected by 0xf5 port message)144 * 0x00 all ports145 * 0x01 .. 0x08 this MTP's ports 1..8146 * 0x09 .. 0x10 networked MTP's ports (9..16)147 * 0x11 networked MTP's computer port148 * 0x63 to ADAT149 *150 * mappig:151 * subdevice 0 - (X-1) ports152 * X - (2*X-1) networked ports153 * X computer154 * X+1 ADAT155 * X+2 all ports156 *157 * where X = chip->num_ports158 */159 160#define MTPAV_PIDX_COMPUTER 0161#define MTPAV_PIDX_ADAT 1162#define MTPAV_PIDX_BROADCAST 2163 164 165static int translate_subdevice_to_hwport(struct mtpav *chip, int subdev)166{167 if (subdev < 0)168 return 0x01; /* invalid - use port 0 as default */169 else if (subdev < chip->num_ports)170 return subdev + 1; /* single mtp port */171 else if (subdev < chip->num_ports * 2)172 return subdev - chip->num_ports + 0x09; /* remote port */173 else if (subdev == chip->num_ports * 2 + MTPAV_PIDX_COMPUTER)174 return 0x11; /* computer port */175 else if (subdev == chip->num_ports + MTPAV_PIDX_ADAT)176 return 0x63; /* ADAT */177 return 0; /* all ports */178}179 180static int translate_hwport_to_subdevice(struct mtpav *chip, int hwport)181{182 int p;183 if (hwport <= 0x00) /* all ports */184 return chip->num_ports + MTPAV_PIDX_BROADCAST;185 else if (hwport <= 0x08) { /* single port */186 p = hwport - 1;187 if (p >= chip->num_ports)188 p = 0;189 return p;190 } else if (hwport <= 0x10) { /* remote port */191 p = hwport - 0x09 + chip->num_ports;192 if (p >= chip->num_ports * 2)193 p = chip->num_ports;194 return p;195 } else if (hwport == 0x11) /* computer port */196 return chip->num_ports + MTPAV_PIDX_COMPUTER;197 else /* ADAT */198 return chip->num_ports + MTPAV_PIDX_ADAT;199}200 201 202/*203 */204 205static u8 snd_mtpav_getreg(struct mtpav *chip, u16 reg)206{207 u8 rval = 0;208 209 if (reg == SREG) {210 rval = inb(chip->port + SREG);211 rval = (rval & 0xf8);212 } else if (reg == CREG) {213 rval = inb(chip->port + CREG);214 rval = (rval & 0x1c);215 }216 217 return rval;218}219 220/*221 */222 223static inline void snd_mtpav_mputreg(struct mtpav *chip, u16 reg, u8 val)224{225 if (reg == DREG || reg == CREG)226 outb(val, chip->port + reg);227}228 229/*230 */231 232static void snd_mtpav_wait_rfdhi(struct mtpav *chip)233{234 int counts = 10000;235 u8 sbyte;236 237 sbyte = snd_mtpav_getreg(chip, SREG);238 while (!(sbyte & SIGS_RFD) && counts--) {239 sbyte = snd_mtpav_getreg(chip, SREG);240 udelay(10);241 }242}243 244static void snd_mtpav_send_byte(struct mtpav *chip, u8 byte)245{246 u8 tcbyt;247 u8 clrwrite;248 u8 setwrite;249 250 snd_mtpav_wait_rfdhi(chip);251 252 /////////////////253 254 tcbyt = snd_mtpav_getreg(chip, CREG);255 clrwrite = tcbyt & (SIGC_WRITE ^ 0xff);256 setwrite = tcbyt | SIGC_WRITE;257 258 snd_mtpav_mputreg(chip, DREG, byte);259 snd_mtpav_mputreg(chip, CREG, clrwrite); // clear write bit260 261 snd_mtpav_mputreg(chip, CREG, setwrite); // set write bit262 263}264 265 266/*267 */268 269/* call this with spin lock held */270static void snd_mtpav_output_port_write(struct mtpav *mtp_card,271 struct mtpav_port *portp,272 struct snd_rawmidi_substream *substream)273{274 u8 outbyte;275 276 // Get the outbyte first, so we can emulate running status if277 // necessary278 if (snd_rawmidi_transmit(substream, &outbyte, 1) != 1)279 return;280 281 // send port change command if necessary282 283 if (portp->hwport != mtp_card->outmidihwport) {284 mtp_card->outmidihwport = portp->hwport;285 286 snd_mtpav_send_byte(mtp_card, 0xf5);287 snd_mtpav_send_byte(mtp_card, portp->hwport);288 if (!(outbyte & 0x80) && portp->running_status)289 snd_mtpav_send_byte(mtp_card, portp->running_status);290 }291 292 // send data293 294 do {295 if (outbyte & 0x80)296 portp->running_status = outbyte;297 298 snd_mtpav_send_byte(mtp_card, outbyte);299 } while (snd_rawmidi_transmit(substream, &outbyte, 1) == 1);300}301 302static void snd_mtpav_output_write(struct snd_rawmidi_substream *substream)303{304 struct mtpav *mtp_card = substream->rmidi->private_data;305 struct mtpav_port *portp = &mtp_card->ports[substream->number];306 unsigned long flags;307 308 spin_lock_irqsave(&mtp_card->spinlock, flags);309 snd_mtpav_output_port_write(mtp_card, portp, substream);310 spin_unlock_irqrestore(&mtp_card->spinlock, flags);311}312 313 314/*315 * mtpav control316 */317 318static void snd_mtpav_portscan(struct mtpav *chip) // put mtp into smart routing mode319{320 u8 p;321 322 for (p = 0; p < 8; p++) {323 snd_mtpav_send_byte(chip, 0xf5);324 snd_mtpav_send_byte(chip, p);325 snd_mtpav_send_byte(chip, 0xfe);326 }327}328 329/*330 */331 332static int snd_mtpav_input_open(struct snd_rawmidi_substream *substream)333{334 struct mtpav *mtp_card = substream->rmidi->private_data;335 struct mtpav_port *portp = &mtp_card->ports[substream->number];336 unsigned long flags;337 338 spin_lock_irqsave(&mtp_card->spinlock, flags);339 portp->mode |= MTPAV_MODE_INPUT_OPENED;340 portp->input = substream;341 if (mtp_card->share_irq++ == 0)342 snd_mtpav_mputreg(mtp_card, CREG, (SIGC_INTEN | SIGC_WRITE)); // enable pport interrupts343 spin_unlock_irqrestore(&mtp_card->spinlock, flags);344 return 0;345}346 347/*348 */349 350static int snd_mtpav_input_close(struct snd_rawmidi_substream *substream)351{352 struct mtpav *mtp_card = substream->rmidi->private_data;353 struct mtpav_port *portp = &mtp_card->ports[substream->number];354 unsigned long flags;355 356 spin_lock_irqsave(&mtp_card->spinlock, flags);357 portp->mode &= ~MTPAV_MODE_INPUT_OPENED;358 portp->input = NULL;359 if (--mtp_card->share_irq == 0)360 snd_mtpav_mputreg(mtp_card, CREG, 0); // disable pport interrupts361 spin_unlock_irqrestore(&mtp_card->spinlock, flags);362 return 0;363}364 365/*366 */367 368static void snd_mtpav_input_trigger(struct snd_rawmidi_substream *substream, int up)369{370 struct mtpav *mtp_card = substream->rmidi->private_data;371 struct mtpav_port *portp = &mtp_card->ports[substream->number];372 unsigned long flags;373 374 spin_lock_irqsave(&mtp_card->spinlock, flags);375 if (up)376 portp->mode |= MTPAV_MODE_INPUT_TRIGGERED;377 else378 portp->mode &= ~MTPAV_MODE_INPUT_TRIGGERED;379 spin_unlock_irqrestore(&mtp_card->spinlock, flags);380 381}382 383 384/*385 * timer interrupt for outputs386 */387 388static void snd_mtpav_output_timer(struct timer_list *t)389{390 unsigned long flags;391 struct mtpav *chip = from_timer(chip, t, timer);392 int p;393 394 spin_lock_irqsave(&chip->spinlock, flags);395 /* reprogram timer */396 mod_timer(&chip->timer, 1 + jiffies);397 /* process each port */398 for (p = 0; p <= chip->num_ports * 2 + MTPAV_PIDX_BROADCAST; p++) {399 struct mtpav_port *portp = &chip->ports[p];400 if ((portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED) && portp->output)401 snd_mtpav_output_port_write(chip, portp, portp->output);402 }403 spin_unlock_irqrestore(&chip->spinlock, flags);404}405 406/* spinlock held! */407static void snd_mtpav_add_output_timer(struct mtpav *chip)408{409 mod_timer(&chip->timer, 1 + jiffies);410}411 412/* spinlock held! */413static void snd_mtpav_remove_output_timer(struct mtpav *chip)414{415 del_timer(&chip->timer);416}417 418/*419 */420 421static int snd_mtpav_output_open(struct snd_rawmidi_substream *substream)422{423 struct mtpav *mtp_card = substream->rmidi->private_data;424 struct mtpav_port *portp = &mtp_card->ports[substream->number];425 unsigned long flags;426 427 spin_lock_irqsave(&mtp_card->spinlock, flags);428 portp->mode |= MTPAV_MODE_OUTPUT_OPENED;429 portp->output = substream;430 spin_unlock_irqrestore(&mtp_card->spinlock, flags);431 return 0;432};433 434/*435 */436 437static int snd_mtpav_output_close(struct snd_rawmidi_substream *substream)438{439 struct mtpav *mtp_card = substream->rmidi->private_data;440 struct mtpav_port *portp = &mtp_card->ports[substream->number];441 unsigned long flags;442 443 spin_lock_irqsave(&mtp_card->spinlock, flags);444 portp->mode &= ~MTPAV_MODE_OUTPUT_OPENED;445 portp->output = NULL;446 spin_unlock_irqrestore(&mtp_card->spinlock, flags);447 return 0;448};449 450/*451 */452 453static void snd_mtpav_output_trigger(struct snd_rawmidi_substream *substream, int up)454{455 struct mtpav *mtp_card = substream->rmidi->private_data;456 struct mtpav_port *portp = &mtp_card->ports[substream->number];457 unsigned long flags;458 459 spin_lock_irqsave(&mtp_card->spinlock, flags);460 if (up) {461 if (! (portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) {462 if (mtp_card->istimer++ == 0)463 snd_mtpav_add_output_timer(mtp_card);464 portp->mode |= MTPAV_MODE_OUTPUT_TRIGGERED;465 }466 } else {467 portp->mode &= ~MTPAV_MODE_OUTPUT_TRIGGERED;468 if (--mtp_card->istimer == 0)469 snd_mtpav_remove_output_timer(mtp_card);470 }471 spin_unlock_irqrestore(&mtp_card->spinlock, flags);472 473 if (up)474 snd_mtpav_output_write(substream);475}476 477/*478 * midi interrupt for inputs479 */480 481static void snd_mtpav_inmidi_process(struct mtpav *mcrd, u8 inbyte)482{483 struct mtpav_port *portp;484 485 if ((int)mcrd->inmidiport > mcrd->num_ports * 2 + MTPAV_PIDX_BROADCAST)486 return;487 488 portp = &mcrd->ports[mcrd->inmidiport];489 if (portp->mode & MTPAV_MODE_INPUT_TRIGGERED)490 snd_rawmidi_receive(portp->input, &inbyte, 1);491}492 493static void snd_mtpav_inmidi_h(struct mtpav *mcrd, u8 inbyte)494{495 if (inbyte >= 0xf8) {496 /* real-time midi code */497 snd_mtpav_inmidi_process(mcrd, inbyte);498 return;499 }500 501 if (mcrd->inmidistate == 0) { // awaiting command502 if (inbyte == 0xf5) // MTP port #503 mcrd->inmidistate = 1;504 else505 snd_mtpav_inmidi_process(mcrd, inbyte);506 } else if (mcrd->inmidistate) {507 mcrd->inmidiport = translate_hwport_to_subdevice(mcrd, inbyte);508 mcrd->inmidistate = 0;509 }510}511 512static void snd_mtpav_read_bytes(struct mtpav *mcrd)513{514 u8 clrread, setread;515 u8 mtp_read_byte;516 u8 sr, cbyt;517 int i;518 519 u8 sbyt = snd_mtpav_getreg(mcrd, SREG);520 521 if (!(sbyt & SIGS_BYTE))522 return;523 524 cbyt = snd_mtpav_getreg(mcrd, CREG);525 clrread = cbyt & (SIGC_READ ^ 0xff);526 setread = cbyt | SIGC_READ;527 528 do {529 530 mtp_read_byte = 0;531 for (i = 0; i < 4; i++) {532 snd_mtpav_mputreg(mcrd, CREG, setread);533 sr = snd_mtpav_getreg(mcrd, SREG);534 snd_mtpav_mputreg(mcrd, CREG, clrread);535 536 sr &= SIGS_IN0 | SIGS_IN1;537 sr >>= 4;538 mtp_read_byte |= sr << (i * 2);539 }540 541 snd_mtpav_inmidi_h(mcrd, mtp_read_byte);542 543 sbyt = snd_mtpav_getreg(mcrd, SREG);544 545 } while (sbyt & SIGS_BYTE);546}547 548static irqreturn_t snd_mtpav_irqh(int irq, void *dev_id)549{550 struct mtpav *mcard = dev_id;551 552 spin_lock(&mcard->spinlock);553 snd_mtpav_read_bytes(mcard);554 spin_unlock(&mcard->spinlock);555 return IRQ_HANDLED;556}557 558/*559 * get ISA resources560 */561static int snd_mtpav_get_ISA(struct mtpav *mcard)562{563 mcard->res_port = devm_request_region(mcard->card->dev, port, 3,564 "MotuMTPAV MIDI");565 if (!mcard->res_port) {566 dev_err(mcard->card->dev, "MTVAP port 0x%lx is busy\n", port);567 return -EBUSY;568 }569 mcard->port = port;570 if (devm_request_irq(mcard->card->dev, irq, snd_mtpav_irqh, 0,571 "MOTU MTPAV", mcard)) {572 dev_err(mcard->card->dev, "MTVAP IRQ %d busy\n", irq);573 return -EBUSY;574 }575 mcard->irq = irq;576 return 0;577}578 579 580/*581 */582 583static const struct snd_rawmidi_ops snd_mtpav_output = {584 .open = snd_mtpav_output_open,585 .close = snd_mtpav_output_close,586 .trigger = snd_mtpav_output_trigger,587};588 589static const struct snd_rawmidi_ops snd_mtpav_input = {590 .open = snd_mtpav_input_open,591 .close = snd_mtpav_input_close,592 .trigger = snd_mtpav_input_trigger,593};594 595 596/*597 * get RAWMIDI resources598 */599 600static void snd_mtpav_set_name(struct mtpav *chip,601 struct snd_rawmidi_substream *substream)602{603 if (substream->number >= 0 && substream->number < chip->num_ports)604 sprintf(substream->name, "MTP direct %d", (substream->number % chip->num_ports) + 1);605 else if (substream->number >= 8 && substream->number < chip->num_ports * 2)606 sprintf(substream->name, "MTP remote %d", (substream->number % chip->num_ports) + 1);607 else if (substream->number == chip->num_ports * 2)608 strcpy(substream->name, "MTP computer");609 else if (substream->number == chip->num_ports * 2 + 1)610 strcpy(substream->name, "MTP ADAT");611 else612 strcpy(substream->name, "MTP broadcast");613}614 615static int snd_mtpav_get_RAWMIDI(struct mtpav *mcard)616{617 int rval;618 struct snd_rawmidi *rawmidi;619 struct snd_rawmidi_substream *substream;620 struct list_head *list;621 622 if (hwports < 1)623 hwports = 1;624 else if (hwports > 8)625 hwports = 8;626 mcard->num_ports = hwports;627 628 rval = snd_rawmidi_new(mcard->card, "MotuMIDI", 0,629 mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1,630 mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1,631 &mcard->rmidi);632 if (rval < 0)633 return rval;634 rawmidi = mcard->rmidi;635 rawmidi->private_data = mcard;636 637 list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {638 substream = list_entry(list, struct snd_rawmidi_substream, list);639 snd_mtpav_set_name(mcard, substream);640 substream->ops = &snd_mtpav_input;641 }642 list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {643 substream = list_entry(list, struct snd_rawmidi_substream, list);644 snd_mtpav_set_name(mcard, substream);645 substream->ops = &snd_mtpav_output;646 mcard->ports[substream->number].hwport = translate_subdevice_to_hwport(mcard, substream->number);647 }648 rawmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT |649 SNDRV_RAWMIDI_INFO_DUPLEX;650 sprintf(rawmidi->name, "MTP AV MIDI");651 return 0;652}653 654/*655 */656 657static void snd_mtpav_free(struct snd_card *card)658{659 struct mtpav *crd = card->private_data;660 unsigned long flags;661 662 spin_lock_irqsave(&crd->spinlock, flags);663 if (crd->istimer > 0)664 snd_mtpav_remove_output_timer(crd);665 spin_unlock_irqrestore(&crd->spinlock, flags);666}667 668/*669 */670static int snd_mtpav_probe(struct platform_device *dev)671{672 struct snd_card *card;673 int err;674 struct mtpav *mtp_card;675 676 err = snd_devm_card_new(&dev->dev, index, id, THIS_MODULE,677 sizeof(*mtp_card), &card);678 if (err < 0)679 return err;680 681 mtp_card = card->private_data;682 spin_lock_init(&mtp_card->spinlock);683 mtp_card->card = card;684 mtp_card->irq = -1;685 mtp_card->share_irq = 0;686 mtp_card->inmidistate = 0;687 mtp_card->outmidihwport = 0xffffffff;688 timer_setup(&mtp_card->timer, snd_mtpav_output_timer, 0);689 690 err = snd_mtpav_get_RAWMIDI(mtp_card);691 if (err < 0)692 return err;693 694 mtp_card->inmidiport = mtp_card->num_ports + MTPAV_PIDX_BROADCAST;695 696 err = snd_mtpav_get_ISA(mtp_card);697 if (err < 0)698 return err;699 700 strcpy(card->driver, "MTPAV");701 strcpy(card->shortname, "MTPAV on parallel port");702 snprintf(card->longname, sizeof(card->longname),703 "MTPAV on parallel port at 0x%lx", port);704 705 snd_mtpav_portscan(mtp_card);706 707 err = snd_card_register(mtp_card->card);708 if (err < 0)709 return err;710 711 card->private_free = snd_mtpav_free;712 713 platform_set_drvdata(dev, card);714 dev_info(card->dev,715 "Motu MidiTimePiece on parallel port irq: %d ioport: 0x%lx\n",716 irq, port);717 return 0;718}719 720#define SND_MTPAV_DRIVER "snd_mtpav"721 722static struct platform_driver snd_mtpav_driver = {723 .probe = snd_mtpav_probe,724 .driver = {725 .name = SND_MTPAV_DRIVER,726 },727};728 729static int __init alsa_card_mtpav_init(void)730{731 int err;732 733 err = platform_driver_register(&snd_mtpav_driver);734 if (err < 0)735 return err;736 737 device = platform_device_register_simple(SND_MTPAV_DRIVER, -1, NULL, 0);738 if (!IS_ERR(device)) {739 if (platform_get_drvdata(device))740 return 0;741 platform_device_unregister(device);742 err = -ENODEV;743 } else744 err = PTR_ERR(device);745 platform_driver_unregister(&snd_mtpav_driver);746 return err;747}748 749static void __exit alsa_card_mtpav_exit(void)750{751 platform_device_unregister(device);752 platform_driver_unregister(&snd_mtpav_driver);753}754 755module_init(alsa_card_mtpav_init)756module_exit(alsa_card_mtpav_exit)757