/*
* EMU10k1 loader
*
* Copyright (c) 2003,2004 by Peter Zubaj
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/ioctl.h>
#include <alsa/asoundlib.h>
#include <alsa/sound/emu10k1.h>
#include <stdint.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "bitops.h"
#include "ld10k1.h"
#include "ld10k1_driver.h"
#include "ld10k1_error.h"
#include "ld10k1_fnc.h"
#include "ld10k1_fnc_int.h"
//#define DEBUG_DRIVER 1
extern snd_hwdep_t *handle;
void ld10k1_syntetize_instr(int audigy, int op, int arg1, int arg2, int arg3, int arg4, unsigned int *out)
{
if (audigy) {
*out = ((arg3 & 0x7ff) << 12) | (arg4 & 0x7ff);
*(out + 1) = ((op & 0x0f) << 24) | ((arg1 & 0x7ff) << 12) | (arg2 & 0x7ff);
} else {
*out = ((arg3 & 0x3ff) << 10) | (arg4 & 0x3ff);
*(out + 1) = ((op & 0x0f) << 20) | ((arg1 & 0x3ff) << 10) | (arg2 & 0x3ff);
}
}
void ld10k1_init_must_init_output(ld10k1_dsp_mgr_t *dsp_mgr);
void ld10k1_set_must_init_output(ld10k1_dsp_mgr_t *dsp_mgr, int reg);
void ld10k1_check_must_init_output(ld10k1_dsp_mgr_t *dsp_mgr, emu10k1_fx8010_code_t *code);
/* outputs what must be initialized on audigy */
static int audigy_must_init_output[] = {
0x68, 0,
0x69, 0,
0x6a, 0,
0x6b, 0,
0x6e, 0,
0x6f, 0,
-1};
#define LD10K1_SIGNATURE "LD10K1 ver. " VERSION " managed DSP code"
void ld10k1_free_code_struct(emu10k1_fx8010_code_t *code)
{
if (code->gpr_map)
free(code->gpr_map);
if (code->tram_data_map)
free(code->tram_data_map);
if (code->tram_addr_map)
free(code->tram_addr_map);
if (code->code)
free(code->code);
}
int ld10k1_alloc_code_struct(emu10k1_fx8010_code_t *code)
{
/* alloc code structure */
code->gpr_map = NULL;
code->tram_data_map = NULL;
code->tram_addr_map = NULL;
code->code = NULL;
code->gpr_map = (uint32_t *)malloc(sizeof(uint32_t) * 0x200);
if (!code->gpr_map)
goto err;
memset(code->gpr_map, 0, sizeof(uint32_t) * 0x200);
code->tram_data_map = (uint32_t *)malloc(sizeof(uint32_t) * 0x100);
if (!code->tram_data_map)
goto err;
memset(code->tram_data_map, 0, sizeof(uint32_t) * 0x100);
code->tram_addr_map = (uint32_t *)malloc(sizeof(uint32_t) * 0x100);
if (!code->tram_addr_map)
goto err;
memset(code->tram_addr_map, 0, sizeof(uint32_t) * 0x100);
code->code = (uint32_t *)malloc(sizeof(uint32_t) * 1024 * 2);
if (!code->code)
goto err;
memset(code->code, 0, sizeof(uint32_t) * 1024 * 2);
return 0;
err:
ld10k1_free_code_struct(code);
return LD10K1_ERR_NO_MEM;
}
int ld10k1_update_driver(ld10k1_dsp_mgr_t *dsp_mgr)
{
emu10k1_fx8010_code_t code;
emu10k1_fx8010_control_gpr_t *add_ctrl;
emu10k1_ctl_elem_id_t *del_ids;
ld10k1_ctl_list_item_t *item;
unsigned int i, j;
int max;
int modified;
unsigned int addr;
unsigned int vaddr;
unsigned int op;
unsigned int idx_offset;
unsigned int *iptr;
ld10k1_ctl_t gctl;
int err;
if ((err = ld10k1_alloc_code_struct(&code)) < 0)
return err;
/* new name */
strcpy(code.name, LD10K1_SIGNATURE);
for (i = 0; i < sizeof(code.gpr_valid) / sizeof(unsigned long); i++)
code.gpr_valid[i] = 0;
for (i = 0; i < sizeof(code.tram_valid) / sizeof(unsigned long); i++)
code.tram_valid[i] = 0;
for (i = 0; i < sizeof(code.code_valid) / sizeof(unsigned long); i++)
code.code_valid[i] = 0;
/* registers */
for (i = 0; i < dsp_mgr->regs_max_count; i++)
if (dsp_mgr->regs[i].modified) {
set_bit(i, code.gpr_valid);
code.gpr_map[i] = dsp_mgr->regs[i].val;
}
/* tram addr + data */
for (j = 0; j < 2; j++) {
max = (j == 0 ? dsp_mgr->max_itram_hwacc : dsp_mgr->max_etram_hwacc);
for (i = 0; i < max; i++) {
modified = (j == 0 ? dsp_mgr->itram_hwacc[i].modified : dsp_mgr->etram_hwacc[i].modified);
if (modified) {
addr = (j == 0 ? dsp_mgr->itram_hwacc[i].addr_val : dsp_mgr->etram_hwacc[i].addr_val);
vaddr = addr & 0xFFFFF;
idx_offset = (j == 0 ? 0 : dsp_mgr->max_itram_hwacc);
op = (j == 0 ? dsp_mgr->itram_hwacc[i].op : dsp_mgr->etram_hwacc[i].op);
set_bit(i + idx_offset, code.tram_valid);
switch(op) {
case TRAM_OP_READ:
if (dsp_mgr->audigy)
vaddr = vaddr | 0x2 << 20;
else
vaddr = vaddr | TANKMEMADDRREG_READ | TANKMEMADDRREG_ALIGN;
break;
case TRAM_OP_WRITE:
if (dsp_mgr->audigy)
vaddr = vaddr | 0x6 << 20;
else
vaddr = vaddr | TANKMEMADDRREG_WRITE | TANKMEMADDRREG_ALIGN;
break;
case TRAM_OP_NULL:
default:
vaddr = 0;
break;
}
code.tram_addr_map[i + idx_offset] = vaddr;
code.tram_data_map[i + idx_offset] = (j == 0 ? dsp_mgr->itram_hwacc[i].data_val : dsp_mgr->etram_hwacc[i].data_val);
}
}
}
/* controls to add */
if (dsp_mgr->add_list_count > 0) {
add_ctrl = calloc(dsp_mgr->add_list_count,
sizeof(emu10k1_fx8010_control_gpr_t));
if (!add_ctrl)
return LD10K1_ERR_NO_MEM;
for (i = 0, item = dsp_mgr->add_ctl_list; item != NULL; item = item->next, i++) {
strcpy(add_ctrl[i].id.name, item->ctl.name);
add_ctrl[i].id.iface = EMU10K1_CTL_ELEM_IFACE_MIXER;
add_ctrl[i].id.index = item->ctl.index;
add_ctrl[i].vcount = item->ctl.vcount;
add_ctrl[i].count = item->ctl.count;
for (j = 0; j < 32; j++) {
add_ctrl[i].gpr[j] = item->ctl.gpr_idx[j];
add_ctrl[i].value[j] = item->ctl.value[j];
}
add_ctrl[i].min = item->ctl.min;
add_ctrl[i].max = item->ctl.max;
add_ctrl[i].translation = item->ctl.translation;
}
} else
add_ctrl = NULL;
code.gpr_add_control_count = dsp_mgr->add_list_count;
code.gpr_add_controls = add_ctrl;
/* controls to del */
if (dsp_mgr->del_list_count > 0) {
del_ids = calloc(dsp_mgr->del_list_count,
sizeof(emu10k1_ctl_elem_id_t));
if (!del_ids)
return LD10K1_ERR_NO_MEM;
for (i = 0, item = dsp_mgr->del_ctl_list; item != NULL; item = item->next, i++) {
strcpy(del_ids[i].name, item->ctl.name);
del_ids[i].iface = EMU10K1_CTL_ELEM_IFACE_MIXER;
del_ids[i].index = item->ctl.index;
}
} else
del_ids = NULL;
code.gpr_del_control_count = dsp_mgr->del_list_count;
code.gpr_del_controls = del_ids;
code.gpr_list_control_count = 0;
for (iptr = code.code, i = 0; i < dsp_mgr->instr_count; i++, iptr += 2) {
if (dsp_mgr->instr[i].modified) {
set_bit(i, code.code_valid);
if (dsp_mgr->instr[i].used) {
if (dsp_mgr->audigy) {
ld10k1_syntetize_instr(dsp_mgr->audigy,
dsp_mgr->instr[i].op_code,
dsp_mgr->instr[i].arg[0], dsp_mgr->instr[i].arg[1], dsp_mgr->instr[i].arg[2], dsp_mgr->instr[i].arg[3], iptr);
} else {
if (i < 0x200) {
ld10k1_syntetize_instr(dsp_mgr->audigy,
dsp_mgr->instr[i].op_code,
dsp_mgr->instr[i].arg[0], dsp_mgr->instr[i].arg[1], dsp_mgr->instr[i].arg[2], dsp_mgr->instr[i].arg[3], iptr);
}
}
} else {
if (dsp_mgr->audigy) {
ld10k1_syntetize_instr(dsp_mgr->audigy,
0x0f,
0xc0, 0xc0, 0xcf, 0xc0, iptr);
} else {
if (i < 0x200) {
ld10k1_syntetize_instr(dsp_mgr->audigy,
0x06,
0x40, 0x40, 0x40, 0x40, iptr);
}
}
}
}
}
/* check initialization of i2s outputs on audigy */
if (dsp_mgr->audigy)
ld10k1_check_must_init_output(dsp_mgr, &code);
#ifndef DEBUG_DRIVER
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_CODE_POKE, &code) < 0) {
error("unable to poke code");
ld10k1_free_code_struct(&code);
if (add_ctrl)
free(add_ctrl);
if (del_ids)
free(del_ids);
return LD10K1_ERR_DRIVER_CODE_POKE;
}
#endif
/* update state */
for (item = dsp_mgr->del_ctl_list; item != NULL; item = item->next) {
strcpy(gctl.name, item->ctl.name);
ld10k1_del_control_from_list(&(dsp_mgr->ctl_list), &(dsp_mgr->ctl_list_count), &gctl);
}
ld10k1_del_all_controls_from_list(&(dsp_mgr->del_ctl_list), &dsp_mgr->del_list_count);
for (item = dsp_mgr->add_ctl_list; item != NULL; item = item->next)
ld10k1_add_control_to_list(&(dsp_mgr->ctl_list), &(dsp_mgr->ctl_list_count), &(item->ctl));
ld10k1_del_all_controls_from_list(&(dsp_mgr->add_ctl_list), &dsp_mgr->add_list_count);
for (i = 0; i < dsp_mgr->regs_max_count; i++)
dsp_mgr->regs[i].modified = 0;
for (i = 0; i < dsp_mgr->instr_count; i++)
dsp_mgr->instr[i].modified = 0;
for (j = 0; j < 2; j++) {
max = (j == 0 ? dsp_mgr->max_itram_hwacc : dsp_mgr->max_etram_hwacc);
for (i = 0; i < max; i++) {
if (j == 0)
dsp_mgr->itram_hwacc[i].modified = 0;
else
dsp_mgr->etram_hwacc[i].modified = 0;
}
}
ld10k1_free_code_struct(&code);
if (add_ctrl)
free(add_ctrl);
if (del_ids)
free(del_ids);
return 0;
}
int ld10k1_init_driver(ld10k1_dsp_mgr_t *dsp_mgr, int tram_size)
{
emu10k1_fx8010_info_t info;
int i;
emu10k1_fx8010_code_t code;
emu10k1_fx8010_control_gpr_t *ctrl;
emu10k1_ctl_elem_id_t *ids;
emu10k1_fx8010_pcm_t ipcm;
unsigned int *iptr;
int err;
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_PVERSION, &i) < 0) {
error("Cannot get emu10k1 driver version, likely an old driver is running.");
return LD10K1_ERR_DRIVER_INFO;
}
if ((err = ld10k1_alloc_code_struct(&code)) < 0)
return err;
/* setup tram size */
if (tram_size >= 0 && snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_TRAM_SETUP, &tram_size) < 0) {
error("unable to setup tram");
if (dsp_mgr->audigy)
error("You are probably user of audigy, audigy 2 and you not aplyed patch to enable tram");
/* this is not fatal, but do not use tram */
dsp_mgr->i_tram.size = 0;
dsp_mgr->e_tram.size = 0;
} else {
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_INFO, &info) < 0) {
error("unable to get info ");
ld10k1_free_code_struct(&code);
return LD10K1_ERR_DRIVER_INFO;
}
dsp_mgr->i_tram.size = info.internal_tram_size;
dsp_mgr->e_tram.size = info.external_tram_size;
}
/* get count of controls */
code.gpr_list_control_count = 0;
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_CODE_PEEK, &code) < 0) {
error("unable to peek code");
ld10k1_free_code_struct(&code);
return LD10K1_ERR_DRIVER_CODE_PEEK;
}
ctrl = calloc(code.gpr_list_control_total,
sizeof(emu10k1_fx8010_control_gpr_t));
if (!ctrl) {
ld10k1_free_code_struct(&code);
return LD10K1_ERR_NO_MEM;
}
code.gpr_list_control_count = code.gpr_list_control_total;
code.gpr_list_controls = ctrl;
for (i = 0; i < sizeof(code.gpr_valid) / sizeof(unsigned long); i++)
code.gpr_valid[i] = 0x0;
for (i = 0; i < sizeof(code.tram_valid) / sizeof(unsigned long); i++)
code.tram_valid[i] = 0x0;
for (i = 0; i < sizeof(code.code_valid) / sizeof(unsigned long); i++)
code.code_valid[i] = 0x0;;
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_CODE_PEEK, &code) < 0) {
error("unable to peek code");
ld10k1_free_code_struct(&code);
free(ctrl);
return LD10K1_ERR_DRIVER_CODE_PEEK;
}
/* new name */
strcpy(code.name, LD10K1_SIGNATURE);
for (i = 0; i < sizeof(code.gpr_valid) / sizeof(unsigned long); i++)
code.gpr_valid[i] = ~0;
for (i = 0; i < sizeof(code.gpr_valid) * 8; i++) {
code.gpr_map[i] = 0;
}
ids = calloc(code.gpr_list_control_total,
sizeof(emu10k1_ctl_elem_id_t));
if (!ids) {
ld10k1_free_code_struct(&code);
free(ctrl);
return LD10K1_ERR_NO_MEM;
}
code.gpr_del_control_count = code.gpr_list_control_total;
if (code.gpr_del_control_count) {
for (i = 0; i < code.gpr_del_control_count; i++) {
memcpy(&(ids[i]), &(ctrl[i].id), sizeof(emu10k1_ctl_elem_id_t));
}
}
free(ctrl);
code.gpr_del_controls = ids;
code.gpr_list_control_count = 0;
code.gpr_add_control_count = 0;
code.gpr_list_control_count = 0;
for (i = 0; i < sizeof(code.tram_valid) / sizeof(unsigned long); i++)
code.tram_valid[i] = ~0;
for (i = 0; i < sizeof(code.code_valid) / sizeof(unsigned long); i++)
code.code_valid[i] = ~0;
for (i = 0; i < sizeof(code.tram_valid) * 8; i++) {
code.tram_addr_map[i] = 0;
code.tram_data_map[i] = 0;
}
for (iptr = code.code, i = 0; i < sizeof(code.code_valid) * 8; i++, iptr += 2)
if (dsp_mgr->audigy) {
ld10k1_syntetize_instr(dsp_mgr->audigy,
0x0f,
0xc0, 0xc0, 0xcf, 0xc0, iptr);
} else {
ld10k1_syntetize_instr(dsp_mgr->audigy,
0x06,
0x40, 0x40, 0x40, 0x40, iptr);
}
/* initialize i2s outputs on audigy */
if (dsp_mgr->audigy) {
for (iptr = code.code, i = 0; audigy_must_init_output[i] > 0; i += 2, iptr += 2)
ld10k1_syntetize_instr(dsp_mgr->audigy, 0x00,
audigy_must_init_output[i], 0xc0, 0xc0, 0xc0, iptr);
}
#ifndef DEBUG_DRIVER
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_CODE_POKE, &code) < 0) {
error("unable to poke code");
ld10k1_free_code_struct(&code);
free(ids);
return LD10K1_ERR_DRIVER_CODE_POKE;
}
#endif
free(ids);
/* delete tram pcm dsp part */
if (!dsp_mgr->audigy) {
for (i = 0; i < EMU10K1_FX8010_PCM_COUNT; i++) {
ipcm.substream = i;
ipcm.channels = 0;
#ifndef DEBUG_DRIVER
if (snd_hwdep_ioctl(handle, SNDRV_EMU10K1_IOCTL_PCM_POKE, &ipcm) < 0) {
error("unable to poke code");
ld10k1_free_code_struct(&code);
return LD10K1_ERR_DRIVER_PCM_POKE;
}
#endif
}
}
ld10k1_free_code_struct(&code);
return 0;
}
void ld10k1_init_must_init_output(ld10k1_dsp_mgr_t *dsp_mgr)
{
int i;
if (dsp_mgr->audigy) {
for (i = 0; audigy_must_init_output[i] > 0; i += 2)
audigy_must_init_output[i + 1] = 1;
}
}
void ld10k1_set_must_init_output(ld10k1_dsp_mgr_t *dsp_mgr, int reg)
{
int i ;
if (dsp_mgr->audigy) {
for (i = 0; audigy_must_init_output[i] > 0; i += 2) {
if (audigy_must_init_output[i] == reg) {
audigy_must_init_output[i + 1] = 0;
return;
}
}
}
}
void ld10k1_check_must_init_output(ld10k1_dsp_mgr_t *dsp_mgr, emu10k1_fx8010_code_t *code)
{
int j;
ld10k1_init_must_init_output(dsp_mgr);
for (j = 0; j < dsp_mgr->instr_count; j++) {
if (dsp_mgr->instr[j].used)
ld10k1_set_must_init_output(dsp_mgr, dsp_mgr->instr[j].arg[0]);
}
int i;
int l;
int ioffset = dsp_mgr->instr_count - 1;
if (dsp_mgr->audigy) {
for (i = 0; audigy_must_init_output[i] > 0; i += 2) {
if (audigy_must_init_output[i + 1]) {
/* find free instruction slot */
for (;ioffset >= 0; ioffset--) {
if (!dsp_mgr->instr[ioffset].used) {
ld10k1_instr_t *instr = &(dsp_mgr->instr[ioffset]);
ld10k1_syntetize_instr(dsp_mgr->audigy,
0x0,
audigy_must_init_output[i], 0xc0, 0xc0, 0xc0,
code->code + ioffset * 2);
instr->op_code = 0;
instr->arg[0] = audigy_must_init_output[i];
for (l = 1; l < 4; l++)
instr->arg[l] = 0xc0;
set_bit(ioffset, code->code_valid);
dsp_mgr->instr[ioffset].used = 1;
ioffset--;
break;
}
}
if (ioffset < 0)
return;
}
}
}
}