Hi Bryan,

On Tuesday 08 August 2006 21:33, Bryan E. Chafy wrote:
> Are you porting the FreeBSD geode gx1 native audio driver to linux? ie:
> http://alumni.cse.ucsc.edu/%7Ebrucem/gx_audio/

Here is the current driver. Does not work correctly yet. I have two Geode GX1 
systems here: One with regular BIOS (and SMM software) that is currently 
working and a second one (different manufacturer) with a not complete 
LinuxBIOS (not working yet). My tests are running on the BIOS based system 
yet. So maybe some of my problems are founded in interference with the SMM. 
-> Someone clears my Bus Master Control bit soon or later, but not my driver 
:-(

There are some open questions the datasheet not answers:

  - How does the Audio Bus Master 0 (and 1) handle the data in memory?
    I think left and right audio sample (16 bit each) in one 32 bit word.
    Correct? What sample in the upper, what in the lower bits?
 - Reading back any PRD pointer from hardware register always has
   bit 0 set. Datasheet says to set the lower two bits to 0. Maybe this bit
   has a meaning? Status?

I did not understand completly how the PCM framework works yet. So I don't 
know what happens if the master DMA unit reaches the last PRD in the chain (I 
did not reach this point yet, someone clears the Bus Master Control bit...). 
This PRD points back to the first PRD. But this would play the same samples 
ever and ever...

Any comments and ideas are appreciated.

Regards,
Juergen
/*
 * Driver for the audio part on multifunction CS5530 companion device
 * Copyright 2006 (C) Juergen Beisert
 *
 * This driver is mostly based on the cs5535 audio driver:
 *     Driver for audio on multifunction CS5535 companion device
 *     Copyright (C) Jaya Kumar
 * also on kahlua driver for kernel 2.4:
 *     (C) Copyright 2003 Red Hat Inc <[EMAIL PROTECTED]>
 *
 * 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, 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.
 *
 * For the avoidance of doubt the "preferred form" of this code is one which
 * is in an open non patent encumbered format. Where cryptographic key signing
 * forms part of the process of creating an executable the information
 * including keys needed to generate an equivalently functional executable
 * are deemed to be part of the source code.
 *
 */

#include <sound/driver.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <asm/delay.h>
#include <sound/initval.h>
#include <linux/completion.h>

#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/ac97_codec.h>

#define DRIVER_NAME "cs5530audio"

static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;

enum { CS5530_DMA_PLAYBACK, CS5530_DMA_CAPTURE, NUM_CS5530_SOUND_DMAS };

#define CS5530_MAX_DESCRIPTORS 128 /* FIXME correct value? */

#define CS5530_DESC_LIST_SIZE \
	PAGE_ALIGN(CS5530_MAX_DESCRIPTORS * sizeof(struct cs5530_dma_desc))

struct cs5530_sound;

struct cs5530_dma_ops {
	int type;
	void (*enable_dma)(struct cs5530_sound *chip);
	void (*disable_dma)(struct cs5530_sound *chip);
	void (*pause_dma)(struct cs5530_sound *chip);
	void (*setup_prd)(struct cs5530_sound *chip, u32 prd_addr);
	u32 (*read_dma_pntr)(struct cs5530_sound *chip);
};

struct cs5530_dma {
	const struct cs5530_dma_ops *ops;
	struct snd_dma_buffer desc_buf;		/* buffer description for DMA able memory */
	struct snd_pcm_substream *substream;
	u32 buf_addr, buf_bytes;
	u32 period_bytes, periods;
};

/*
 * control bits used in the hardware in each PRD
 * These are used in ctrlreserved in struct cs5530_dma_desc
 *
 * Note: Hardware wants bits 29 to 31, we use bits
 * 13 to 15 (ctrlreserved is u16)
 */
#define PRD_JMP 0x2000
#define PRD_EOP 0x4000
#define PRD_EOT 0x8000

/*
 * hardware PRD structure for the
 * master DMA units
 */
struct cs5530_dma_desc {
	u32 addr;	/* physical address of buffer */
	u16 size;	/* lower 16 bit: size of the buffer */
	u16 ctlreserved;	/* higher 16 bit: control bits used by hardware */
};

struct cs5530_sound {
	struct snd_card *card;
	struct snd_ac97 *ac97;
	int irq;	/* currently not used */
	struct pci_dev *pci;
	unsigned long port;	/* physical io space */
	void __iomem *regs;	/* mapped io space */
	spinlock_t reg_lock;
	u32	irq_register;	/* fake! */
	unsigned int irq_enabled;
	struct timer_list polling_timer;	/* for dummy interrupt emulation */
	struct snd_pcm_substream *playback_substream;
	struct snd_pcm_substream *capture_substream;
	struct cs5530_dma dmas[NUM_CS5530_SOUND_DMAS];
};

#define CODEC_GPIO_STATUS 0x00
# define CODEC_GPIO_STATUS_AC97_ENABLE 0x80000000
# define CODEC_GPIO_STATUS_VALID 0x00100000

#define CODEC_GPIO_CONTROL 0x04

#define CODEC_STATUS 0x08
# define CODEC_STATUS_NEW 0x00020000
# define CODEC_STATUS_VALID 0x00030000
# define CODEC_STATUS_MASK 0x0000FFFF

#define CODEC_COMMAND 0x0C
# define CODEC_COMMAND_VALID 0x00010000
# define CODEC_COMMAND_MASK 0x0000ffff
# define CODEC_COMMAND_WRITE_MASK 0x7F000000
# define CODEC_COMMAND_WRITE_BIT 0x80000000

#define SECOND_SMI_STATUS_MIRROR 0x10
#define SECOND_SMI_STATUS 0x12
#define SMI_TRAP_FAST_PATH_STATUS 0x14
#define SMI_TRAP_ENABLE 0x18

#define AUDIO_MASTER_0_COMMAND 0x20
#define AUDIO_MASTER_0_SMI_STATUS 0x21

#define MAX_LOOP 20

/*
 * spin until a command is sent or register received
 * @chip dedicated chip
 *
 * Returns -1 on timeout, 0 on success
 */
static int spin_until_command_ready(struct cs5530_sound *chip)
{
	int i;

	/*
	 * Busy wait. Sorry for that
	 * Everything works on 48kHz, so we will
	 * waste only up to 20us
	 */
	for (i=0;i<MAX_LOOP;i++) {
		if ((readl(chip->regs+CODEC_COMMAND) & CODEC_COMMAND_VALID) == 0)
			break;
		udelay(10);
	}
	if (i == MAX_LOOP)
		return -1;

	return 0;
}

/*
 * Get status from codec
 * @chip dedicated chip
 *
 * Returns -1 on timeout, 0 on success
 */
static unsigned long snd_cs5530_codec_get_status(struct cs5530_sound *chip)
{
	int i;
	unsigned long rc;

	for (i=0;i<MAX_LOOP;i++) {
		if (((rc=readl(chip->regs+CODEC_STATUS)) & CODEC_STATUS_VALID) == 0)
			break;
		udelay(10);
	}

	return rc;
}

/*
 * Read a register from AC97 codec
 * @chip dedicated chip
 * @reg register to write (max 8 bit)
 *
 * Returns -1 on timeout, value on success
 */
static unsigned short snd_cs5530_codec_read(struct cs5530_sound *chip,
						unsigned short reg)
{
	unsigned int reg_val;
	unsigned long rc=0;
	int i;

	reg_val=reg;
	reg_val <<= 24;	/* shift to bit 31:24 */
	reg_val |= 0x80000000;	/* this means read to the codec */

	if (spin_until_command_ready(chip) != 0) {
		snd_printk("Timeout before reading in %s\n",__FUNCTION__);
	}
	/*
	 * maybe we need more then one turn
	 * to get the correct value
	 */
	for (i=0;i<MAX_LOOP;i++) {
		writel(reg_val,chip->regs+CODEC_COMMAND);
		if (spin_until_command_ready(chip) != 0) {
			snd_printk("Timeout in %s,%s\n",__FILE__,__FUNCTION__);
			break;
		}
		rc=snd_cs5530_codec_get_status(chip);
		if ((reg & 0xff) == ((rc>>24)&0xff))
			break;
	}
	if (i == MAX_LOOP) {
		snd_printk("%s: Read timed out\n",__FUNCTION__);
		rc=-1;
	}

	return (unsigned short)rc;
}

/*
 * Write a command to the AC97 codec
 * @chip dedicated chip
 * @reg register to write (max 8 bit)
 * @val val to write (max 16 bit)
 */
static void snd_cs5530_codec_write(struct cs5530_sound *chip,
					unsigned short reg, unsigned short val)
{
	unsigned int reg_val,temp;

	val &= CODEC_COMMAND_MASK;
	reg &= 0x7F;

	reg_val=reg;
	reg_val <<= 24;	/* shift to bit 31:24 */
	reg_val |= val;

	writel(reg_val,chip->regs+CODEC_COMMAND);
	temp=readl(chip->regs+CODEC_COMMAND);

	if (spin_until_command_ready(chip) != 0)
		snd_printk("Timeout in %s,%s\n",__FILE__,__FUNCTION__);
}

/*
 * Write a value into the AC97 codex
 */
static void snd_cs5530_ac97_codec_write(struct snd_ac97 *ac97,
					     unsigned short reg, unsigned short val)
{
	struct cs5530_sound *chip = ac97->private_data;
	snd_cs5530_codec_write(chip, reg, val);
}

/*
 * Read a value from AC97 codec
 */
static unsigned short snd_cs5530_ac97_codec_read(struct snd_ac97 *ac97,
						      unsigned short reg)
{
	struct cs5530_sound *chip = ac97->private_data;
	return snd_cs5530_codec_read(chip, reg);
}

/*
 */
static int snd_cs5530_free(struct cs5530_sound *chip)
{
	synchronize_irq(chip->irq);
	pci_set_power_state(chip->pci, 3);
	iounmap(chip->regs);

	if (chip->irq >= 0)
		free_irq(chip->irq, chip);

	pci_release_regions(chip->pci);
	pci_disable_device(chip->pci);
	kfree(chip);
	return 0;
}

/*
 * Setup memory management for the master DMA units
 * @chip: Device to work with
 * @dma: Data for the DMA management
 * @substream: Sound samples with buffer information
 * @periods: ??
 * @period_bytes: ??
 */
static int cs5530_build_dma_packets(struct cs5530_sound *chip,
					 struct cs5530_dma *dma,
					 struct snd_pcm_substream *substream,
					 unsigned int periods,
					 unsigned int period_bytes)
{
	unsigned int u;
	void *sample_phys_addr;
	struct cs5530_dma_desc *prd_desc;

	printk("%s called\n",__FUNCTION__);

	if (periods > CS5530_MAX_DESCRIPTORS)
		return -ENOMEM;

	/*
	 * allocate memory for the PRD list
	 */
	if (dma->desc_buf.area == NULL) {
		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
					snd_dma_pci_data(chip->pci),
					CS5530_DESC_LIST_SIZE+1,	/* add one reserved PRD */
					&dma->desc_buf) < 0)
			return -ENOMEM;
		dma->period_bytes = dma->periods = 0;
	}

	if (dma->periods == periods && dma->period_bytes == period_bytes) {
		printk("Whats that?????\n");
		return 0;
	}

	/*
	 * generate the PRD list points into the sound buffer
	 */
	sample_phys_addr = (void*) substream->runtime->dma_addr;	/* this is the source (physical address) */
	prd_desc=(struct cs5530_dma_desc*)dma->desc_buf.area;

	printk("The PRD table starts at %p virtually and 0x%08X physically\n",dma->desc_buf.area,dma->desc_buf.addr);

	for (u = 0; u < periods; u++) {
		printk("The %ith PRD points to %p with %u bytes\n",u,sample_phys_addr,period_bytes);
		prd_desc[u].addr = cpu_to_le32(sample_phys_addr);	/* this PRD points to this part of data */
		prd_desc[u].size = cpu_to_le16(period_bytes);
		prd_desc[u].ctlreserved = cpu_to_le16(PRD_EOP);
		sample_phys_addr += period_bytes;
	}
	/*
	 * append one dummy PRD that only jumps back to the beginning
	 */
	prd_desc[u].addr=cpu_to_le32(dma->desc_buf.addr);	/* pointer back to the first PRD in this list */
	prd_desc[u].size=0;
	prd_desc[u].ctlreserved=cpu_to_le16(PRD_JMP);
	printk("The %ith PRD points to back to %08x\n",u,dma->desc_buf.addr);

	dma->period_bytes = period_bytes;
	dma->periods = periods;

	spin_lock_irq(&chip->reg_lock);
	dma->ops->disable_dma(chip);
	dma->ops->setup_prd(chip, cpu_to_le32(dma->desc_buf.addr));
	spin_unlock_irq(&chip->reg_lock);

	return 0;
}

