From: Kristoffer Ericson <kristoffer.ericson@gmail.com>
To: linux-sh@vger.kernel.org
Subject: Re: [PATCH] sh: add SuperH DAC audio driver for ALSA
Date: Thu, 08 Oct 2009 08:38:37 +0000 [thread overview]
Message-ID: <20091008103837.38621f31.kristoffer.ericson@gmail.com> (raw)
In-Reply-To: <20091008013423.GA26059@rafazurita.homelinux.net>
Acked
On Wed, 7 Oct 2009 22:34:23 -0300
Rafael Ignacio Zurita <rizurita@yahoo.com> wrote:
>
> This is a port of the sound/oss/sh_dac_audio.c driver.
> The driver uses an on-chip 8-bit D/A converter, which has a speaker connected
> to one of its channels, found in several ancient HP machines.
> For interrupts it uses a high-resolution timer (hrtimer).
> Tested on SH7709 based hp6xx (HP Jornada 680/690 and HP Palmtop 620lx/660lx).
>
> Signed-off-by: Rafael Ignacio Zurita <rizurita@yahoo.com>
> ---
> arch/sh/include/mach-common/mach/hp6xx.h | 4 +
> sound/sh/Kconfig | 9 +
> sound/sh/Makefile | 1 +
> sound/sh/snd_sh_dac_audio.c | 521 ++++++++++++++++++++++++++++++
> 4 files changed, 535 insertions(+), 0 deletions(-)
>
> diff --git a/arch/sh/include/mach-common/mach/hp6xx.h b/arch/sh/include/mach-common/mach/hp6xx.h
> index 0d4165a..bcc301a 100644
> --- a/arch/sh/include/mach-common/mach/hp6xx.h
> +++ b/arch/sh/include/mach-common/mach/hp6xx.h
> @@ -29,6 +29,9 @@
>
> #define PKDR_LED_GREEN 0x10
>
> +/* HP Palmtop 620lx/660lx speaker on/off */
> +#define PKDR_SPEAKER 0x20
> +
> #define SCPDR_TS_SCAN_ENABLE 0x20
> #define SCPDR_TS_SCAN_Y 0x02
> #define SCPDR_TS_SCAN_X 0x01
> @@ -42,6 +45,7 @@
> #define ADC_CHANNEL_BACKUP 4
> #define ADC_CHANNEL_CHARGE 5
>
> +/* HP Jornada 680/690 speaker on/off */
> #define HD64461_GPADR_SPEAKER 0x01
> #define HD64461_GPADR_PCMCIA0 (0x02|0x08)
>
> diff --git a/sound/sh/Kconfig b/sound/sh/Kconfig
> index aed0f90..fecb198 100644
> --- a/sound/sh/Kconfig
> +++ b/sound/sh/Kconfig
> @@ -19,5 +19,14 @@ config SND_AICA
> help
> ALSA Sound driver for the SEGA Dreamcast console.
>
> +config SND_SH_DAC_AUDIO
> + tristate "SuperH DAC audio support"
> + depends on SND
> + depends on CPU_SH3 && HIGH_RES_TIMERS
> + select SND_PCM
> + help
> + Alsa Sound driver for the HP Palmtop 620lx/660lx
> + and HP Jornada 680/690.
> +
> endif # SND_SUPERH
>
> diff --git a/sound/sh/Makefile b/sound/sh/Makefile
> index 8fdcb6e..c4b81c4 100644
> --- a/sound/sh/Makefile
> +++ b/sound/sh/Makefile
> @@ -6,3 +6,4 @@ snd-aica-objs := aica.o
>
> # Toplevel Module Dependency
> obj-$(CONFIG_SND_AICA) += snd-aica.o
> +obj-$(CONFIG_SND_SH_DAC_AUDIO) += snd_sh_dac_audio.o
> diff --git a/sound/sh/snd_sh_dac_audio.c b/sound/sh/snd_sh_dac_audio.c
> new file mode 100644
> index 0000000..074c43d
> --- /dev/null
> +++ b/sound/sh/snd_sh_dac_audio.c
> @@ -0,0 +1,521 @@
> +/*
> + * snd_sh_dac_audio.c - SuperH DAC audio driver for ALSA
> + *
> + * Copyright (c) 2009 by Rafael Ignacio Zurita <rizurita@yahoo.com>
> + *
> + *
> + * Based on sh_dac_audio.c (Copyright (C) 2004, 2005 by Andriy Skulysh)
> + *
> + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
> + *
> + */
> +
> +#include <linux/hrtimer.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/platform_device.h>
> +#include <sound/core.h>
> +#include <sound/initval.h>
> +#include <sound/pcm.h>
> +#include <asm/clock.h>
> +#include <asm/hd64461.h>
> +#include <mach-common/mach/hp6xx.h>
> +#include <cpu/dac.h>
> +
> +MODULE_AUTHOR("Rafael Ignacio Zurita <rizurita@yahoo.com>");
> +MODULE_DESCRIPTION("SuperH DAC audio driver");
> +MODULE_LICENSE("GPL");
> +MODULE_SUPPORTED_DEVICE("{{SuperH DAC audio support}}");
> +
> +/* Module Parameters */
> +static int index = SNDRV_DEFAULT_IDX1;
> +static char *id = SNDRV_DEFAULT_STR1;
> +module_param(index, int, 0444);
> +MODULE_PARM_DESC(index, "Index value for SuperH DAC audio.");
> +module_param(id, charp, 0444);
> +MODULE_PARM_DESC(id, "ID string for SuperH DAC audio.");
> +
> +/* Simple platform device */
> +static struct platform_device *pd;
> +
> +#define SND_SH_DAC_DRIVER "SH_DAC"
> +#define BUFFER_SIZE 64000
> +#define SH_DAC_AUDIO_CHANNEL 1
> +
> +/* main struct */
> +struct snd_sh_dac {
> + struct snd_card *card;
> + struct snd_pcm_substream *substream;
> + struct hrtimer hrtimer;
> + ktime_t wakeups_per_second;
> +
> + int rate;
> + int empty;
> + char *data_buffer, *buffer_begin, *buffer_end;
> + int processed; /* bytes proccesed, to compare with period_size */
> + int buffer_size;
> +};
> +
> +
> +static void dac_audio_start_timer(struct snd_sh_dac *chip)
> +{
> + hrtimer_start(&chip->hrtimer, chip->wakeups_per_second,
> + HRTIMER_MODE_REL);
> +}
> +
> +static void dac_audio_stop_timer(struct snd_sh_dac *chip)
> +{
> + hrtimer_cancel(&chip->hrtimer);
> +}
> +
> +static void dac_audio_reset(struct snd_sh_dac *chip)
> +{
> + dac_audio_stop_timer(chip);
> + chip->buffer_begin = chip->buffer_end = chip->data_buffer;
> + chip->processed = 0;
> + chip->empty = 1;
> +}
> +
> +static void dac_audio_sync(struct snd_sh_dac *chip)
> +{
> + while (!chip->empty)
> + schedule();
> +}
> +
> +static void dac_audio_start(void)
> +{
> +#ifdef CONFIG_SH_HP6XX
> + u16 v;
> + u8 v8;
> +
> + /* HP Jornada 680/690 speaker on */
> + v = inw(HD64461_GPADR);
> + v &= ~HD64461_GPADR_SPEAKER;
> + outw(v, HD64461_GPADR);
> +
> + /* HP Palmtop 620lx/660lx speaker on */
> + v8 = inb(PKDR);
> + v8 &= ~PKDR_SPEAKER;
> + outb(v8, PKDR);
> +#endif
> + sh_dac_enable(SH_DAC_AUDIO_CHANNEL);
> +}
> +
> +static void dac_audio_stop(struct snd_sh_dac *chip)
> +{
> +
> +#ifdef CONFIG_SH_HP6XX
> + u16 v;
> + u8 v8;
> +#endif
> +
> + dac_audio_stop_timer(chip);
> +
> +#ifdef CONFIG_SH_HP6XX
> + /* HP Jornada 680/690 speaker off */
> + v = inw(HD64461_GPADR);
> + v |= HD64461_GPADR_SPEAKER;
> + outw(v, HD64461_GPADR);
> +
> + /* HP Palmtop 620lx/660lx speaker off */
> + v8 = inb(PKDR);
> + v8 |= PKDR_SPEAKER;
> + outb(v8, PKDR);
> +#endif
> + sh_dac_output(0, SH_DAC_AUDIO_CHANNEL);
> + sh_dac_disable(SH_DAC_AUDIO_CHANNEL);
> +}
> +
> +static void dac_audio_set_rate(struct snd_sh_dac *chip)
> +{
> + chip->wakeups_per_second = ktime_set(0, 1000000000 / chip->rate);
> +}
> +
> +
> +/* PCM INTERFACE */
> +
> +static struct snd_pcm_hardware snd_sh_dac_pcm_hw = {
> + .info = (SNDRV_PCM_INFO_MMAP |
> + SNDRV_PCM_INFO_MMAP_VALID |
> + SNDRV_PCM_INFO_INTERLEAVED |
> + SNDRV_PCM_INFO_HALF_DUPLEX),
> + .formats = SNDRV_PCM_FMTBIT_U8,
> + .rates = SNDRV_PCM_RATE_8000,
> + .rate_min = 8000,
> + .rate_max = 8000,
> + .channels_min = 1,
> + .channels_max = 1,
> + .buffer_bytes_max = (48*1024),
> + .period_bytes_min = 1,
> + .period_bytes_max = (48*1024),
> + .periods_min = 1,
> + .periods_max = 1024,
> +};
> +
> +static int snd_sh_dac_pcm_open(struct snd_pcm_substream *substream)
> +{
> + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
> + struct snd_pcm_runtime *runtime = substream->runtime;
> +
> + runtime->hw = snd_sh_dac_pcm_hw;
> +
> + chip->substream = substream;
> + chip->buffer_begin = chip->buffer_end = chip->data_buffer;
> + chip->processed = 0;
> + chip->empty = 1;
> +
> + dac_audio_start();
> +
> + return 0;
> +}
> +
> +static int snd_sh_dac_pcm_close(struct snd_pcm_substream *substream)
> +{
> + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
> +
> + dac_audio_sync(chip);
> + dac_audio_stop(chip);
> +
> + return 0;
> +}
> +
> +static int snd_sh_dac_pcm_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *hw_params)
> +{
> + return
> + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
> +}
> +
> +static int snd_sh_dac_pcm_hw_free(struct snd_pcm_substream *substream)
> +{
> + return snd_pcm_lib_free_pages(substream);
> +}
> +
> +static int snd_sh_dac_pcm_prepare(struct snd_pcm_substream *substream)
> +{
> + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
> + struct snd_pcm_runtime *runtime = chip->substream->runtime;
> +
> + chip->buffer_size = runtime->buffer_size;
> + memset(chip->data_buffer, 0, BUFFER_SIZE);
> +
> + return 0;
> +}
> +
> +static int snd_sh_dac_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
> +
> + switch (cmd) {
> + case SNDRV_PCM_TRIGGER_START:
> + dac_audio_start_timer(chip);
> + break;
> + case SNDRV_PCM_TRIGGER_STOP:
> + chip->buffer_begin = chip->buffer_end = chip->data_buffer;
> + chip->processed = 0;
> + chip->empty = 1;
> + dac_audio_stop_timer(chip);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int snd_sh_dac_pcm_copy(struct snd_pcm_substream *substream, int channel,
> + snd_pcm_uframes_t pos, void __user *src, snd_pcm_uframes_t count)
> +{
> + /* channel is not used (interleaved data) */
> + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + ssize_t b_count = frames_to_bytes(runtime , count);
> + ssize_t b_pos = frames_to_bytes(runtime , pos);
> +
> + if (count < 0)
> + return -EINVAL;
> +
> + if (!count) {
> + dac_audio_sync(chip);
> + return 0;
> + }
> +
> + memcpy_toio(chip->data_buffer + b_pos, src, b_count);
> + chip->buffer_end = chip->data_buffer + b_pos + b_count;
> +
> + if (chip->empty) {
> + chip->empty = 0;
> + dac_audio_start_timer(chip);
> + }
> +
> + return 0;
> +}
> +
> +static int snd_sh_dac_pcm_silence(struct snd_pcm_substream *substream,
> + int channel, snd_pcm_uframes_t pos,
> + snd_pcm_uframes_t count)
> +{
> + /* channel is not used (interleaved data) */
> + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + ssize_t b_count = frames_to_bytes(runtime , count);
> + ssize_t b_pos = frames_to_bytes(runtime , pos);
> +
> + if (count < 0)
> + return -EINVAL;
> +
> + if (!count) {
> + dac_audio_sync(chip);
> + return 0;
> + }
> +
> + memset_io(chip->data_buffer + b_pos, 0, b_count);
> + chip->buffer_end = chip->data_buffer + b_pos + b_count;
> +
> + if (chip->empty) {
> + chip->empty = 0;
> + dac_audio_start_timer(chip);
> + }
> +
> + return 0;
> +}
> +
> +static
> +snd_pcm_uframes_t snd_sh_dac_pcm_pointer(struct snd_pcm_substream *substream)
> +{
> + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
> + int pointer = chip->buffer_begin - chip->data_buffer;
> +
> + return pointer;
> +}
> +
> +/* pcm ops */
> +static struct snd_pcm_ops snd_sh_dac_pcm_ops = {
> + .open = snd_sh_dac_pcm_open,
> + .close = snd_sh_dac_pcm_close,
> + .ioctl = snd_pcm_lib_ioctl,
> + .hw_params = snd_sh_dac_pcm_hw_params,
> + .hw_free = snd_sh_dac_pcm_hw_free,
> + .prepare = snd_sh_dac_pcm_prepare,
> + .trigger = snd_sh_dac_pcm_trigger,
> + .pointer = snd_sh_dac_pcm_pointer,
> + .copy = snd_sh_dac_pcm_copy,
> + .silence = snd_sh_dac_pcm_silence,
> + .mmap = snd_pcm_lib_mmap_iomem,
> +};
> +
> +static int __devinit snd_sh_dac_pcm(struct snd_sh_dac *chip, int device)
> +{
> + int err;
> + struct snd_pcm *pcm;
> +
> + /* device should be always 0 for us */
> + err = snd_pcm_new(chip->card, "SH_DAC PCM", device, 1, 0, &pcm);
> + if (err < 0)
> + return err;
> +
> + pcm->private_data = chip;
> + strcpy(pcm->name, "SH_DAC PCM");
> + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sh_dac_pcm_ops);
> +
> + /* buffer sizeHK */
> + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
> + snd_dma_continuous_data(GFP_KERNEL),
> + 48 * 1024,
> + 48 * 1024);
> +
> + return 0;
> +}
> +/* END OF PCM INTERFACE */
> +
> +
> +/* driver .remove -- destructor */
> +static int snd_sh_dac_remove(struct platform_device *devptr)
> +{
> + snd_card_free(platform_get_drvdata(devptr));
> + platform_set_drvdata(devptr, NULL);
> +
> + return 0;
> +}
> +
> +/* free -- it has been defined by create */
> +static int snd_sh_dac_free(struct snd_sh_dac *chip)
> +{
> + /* release the data */
> + kfree(chip->data_buffer);
> + kfree(chip);
> +
> + return 0;
> +}
> +
> +static int snd_sh_dac_dev_free(struct snd_device *device)
> +{
> + struct snd_sh_dac *chip = device->device_data;
> +
> + return snd_sh_dac_free(chip);
> +}
> +
> +static enum hrtimer_restart sh_dac_audio_timer(struct hrtimer *handle)
> +{
> + struct snd_sh_dac *chip = container_of(handle, struct snd_sh_dac,
> + hrtimer);
> + struct snd_pcm_runtime *runtime = chip->substream->runtime;
> + ssize_t b_ps = frames_to_bytes(runtime, runtime->period_size);
> +
> + if (!chip->empty) {
> + sh_dac_output(*chip->buffer_begin, SH_DAC_AUDIO_CHANNEL);
> + chip->buffer_begin++;
> +
> + chip->processed++;
> + if (chip->processed >= b_ps) {
> + chip->processed -= b_ps;
> + snd_pcm_period_elapsed(chip->substream);
> + }
> +
> + if (chip->buffer_begin = (chip->data_buffer +
> + chip->buffer_size - 1))
> + chip->buffer_begin = chip->data_buffer;
> +
> + if (chip->buffer_begin = chip->buffer_end)
> + chip->empty = 1;
> +
> + }
> +
> + if (!chip->empty)
> + hrtimer_start(&chip->hrtimer, chip->wakeups_per_second,
> + HRTIMER_MODE_REL);
> +
> + return HRTIMER_NORESTART;
> +}
> +
> +/* create -- chip-specific constructor for the cards components */
> +static int __devinit snd_sh_dac_create(struct snd_card *card,
> + struct platform_device *devptr,
> + struct snd_sh_dac **rchip)
> +{
> + struct snd_sh_dac *chip;
> + int err;
> +
> + static struct snd_device_ops ops = {
> + .dev_free = snd_sh_dac_dev_free,
> + };
> +
> + *rchip = NULL;
> +
> + chip = kzalloc(sizeof(*chip), GFP_KERNEL);
> + if (chip = NULL)
> + return -ENOMEM;
> +
> + chip->card = card;
> +
> + hrtimer_init(&chip->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> + chip->hrtimer.function = sh_dac_audio_timer;
> +
> + dac_audio_reset(chip);
> + chip->rate = 8000;
> + dac_audio_set_rate(chip);
> +
> + chip->data_buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
> + if (chip->data_buffer = NULL)
> + return -ENOMEM;
> +
> + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
> + if (err < 0) {
> + snd_sh_dac_free(chip);
> + return err;
> + }
> +
> + *rchip = chip;
> +
> + return 0;
> +}
> +
> +/* driver .probe -- constructor */
> +static int __devinit snd_sh_dac_probe(struct platform_device *devptr)
> +{
> + struct snd_sh_dac *chip;
> + struct snd_card *card;
> + int err;
> +
> + err = snd_card_create(index, id, THIS_MODULE, 0, &card);
> + if (err < 0) {
> + snd_printk(KERN_ERR "cannot allocate the card\n");
> + return err;
> + }
> +
> + err = snd_sh_dac_create(card, devptr, &chip);
> + if (err < 0)
> + goto probe_error;
> +
> + err = snd_sh_dac_pcm(chip, 0);
> + if (err < 0)
> + goto probe_error;
> +
> + strcpy(card->driver, "snd_sh_dac");
> + strcpy(card->shortname, "SuperH DAC audio driver");
> + printk(KERN_INFO "%s %s", card->longname, card->shortname);
> +
> + err = snd_card_register(card);
> + if (err < 0)
> + goto probe_error;
> +
> + snd_printk("ALSA driver for SuperH DAC audio");
> +
> + platform_set_drvdata(devptr, card);
> + return 0;
> +
> +probe_error:
> + snd_card_free(card);
> + return err;
> +}
> +
> +/*
> + * "driver" definition
> + */
> +static struct platform_driver driver = {
> + .probe = snd_sh_dac_probe,
> + .remove = snd_sh_dac_remove,
> + .driver = {
> + .name = SND_SH_DAC_DRIVER,
> + },
> +};
> +
> +/* clean up the module */
> +static void __exit sh_dac_exit(void)
> +{
> + platform_device_unregister(pd);
> + platform_driver_unregister(&driver);
> +}
> +
> +
> +static int __init sh_dac_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&driver);
> + if (unlikely(err < 0))
> + return err;
> +
> + pd = platform_device_register_simple(SND_SH_DAC_DRIVER, -1, NULL, 0);
> + if (unlikely(IS_ERR(pd))) {
> + platform_driver_unregister(&driver);
> + return PTR_ERR(pd);
> + }
> +
> + return 0;
> +}
> +
> +module_init(sh_dac_init);
> +module_exit(sh_dac_exit);
> --
> To unsubscribe from this list: send the line "unsubscribe linux-sh" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Kristoffer Ericson <kristoffer.ericson@gmail.com>
next prev parent reply other threads:[~2009-10-08 8:38 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2009-10-08 1:34 [PATCH] sh: add SuperH DAC audio driver for ALSA Rafael Ignacio Zurita
2009-10-08 8:38 ` Kristoffer Ericson [this message]
2009-10-09 1:22 ` Paul Mundt
2009-10-16 15:22 ` Rafael Ignacio Zurita
2009-10-19 7:01 ` Paul Mundt
2009-10-21 1:38 ` [PATCH] sh: add SuperH DAC audio driver for ALSA V2 Rafael Ignacio Zurita
2009-10-22 1:56 ` Paul Mundt
2009-10-22 20:25 ` [PATCH] sh: add SuperH DAC audio driver for ALSA V3 Rafael Ignacio Zurita
2009-10-26 0:31 ` Paul Mundt
2009-11-03 20:16 ` [PATCH] sh: add SuperH DAC audio driver for ALSA V4 Rafael Ignacio Zurita
2009-11-04 3:13 ` Paul Mundt
2009-11-04 8:19 ` [alsa-devel] " 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=20091008103837.38621f31.kristoffer.ericson@gmail.com \
--to=kristoffer.ericson@gmail.com \
--cc=linux-sh@vger.kernel.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).