All of lore.kernel.org
 help / color / mirror / Atom feed
From: Takashi Iwai <tiwai@suse.de>
To: Adrian McMenamin <adrian@mcmen.demon.co.uk>
Cc: Alsa-devel <alsa-devel@lists.sourceforge.net>,
	linux-sh <linuxsh-dev@lists.sourceforge.net>,
	LKML <linux-kernel@vger.kernel.org>,
	Lee Revell <rlrevell@joe-job.com>,
	Paul Mundt <lethal@linux-sh.org>
Subject: Re: [PATCH] Add Dreamcast AICA driver to alsa-driver
Date: Mon, 24 Apr 2006 13:10:11 +0200	[thread overview]
Message-ID: <s5hacab44bw.wl%tiwai@suse.de> (raw)
In-Reply-To: <1145832383.9242.41.camel@localhost.localdomain>

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


-------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642

WARNING: multiple messages have this Message-ID (diff)
From: Takashi Iwai <tiwai@suse.de>
To: Adrian McMenamin <adrian@mcmen.demon.co.uk>
Cc: Alsa-devel <alsa-devel@lists.sourceforge.net>,
	linux-sh <linuxsh-dev@lists.sourceforge.net>,
	LKML <linux-kernel@vger.kernel.org>,
	Lee Revell <rlrevell@joe-job.com>,
	Paul Mundt <lethal@linux-sh.org>
Subject: Re: [Alsa-devel] [PATCH] Add Dreamcast AICA driver to alsa-driver
Date: Mon, 24 Apr 2006 13:10:11 +0200	[thread overview]
Message-ID: <s5hacab44bw.wl%tiwai@suse.de> (raw)
In-Reply-To: <1145832383.9242.41.camel@localhost.localdomain>

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

  reply	other threads:[~2006-04-24 11:10 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2006-04-23 22:36 [PATCH] Add Dreamcast AICA driver to alsa-driver Adrian McMenamin
2006-04-23 22:46 ` [Alsa-devel] " Adrian McMenamin
2006-04-24 11:10   ` Takashi Iwai [this message]
2006-04-24 11:10     ` Takashi Iwai
2006-04-24 17:44     ` [linuxsh-dev] " Adrian McMenamin
2006-04-24 17:57       ` Takashi Iwai
2006-04-25 19:20         ` Adrian McMenamin
2006-04-26  9:44           ` [linuxsh-dev] " Takashi Iwai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=s5hacab44bw.wl%tiwai@suse.de \
    --to=tiwai@suse.de \
    --cc=adrian@mcmen.demon.co.uk \
    --cc=alsa-devel@lists.sourceforge.net \
    --cc=lethal@linux-sh.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linuxsh-dev@lists.sourceforge.net \
    --cc=rlrevell@joe-job.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.