/*
 */
static void cs5530_clear_dma_packets(struct cs5530_sound *chip,
					  struct cs5530_dma *dma,
					  struct snd_pcm_substream *substream)
{
	printk("%s called\n",__FUNCTION__);
	snd_dma_free_pages(&dma->desc_buf);
	dma->desc_buf.area = NULL;
}

/*
 * fake interrupt routine.
 * FIXME: Gets never called yet (Why??????)
 */
static irqreturn_t snd_cs5530_interrupt(int irq, void *dev_id,struct pt_regs *regs)
{
	struct cs5530_sound *chip = dev_id;

	printk("%s called\n",__FUNCTION__);
	spin_lock(&chip->reg_lock);
	if (chip->irq_register != 0) {
		spin_unlock(&chip->reg_lock);
		if (chip->irq_register & 0x1) {
			snd_pcm_period_elapsed(chip->playback_substream);
			chip->irq_register &= ~0x1;
		}
		if (chip->irq_register & 0x4) {
			snd_pcm_period_elapsed(chip->capture_substream);
			chip->irq_register &= ~0x4;
		}

		if (chip->irq_register & 0x2) {
			printk("Audio Bus Master 0 reports Bus Error!\n");
			chip->irq_register &= ~0x2;
		}

		if (chip->irq_register & 0x8) {
			printk("Audio Bus Master 1 reports Bus Error!\n");
			chip->irq_register &= ~0x8;
		}
	}
	else
		spin_unlock(&chip->reg_lock);

	return IRQ_HANDLED;
}

