📄 mpu401.c
字号:
if (check_region(hw_config->io_base, 2)) { printk(KERN_ERR "mpu401: I/O port %x already in use\n\n", hw_config->io_base); return 0; } tmp_devc.base = hw_config->io_base; tmp_devc.irq = hw_config->irq; tmp_devc.initialized = 0; tmp_devc.opened = 0; tmp_devc.osp = hw_config->osp; if (hw_config->always_detect) return 1; if (inb(hw_config->io_base + 1) == 0xff) { DDB(printk("MPU401: Port %x looks dead.\n", hw_config->io_base)); return 0; /* Just bus float? */ } ok = reset_mpu401(&tmp_devc); if (!ok) { DDB(printk("MPU401: Reset failed on port %x\n", hw_config->io_base)); } return ok;}void unload_mpu401(struct address_info *hw_config){ void *p; int n=hw_config->slots[1]; release_region(hw_config->io_base, 2); if (hw_config->always_detect == 0 && hw_config->irq > 0) free_irq(hw_config->irq, (void *)n); p=mpu401_synth_operations[n]; sound_unload_mididev(n); sound_unload_timerdev(hw_config->slots[2]); if(p) kfree(p);}/***************************************************** * Timer stuff ****************************************************/static volatile int timer_initialized = 0, timer_open = 0, tmr_running = 0;static volatile int curr_tempo, curr_timebase, hw_timebase;static int max_timebase = 8; /* 8*24=192 ppqn */static volatile unsigned long next_event_time;static volatile unsigned long curr_ticks, curr_clocks;static unsigned long prev_event_time;static int metronome_mode;static unsigned long clocks2ticks(unsigned long clocks){ /* * The MPU-401 supports just a limited set of possible timebase values. * Since the applications require more choices, the driver has to * program the HW to do its best and to convert between the HW and * actual timebases. */ return ((clocks * curr_timebase) + (hw_timebase / 2)) / hw_timebase;}static void set_timebase(int midi_dev, int val){ int hw_val; if (val < 48) val = 48; if (val > 1000) val = 1000; hw_val = val; hw_val = (hw_val + 12) / 24; if (hw_val > max_timebase) hw_val = max_timebase; if (mpu_cmd(midi_dev, 0xC0 | (hw_val & 0x0f), 0) < 0) { printk(KERN_WARNING "mpu401: Can't set HW timebase to %d\n", hw_val * 24); return; } hw_timebase = hw_val * 24; curr_timebase = val;}static void tmr_reset(void){ unsigned long flags; save_flags(flags); cli(); next_event_time = (unsigned long) -1; prev_event_time = 0; curr_ticks = curr_clocks = 0; restore_flags(flags);}static void set_timer_mode(int midi_dev){ if (timer_mode & TMR_MODE_CLS) mpu_cmd(midi_dev, 0x3c, 0); /* Use CLS sync */ else if (timer_mode & TMR_MODE_SMPTE) mpu_cmd(midi_dev, 0x3d, 0); /* Use SMPTE sync */ if (timer_mode & TMR_INTERNAL) { mpu_cmd(midi_dev, 0x80, 0); /* Use MIDI sync */ } else { if (timer_mode & (TMR_MODE_MIDI | TMR_MODE_CLS)) { mpu_cmd(midi_dev, 0x82, 0); /* Use MIDI sync */ mpu_cmd(midi_dev, 0x91, 0); /* Enable ext MIDI ctrl */ } else if (timer_mode & TMR_MODE_FSK) mpu_cmd(midi_dev, 0x81, 0); /* Use FSK sync */ }}static void stop_metronome(int midi_dev){ mpu_cmd(midi_dev, 0x84, 0); /* Disable metronome */}static void setup_metronome(int midi_dev){ int numerator, denominator; int clks_per_click, num_32nds_per_beat; int beats_per_measure; numerator = ((unsigned) metronome_mode >> 24) & 0xff; denominator = ((unsigned) metronome_mode >> 16) & 0xff; clks_per_click = ((unsigned) metronome_mode >> 8) & 0xff; num_32nds_per_beat = (unsigned) metronome_mode & 0xff; beats_per_measure = (numerator * 4) >> denominator; if (!metronome_mode) mpu_cmd(midi_dev, 0x84, 0); /* Disable metronome */ else { mpu_cmd(midi_dev, 0xE4, clks_per_click); mpu_cmd(midi_dev, 0xE6, beats_per_measure); mpu_cmd(midi_dev, 0x83, 0); /* Enable metronome without accents */ }}static int mpu_start_timer(int midi_dev){ tmr_reset(); set_timer_mode(midi_dev); if (tmr_running) return TIMER_NOT_ARMED; /* Already running */ if (timer_mode & TMR_INTERNAL) { mpu_cmd(midi_dev, 0x02, 0); /* Send MIDI start */ tmr_running = 1; return TIMER_NOT_ARMED; } else { mpu_cmd(midi_dev, 0x35, 0); /* Enable mode messages to PC */ mpu_cmd(midi_dev, 0x38, 0); /* Enable sys common messages to PC */ mpu_cmd(midi_dev, 0x39, 0); /* Enable real time messages to PC */ mpu_cmd(midi_dev, 0x97, 0); /* Enable system exclusive messages to PC */ } return TIMER_ARMED;}static int mpu_timer_open(int dev, int mode){ int midi_dev = sound_timer_devs[dev]->devlink; if (timer_open) return -EBUSY; tmr_reset(); curr_tempo = 50; mpu_cmd(midi_dev, 0xE0, 50); curr_timebase = hw_timebase = 120; set_timebase(midi_dev, 120); timer_open = 1; metronome_mode = 0; set_timer_mode(midi_dev); mpu_cmd(midi_dev, 0xe7, 0x04); /* Send all clocks to host */ mpu_cmd(midi_dev, 0x95, 0); /* Enable clock to host */ return 0;}static void mpu_timer_close(int dev){ int midi_dev = sound_timer_devs[dev]->devlink; timer_open = tmr_running = 0; mpu_cmd(midi_dev, 0x15, 0); /* Stop all */ mpu_cmd(midi_dev, 0x94, 0); /* Disable clock to host */ mpu_cmd(midi_dev, 0x8c, 0); /* Disable measure end messages to host */ stop_metronome(midi_dev);}static int mpu_timer_event(int dev, unsigned char *event){ unsigned char command = event[1]; unsigned long parm = *(unsigned int *) &event[4]; int midi_dev = sound_timer_devs[dev]->devlink; switch (command) { case TMR_WAIT_REL: parm += prev_event_time; case TMR_WAIT_ABS: if (parm > 0) { long time; if (parm <= curr_ticks) /* It's the time */ return TIMER_NOT_ARMED; time = parm; next_event_time = prev_event_time = time; return TIMER_ARMED; } break; case TMR_START: if (tmr_running) break; return mpu_start_timer(midi_dev); case TMR_STOP: mpu_cmd(midi_dev, 0x01, 0); /* Send MIDI stop */ stop_metronome(midi_dev); tmr_running = 0; break; case TMR_CONTINUE: if (tmr_running) break; mpu_cmd(midi_dev, 0x03, 0); /* Send MIDI continue */ setup_metronome(midi_dev); tmr_running = 1; break; case TMR_TEMPO: if (parm) { if (parm < 8) parm = 8; if (parm > 250) parm = 250; if (mpu_cmd(midi_dev, 0xE0, parm) < 0) printk(KERN_WARNING "mpu401: Can't set tempo to %d\n", (int) parm); curr_tempo = parm; } break; case TMR_ECHO: seq_copy_to_input(event, 8); break; case TMR_TIMESIG: if (metronome_mode) /* Metronome enabled */ { metronome_mode = parm; setup_metronome(midi_dev); } break; default:; } return TIMER_NOT_ARMED;}static unsigned long mpu_timer_get_time(int dev){ if (!timer_open) return 0; return curr_ticks;}static int mpu_timer_ioctl(int dev, unsigned int command, caddr_t arg){ int midi_dev = sound_timer_devs[dev]->devlink; switch (command) { case SNDCTL_TMR_SOURCE: { int parm; parm = *(int *) arg; parm &= timer_caps; if (parm != 0) { timer_mode = parm; if (timer_mode & TMR_MODE_CLS) mpu_cmd(midi_dev, 0x3c, 0); /* Use CLS sync */ else if (timer_mode & TMR_MODE_SMPTE) mpu_cmd(midi_dev, 0x3d, 0); /* Use SMPTE sync */ } return (*(int *) arg = timer_mode); } break; case SNDCTL_TMR_START: mpu_start_timer(midi_dev); return 0; case SNDCTL_TMR_STOP: tmr_running = 0; mpu_cmd(midi_dev, 0x01, 0); /* Send MIDI stop */ stop_metronome(midi_dev); return 0; case SNDCTL_TMR_CONTINUE: if (tmr_running) return 0; tmr_running = 1; mpu_cmd(midi_dev, 0x03, 0); /* Send MIDI continue */ return 0; case SNDCTL_TMR_TIMEBASE: { int val; val = *(int *) arg; if (val) set_timebase(midi_dev, val); return (*(int *) arg = curr_timebase); } break; case SNDCTL_TMR_TEMPO: { int val; int ret; val = *(int *) arg; if (val) { if (val < 8) val = 8; if (val > 250) val = 250; if ((ret = mpu_cmd(midi_dev, 0xE0, val)) < 0) { printk(KERN_WARNING "mpu401: Can't set tempo to %d\n", (int) val); return ret; } curr_tempo = val; } return (*(int *) arg = curr_tempo); } break; case SNDCTL_SEQ_CTRLRATE: { int val; val = *(int *) arg; if (val != 0) /* Can't change */ return -EINVAL; return (*(int *) arg = ((curr_tempo * curr_timebase) + 30) / 60); } break; case SNDCTL_SEQ_GETTIME: return (*(int *) arg = curr_ticks); case SNDCTL_TMR_METRONOME: metronome_mode = *(int *) arg; setup_metronome(midi_dev); return 0; default:; } return -EINVAL;}static void mpu_timer_arm(int dev, long time){ if (time < 0) time = curr_ticks + 1; else if (time <= curr_ticks) /* It's the time */ return; next_event_time = prev_event_time = time; return;}static struct sound_timer_operations mpu_timer ={ owner: THIS_MODULE, info: {"MPU-401 Timer", 0}, priority: 10, /* Priority */ devlink: 0, /* Local device link */ open: mpu_timer_open, close: mpu_timer_close, event: mpu_timer_event, get_time: mpu_timer_get_time, ioctl: mpu_timer_ioctl, arm_timer: mpu_timer_arm};static void mpu_timer_interrupt(void){ if (!timer_open) return; if (!tmr_running) return; curr_clocks++; curr_ticks = clocks2ticks(curr_clocks); if (curr_ticks >= next_event_time) { next_event_time = (unsigned long) -1; sequencer_timer(0); }}static void timer_ext_event(struct mpu_config *devc, int event, int parm){ int midi_dev = devc->devno; if (!devc->timer_flag) return; switch (event) { case TMR_CLOCK: printk("<MIDI clk>"); break; case TMR_START: printk("Ext MIDI start\n"); if (!tmr_running) { if (timer_mode & TMR_EXTERNAL) { tmr_running = 1; setup_metronome(midi_dev); next_event_time = 0; STORE(SEQ_START_TIMER()); } } break; case TMR_STOP: printk("Ext MIDI stop\n"); if (timer_mode & TMR_EXTERNAL) { tmr_running = 0; stop_metronome(midi_dev); STORE(SEQ_STOP_TIMER()); } break; case TMR_CONTINUE: printk("Ext MIDI continue\n"); if (timer_mode & TMR_EXTERNAL) { tmr_running = 1; setup_metronome(midi_dev); STORE(SEQ_CONTINUE_TIMER()); } break; case TMR_SPP: printk("Songpos: %d\n", parm); if (timer_mode & TMR_EXTERNAL) { STORE(SEQ_SONGPOS(parm)); } break; }}static int mpu_timer_init(int midi_dev){ struct mpu_config *devc; int n; devc = &dev_conf[midi_dev]; if (timer_initialized) return -1; /* There is already a similar timer */ timer_initialized = 1; mpu_timer.devlink = midi_dev; dev_conf[midi_dev].timer_flag = 1; n = sound_alloc_timerdev(); if (n == -1) n = 0; sound_timer_devs[n] = &mpu_timer; if (devc->version < 0x20) /* Original MPU-401 */ timer_caps = TMR_INTERNAL | TMR_EXTERNAL | TMR_MODE_FSK | TMR_MODE_MIDI; else { /* * The version number 2.0 is used (at least) by the * MusicQuest cards and the Roland Super-MPU. * * MusicQuest has given a special meaning to the bits of the * revision number. The Super-MPU returns 0. */ if (devc->revision) timer_caps |= TMR_EXTERNAL | TMR_MODE_MIDI; if (devc->revision & 0x02) timer_caps |= TMR_MODE_CLS; if (devc->revision & 0x40) max_timebase = 10; /* Has the 216 and 240 ppqn modes */ } timer_mode = (TMR_INTERNAL | TMR_MODE_MIDI) & timer_caps; return n;}EXPORT_SYMBOL(probe_mpu401);EXPORT_SYMBOL(attach_mpu401);EXPORT_SYMBOL(unload_mpu401);EXPORT_SYMBOL(intchk_mpu401);EXPORT_SYMBOL(mpuintr);static struct address_info cfg;static int __initdata io = -1;static int __initdata irq = -1;MODULE_PARM(irq, "i");MODULE_PARM(io, "i");int __init init_mpu401(void){ /* Can be loaded either for module use or to provide functions to others */ if (io != -1 && irq != -1) { cfg.irq = irq; cfg.io_base = io; if (probe_mpu401(&cfg) == 0) return -ENODEV; attach_mpu401(&cfg, THIS_MODULE); } return 0;}void __exit cleanup_mpu401(void){ if (io != -1 && irq != -1) { /* Check for use by, for example, sscape driver */ unload_mpu401(&cfg); }}module_init(init_mpu401);module_exit(cleanup_mpu401);#ifndef MODULEstatic int __init setup_mpu401(char *str){ /* io, irq */ int ints[3]; str = get_options(str, ARRAY_SIZE(ints), ints); io = ints[1]; irq = ints[2]; return 1;}__setup("mpu401=", setup_mpu401);#endifMODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -