* Re: [Alsa-devel] [PATCH] Add Dreamcast AICA driver to alsa-driver [not found] ` <1145832383.9242.41.camel@localhost.localdomain> @ 2006-04-24 11:10 ` Takashi Iwai 2006-04-24 17:44 ` [linuxsh-dev] " Adrian McMenamin 0 siblings, 1 reply; 3+ messages in thread From: Takashi Iwai @ 2006-04-24 11:10 UTC (permalink / raw) To: Adrian McMenamin; +Cc: Alsa-devel, linux-sh, LKML, Lee Revell, Paul Mundt At Sun, 23 Apr 2006 23:46:22 +0100, Adrian McMenamin wrote: +/* module parameters */ +#define CARD_NAME "AICA" + +static int index; Initialize it to -1 (or SNDRV_DEFAULT_IDX1), and pass it to the first argument of snd_card_new(). Otherwise this option is useless. > +/* Spinlocks */ > +DEFINE_SPINLOCK(spu_memlock); Missing static. > +/* spu_memset - write to memory in SPU address space */ > +static void spu_memset(uint32_t toi, void __iomem * what, int length) > +{ > + uint32_t *to = (uint32_t *) (SPU_MEMORY_BASE + toi); > + int i; > + if (length % 4) > + length = (length / 4) + 1; > + else > + length = length / 4; If you use writel(), the length must be aligned to 4. > + spu_write_wait(); > + for (i = 0; i < length; i++) { > + spin_lock(&spu_memlock); > + writel(what, to); > + spin_unlock(&spu_memlock); What's the purpose of this lock? > + to++; > + if (i && !(i % 8)) > + spu_write_wait(); > + } > +} > + > +/* spu_memload - write to SPU address space */ > +static void spu_memload(uint32_t toi, void __iomem * from, int length) > +{ > + uint32_t __iomem *froml = from; > + uint32_t __iomem *to = > + (uint32_t __iomem *) (SPU_MEMORY_BASE + toi); > + int i, val; > + if (length % 4) > + length = (length / 4) + 1; > + else > + length = length / 4; Ditto. > +/* spu_disable - set spu registers to stop sound output */ > +static void spu_disable(void) > +{ > + int i; > + uint32_t regval; > + spu_write_wait(); > + regval = readl(ARM_RESET_REGISTER); > + regval |= 1; > + spu_write_wait(); > + spin_lock(&spu_memlock); > + writel(regval, ARM_RESET_REGISTER); > + spin_unlock(&spu_memlock); > + for (i = 0; i < 64; i++) { > + spu_write_wait(); > + regval = readl(SPU_REGISTER_BASE + (i * 0x80)); > + regval = (regval & ~0x4000) | 0x8000; > + spu_write_wait(); > + spin_lock(&spu_memlock); > + writel(regval, SPU_REGISTER_BASE + (i * 0x80)); > + spin_unlock(&spu_memlock); For what is this lock? > +/* aica_chn_start - write to spu to start playback */ > +inline static void aica_chn_start(void) > +{ > + spu_write_wait(); > + spin_lock(&spu_memlock); > + writel(AICA_CMD_KICK | AICA_CMD_START, > + (uint32_t *) AICA_CONTROL_POINT); The cast looks strange... > + spin_unlock(&spu_memlock); > +} > + > +/* aica_chn_halt - write to spu to halt playback */ > +inline static void aica_chn_halt(void) > +{ > + spu_write_wait(); > + spin_lock(&spu_memlock); > + writel(AICA_CMD_KICK | AICA_CMD_STOP, > + (uint32_t *) AICA_CONTROL_POINT); > + spin_unlock(&spu_memlock); > +} > + > +/* ALSA code below */ > +static struct snd_pcm_hardware snd_pcm_aica_playback_hw = { > + .info = (SNDRV_PCM_INFO_NONINTERLEAVED),.formats = > + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | > + SNDRV_PCM_FMTBIT_IMA_ADPCM),.rates = > + SNDRV_PCM_RATE_8000_48000,.rate_min = 8000,.rate_max = > + 48000,.channels_min = 1,.channels_max = 2,.buffer_bytes_max = > + AICA_BUFFER_SIZE,.period_bytes_min = > + AICA_PERIOD_SIZE,.period_bytes_max = > + AICA_PERIOD_SIZE,.periods_min = > + AICA_PERIOD_NUMBER,.periods_max = AICA_PERIOD_NUMBER, > +}; Put the field ids to the beginning of the line. Found in other structs, too. > +static int stereo_buffer_transfer(struct snd_pcm_substream > + *substream, int buffer_size, int period) > +{ I feel this transfer-and-wait could be done more efficiently using workq than doing it in timer callback. The trigger(start) and aica_period_elapsed() calls queue_work() at each time. The most work of spu_begin_dma() should be put in the workq, too. > + int transferred; > + int dma_countout; > + struct snd_pcm_runtime *runtime; > + int period_offset; > + long dma_flags; > + period_offset = period; > + period_offset %= (AICA_PERIOD_NUMBER / 2); > + runtime = substream->runtime; > + /* transfer left and then right */ > + dma_flags = claim_dma_lock(); > + dma_countout = 0; > + dma_xfer(0, > + runtime->dma_area + (AICA_PERIOD_SIZE * period_offset), > + AICA_CHANNEL0_OFFSET + > + (AICA_PERIOD_SIZE * period_offset), buffer_size / 2, 5); > + /* wait for completion */ > + do { > + udelay(5); > + transferred = get_dma_residue(0); > + dma_countout++; > + if (dma_countout > 0x10000) > + break; /* Approx 1/3 sec timeout in case of hardware failure */ > + } > + while (transferred < buffer_size / 2); > + dma_xfer(0, > + AICA_BUFFER_SIZE / 2 + runtime->dma_area + > + (AICA_PERIOD_SIZE * period_offset), > + AICA_CHANNEL1_OFFSET + > + (AICA_PERIOD_SIZE * period_offset), buffer_size / 2, 5); > + /* have to wait again */ > + dma_countout = 0; > + do { > + udelay(5); > + transferred = get_dma_residue(0); > + dma_countout++; > + if (dma_countout > 0x10000) > + break; > + } > + while (transferred < buffer_size / 2); > + release_dma_lock(dma_flags); > + return 0; > +} You can write a single function for both mono and two-channel streams. > +static int snd_aicapcm_pcm_open(struct snd_pcm_substream > + *substream) > +{ > + struct snd_pcm_runtime *runtime; > + struct aica_channel *channel; > + struct snd_card_aica *dreamcastcard; > + if (!enable) > + return -ENOENT; You don't need to check enable here. The "enable" module option was introduced to enable/disable the speicfic device when multiple same devices are on the system. Hence it makes no sense for the drivers that support only a single device. But we keep this option as a dummy one in most of drivers just for compatibility reason. The sound configurator tends to set this option unconditionally. > +/* TO DO: set up to handle more than one pcm instance */ > +static int __init snd_aicapcmchip(struct snd_card_aica > + *dreamcastcard, int pcm_index) Use __devinit prefix as long as you use platform_driver. > +static int remove_dreamcastcard(struct device *dreamcast_device) > +{ > + struct snd_card_aica *dreamcastcard = > + dreamcast_device->driver_data; > + snd_card_free(dreamcastcard->card); > + kfree(dreamcastcard); > + return 0; > +} > + > +static struct device_driver aica_driver = { > + .name = "AICA",.bus = &platform_bus_type, > + .remove = remove_dreamcastcard, > +}; Any reason not to use struct platform_driver? > +static int load_aica_firmware() > +{ > + int err; > + err = 0; > + spu_init(); > + const struct firmware *fw_entry; The variable definition must be in the beginning of the function block. > +static int __init aica_init(void) > +{ > + int err; > + struct snd_card_aica *dreamcastcard; > + /* Are we in a Dreamcast at all? */ > + if (!mach_is_dreamcast()) > + return -ENODEV; The typical flow in init entry should be: - register platform_driver - register platform_device -> initialize the card and releveant instances in probe callback BTW, with the latest HG repo, you can add alsa-driver/Kconfig, so that you can keep alsa-kernel tree intact. Takashi ^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [linuxsh-dev] Re: [Alsa-devel] [PATCH] Add Dreamcast AICA driver to alsa-driver 2006-04-24 11:10 ` [Alsa-devel] [PATCH] Add Dreamcast AICA driver to alsa-driver Takashi Iwai @ 2006-04-24 17:44 ` Adrian McMenamin 2006-04-24 17:57 ` Takashi Iwai 0 siblings, 1 reply; 3+ messages in thread From: Adrian McMenamin @ 2006-04-24 17:44 UTC (permalink / raw) To: Takashi Iwai; +Cc: Alsa-devel, linux-sh, LKML, Lee Revell, Paul Mundt On Mon, 2006-04-24 at 13:10 +0200, Takashi Iwai wrote: > At Sun, 23 Apr 2006 23:46:22 +0100, > Adrian McMenamin wrote: > +/* module parameters */ > +#define CARD_NAME "AICA" > + > +static int index; > > Initialize it to -1 (or SNDRV_DEFAULT_IDX1), and pass it to the first > argument of snd_card_new(). Otherwise this option is useless. > OK > > > +/* Spinlocks */ > > +DEFINE_SPINLOCK(spu_memlock); > > Missing static. > OK > > > +/* spu_memset - write to memory in SPU address space */ > > +static void spu_memset(uint32_t toi, void __iomem * what, int length) > > +{ > > + uint32_t *to = (uint32_t *) (SPU_MEMORY_BASE + toi); > > + int i; > > + if (length % 4) > > + length = (length / 4) + 1; > > + else > > + length = length / 4; > > If you use writel(), the length must be aligned to 4. > > > > + spu_write_wait(); > > + for (i = 0; i < length; i++) { > > + spin_lock(&spu_memlock); > > + writel(what, to); > > + spin_unlock(&spu_memlock); > > What's the purpose of this lock? > If I add more than one instance to the driver, which could be done, it's to stop two instances attempting to write to the ARM7 memory space concurrently. > > > + to++; > > + if (i && !(i % 8)) > > + spu_write_wait(); > > + } > > +} > > + > > +/* spu_memload - write to SPU address space */ > > +static void spu_memload(uint32_t toi, void __iomem * from, int length) > > +{ > > + uint32_t __iomem *froml = from; > > + uint32_t __iomem *to = > > + (uint32_t __iomem *) (SPU_MEMORY_BASE + toi); > > + int i, val; > > + if (length % 4) > > + length = (length / 4) + 1; > > + else > > + length = length / 4; > > Ditto. > > > > +/* spu_disable - set spu registers to stop sound output */ > > +static void spu_disable(void) > > +{ > > + int i; > > + uint32_t regval; > > + spu_write_wait(); > > + regval = readl(ARM_RESET_REGISTER); > > + regval |= 1; > > + spu_write_wait(); > > + spin_lock(&spu_memlock); > > + writel(regval, ARM_RESET_REGISTER); > > + spin_unlock(&spu_memlock); > > + for (i = 0; i < 64; i++) { > > + spu_write_wait(); > > + regval = readl(SPU_REGISTER_BASE + (i * 0x80)); > > + regval = (regval & ~0x4000) | 0x8000; > > + spu_write_wait(); > > + spin_lock(&spu_memlock); > > + writel(regval, SPU_REGISTER_BASE + (i * 0x80)); > > + spin_unlock(&spu_memlock); > > For what is this lock? > > > > +/* aica_chn_start - write to spu to start playback */ > > +inline static void aica_chn_start(void) > > +{ > > + spu_write_wait(); > > + spin_lock(&spu_memlock); > > + writel(AICA_CMD_KICK | AICA_CMD_START, > > + (uint32_t *) AICA_CONTROL_POINT); > > The cast looks strange... > > > > + spin_unlock(&spu_memlock); > > +} > > + > > +/* aica_chn_halt - write to spu to halt playback */ > > +inline static void aica_chn_halt(void) > > +{ > > + spu_write_wait(); > > + spin_lock(&spu_memlock); > > + writel(AICA_CMD_KICK | AICA_CMD_STOP, > > + (uint32_t *) AICA_CONTROL_POINT); > > + spin_unlock(&spu_memlock); > > +} > > + > > +/* ALSA code below */ > > +static struct snd_pcm_hardware snd_pcm_aica_playback_hw = { > > + .info = (SNDRV_PCM_INFO_NONINTERLEAVED),.formats = > > + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | > > + SNDRV_PCM_FMTBIT_IMA_ADPCM),.rates = > > + SNDRV_PCM_RATE_8000_48000,.rate_min = 8000,.rate_max = > > + 48000,.channels_min = 1,.channels_max = 2,.buffer_bytes_max = > > + AICA_BUFFER_SIZE,.period_bytes_min = > > + AICA_PERIOD_SIZE,.period_bytes_max = > > + AICA_PERIOD_SIZE,.periods_min = > > + AICA_PERIOD_NUMBER,.periods_max = AICA_PERIOD_NUMBER, > > +}; > > Put the field ids to the beginning of the line. Found in other > structs, too. > Indent messed up my code. I'll fix that. > > > +static int stereo_buffer_transfer(struct snd_pcm_substream > > + *substream, int buffer_size, int period) > > +{ > > I feel this transfer-and-wait could be done more efficiently using > workq than doing it in timer callback. The trigger(start) and > aica_period_elapsed() calls queue_work() at each time. > > The most work of spu_begin_dma() should be put in the workq, too. > heh. If you remember, a couple of months ago I had this in a kernel thread - ie much the same - and you or Lee said I should absolutely not use that mechanism :) > > > + int transferred; > > + int dma_countout; > > + struct snd_pcm_runtime *runtime; > > + int period_offset; > > + long dma_flags; > > + period_offset = period; > > + period_offset %= (AICA_PERIOD_NUMBER / 2); > > + runtime = substream->runtime; > > + /* transfer left and then right */ > > + dma_flags = claim_dma_lock(); > > + dma_countout = 0; > > + dma_xfer(0, > > + runtime->dma_area + (AICA_PERIOD_SIZE * period_offset), > > + AICA_CHANNEL0_OFFSET + > > + (AICA_PERIOD_SIZE * period_offset), buffer_size / 2, 5); > > + /* wait for completion */ > > + do { > > + udelay(5); > > + transferred = get_dma_residue(0); > > + dma_countout++; > > + if (dma_countout > 0x10000) > > + break; /* Approx 1/3 sec timeout in case of hardware failure */ > > + } > > + while (transferred < buffer_size / 2); > > + dma_xfer(0, > > + AICA_BUFFER_SIZE / 2 + runtime->dma_area + > > + (AICA_PERIOD_SIZE * period_offset), > > + AICA_CHANNEL1_OFFSET + > > + (AICA_PERIOD_SIZE * period_offset), buffer_size / 2, 5); > > + /* have to wait again */ > > + dma_countout = 0; > > + do { > > + udelay(5); > > + transferred = get_dma_residue(0); > > + dma_countout++; > > + if (dma_countout > 0x10000) > > + break; > > + } > > + while (transferred < buffer_size / 2); > > + release_dma_lock(dma_flags); > > + return 0; > > +} > > You can write a single function for both mono and two-channel > streams. > How can I do that given the dma has to be serialised? > > > +static int snd_aicapcm_pcm_open(struct snd_pcm_substream > > + *substream) > > +{ > > + struct snd_pcm_runtime *runtime; > > + struct aica_channel *channel; > > + struct snd_card_aica *dreamcastcard; > > + if (!enable) > > + return -ENOENT; > > You don't need to check enable here. > > The "enable" module option was introduced to enable/disable the > speicfic device when multiple same devices are on the system. Hence > it makes no sense for the drivers that support only a single device. > But we keep this option as a dummy one in most of drivers just for > compatibility reason. The sound configurator tends to set this option > unconditionally. > > > > +/* TO DO: set up to handle more than one pcm instance */ > > +static int __init snd_aicapcmchip(struct snd_card_aica > > + *dreamcastcard, int pcm_index) > > Use __devinit prefix as long as you use platform_driver. > > > > +static int remove_dreamcastcard(struct device *dreamcast_device) > > +{ > > + struct snd_card_aica *dreamcastcard = > > + dreamcast_device->driver_data; > > + snd_card_free(dreamcastcard->card); > > + kfree(dreamcastcard); > > + return 0; > > +} > > + > > +static struct device_driver aica_driver = { > > + .name = "AICA",.bus = &platform_bus_type, > > + .remove = remove_dreamcastcard, > > +}; > > Any reason not to use struct platform_driver? > > > > +static int load_aica_firmware() > > +{ > > + int err; > > + err = 0; > > + spu_init(); > > + const struct firmware *fw_entry; > > The variable definition must be in the beginning of the function > block. > > > > +static int __init aica_init(void) > > +{ > > + int err; > > + struct snd_card_aica *dreamcastcard; > > + /* Are we in a Dreamcast at all? */ > > + if (!mach_is_dreamcast()) > > + return -ENODEV; > > The typical flow in init entry should be: > > - register platform_driver > - register platform_device > -> initialize the card and releveant instances in probe > callback > > > BTW, with the latest HG repo, you can add alsa-driver/Kconfig, so that > you can keep alsa-kernel tree intact. hmmm, I have no HG tools. I'll have to get some. My distro seems to have none available ^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [linuxsh-dev] Re: [Alsa-devel] [PATCH] Add Dreamcast AICA driver to alsa-driver 2006-04-24 17:44 ` [linuxsh-dev] " Adrian McMenamin @ 2006-04-24 17:57 ` Takashi Iwai 0 siblings, 0 replies; 3+ messages in thread From: Takashi Iwai @ 2006-04-24 17:57 UTC (permalink / raw) To: Adrian McMenamin; +Cc: Alsa-devel, linux-sh, LKML, Lee Revell, Paul Mundt At Mon, 24 Apr 2006 18:44:55 +0100, Adrian McMenamin wrote: > > > > > > +static int stereo_buffer_transfer(struct snd_pcm_substream > > > + *substream, int buffer_size, int period) > > > +{ > > > > I feel this transfer-and-wait could be done more efficiently using > > workq than doing it in timer callback. The trigger(start) and > > aica_period_elapsed() calls queue_work() at each time. > > > > The most work of spu_begin_dma() should be put in the workq, too. > > > > heh. If you remember, a couple of months ago I had this in a kernel > thread - ie much the same - and you or Lee said I should absolutely not > use that mechanism :) Well, a source code tell you better than hundreds words :) > > You can write a single function for both mono and two-channel > > streams. > > > > How can I do that given the dma has to be serialised? The second dma_xfer() can be in if (channels > 1) block (in addition to different transfer bytes). Something like below: static int buffer_transfer(struct snd_pcm_substream*substream, int buffer_size, int period) { int transferred; int dma_countout; struct snd_pcm_runtime *runtime; int period_offset; long dma_flags; period_offset = period; period_offset %= (AICA_PERIOD_NUMBER / runtime->channels); runtime = substream->runtime; /* transfer left and then right */ dma_flags = claim_dma_lock(); dma_countout = 0; dma_xfer(0, runtime->dma_area + (AICA_PERIOD_SIZE * period_offset), AICA_CHANNEL0_OFFSET + (AICA_PERIOD_SIZE * period_offset), buffer_size / 2, 5); /* wait for completion */ do { udelay(5); transferred = get_dma_residue(0); dma_countout++; if (dma_countout > 0x10000) break; /* Approx 1/3 sec timeout in case of hardware failure */ } while (transferred < buffer_size / 2); if (runtime->channels > 1) { dma_xfer(0, AICA_BUFFER_SIZE / 2 + runtime->dma_area + (AICA_PERIOD_SIZE * period_offset), AICA_CHANNEL1_OFFSET + (AICA_PERIOD_SIZE * period_offset), buffer_size / 2, 5); /* have to wait again */ dma_countout = 0; do { udelay(5); transferred = get_dma_residue(0); dma_countout++; if (dma_countout > 0x10000) break; } while (transferred < buffer_size / 2); } release_dma_lock(dma_flags); return 0; } Takashi ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2006-04-24 17:57 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <1145831786.9242.38.camel@localhost.localdomain>
[not found] ` <1145832383.9242.41.camel@localhost.localdomain>
2006-04-24 11:10 ` [Alsa-devel] [PATCH] Add Dreamcast AICA driver to alsa-driver Takashi Iwai
2006-04-24 17:44 ` [linuxsh-dev] " Adrian McMenamin
2006-04-24 17:57 ` Takashi Iwai
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®