/*
 * the cs5530 is only able to generate SMI instead of regular
 * IRQs. So we must poll some SMI status registers to emulate
 * an IRQ when the master DMA changes to the next PRD
 * This routine will be called periodically, checks the SMI
 * status register and calls the interrupt routine if something
 * was happend.
 */
static void cs5530_check_smi_status(struct cs5530_sound *chip)
{
	/*
	 * we only read the SMI status if the chip is working,
	 * means ony of the master DMA units are tranfering data
	 */
	spin_lock(&chip->reg_lock);
	if ((readb(chip->regs+0x20) & 0x01) || (readb(chip->regs+0x28) & 0x01)) {
		chip->irq_register |= ((readb(chip->regs+0x21) & 0x03) | ((readb(chip->regs+0x29) & 0x03)<<2));
		spin_unlock(&chip->reg_lock);

		if (chip->irq_register != 0)
			snd_cs5530_interrupt(-1,(void*)chip,NULL);
	}
	else
		spin_unlock(&chip->reg_lock);
}

/*
 * Called by an expired timer to check SMI status
 */
static void cs5530_poll_smi_status(unsigned long ul)
{
	struct cs5530_sound *chip=(struct cs5530_sound*)ul;

	cs5530_check_smi_status(chip);
	if (chip->irq_enabled != 0) {
		chip->polling_timer.expires = jiffies + 2 /*(HZ>>3)*/;	/* FIXME better timebase */
		add_timer(&chip->polling_timer);
	}
}

