Browse Source
This driver emulates the ARM AACI interface (PL041) connected to a LM4549 codec. It enables audio playback for the Versatile/PB platform. Limitations: - Supports only a playback on one channel (Versatile/Vexpress) - Supports only one TX FIFO in compact-mode or non-compact mode. - Supports playback of 12, 16, 18 and 20 bits samples. - Record is not supported. - The PL041 is hardwired to a LM4549 codec. Versatile/PB test build: linux-2.6.38.5 buildroot-2010.11 alsa-lib-1.0.22 alsa-utils-1.0.22 mpg123-0.66 Qemu host: Ubuntu 10.04 in Vmware/OS X Playback tested successfully with speaker-test/aplay/mpg123. Signed-off-by: Mathieu Sonet <contact@elasticsheep.com> [Peter Maydell: fixed typo in code clearing SL1RXBUSY/SL2RXBUSY bits, as spotted by Andrzej Zaborowski] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Andrzej Zaborowski <andrew.zaborowski@intel.com>stable-1.0
committed by
Andrzej Zaborowski
7 changed files with 1240 additions and 0 deletions
@ -0,0 +1,336 @@ |
|||
/*
|
|||
* LM4549 Audio Codec Interface |
|||
* |
|||
* Copyright (c) 2011 |
|||
* Written by Mathieu Sonet - www.elasticsheep.com |
|||
* |
|||
* This code is licenced under the GPL. |
|||
* |
|||
* ***************************************************************** |
|||
* |
|||
* This driver emulates the LM4549 codec. |
|||
* |
|||
* It supports only one playback voice and no record voice. |
|||
*/ |
|||
|
|||
#include "hw.h" |
|||
#include "audio/audio.h" |
|||
#include "lm4549.h" |
|||
|
|||
#if 0 |
|||
#define LM4549_DEBUG 1 |
|||
#endif |
|||
|
|||
#if 0 |
|||
#define LM4549_DUMP_DAC_INPUT 1 |
|||
#endif |
|||
|
|||
#ifdef LM4549_DEBUG |
|||
#define DPRINTF(fmt, ...) \ |
|||
do { printf("lm4549: " fmt , ## __VA_ARGS__); } while (0) |
|||
#else |
|||
#define DPRINTF(fmt, ...) do {} while (0) |
|||
#endif |
|||
|
|||
#if defined(LM4549_DUMP_DAC_INPUT) |
|||
#include <stdio.h> |
|||
static FILE *fp_dac_input; |
|||
#endif |
|||
|
|||
/* LM4549 register list */ |
|||
enum { |
|||
LM4549_Reset = 0x00, |
|||
LM4549_Master_Volume = 0x02, |
|||
LM4549_Line_Out_Volume = 0x04, |
|||
LM4549_Master_Volume_Mono = 0x06, |
|||
LM4549_PC_Beep_Volume = 0x0A, |
|||
LM4549_Phone_Volume = 0x0C, |
|||
LM4549_Mic_Volume = 0x0E, |
|||
LM4549_Line_In_Volume = 0x10, |
|||
LM4549_CD_Volume = 0x12, |
|||
LM4549_Video_Volume = 0x14, |
|||
LM4549_Aux_Volume = 0x16, |
|||
LM4549_PCM_Out_Volume = 0x18, |
|||
LM4549_Record_Select = 0x1A, |
|||
LM4549_Record_Gain = 0x1C, |
|||
LM4549_General_Purpose = 0x20, |
|||
LM4549_3D_Control = 0x22, |
|||
LM4549_Powerdown_Ctrl_Stat = 0x26, |
|||
LM4549_Ext_Audio_ID = 0x28, |
|||
LM4549_Ext_Audio_Stat_Ctrl = 0x2A, |
|||
LM4549_PCM_Front_DAC_Rate = 0x2C, |
|||
LM4549_PCM_ADC_Rate = 0x32, |
|||
LM4549_Vendor_ID1 = 0x7C, |
|||
LM4549_Vendor_ID2 = 0x7E |
|||
}; |
|||
|
|||
static void lm4549_reset(lm4549_state *s) |
|||
{ |
|||
uint16_t *regfile = s->regfile; |
|||
|
|||
regfile[LM4549_Reset] = 0x0d50; |
|||
regfile[LM4549_Master_Volume] = 0x8008; |
|||
regfile[LM4549_Line_Out_Volume] = 0x8000; |
|||
regfile[LM4549_Master_Volume_Mono] = 0x8000; |
|||
regfile[LM4549_PC_Beep_Volume] = 0x0000; |
|||
regfile[LM4549_Phone_Volume] = 0x8008; |
|||
regfile[LM4549_Mic_Volume] = 0x8008; |
|||
regfile[LM4549_Line_In_Volume] = 0x8808; |
|||
regfile[LM4549_CD_Volume] = 0x8808; |
|||
regfile[LM4549_Video_Volume] = 0x8808; |
|||
regfile[LM4549_Aux_Volume] = 0x8808; |
|||
regfile[LM4549_PCM_Out_Volume] = 0x8808; |
|||
regfile[LM4549_Record_Select] = 0x0000; |
|||
regfile[LM4549_Record_Gain] = 0x8000; |
|||
regfile[LM4549_General_Purpose] = 0x0000; |
|||
regfile[LM4549_3D_Control] = 0x0101; |
|||
regfile[LM4549_Powerdown_Ctrl_Stat] = 0x000f; |
|||
regfile[LM4549_Ext_Audio_ID] = 0x0001; |
|||
regfile[LM4549_Ext_Audio_Stat_Ctrl] = 0x0000; |
|||
regfile[LM4549_PCM_Front_DAC_Rate] = 0xbb80; |
|||
regfile[LM4549_PCM_ADC_Rate] = 0xbb80; |
|||
regfile[LM4549_Vendor_ID1] = 0x4e53; |
|||
regfile[LM4549_Vendor_ID2] = 0x4331; |
|||
} |
|||
|
|||
static void lm4549_audio_transfer(lm4549_state *s) |
|||
{ |
|||
uint32_t written_bytes, written_samples; |
|||
uint32_t i; |
|||
|
|||
/* Activate the voice */ |
|||
AUD_set_active_out(s->voice, 1); |
|||
s->voice_is_active = 1; |
|||
|
|||
/* Try to write the buffer content */ |
|||
written_bytes = AUD_write(s->voice, s->buffer, |
|||
s->buffer_level * sizeof(uint16_t)); |
|||
written_samples = written_bytes >> 1; |
|||
|
|||
#if defined(LM4549_DUMP_DAC_INPUT) |
|||
fwrite(s->buffer, sizeof(uint8_t), written_bytes, fp_dac_input); |
|||
#endif |
|||
|
|||
s->buffer_level -= written_samples; |
|||
|
|||
if (s->buffer_level > 0) { |
|||
/* Move the data back to the start of the buffer */ |
|||
for (i = 0; i < s->buffer_level; i++) { |
|||
s->buffer[i] = s->buffer[i + written_samples]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static void lm4549_audio_out_callback(void *opaque, int free) |
|||
{ |
|||
lm4549_state *s = (lm4549_state *)opaque; |
|||
static uint32_t prev_buffer_level; |
|||
|
|||
#ifdef LM4549_DEBUG |
|||
int size = AUD_get_buffer_size_out(s->voice); |
|||
DPRINTF("audio_out_callback size = %i free = %i\n", size, free); |
|||
#endif |
|||
|
|||
/* Detect that no data are consumed
|
|||
=> disable the voice */ |
|||
if (s->buffer_level == prev_buffer_level) { |
|||
AUD_set_active_out(s->voice, 0); |
|||
s->voice_is_active = 0; |
|||
} |
|||
prev_buffer_level = s->buffer_level; |
|||
|
|||
/* Check if a buffer transfer is pending */ |
|||
if (s->buffer_level == LM4549_BUFFER_SIZE) { |
|||
lm4549_audio_transfer(s); |
|||
|
|||
/* Request more data */ |
|||
if (s->data_req_cb != NULL) { |
|||
(s->data_req_cb)(s->opaque); |
|||
} |
|||
} |
|||
} |
|||
|
|||
uint32_t lm4549_read(lm4549_state *s, target_phys_addr_t offset) |
|||
{ |
|||
uint16_t *regfile = s->regfile; |
|||
uint32_t value = 0; |
|||
|
|||
/* Read the stored value */ |
|||
assert(offset < 128); |
|||
value = regfile[offset]; |
|||
|
|||
DPRINTF("read [0x%02x] = 0x%04x\n", offset, value); |
|||
|
|||
return value; |
|||
} |
|||
|
|||
void lm4549_write(lm4549_state *s, |
|||
target_phys_addr_t offset, uint32_t value) |
|||
{ |
|||
uint16_t *regfile = s->regfile; |
|||
|
|||
assert(offset < 128); |
|||
DPRINTF("write [0x%02x] = 0x%04x\n", offset, value); |
|||
|
|||
switch (offset) { |
|||
case LM4549_Reset: |
|||
lm4549_reset(s); |
|||
break; |
|||
|
|||
case LM4549_PCM_Front_DAC_Rate: |
|||
regfile[LM4549_PCM_Front_DAC_Rate] = value; |
|||
DPRINTF("DAC rate change = %i\n", value); |
|||
|
|||
/* Re-open a voice with the new sample rate */ |
|||
struct audsettings as; |
|||
as.freq = value; |
|||
as.nchannels = 2; |
|||
as.fmt = AUD_FMT_S16; |
|||
as.endianness = 0; |
|||
|
|||
s->voice = AUD_open_out( |
|||
&s->card, |
|||
s->voice, |
|||
"lm4549.out", |
|||
s, |
|||
lm4549_audio_out_callback, |
|||
&as |
|||
); |
|||
break; |
|||
|
|||
case LM4549_Powerdown_Ctrl_Stat: |
|||
value &= ~0xf; |
|||
value |= regfile[LM4549_Powerdown_Ctrl_Stat] & 0xf; |
|||
regfile[LM4549_Powerdown_Ctrl_Stat] = value; |
|||
break; |
|||
|
|||
case LM4549_Ext_Audio_ID: |
|||
case LM4549_Vendor_ID1: |
|||
case LM4549_Vendor_ID2: |
|||
DPRINTF("Write to read-only register 0x%x\n", (int)offset); |
|||
break; |
|||
|
|||
default: |
|||
/* Store the new value */ |
|||
regfile[offset] = value; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
uint32_t lm4549_write_samples(lm4549_state *s, uint32_t left, uint32_t right) |
|||
{ |
|||
/* The left and right samples are in 20-bit resolution.
|
|||
The LM4549 has 18-bit resolution and only uses the bits [19:2]. |
|||
This model supports 16-bit playback. |
|||
*/ |
|||
|
|||
if (s->buffer_level >= LM4549_BUFFER_SIZE) { |
|||
DPRINTF("write_sample Buffer full\n"); |
|||
return 0; |
|||
} |
|||
|
|||
/* Store 16-bit samples in the buffer */ |
|||
s->buffer[s->buffer_level++] = (left >> 4); |
|||
s->buffer[s->buffer_level++] = (right >> 4); |
|||
|
|||
if (s->buffer_level == LM4549_BUFFER_SIZE) { |
|||
/* Trigger the transfer of the buffer to the audio host */ |
|||
lm4549_audio_transfer(s); |
|||
} |
|||
|
|||
return 1; |
|||
} |
|||
|
|||
static int lm4549_post_load(void *opaque, int version_id) |
|||
{ |
|||
lm4549_state *s = (lm4549_state *)opaque; |
|||
uint16_t *regfile = s->regfile; |
|||
|
|||
/* Re-open a voice with the current sample rate */ |
|||
uint32_t freq = regfile[LM4549_PCM_Front_DAC_Rate]; |
|||
|
|||
DPRINTF("post_load freq = %i\n", freq); |
|||
DPRINTF("post_load voice_is_active = %i\n", s->voice_is_active); |
|||
|
|||
struct audsettings as; |
|||
as.freq = freq; |
|||
as.nchannels = 2; |
|||
as.fmt = AUD_FMT_S16; |
|||
as.endianness = 0; |
|||
|
|||
s->voice = AUD_open_out( |
|||
&s->card, |
|||
s->voice, |
|||
"lm4549.out", |
|||
s, |
|||
lm4549_audio_out_callback, |
|||
&as |
|||
); |
|||
|
|||
/* Request data */ |
|||
if (s->voice_is_active == 1) { |
|||
lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->voice)); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque) |
|||
{ |
|||
struct audsettings as; |
|||
|
|||
/* Store the callback and opaque pointer */ |
|||
s->data_req_cb = data_req_cb; |
|||
s->opaque = opaque; |
|||
|
|||
/* Init the registers */ |
|||
lm4549_reset(s); |
|||
|
|||
/* Register an audio card */ |
|||
AUD_register_card("lm4549", &s->card); |
|||
|
|||
/* Open a default voice */ |
|||
as.freq = 48000; |
|||
as.nchannels = 2; |
|||
as.fmt = AUD_FMT_S16; |
|||
as.endianness = 0; |
|||
|
|||
s->voice = AUD_open_out( |
|||
&s->card, |
|||
s->voice, |
|||
"lm4549.out", |
|||
s, |
|||
lm4549_audio_out_callback, |
|||
&as |
|||
); |
|||
|
|||
AUD_set_volume_out(s->voice, 0, 255, 255); |
|||
|
|||
s->voice_is_active = 0; |
|||
|
|||
/* Reset the input buffer */ |
|||
memset(s->buffer, 0x00, sizeof(s->buffer)); |
|||
s->buffer_level = 0; |
|||
|
|||
#if defined(LM4549_DUMP_DAC_INPUT) |
|||
fp_dac_input = fopen("lm4549_dac_input.pcm", "wb"); |
|||
if (!fp_dac_input) { |
|||
hw_error("Unable to open lm4549_dac_input.pcm for writing\n"); |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
const VMStateDescription vmstate_lm4549_state = { |
|||
.name = "lm4549_state", |
|||
.version_id = 1, |
|||
.minimum_version_id = 1, |
|||
.minimum_version_id_old = 1, |
|||
.post_load = &lm4549_post_load, |
|||
.fields = (VMStateField[]) { |
|||
VMSTATE_UINT32(voice_is_active, lm4549_state), |
|||
VMSTATE_UINT16_ARRAY(regfile, lm4549_state, 128), |
|||
VMSTATE_UINT16_ARRAY(buffer, lm4549_state, LM4549_BUFFER_SIZE), |
|||
VMSTATE_UINT32(buffer_level, lm4549_state), |
|||
VMSTATE_END_OF_LIST() |
|||
} |
|||
}; |
|||
@ -0,0 +1,43 @@ |
|||
/*
|
|||
* LM4549 Audio Codec Interface |
|||
* |
|||
* Copyright (c) 2011 |
|||
* Written by Mathieu Sonet - www.elasticsheep.com |
|||
* |
|||
* This code is licenced under the GPL. |
|||
* |
|||
* ***************************************************************** |
|||
*/ |
|||
|
|||
#ifndef HW_LM4549_H |
|||
#define HW_LM4549_H |
|||
|
|||
#include "audio/audio.h" |
|||
|
|||
typedef void (*lm4549_callback)(void *opaque); |
|||
|
|||
#define LM4549_BUFFER_SIZE (512 * 2) /* 512 16-bit stereo samples */ |
|||
|
|||
|
|||
typedef struct { |
|||
QEMUSoundCard card; |
|||
SWVoiceOut *voice; |
|||
uint32_t voice_is_active; |
|||
|
|||
uint16_t regfile[128]; |
|||
lm4549_callback data_req_cb; |
|||
void *opaque; |
|||
|
|||
uint16_t buffer[LM4549_BUFFER_SIZE]; |
|||
uint32_t buffer_level; |
|||
} lm4549_state; |
|||
|
|||
extern const VMStateDescription vmstate_lm4549_state; |
|||
|
|||
|
|||
void lm4549_init(lm4549_state *s, lm4549_callback data_req, void *opaque); |
|||
uint32_t lm4549_read(lm4549_state *s, target_phys_addr_t offset); |
|||
void lm4549_write(lm4549_state *s, target_phys_addr_t offset, uint32_t value); |
|||
uint32_t lm4549_write_samples(lm4549_state *s, uint32_t left, uint32_t right); |
|||
|
|||
#endif /* #ifndef HW_LM4549_H */ |
|||
@ -0,0 +1,636 @@ |
|||
/*
|
|||
* Arm PrimeCell PL041 Advanced Audio Codec Interface |
|||
* |
|||
* Copyright (c) 2011 |
|||
* Written by Mathieu Sonet - www.elasticsheep.com |
|||
* |
|||
* This code is licenced under the GPL. |
|||
* |
|||
* ***************************************************************** |
|||
* |
|||
* This driver emulates the ARM AACI interface |
|||
* connected to a LM4549 codec. |
|||
* |
|||
* Limitations: |
|||
* - Supports only a playback on one channel (Versatile/Vexpress) |
|||
* - Supports only one TX FIFO in compact-mode or non-compact mode. |
|||
* - Supports playback of 12, 16, 18 and 20 bits samples. |
|||
* - Record is not supported. |
|||
* - The PL041 is hardwired to a LM4549 codec. |
|||
* |
|||
*/ |
|||
|
|||
#include "sysbus.h" |
|||
|
|||
#include "pl041.h" |
|||
#include "lm4549.h" |
|||
|
|||
#if 0 |
|||
#define PL041_DEBUG_LEVEL 1 |
|||
#endif |
|||
|
|||
#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 1) |
|||
#define DBG_L1(fmt, ...) \ |
|||
do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0) |
|||
#else |
|||
#define DBG_L1(fmt, ...) \ |
|||
do { } while (0) |
|||
#endif |
|||
|
|||
#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 2) |
|||
#define DBG_L2(fmt, ...) \ |
|||
do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0) |
|||
#else |
|||
#define DBG_L2(fmt, ...) \ |
|||
do { } while (0) |
|||
#endif |
|||
|
|||
|
|||
#define MAX_FIFO_DEPTH (1024) |
|||
#define DEFAULT_FIFO_DEPTH (8) |
|||
|
|||
#define SLOT1_RW (1 << 19) |
|||
|
|||
/* This FIFO only stores 20-bit samples on 32-bit words.
|
|||
So its level is independent of the selected mode */ |
|||
typedef struct { |
|||
uint32_t level; |
|||
uint32_t data[MAX_FIFO_DEPTH]; |
|||
} pl041_fifo; |
|||
|
|||
typedef struct { |
|||
pl041_fifo tx_fifo; |
|||
uint8_t tx_enabled; |
|||
uint8_t tx_compact_mode; |
|||
uint8_t tx_sample_size; |
|||
|
|||
pl041_fifo rx_fifo; |
|||
uint8_t rx_enabled; |
|||
uint8_t rx_compact_mode; |
|||
uint8_t rx_sample_size; |
|||
} pl041_channel; |
|||
|
|||
typedef struct { |
|||
SysBusDevice busdev; |
|||
MemoryRegion iomem; |
|||
qemu_irq irq; |
|||
|
|||
uint32_t fifo_depth; /* FIFO depth in non-compact mode */ |
|||
|
|||
pl041_regfile regs; |
|||
pl041_channel fifo1; |
|||
lm4549_state codec; |
|||
} pl041_state; |
|||
|
|||
|
|||
static const unsigned char pl041_default_id[8] = { |
|||
0x41, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 |
|||
}; |
|||
|
|||
#if defined(PL041_DEBUG_LEVEL) |
|||
#define REGISTER(name, offset) #name, |
|||
static const char *pl041_regs_name[] = { |
|||
#include "pl041.hx" |
|||
}; |
|||
#undef REGISTER |
|||
#endif |
|||
|
|||
|
|||
#if defined(PL041_DEBUG_LEVEL) |
|||
static const char *get_reg_name(target_phys_addr_t offset) |
|||
{ |
|||
if (offset <= PL041_dr1_7) { |
|||
return pl041_regs_name[offset >> 2]; |
|||
} |
|||
|
|||
return "unknown"; |
|||
} |
|||
#endif |
|||
|
|||
static uint8_t pl041_compute_periphid3(pl041_state *s) |
|||
{ |
|||
uint8_t id3 = 1; /* One channel */ |
|||
|
|||
/* Add the fifo depth information */ |
|||
switch (s->fifo_depth) { |
|||
case 8: |
|||
id3 |= 0 << 3; |
|||
break; |
|||
case 32: |
|||
id3 |= 1 << 3; |
|||
break; |
|||
case 64: |
|||
id3 |= 2 << 3; |
|||
break; |
|||
case 128: |
|||
id3 |= 3 << 3; |
|||
break; |
|||
case 256: |
|||
id3 |= 4 << 3; |
|||
break; |
|||
case 512: |
|||
id3 |= 5 << 3; |
|||
break; |
|||
case 1024: |
|||
id3 |= 6 << 3; |
|||
break; |
|||
case 2048: |
|||
id3 |= 7 << 3; |
|||
break; |
|||
} |
|||
|
|||
return id3; |
|||
} |
|||
|
|||
static void pl041_reset(pl041_state *s) |
|||
{ |
|||
DBG_L1("pl041_reset\n"); |
|||
|
|||
memset(&s->regs, 0x00, sizeof(pl041_regfile)); |
|||
|
|||
s->regs.slfr = SL1TXEMPTY | SL2TXEMPTY | SL12TXEMPTY; |
|||
s->regs.sr1 = TXFE | RXFE | TXHE; |
|||
s->regs.isr1 = 0; |
|||
|
|||
memset(&s->fifo1, 0x00, sizeof(s->fifo1)); |
|||
} |
|||
|
|||
|
|||
static void pl041_fifo1_write(pl041_state *s, uint32_t value) |
|||
{ |
|||
pl041_channel *channel = &s->fifo1; |
|||
pl041_fifo *fifo = &s->fifo1.tx_fifo; |
|||
|
|||
/* Push the value in the FIFO */ |
|||
if (channel->tx_compact_mode == 0) { |
|||
/* Non-compact mode */ |
|||
|
|||
if (fifo->level < s->fifo_depth) { |
|||
/* Pad the value with 0 to obtain a 20-bit sample */ |
|||
switch (channel->tx_sample_size) { |
|||
case 12: |
|||
value = (value << 8) & 0xFFFFF; |
|||
break; |
|||
case 16: |
|||
value = (value << 4) & 0xFFFFF; |
|||
break; |
|||
case 18: |
|||
value = (value << 2) & 0xFFFFF; |
|||
break; |
|||
case 20: |
|||
default: |
|||
break; |
|||
} |
|||
|
|||
/* Store the sample in the FIFO */ |
|||
fifo->data[fifo->level++] = value; |
|||
} |
|||
#if defined(PL041_DEBUG_LEVEL) |
|||
else { |
|||
DBG_L1("fifo1 write: overrun\n"); |
|||
} |
|||
#endif |
|||
} else { |
|||
/* Compact mode */ |
|||
|
|||
if ((fifo->level + 2) < s->fifo_depth) { |
|||
uint32_t i = 0; |
|||
uint32_t sample = 0; |
|||
|
|||
for (i = 0; i < 2; i++) { |
|||
sample = value & 0xFFFF; |
|||
value = value >> 16; |
|||
|
|||
/* Pad each sample with 0 to obtain a 20-bit sample */ |
|||
switch (channel->tx_sample_size) { |
|||
case 12: |
|||
sample = sample << 8; |
|||
break; |
|||
case 16: |
|||
default: |
|||
sample = sample << 4; |
|||
break; |
|||
} |
|||
|
|||
/* Store the sample in the FIFO */ |
|||
fifo->data[fifo->level++] = sample; |
|||
} |
|||
} |
|||
#if defined(PL041_DEBUG_LEVEL) |
|||
else { |
|||
DBG_L1("fifo1 write: overrun\n"); |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
/* Update the status register */ |
|||
if (fifo->level > 0) { |
|||
s->regs.sr1 &= ~(TXUNDERRUN | TXFE); |
|||
} |
|||
|
|||
if (fifo->level >= (s->fifo_depth / 2)) { |
|||
s->regs.sr1 &= ~TXHE; |
|||
} |
|||
|
|||
if (fifo->level >= s->fifo_depth) { |
|||
s->regs.sr1 |= TXFF; |
|||
} |
|||
|
|||
DBG_L2("fifo1_push sr1 = 0x%08x\n", s->regs.sr1); |
|||
} |
|||
|
|||
static void pl041_fifo1_transmit(pl041_state *s) |
|||
{ |
|||
pl041_channel *channel = &s->fifo1; |
|||
pl041_fifo *fifo = &s->fifo1.tx_fifo; |
|||
uint32_t slots = s->regs.txcr1 & TXSLOT_MASK; |
|||
uint32_t written_samples; |
|||
|
|||
/* Check if FIFO1 transmit is enabled */ |
|||
if ((channel->tx_enabled) && (slots & (TXSLOT3 | TXSLOT4))) { |
|||
if (fifo->level >= (s->fifo_depth / 2)) { |
|||
int i; |
|||
|
|||
DBG_L1("Transfer FIFO level = %i\n", fifo->level); |
|||
|
|||
/* Try to transfer the whole FIFO */ |
|||
for (i = 0; i < (fifo->level / 2); i++) { |
|||
uint32_t left = fifo->data[i * 2]; |
|||
uint32_t right = fifo->data[i * 2 + 1]; |
|||
|
|||
/* Transmit two 20-bit samples to the codec */ |
|||
if (lm4549_write_samples(&s->codec, left, right) == 0) { |
|||
DBG_L1("Codec buffer full\n"); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
written_samples = i * 2; |
|||
if (written_samples > 0) { |
|||
/* Update the FIFO level */ |
|||
fifo->level -= written_samples; |
|||
|
|||
/* Move back the pending samples to the start of the FIFO */ |
|||
for (i = 0; i < fifo->level; i++) { |
|||
fifo->data[i] = fifo->data[written_samples + i]; |
|||
} |
|||
|
|||
/* Update the status register */ |
|||
s->regs.sr1 &= ~TXFF; |
|||
|
|||
if (fifo->level <= (s->fifo_depth / 2)) { |
|||
s->regs.sr1 |= TXHE; |
|||
} |
|||
|
|||
if (fifo->level == 0) { |
|||
s->regs.sr1 |= TXFE | TXUNDERRUN; |
|||
DBG_L1("Empty FIFO\n"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
static void pl041_isr1_update(pl041_state *s) |
|||
{ |
|||
/* Update ISR1 */ |
|||
if (s->regs.sr1 & TXUNDERRUN) { |
|||
s->regs.isr1 |= URINTR; |
|||
} else { |
|||
s->regs.isr1 &= ~URINTR; |
|||
} |
|||
|
|||
if (s->regs.sr1 & TXHE) { |
|||
s->regs.isr1 |= TXINTR; |
|||
} else { |
|||
s->regs.isr1 &= ~TXINTR; |
|||
} |
|||
|
|||
if (!(s->regs.sr1 & TXBUSY) && (s->regs.sr1 & TXFE)) { |
|||
s->regs.isr1 |= TXCINTR; |
|||
} else { |
|||
s->regs.isr1 &= ~TXCINTR; |
|||
} |
|||
|
|||
/* Update the irq state */ |
|||
qemu_set_irq(s->irq, ((s->regs.isr1 & s->regs.ie1) > 0) ? 1 : 0); |
|||
DBG_L2("Set interrupt sr1 = 0x%08x isr1 = 0x%08x masked = 0x%08x\n", |
|||
s->regs.sr1, s->regs.isr1, s->regs.isr1 & s->regs.ie1); |
|||
} |
|||
|
|||
static void pl041_request_data(void *opaque) |
|||
{ |
|||
pl041_state *s = (pl041_state *)opaque; |
|||
|
|||
/* Trigger pending transfers */ |
|||
pl041_fifo1_transmit(s); |
|||
pl041_isr1_update(s); |
|||
} |
|||
|
|||
static uint64_t pl041_read(void *opaque, target_phys_addr_t offset, |
|||
unsigned size) |
|||
{ |
|||
pl041_state *s = (pl041_state *)opaque; |
|||
int value; |
|||
|
|||
if ((offset >= PL041_periphid0) && (offset <= PL041_pcellid3)) { |
|||
if (offset == PL041_periphid3) { |
|||
value = pl041_compute_periphid3(s); |
|||
} else { |
|||
value = pl041_default_id[(offset - PL041_periphid0) >> 2]; |
|||
} |
|||
|
|||
DBG_L1("pl041_read [0x%08x] => 0x%08x\n", offset, value); |
|||
return value; |
|||
} else if (offset <= PL041_dr4_7) { |
|||
value = *((uint32_t *)&s->regs + (offset >> 2)); |
|||
} else { |
|||
DBG_L1("pl041_read: Reserved offset %x\n", (int)offset); |
|||
return 0; |
|||
} |
|||
|
|||
switch (offset) { |
|||
case PL041_allints: |
|||
value = s->regs.isr1 & 0x7F; |
|||
break; |
|||
} |
|||
|
|||
DBG_L1("pl041_read [0x%08x] %s => 0x%08x\n", offset, |
|||
get_reg_name(offset), value); |
|||
|
|||
return value; |
|||
} |
|||
|
|||
static void pl041_write(void *opaque, target_phys_addr_t offset, |
|||
uint64_t value, unsigned size) |
|||
{ |
|||
pl041_state *s = (pl041_state *)opaque; |
|||
uint16_t control, data; |
|||
uint32_t result; |
|||
|
|||
DBG_L1("pl041_write [0x%08x] %s <= 0x%08x\n", offset, |
|||
get_reg_name(offset), (unsigned int)value); |
|||
|
|||
/* Write the register */ |
|||
if (offset <= PL041_dr4_7) { |
|||
*((uint32_t *)&s->regs + (offset >> 2)) = value; |
|||
} else { |
|||
DBG_L1("pl041_write: Reserved offset %x\n", (int)offset); |
|||
return; |
|||
} |
|||
|
|||
/* Execute the actions */ |
|||
switch (offset) { |
|||
case PL041_txcr1: |
|||
{ |
|||
pl041_channel *channel = &s->fifo1; |
|||
|
|||
uint32_t txen = s->regs.txcr1 & TXEN; |
|||
uint32_t tsize = (s->regs.txcr1 & TSIZE_MASK) >> TSIZE_MASK_BIT; |
|||
uint32_t compact_mode = (s->regs.txcr1 & TXCOMPACT) ? 1 : 0; |
|||
#if defined(PL041_DEBUG_LEVEL) |
|||
uint32_t slots = (s->regs.txcr1 & TXSLOT_MASK) >> TXSLOT_MASK_BIT; |
|||
uint32_t txfen = (s->regs.txcr1 & TXFEN) > 0 ? 1 : 0; |
|||
#endif |
|||
|
|||
DBG_L1("=> txen = %i slots = 0x%01x tsize = %i compact = %i " |
|||
"txfen = %i\n", txen, slots, tsize, compact_mode, txfen); |
|||
|
|||
channel->tx_enabled = txen; |
|||
channel->tx_compact_mode = compact_mode; |
|||
|
|||
switch (tsize) { |
|||
case 0: |
|||
channel->tx_sample_size = 16; |
|||
break; |
|||
case 1: |
|||
channel->tx_sample_size = 18; |
|||
break; |
|||
case 2: |
|||
channel->tx_sample_size = 20; |
|||
break; |
|||
case 3: |
|||
channel->tx_sample_size = 12; |
|||
break; |
|||
} |
|||
|
|||
DBG_L1("TX enabled = %i\n", channel->tx_enabled); |
|||
DBG_L1("TX compact mode = %i\n", channel->tx_compact_mode); |
|||
DBG_L1("TX sample width = %i\n", channel->tx_sample_size); |
|||
|
|||
/* Check if compact mode is allowed with selected tsize */ |
|||
if (channel->tx_compact_mode == 1) { |
|||
if ((channel->tx_sample_size == 18) || |
|||
(channel->tx_sample_size == 20)) { |
|||
channel->tx_compact_mode = 0; |
|||
DBG_L1("Compact mode not allowed with 18/20-bit sample size\n"); |
|||
} |
|||
} |
|||
|
|||
break; |
|||
} |
|||
case PL041_sl1tx: |
|||
s->regs.slfr &= ~SL1TXEMPTY; |
|||
|
|||
control = (s->regs.sl1tx >> 12) & 0x7F; |
|||
data = (s->regs.sl2tx >> 4) & 0xFFFF; |
|||
|
|||
if ((s->regs.sl1tx & SLOT1_RW) == 0) { |
|||
/* Write operation */ |
|||
lm4549_write(&s->codec, control, data); |
|||
} else { |
|||
/* Read operation */ |
|||
result = lm4549_read(&s->codec, control); |
|||
|
|||
/* Store the returned value */ |
|||
s->regs.sl1rx = s->regs.sl1tx & ~SLOT1_RW; |
|||
s->regs.sl2rx = result << 4; |
|||
|
|||
s->regs.slfr &= ~(SL1RXBUSY | SL2RXBUSY); |
|||
s->regs.slfr |= SL1RXVALID | SL2RXVALID; |
|||
} |
|||
break; |
|||
|
|||
case PL041_sl2tx: |
|||
s->regs.sl2tx = value; |
|||
s->regs.slfr &= ~SL2TXEMPTY; |
|||
break; |
|||
|
|||
case PL041_intclr: |
|||
DBG_L1("=> Clear interrupt intclr = 0x%08x isr1 = 0x%08x\n", |
|||
s->regs.intclr, s->regs.isr1); |
|||
|
|||
if (s->regs.intclr & TXUEC1) { |
|||
s->regs.sr1 &= ~TXUNDERRUN; |
|||
} |
|||
break; |
|||
|
|||
case PL041_maincr: |
|||
{ |
|||
#if defined(PL041_DEBUG_LEVEL) |
|||
char debug[] = " AACIFE SL1RXEN SL1TXEN"; |
|||
if (!(value & AACIFE)) { |
|||
debug[0] = '!'; |
|||
} |
|||
if (!(value & SL1RXEN)) { |
|||
debug[8] = '!'; |
|||
} |
|||
if (!(value & SL1TXEN)) { |
|||
debug[17] = '!'; |
|||
} |
|||
DBG_L1("%s\n", debug); |
|||
#endif |
|||
|
|||
if ((s->regs.maincr & AACIFE) == 0) { |
|||
pl041_reset(s); |
|||
} |
|||
break; |
|||
} |
|||
|
|||
case PL041_dr1_0: |
|||
case PL041_dr1_1: |
|||
case PL041_dr1_2: |
|||
case PL041_dr1_3: |
|||
pl041_fifo1_write(s, value); |
|||
break; |
|||
} |
|||
|
|||
/* Transmit the FIFO content */ |
|||
pl041_fifo1_transmit(s); |
|||
|
|||
/* Update the ISR1 register */ |
|||
pl041_isr1_update(s); |
|||
} |
|||
|
|||
static void pl041_device_reset(DeviceState *d) |
|||
{ |
|||
pl041_state *s = DO_UPCAST(pl041_state, busdev.qdev, d); |
|||
|
|||
pl041_reset(s); |
|||
} |
|||
|
|||
static const MemoryRegionOps pl041_ops = { |
|||
.read = pl041_read, |
|||
.write = pl041_write, |
|||
.endianness = DEVICE_NATIVE_ENDIAN, |
|||
}; |
|||
|
|||
static int pl041_init(SysBusDevice *dev) |
|||
{ |
|||
pl041_state *s = FROM_SYSBUS(pl041_state, dev); |
|||
|
|||
DBG_L1("pl041_init 0x%08x\n", (uint32_t)s); |
|||
|
|||
/* Check the device properties */ |
|||
switch (s->fifo_depth) { |
|||
case 8: |
|||
case 32: |
|||
case 64: |
|||
case 128: |
|||
case 256: |
|||
case 512: |
|||
case 1024: |
|||
case 2048: |
|||
break; |
|||
case 16: |
|||
default: |
|||
/* NC FIFO depth of 16 is not allowed because its id bits in
|
|||
AACIPERIPHID3 overlap with the id for the default NC FIFO depth */ |
|||
fprintf(stderr, "pl041: unsupported non-compact fifo depth [%i]\n", |
|||
s->fifo_depth); |
|||
return -1; |
|||
} |
|||
|
|||
/* Connect the device to the sysbus */ |
|||
memory_region_init_io(&s->iomem, &pl041_ops, s, "pl041", 0x1000); |
|||
sysbus_init_mmio_region(dev, &s->iomem); |
|||
sysbus_init_irq(dev, &s->irq); |
|||
|
|||
/* Init the codec */ |
|||
lm4549_init(&s->codec, &pl041_request_data, (void *)s); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static const VMStateDescription vmstate_pl041_regfile = { |
|||
.name = "pl041_regfile", |
|||
.version_id = 1, |
|||
.minimum_version_id = 1, |
|||
.minimum_version_id_old = 1, |
|||
.fields = (VMStateField[]) { |
|||
#define REGISTER(name, offset) VMSTATE_UINT32(name, pl041_regfile), |
|||
#include "pl041.hx" |
|||
#undef REGISTER |
|||
VMSTATE_END_OF_LIST() |
|||
} |
|||
}; |
|||
|
|||
static const VMStateDescription vmstate_pl041_fifo = { |
|||
.name = "pl041_fifo", |
|||
.version_id = 1, |
|||
.minimum_version_id = 1, |
|||
.minimum_version_id_old = 1, |
|||
.fields = (VMStateField[]) { |
|||
VMSTATE_UINT32(level, pl041_fifo), |
|||
VMSTATE_UINT32_ARRAY(data, pl041_fifo, MAX_FIFO_DEPTH), |
|||
VMSTATE_END_OF_LIST() |
|||
} |
|||
}; |
|||
|
|||
static const VMStateDescription vmstate_pl041_channel = { |
|||
.name = "pl041_channel", |
|||
.version_id = 1, |
|||
.minimum_version_id = 1, |
|||
.minimum_version_id_old = 1, |
|||
.fields = (VMStateField[]) { |
|||
VMSTATE_STRUCT(tx_fifo, pl041_channel, 0, |
|||
vmstate_pl041_fifo, pl041_fifo), |
|||
VMSTATE_UINT8(tx_enabled, pl041_channel), |
|||
VMSTATE_UINT8(tx_compact_mode, pl041_channel), |
|||
VMSTATE_UINT8(tx_sample_size, pl041_channel), |
|||
VMSTATE_STRUCT(rx_fifo, pl041_channel, 0, |
|||
vmstate_pl041_fifo, pl041_fifo), |
|||
VMSTATE_UINT8(rx_enabled, pl041_channel), |
|||
VMSTATE_UINT8(rx_compact_mode, pl041_channel), |
|||
VMSTATE_UINT8(rx_sample_size, pl041_channel), |
|||
VMSTATE_END_OF_LIST() |
|||
} |
|||
}; |
|||
|
|||
static const VMStateDescription vmstate_pl041 = { |
|||
.name = "pl041", |
|||
.version_id = 1, |
|||
.minimum_version_id = 1, |
|||
.fields = (VMStateField[]) { |
|||
VMSTATE_UINT32(fifo_depth, pl041_state), |
|||
VMSTATE_STRUCT(regs, pl041_state, 0, |
|||
vmstate_pl041_regfile, pl041_regfile), |
|||
VMSTATE_STRUCT(fifo1, pl041_state, 0, |
|||
vmstate_pl041_channel, pl041_channel), |
|||
VMSTATE_STRUCT(codec, pl041_state, 0, |
|||
vmstate_lm4549_state, lm4549_state), |
|||
VMSTATE_END_OF_LIST() |
|||
} |
|||
}; |
|||
|
|||
static SysBusDeviceInfo pl041_device_info = { |
|||
.init = pl041_init, |
|||
.qdev.name = "pl041", |
|||
.qdev.size = sizeof(pl041_state), |
|||
.qdev.vmsd = &vmstate_pl041, |
|||
.qdev.reset = pl041_device_reset, |
|||
.qdev.no_user = 1, |
|||
.qdev.props = (Property[]) { |
|||
/* Non-compact FIFO depth property */ |
|||
DEFINE_PROP_UINT32("nc_fifo_depth", pl041_state, |
|||
fifo_depth, DEFAULT_FIFO_DEPTH), |
|||
DEFINE_PROP_END_OF_LIST(), |
|||
}, |
|||
}; |
|||
|
|||
static void pl041_register_device(void) |
|||
{ |
|||
sysbus_register_withprop(&pl041_device_info); |
|||
} |
|||
|
|||
device_init(pl041_register_device) |
|||
@ -0,0 +1,135 @@ |
|||
/*
|
|||
* Arm PrimeCell PL041 Advanced Audio Codec Interface |
|||
* |
|||
* Copyright (c) 2011 |
|||
* Written by Mathieu Sonet - www.elasticsheep.com |
|||
* |
|||
* This code is licenced under the GPL. |
|||
* |
|||
* ***************************************************************** |
|||
*/ |
|||
|
|||
#ifndef HW_PL041_H |
|||
#define HW_PL041_H |
|||
|
|||
/* Register file */ |
|||
#define REGISTER(name, offset) uint32_t name; |
|||
typedef struct { |
|||
#include "pl041.hx" |
|||
} pl041_regfile; |
|||
#undef REGISTER |
|||
|
|||
/* Register addresses */ |
|||
#define REGISTER(name, offset) PL041_##name = offset, |
|||
enum { |
|||
#include "pl041.hx" |
|||
|
|||
PL041_periphid0 = 0xFE0, |
|||
PL041_periphid1 = 0xFE4, |
|||
PL041_periphid2 = 0xFE8, |
|||
PL041_periphid3 = 0xFEC, |
|||
PL041_pcellid0 = 0xFF0, |
|||
PL041_pcellid1 = 0xFF4, |
|||
PL041_pcellid2 = 0xFF8, |
|||
PL041_pcellid3 = 0xFFC, |
|||
}; |
|||
#undef REGISTER |
|||
|
|||
/* Register bits */ |
|||
|
|||
/* IEx */ |
|||
#define TXCIE (1 << 0) |
|||
#define RXTIE (1 << 1) |
|||
#define TXIE (1 << 2) |
|||
#define RXIE (1 << 3) |
|||
#define RXOIE (1 << 4) |
|||
#define TXUIE (1 << 5) |
|||
#define RXTOIE (1 << 6) |
|||
|
|||
/* TXCRx */ |
|||
#define TXEN (1 << 0) |
|||
#define TXSLOT1 (1 << 1) |
|||
#define TXSLOT2 (1 << 2) |
|||
#define TXSLOT3 (1 << 3) |
|||
#define TXSLOT4 (1 << 4) |
|||
#define TXCOMPACT (1 << 15) |
|||
#define TXFEN (1 << 16) |
|||
|
|||
#define TXSLOT_MASK_BIT (1) |
|||
#define TXSLOT_MASK (0xFFF << TXSLOT_MASK_BIT) |
|||
|
|||
#define TSIZE_MASK_BIT (13) |
|||
#define TSIZE_MASK (0x3 << TSIZE_MASK_BIT) |
|||
|
|||
#define TSIZE_16BITS (0x0 << TSIZE_MASK_BIT) |
|||
#define TSIZE_18BITS (0x1 << TSIZE_MASK_BIT) |
|||
#define TSIZE_20BITS (0x2 << TSIZE_MASK_BIT) |
|||
#define TSIZE_12BITS (0x3 << TSIZE_MASK_BIT) |
|||
|
|||
/* SRx */ |
|||
#define RXFE (1 << 0) |
|||
#define TXFE (1 << 1) |
|||
#define RXHF (1 << 2) |
|||
#define TXHE (1 << 3) |
|||
#define RXFF (1 << 4) |
|||
#define TXFF (1 << 5) |
|||
#define RXBUSY (1 << 6) |
|||
#define TXBUSY (1 << 7) |
|||
#define RXOVERRUN (1 << 8) |
|||
#define TXUNDERRUN (1 << 9) |
|||
#define RXTIMEOUT (1 << 10) |
|||
#define RXTOFE (1 << 11) |
|||
|
|||
/* ISRx */ |
|||
#define TXCINTR (1 << 0) |
|||
#define RXTOINTR (1 << 1) |
|||
#define TXINTR (1 << 2) |
|||
#define RXINTR (1 << 3) |
|||
#define ORINTR (1 << 4) |
|||
#define URINTR (1 << 5) |
|||
#define RXTOFEINTR (1 << 6) |
|||
|
|||
/* SLFR */ |
|||
#define SL1RXBUSY (1 << 0) |
|||
#define SL1TXBUSY (1 << 1) |
|||
#define SL2RXBUSY (1 << 2) |
|||
#define SL2TXBUSY (1 << 3) |
|||
#define SL12RXBUSY (1 << 4) |
|||
#define SL12TXBUSY (1 << 5) |
|||
#define SL1RXVALID (1 << 6) |
|||
#define SL1TXEMPTY (1 << 7) |
|||
#define SL2RXVALID (1 << 8) |
|||
#define SL2TXEMPTY (1 << 9) |
|||
#define SL12RXVALID (1 << 10) |
|||
#define SL12TXEMPTY (1 << 11) |
|||
#define RAWGPIOINT (1 << 12) |
|||
#define RWIS (1 << 13) |
|||
|
|||
/* MAINCR */ |
|||
#define AACIFE (1 << 0) |
|||
#define LOOPBACK (1 << 1) |
|||
#define LOWPOWER (1 << 2) |
|||
#define SL1RXEN (1 << 3) |
|||
#define SL1TXEN (1 << 4) |
|||
#define SL2RXEN (1 << 5) |
|||
#define SL2TXEN (1 << 6) |
|||
#define SL12RXEN (1 << 7) |
|||
#define SL12TXEN (1 << 8) |
|||
#define DMAENABLE (1 << 9) |
|||
|
|||
/* INTCLR */ |
|||
#define WISC (1 << 0) |
|||
#define RXOEC1 (1 << 1) |
|||
#define RXOEC2 (1 << 2) |
|||
#define RXOEC3 (1 << 3) |
|||
#define RXOEC4 (1 << 4) |
|||
#define TXUEC1 (1 << 5) |
|||
#define TXUEC2 (1 << 6) |
|||
#define TXUEC3 (1 << 7) |
|||
#define TXUEC4 (1 << 8) |
|||
#define RXTOFEC1 (1 << 9) |
|||
#define RXTOFEC2 (1 << 10) |
|||
#define RXTOFEC3 (1 << 11) |
|||
#define RXTOFEC4 (1 << 12) |
|||
|
|||
#endif /* #ifndef HW_PL041_H */ |
|||
@ -0,0 +1,81 @@ |
|||
/* |
|||
* Arm PrimeCell PL041 Advanced Audio Codec Interface |
|||
* |
|||
* Copyright (c) 2011 |
|||
* Written by Mathieu Sonet - www.elasticsheep.com |
|||
* |
|||
* This code is licenced under the GPL. |
|||
* |
|||
* ***************************************************************** |
|||
*/ |
|||
|
|||
/* PL041 register file description */ |
|||
|
|||
REGISTER( rxcr1, 0x00 ) |
|||
REGISTER( txcr1, 0x04 ) |
|||
REGISTER( sr1, 0x08 ) |
|||
REGISTER( isr1, 0x0C ) |
|||
REGISTER( ie1, 0x10 ) |
|||
REGISTER( rxcr2, 0x14 ) |
|||
REGISTER( txcr2, 0x18 ) |
|||
REGISTER( sr2, 0x1C ) |
|||
REGISTER( isr2, 0x20 ) |
|||
REGISTER( ie2, 0x24 ) |
|||
REGISTER( rxcr3, 0x28 ) |
|||
REGISTER( txcr3, 0x2C ) |
|||
REGISTER( sr3, 0x30 ) |
|||
REGISTER( isr3, 0x34 ) |
|||
REGISTER( ie3, 0x38 ) |
|||
REGISTER( rxcr4, 0x3C ) |
|||
REGISTER( txcr4, 0x40 ) |
|||
REGISTER( sr4, 0x44 ) |
|||
REGISTER( isr4, 0x48 ) |
|||
REGISTER( ie4, 0x4C ) |
|||
REGISTER( sl1rx, 0x50 ) |
|||
REGISTER( sl1tx, 0x54 ) |
|||
REGISTER( sl2rx, 0x58 ) |
|||
REGISTER( sl2tx, 0x5C ) |
|||
REGISTER( sl12rx, 0x60 ) |
|||
REGISTER( sl12tx, 0x64 ) |
|||
REGISTER( slfr, 0x68 ) |
|||
REGISTER( slistat, 0x6C ) |
|||
REGISTER( slien, 0x70 ) |
|||
REGISTER( intclr, 0x74 ) |
|||
REGISTER( maincr, 0x78 ) |
|||
REGISTER( reset, 0x7C ) |
|||
REGISTER( sync, 0x80 ) |
|||
REGISTER( allints, 0x84 ) |
|||
REGISTER( mainfr, 0x88 ) |
|||
REGISTER( unused, 0x8C ) |
|||
REGISTER( dr1_0, 0x90 ) |
|||
REGISTER( dr1_1, 0x94 ) |
|||
REGISTER( dr1_2, 0x98 ) |
|||
REGISTER( dr1_3, 0x9C ) |
|||
REGISTER( dr1_4, 0xA0 ) |
|||
REGISTER( dr1_5, 0xA4 ) |
|||
REGISTER( dr1_6, 0xA8 ) |
|||
REGISTER( dr1_7, 0xAC ) |
|||
REGISTER( dr2_0, 0xB0 ) |
|||
REGISTER( dr2_1, 0xB4 ) |
|||
REGISTER( dr2_2, 0xB8 ) |
|||
REGISTER( dr2_3, 0xBC ) |
|||
REGISTER( dr2_4, 0xC0 ) |
|||
REGISTER( dr2_5, 0xC4 ) |
|||
REGISTER( dr2_6, 0xC8 ) |
|||
REGISTER( dr2_7, 0xCC ) |
|||
REGISTER( dr3_0, 0xD0 ) |
|||
REGISTER( dr3_1, 0xD4 ) |
|||
REGISTER( dr3_2, 0xD8 ) |
|||
REGISTER( dr3_3, 0xDC ) |
|||
REGISTER( dr3_4, 0xE0 ) |
|||
REGISTER( dr3_5, 0xE4 ) |
|||
REGISTER( dr3_6, 0xE8 ) |
|||
REGISTER( dr3_7, 0xEC ) |
|||
REGISTER( dr4_0, 0xF0 ) |
|||
REGISTER( dr4_1, 0xF4 ) |
|||
REGISTER( dr4_2, 0xF8 ) |
|||
REGISTER( dr4_3, 0xFC ) |
|||
REGISTER( dr4_4, 0x100 ) |
|||
REGISTER( dr4_5, 0x104 ) |
|||
REGISTER( dr4_6, 0x108 ) |
|||
REGISTER( dr4_7, 0x10C ) |
|||
Loading…
Reference in new issue