/*
 */
static int snd_cs5530_dev_free(struct snd_device *device)
{
	struct cs5530_sound *chip = device->device_data;
	return snd_cs5530_free(chip);
}

/*
 * create managing strucures and init the device
 * @card
 * @pci
 * @rchip output of private data
 */
static int __devinit snd_cs5530_create(struct snd_card *card,struct pci_dev *pci,
	struct cs5530_sound **rchip)
{
	struct cs5530_sound *chip;
	int err;
	static struct snd_device_ops ops = {
		.dev_free = snd_cs5530_dev_free,
	};

	*rchip = NULL;

	if ((err = pci_enable_device(pci)) < 0)
		return err;

	if (pci_set_dma_mask(pci, DMA_32BIT_MASK) < 0 ||
			pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK) < 0) {
		printk(KERN_WARNING "unable to get 32bit dma\n");
		err = -ENXIO;
		goto pcifail;
	}

	if ((chip = kzalloc(sizeof(*chip), GFP_KERNEL)) == NULL)
		return -ENOMEM;

	spin_lock_init(&chip->reg_lock);
	chip->card = card;
	chip->pci=pci;
	chip->irq=-1;

	if ((err = pci_request_regions(pci, "CS5530 (Kahlua) Audio")) < 0) {
		kfree(chip);
		goto pcifail;
	}

	chip->port = pci_resource_start(pci, 0);
	chip->regs = ioremap_nocache(chip->port,pci_resource_len(pci,0));
	if (chip->regs == NULL) {
		kfree(chip);
		goto pcifail;	/* FIXME: should free all resources! */
	}
	snd_printk("Found Chip at 0x%lX, mapped to 0x%p\n",chip->port,chip->regs);

	/*
	 * At this point of time the whole audio may be in
	 * sb emulation. We will work native, so we disable
	 * all SMI traps first
	 */
	writew(0x0000,chip->regs+SMI_TRAP_ENABLE);
	readw(chip->regs+SECOND_SMI_STATUS_MIRROR);	/* read and clear */
	readl(chip->regs+SMI_TRAP_FAST_PATH_STATUS);	/* read and clear */
	writel(0x00000000,chip->regs+CODEC_GPIO_STATUS);	/* stop the AC97 clock */
	writel(0x00000000,chip->regs+CODEC_STATUS);
	/*
	 * disable all bus masters first
	 * FIXME this is dangerous if any of the bus
	 * muster is currently running! Add sanity check!
	 */
	writeb(0x00,chip->regs+0x20); /* Master 0: Output to codec */
	readb(chip->regs+0x21);	/* clear status */
	writeb(0x08,chip->regs+0x28); /* Master 1: Input from codec */
	readb(chip->regs+0x29);
	writeb(0x00,chip->regs+0x30); /* Master 2: Modem output to codec */
	readb(chip->regs+0x31);
	writeb(0x08,chip->regs+0x38); /* Master 3: Modem input from codec */
	readb(chip->regs+0x39);
	writeb(0x00,chip->regs+0x40); /* Master 4: Output to mono channel */
	readb(chip->regs+0x41);
	writeb(0x08,chip->regs+0x48); /* Master 5: Input from microphone */
	readb(chip->regs+0x49);

	udelay(100);
	/*
	 * soft reset to the AC97 codec
	 * -> set the SYNC for about 1us without clock.
	 */
	writel(0x00400000,chip->regs+CODEC_STATUS);
	udelay(5);
	writel(0x00000000,chip->regs+CODEC_STATUS);
	/*
	 * enable the AC97 interface and wake up the controller
	 */
	snd_printk("Enabling Kahlua AC97 support\n");
	writel(CODEC_GPIO_STATUS_AC97_ENABLE,chip->regs+CODEC_GPIO_STATUS);
	writel(0x00000000,chip->regs+CODEC_STATUS);
	writel(0x00000000,chip->regs+CODEC_GPIO_CONTROL);

	while((readl(chip->regs+CODEC_GPIO_STATUS) & CODEC_GPIO_STATUS_VALID) == 0)
		; /* FIXME timeout required */

	pci_set_master(pci);

	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
		chip, &ops)) < 0) {
		snd_cs5530_free(chip);
		return err;
	}

	snd_card_set_dev(card, &pci->dev);

	*rchip = chip;
	return 0;	/* return happy */

/* FIXME disable chip when anything fails */
pcifail:
	pci_disable_device(pci);
	return err;
}

/*
 * setup the mixer
 * @chip
 */
static int snd_cs5530_mixer(struct cs5530_sound *chip)
{
	struct snd_card *card = chip->card;
	struct snd_ac97_bus *pbus;
	struct snd_ac97_template ac97;
	int err;
	static struct snd_ac97_bus_ops ops = {
		.write = snd_cs5530_ac97_codec_write,
		.read = snd_cs5530_ac97_codec_read,
	};

	if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 0)
		return err;

	memset(&ac97, 0, sizeof(ac97));
	/*
	 * define the capabilities of this design
	 */
	ac97.scaps = AC97_SCAP_AUDIO |
				AC97_SCAP_NO_SPDIF |
				AC97_SCAP_SKIP_MODEM;	/* FIXME: There is no microphone support in my system */
	ac97.private_data = chip;
	ac97.pci = chip->pci;

	if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 0) {
		snd_printk(KERN_ERR "mixer failed\n");
		return err;
	}

	return 0;
}

/*
 */
static struct snd_pcm_hardware snd_cs5530_playback_description =
{
	.info =			(
				SNDRV_PCM_INFO_MMAP |
				SNDRV_PCM_INFO_INTERLEAVED |	/* FIXME */
		 		SNDRV_PCM_INFO_BLOCK_TRANSFER |
		 		SNDRV_PCM_INFO_MMAP_VALID |
		 		SNDRV_PCM_INFO_PAUSE |	/* FIXME */
				SNDRV_PCM_INFO_SYNC_START	/* FIXME */
				),
	.formats =		(
				SNDRV_PCM_FMTBIT_S16_LE
				),
	.rates =		(
				SNDRV_PCM_RATE_CONTINUOUS |
				SNDRV_PCM_RATE_8000_48000
				),
	.rate_min =		4000,
	.rate_max =		48000,
	.channels_min =		2,
	.channels_max =		2,
	.buffer_bytes_max =	(64*1024),	/* maximal size of one PRD */
	.period_bytes_min =	64,
	.period_bytes_max =	(64*1024 - 16),
	.periods_min =		1,	/* FIXME */
	.periods_max =		CS5530_MAX_DESCRIPTORS,	/* FIXME */
	.fifo_size =		0,
};

/*
 */
static struct snd_pcm_hardware snd_cs5530_capture_description =
{
	.info =			(
				SNDRV_PCM_INFO_MMAP |
				SNDRV_PCM_INFO_INTERLEAVED |	/* FIXME */
		 		SNDRV_PCM_INFO_BLOCK_TRANSFER |
		 		SNDRV_PCM_INFO_MMAP_VALID |
				SNDRV_PCM_INFO_SYNC_START
				),
	.formats =		(
				SNDRV_PCM_FMTBIT_S16_LE	/* FIXME */
				),
	.rates =		(
				SNDRV_PCM_RATE_CONTINUOUS |
				SNDRV_PCM_RATE_8000_48000
				),
	.rate_min =		4000,
	.rate_max =		48000,
	.channels_min =		2,
	.channels_max =		2,
	.buffer_bytes_max =	(64*1024),
	.period_bytes_min =	64,
	.period_bytes_max =	(64*1024 - 16),
	.periods_min =		1,	/* FIXME */
	.periods_max =		CS5530_MAX_DESCRIPTORS,	/* FIXME */
	.fifo_size =		0,
};

/*
 * open callback for playback
 * @substream
 */
static int snd_cs5530_playback_open(struct snd_pcm_substream *substream)
{
	int err;
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);
	struct snd_pcm_runtime *runtime = substream->runtime;

	printk("%s called\n",__FUNCTION__);
	runtime->hw = snd_cs5530_playback_description;
	chip->playback_substream = substream;
	runtime->private_data = &(chip->dmas[CS5530_DMA_PLAYBACK]);

	if (chip->irq_enabled == 0) {	/* FIXME: Must be atomic */
		chip->irq_enabled |= 0x01;
		cs5530_poll_smi_status((unsigned long)chip);
	}
	else
		chip->irq_enabled |= 0x01;
#if 1
	snd_pcm_set_sync(substream);	/* FIXME ???? */
	if ((err = snd_pcm_hw_constraint_integer(runtime,
				SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
		return err;
#endif
	return 0;
}

/*
 * close callback for playback
 * @substream
 */
static int snd_cs5530_playback_close(struct snd_pcm_substream *substream)
{
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);

	printk("%s called\n",__FUNCTION__);
	chip->irq_enabled &= ~0x01;
	return 0;
}

/*
 * This is called when the hardware parameter (hw_params) is set up by the
 * application, that is, once when the buffer size, the period size, the format,
 * etc. are defined for the pcm substream.
 * @substream
 * @hw_params
 */
static int snd_cs5530_hw_params(struct snd_pcm_substream *substream,
				struct snd_pcm_hw_params *hw_params)
{
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);
	struct cs5530_dma *dma = substream->runtime->private_data;
	int err;

	printk("%s called\n",__FUNCTION__);
	if ((err = snd_pcm_lib_malloc_pages(substream,
					params_buffer_bytes(hw_params))) < 0)
		return err;

	dma->buf_addr = substream->runtime->dma_addr;
	dma->buf_bytes = params_buffer_bytes(hw_params);

	printk("Samples at buf_addr=%X, buf_bytes=%X\n",dma->buf_addr,dma->buf_bytes);

	return cs5530_build_dma_packets(chip, dma, substream,
					params_periods(hw_params),
					params_period_bytes(hw_params));
}

/*
 */
static int snd_cs5530_hw_free(struct snd_pcm_substream *substream)
{
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);
	struct cs5530_dma *dma = substream->runtime->private_data;

	printk("%s called\n",__FUNCTION__);
	cs5530_clear_dma_packets(chip, dma, substream);
	return snd_pcm_lib_free_pages(substream);
}

/*
 */
static int snd_cs5530_playback_prepare(struct snd_pcm_substream *substream)
{
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);

	printk("%s called\n",__FUNCTION__);
	return snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE,
				 substream->runtime->rate);
}

/*
 * Let the master DMA engine do its work
 * @chip hardware to work with
 *
 * Note: Once enabled it only can be stopped when it pauses or
 * reached a EOT marked PRD!
 */
static void cs5530_playback_enable_dma(struct cs5530_sound *chip)
{
	printk("%s called\n",__FUNCTION__);
	chip->irq_register |= readb(chip->regs+0x21) & 0x3;	/* read and clear the status */
/*
 * enable the DMA engine. Note: If there is a regulare
 * Geode GX1 SMM present, it interfere with this driver!
 * Also if the sound hardware "emulation" is disabled
 */
	writeb(0x01,chip->regs+0x20);	/* enable the master DMA */
}

/*
 * Stop whole engine
 * @chip hardware to work with
 *
 * Note: Could only be done with the current or the next PRD
 * to set it to an EOT type. Or wait for two PRD without
 * reading the SMI status. This lets the master DMA pauses.
 * In this state disabling is possible
 */
static void cs5530_playback_disable_dma(struct cs5530_sound *chips)
{
	printk("%s called\n",__FUNCTION__);
	/* cs_writeb(cs5535au, ACC_BM0_CMD, 0); FIXME */
}

/*
 * Pause current playback
 * @chip hardware to work with
 *
 * Note: Could only be done with the current or the next PRD
 * to set it to an EOT type.
 *
 */
static void cs5530_playback_pause_dma(struct cs5530_sound *chip)
{
	printk("%s called\n",__FUNCTION__);
	/* cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE); FIXME */
}

/*
 * set the pointer to the first PRD in a chain
 * @chip hardware to work with
 * @prd_addr physical address of first PRD in chain
 *
 * Playback is handled by Audio Master 0. It sends its data
 * through AC97 slot 3 + 4
 */
static void cs5530_playback_setup_prd(struct cs5530_sound *chip,
					   u32 prd_addr)
{
	printk("%s called, setting PRD to 0x%08X\n",__FUNCTION__,prd_addr);
	writel(prd_addr,chip->regs+0x24);
}

/*
 * Read back the current PRD in use
 * @chip hardware to work with
 */
static u32 cs5530_playback_read_dma_pntr(struct cs5530_sound *chip)
{
	/*
	 * the lower bit seems always 1 on readback. Why?
	 * Datasheet remains silent
	 */
	return readl(chip->regs+0x24) & 0xFFFFFFFC;
}

/*
 */
static int snd_cs5530_capture_open(struct snd_pcm_substream *substream)
{
	int err;
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);
	struct snd_pcm_runtime *runtime = substream->runtime;

	printk("%s called\n",__FUNCTION__);
	runtime->hw = snd_cs5530_capture_description;
	chip->capture_substream = substream;
	runtime->private_data = &(chip->dmas[CS5530_DMA_CAPTURE]);
	snd_pcm_set_sync(substream);
	if ((err = snd_pcm_hw_constraint_integer(runtime,
					 SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
		return err;

	if (chip->irq_enabled == 0) {	/* FIXME: Must be atomic */
		chip->irq_enabled |= 0x02;
		cs5530_poll_smi_status((unsigned long)chip);
	}
	else
		chip->irq_enabled |= 0x02;

	return 0;
}

/*
 */
static int snd_cs5530_capture_close(struct snd_pcm_substream *substream)
{
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);

	printk("%s called\n",__FUNCTION__);
	chip->irq_enabled &= ~0x02;
	return 0;
}

/*
 */
static void cs5530_capture_enable_dma(struct cs5530_sound *chip)
{
	printk("%s called\n",__FUNCTION__);
}

/*
 */
static void cs5530_capture_disable_dma(struct cs5530_sound *chip)
{
	printk("%s called\n",__FUNCTION__);
	/* cs_writeb(cs5535au, ACC_BM1_CMD, 0); FIXME */
}

/*
 */
static void cs5530_capture_pause_dma(struct cs5530_sound *chip)
{
	printk("%s called\n",__FUNCTION__);
	/* writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE); FIXME */
}

/*
 */
static void cs5530_capture_setup_prd(struct cs5530_sound *chip,
					  u32 prd_addr)
{
	printk("%s called\n",__FUNCTION__);
	writel(prd_addr,chip->regs+0 /* ACC_BM1_PRD*/);	/* FIXME ACC_BM1_PRD? */
}

/*
 */
static u32 cs5530_capture_read_dma_pntr(struct cs5530_sound *chip)
{
	printk("%s called\n",__FUNCTION__);
	return readl(chip->regs+0x30);	/* FIXME ACC_BM1_PNTR? */
}

/*
 */
static int snd_cs5530_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);
	struct cs5530_dma *dma = substream->runtime->private_data;
	int err = 0;

	printk("%s called\n",__FUNCTION__);
	spin_lock(&chip->reg_lock);
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		dma->ops->pause_dma(chip);
		break;
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		dma->ops->enable_dma(chip);
		break;
	case SNDRV_PCM_TRIGGER_START:
		dma->ops->enable_dma(chip);
		break;
	case SNDRV_PCM_TRIGGER_STOP:
		dma->ops->disable_dma(chip);
		break;
	default:
		snd_printk(KERN_ERR "unhandled trigger\n");
		err = -EINVAL;
		break;
	}
	spin_unlock(&chip->reg_lock);
	return err;
}

/*
 * calculate where we are currently playing a sound
 * @substream currently playing stream
 */
static snd_pcm_uframes_t snd_cs5530_pcm_pointer(struct snd_pcm_substream
							*substream)
{
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);
	u32 curdma,current_prd;
	struct cs5530_dma *dma;
	struct cs5530_dma_desc *prd_desc;

	printk("%s called\n",__FUNCTION__);
	dma = substream->runtime->private_data;

	curdma = dma->ops->read_dma_pntr(chip);	/* get physical address of current PRD */
	printk("Current PRD is at %08X\n",curdma);
	/*
	 * find the current PRD in work
	 */
	current_prd = (curdma - dma->desc_buf.addr) / sizeof(struct cs5530_dma_desc);
	prd_desc = (struct cs5530_dma_desc*)dma->desc_buf.area;
	printk("Current PRD is %u\n",current_prd);

	if (prd_desc[current_prd].addr < dma->buf_addr) {
		snd_printk(KERN_ERR "curdma=%x < %x bufaddr.\n",
			prd_desc[current_prd].addr, dma->buf_addr);
		return 0;
	}

	curdma = prd_desc[current_prd].addr - dma->buf_addr;
	if (curdma >= dma->buf_bytes) {
		snd_printk(KERN_ERR "diff=%x >= %x buf_bytes.\n",
			curdma, dma->buf_bytes);
		return 0;
	}

	return bytes_to_frames(substream->runtime, curdma);
}


/*
 */
static int snd_cs5530_capture_prepare(struct snd_pcm_substream *substream)
{
	struct cs5530_sound *chip = snd_pcm_substream_chip(substream);

	printk("%s called\n",__FUNCTION__);
	return snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE,
			substream->runtime->rate);
}

static struct snd_pcm_ops snd_cs5530_playback_ops = {
	.open =		snd_cs5530_playback_open,
	.close =	snd_cs5530_playback_close,
	.ioctl =	snd_pcm_lib_ioctl,
	.hw_params =	snd_cs5530_hw_params,
	.hw_free =	snd_cs5530_hw_free,
	.prepare =	snd_cs5530_playback_prepare,
	.trigger =	snd_cs5530_trigger,
	.pointer =	snd_cs5530_pcm_pointer,
};

static struct snd_pcm_ops snd_cs5530_capture_ops = {
	.open =		snd_cs5530_capture_open,
	.close =	snd_cs5530_capture_close,
	.ioctl =	snd_pcm_lib_ioctl,
	.hw_params =	snd_cs5530_hw_params,
	.hw_free =	snd_cs5530_hw_free,
	.prepare =	snd_cs5530_capture_prepare,
	.trigger =	snd_cs5530_trigger,
	.pointer =	snd_cs5530_pcm_pointer,
};

static const struct cs5530_dma_ops snd_cs5530_playback_dma_ops = {
	.type = CS5530_DMA_PLAYBACK,
	.enable_dma = cs5530_playback_enable_dma,
	.disable_dma = cs5530_playback_disable_dma,
	.setup_prd = cs5530_playback_setup_prd,
	.pause_dma = cs5530_playback_pause_dma,
	.read_dma_pntr = cs5530_playback_read_dma_pntr,
};

static const struct cs5530_dma_ops snd_cs5530_capture_dma_ops = {
	.type = CS5530_DMA_CAPTURE,
	.enable_dma = cs5530_capture_enable_dma,
	.disable_dma = cs5530_capture_disable_dma,
	.setup_prd = cs5530_capture_setup_prd,
	.pause_dma = cs5530_capture_pause_dma,
	.read_dma_pntr = cs5530_capture_read_dma_pntr,
};

/*
 * setup info for pcm handling
 * @chip
 */
static int __devinit snd_cs5530_pcm(struct cs5530_sound *chip)
{
	struct snd_pcm *pcm;
	int err;

	printk("%s called\n",__FUNCTION__);
	if ((err = snd_pcm_new(chip->card, "CS5530 Audio",
			0,	/* this is the sole pcm device */
			1,	/* one playback strem possible */
			1,	/* one record stream possible */
			&pcm)) < 0)
		return err;

	chip->dmas[CS5530_DMA_PLAYBACK].ops =
					&snd_cs5530_playback_dma_ops;
	chip->dmas[CS5530_DMA_CAPTURE].ops =
					&snd_cs5530_capture_dma_ops;
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
					&snd_cs5530_playback_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
					&snd_cs5530_capture_ops);

	pcm->private_data = chip;
	pcm->info_flags = 0;
	strcpy(pcm->name, "CS5530 Audio");
	/*
	 * this chip supports scatter gather
	 * later....when I understand how the pcm DMA
	 * management works....
	 */
/*	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
					snd_dma_pci_data(chip->pci),
					64*1024, 64*1024); */
	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
					snd_dma_pci_data(chip->pci),
					64*1024, 64*1024);

	return 0;
}

/*
 * device contructor
 * @pcidev
 * @ent
 */
static int __devinit snd_cs5530audio_probe(struct pci_dev *pci, const struct pci_device_id *ent)
{
	static int dev;
	struct snd_card *card;
	struct cs5530_sound *chip;
	int err;

	if (dev >= SNDRV_CARDS)
		return -ENODEV;
	if (!enable[dev]) {
		dev++;
		return -ENOENT;
	}

	if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0)) == NULL)
		return -ENOMEM;

	if ((err = snd_cs5530_create(card, pci, &chip)) < 0)
		goto probefail_out;

	if ((err = snd_cs5530_mixer(chip)) < 0)
		goto probefail_out;

	if ((err = snd_cs5530_pcm(chip)) < 0)
		goto probefail_out;

	strcpy(card->driver, DRIVER_NAME);
	strcpy(card->shortname, "ns5530");
	sprintf(card->longname, "%s at 0x%lu irq %i",	/* FIXME: should be snprintf */
		card->shortname, chip->port, chip->irq);

	if ((err = snd_card_register(card)) < 0)
		goto probefail_out;

	/*
	 * init a timer that should replace the real IRQ
	 * hack alert!!!!
	 */
	init_timer(&chip->polling_timer);
	chip->polling_timer.function=cs5530_poll_smi_status;
	chip->polling_timer.data=(unsigned long)chip;

	pci_set_drvdata(pci, card);
	dev++;
	return 0;

probefail_out:
	snd_card_free(card);
	return err;
}

/*
 */
static void __devexit snd_cs5530audio_remove(struct pci_dev *pcidev)
{
	struct cs5530_sound *chip;

	chip=(struct cs5530_sound*)pci_get_drvdata(pcidev);
	chip->irq_enabled=0;
	del_timer_sync(&chip->polling_timer);
	snd_card_free(pci_get_drvdata(pcidev));
	pci_set_drvdata(pcidev, NULL);
}

/*
 * currently 5530 only.
 */
static struct pci_device_id cs5530audio_ids[] = {
	{ PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO,
	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ }
};

MODULE_DEVICE_TABLE(pci, cs5530audio_ids/*id_tbl*/);

static struct pci_driver kahlua_driver = {
	.name =	DRIVER_NAME,
	.id_table =	cs5530audio_ids,
	.probe =	snd_cs5530audio_probe,
	.remove =	__devexit_p(snd_cs5530audio_remove),
};

static int __init alsa_kahlua_init_module(void)
{
	printk(KERN_INFO "Cyrix Kahlua native port XpressAudio support (c) Juergen Beisert 2006 [EMAIL PROTECTED]");
	return pci_module_init(&kahlua_driver);
}

static void __devexit alsa_kahlua_cleanup_module(void)
{
	return pci_unregister_driver(&kahlua_driver);
}

module_init(alsa_kahlua_init_module);
module_exit(alsa_kahlua_cleanup_module);

MODULE_AUTHOR("Juergen Beisert");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Kahlua (CS5530) native PCI Audio");
MODULE_SUPPORTED_DEVICE("CS5530 Audio");

-- 
linuxbios mailing list
linuxbios@linuxbios.org
http://www.openbios.org/mailman/listinfo/linuxbios

Reply via email to