Non-ALSA SSC/DMA support?

Discussion around products based on ARM Cortex-A5 core.

Moderator: nferre

ColonelPanic
Posts: 7
Joined: Wed Mar 19, 2014 10:48 pm

Non-ALSA SSC/DMA support?

Thu Mar 20, 2014 10:45 pm

I've designed a board around the SAMA5D34 module, so the board is similar to the SAMA5D3-EK. But instead of the WM8904 codec I have used four TI TLV320AIC3106 stereo codecs. It's my intention to use these to give me eight 16-bit audio channels running at an 8 KHz sample rate.

In my application I never need to change the word size or sample rate. I don't care about mixers or any of the alternate inputs or outputs on the codecs. I only need to change the analog gain settings which can easily be done from user space. So it seems to me that ALSA is way overkill for this application. And my preliminary work with the ASoC stuff leads me to believe that multichannel TDM stuff still isn't quite there anyway, but I could be wrong about that.

What I'd like to do is create a driver specifically for using these codecs with SSC0 and DMA, and just ditch ALSA altogether. Make a simple interface for reading and writing the audio data. Looking at the existing ALSA drivers it's hard for me to get past the abstraction and see exactly how to do this, but I'm studying it. My questions at this point are:

1. Has anyone done this? I mean created an audio I/O system that uses DMA and does not use ALSA and ASoC? I can program the codecs and set up their time slots without a driver, I just need to have SSC0 working with DMA.

2. Is this a dumb idea? Am I overestimating the complexity and work required to make my setup work with ALSA?

3. Anybody know of a consultant who has deep knowledge in this area? I might be willing to contract some help.

Regards,
Doug
luntar
Posts: 1
Joined: Fri Mar 21, 2014 6:53 pm

Re: Non-ALSA SSC/DMA support?

Fri Mar 21, 2014 11:19 pm

Hi Doug,
I’m pretty green in the embedded Linux arena, so for what it’s worth, I think this is a reasonable approach. I’m currently staging a SAMA5D3x based design and considering the same option.
Best,
John
aretra4
Posts: 4
Joined: Mon Dec 15, 2014 1:46 am

Re: Non-ALSA SSC/DMA support?

Mon Feb 23, 2015 11:44 am

ColonelPanic wrote: 1. Has anyone done this? I mean created an audio I/O system that uses DMA and does not use ALSA and ASoC? I can program the codecs and set up their time slots without a driver, I just need to have SSC0 working with DMA.

2. Is this a dumb idea? Am I overestimating the complexity and work required to make my setup work with ALSA?

3. Anybody know of a consultant who has deep knowledge in this area? I might be willing to contract some help.
1. yes, or at least someone before us did it inside an avr32 linux char driver and I'm porting it to the at91sam9g25

2. In my opinion it is a very good idea, it's really a matter of maybe few hundred lines of code

3. sorry no.


As far I do understand you (and I...) really should be able to:
- use i2c/whatever versus the codecs to configure/start/stop them
- program both the ssc interface and the dmac inside the simple char driver init code to setup them, with dma buffer and registering an irq handler for irq 28
- use an ioctl to start/stop the acquisitions on the micro side
- use the read function interface to get the bufferized data

I hope to collect all of the details in a few days, I'll keep this thread up-to-date even if the topic is old.
aretra4
Posts: 4
Joined: Mon Dec 15, 2014 1:46 am

Re: Non-ALSA SSC/DMA support?

Thu May 07, 2015 10:43 am

At last I got it, my code is for something very similar that handles ssc , dma (with the kernel dmaengine) and the ak5702 sampler; I use to disable all of the interferences from both the alsa and the atmel sound

drivers inside the kernel and I directly put my files inside drivers/char because it looks like to be the simplest way to handle a driver (unluckily I'm not a kernel expert, I just copy/pasted and modified some other code). Then I have some simple user space code to open the data stream and copy (yes, still copy :( ) the buffered data. So, ak5702.c is

Code: Select all

// -----------------------------------------------------------------------
#include <linux/interrupt.h>
#include <../drivers/dma/at_hdmac_regs.h>
#include <../sound/soc/atmel/atmel_ssc_dai.h>
#include <linux/platform_data/dma-atmel.h>

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* for put_user */
#include <linux/platform_device.h>
//#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/delay.h>
#include <linux/sched.h>

#include <linux/io.h>
#include <linux/wait.h>
#include <linux/seq_file.h>
//#include <linux/proc_fs.h>
#include <linux/atmel-ssc.h>

#define DEBUG

#ifdef DEBUG
#define	printdbg printk
#else
#define	printdbg
#endif

#include "ak5702.h"

#define SUCCESS 0

// -----------------------------------------------------------------------
//
DECLARE_WAIT_censored_HEAD( ak5702_wait);

static int                _major;              /* Major number assigned to our device driver */
static int                _is_device_open = 0; /* Is device open?  Used to prevent multiple access to device */

// static struct dma_chan    *_tx_channel = 0;    /* dma transmitter channel */
static struct dma_chan    *_rx_channel = 0;    /* dma receiver channel */

// static struct at_dma_chan *_at_tx_channel = 0;
// static struct at_dma_chan *_at_rx_channel = 0;

static struct at_dma      *_at_dma = 0;

static struct ssc_device  *_ssc_dev;
static int                _ssc_irq_number = -1;
static spinlock_t         _ssc_lock;           /* Protect SSC registers against concurrent access. */
static spinlock_t         _indexes_locker;     /* Protect the r/w indexes against concurrent access. */


static dma_addr_t      _dma_buffer_pointer_for_configurations;
static u8              *_dma_buffer_pointer_for_driver;

static int            _split_index_to_read = 0;
static int            _number_of_available_splits = 0;

static int            _number_of_dmac_callbacks = 0;
static int            _number_of_ssc_overrun_interrupts = 0;

// static struct dma_async_tx_descriptor *_tx_async_descriptor;
static struct dma_async_tx_descriptor *_rx_async_descriptor;

//static const struct file_operations proc_bri_info_ops;

// -----------------------------------------------------------------------
//
// static void dmac_dump_channel( struct dma_chan *channelPointer)
// {
// 	struct at_dma_chan *atmelChannelPointer = to_at_dma_chan( channelPointer);
// 	struct at_dma      *atmelDmaPointer = to_at_dma( atmelChannelPointer->chan_common.device);
//
// 	printdbg( KERN_ALERT "ak5702: dma0chan%d, mask 0x%08x, mem_if: %d, per_if: %d\n", channelPointer->chan_id, atmelChannelPointer->mask, atmelChannelPointer->mem_if, atmelChannelPointer->per_if);
// 	printdbg( KERN_ALERT "ak5702: dma0gcfg:      0x%08x [GLOBAL CONFIGURATION]\n", dma_readl(atmelDmaPointer, GCFG));
// 	printdbg( KERN_ALERT "ak5702: dma0en:        0x%08x [ENABLE]\n", dma_readl(atmelDmaPointer, EN));
// 	printdbg( KERN_ALERT "ak5702: dma0sreq:      0x%08x [SOFTWARE SINGLE REQUEST (dst/src, dst/src...two bits per channel)]\n", dma_readl(atmelDmaPointer, SREQ));
// 	printdbg( KERN_ALERT "ak5702: dma0creq:      0x%08x [SOFTWARE CHUNK TRANSFER REQUEST (dst/src, dst/src...two bits per channel)]\n", dma_readl(atmelDmaPointer, CREQ));
// 	printdbg( KERN_ALERT "ak5702: dma0ebcimr:    0x%08x [INTERRUPT MASKS: ZERO, ERROR, CHAINED BUFFER TRANSFER COMPLETED, BUFFER TRANSFER COMPLETED]\n", dma_readl(atmelDmaPointer, EBCIMR));
// 	printdbg( KERN_ALERT "ak5702: dma0ebcisr:    0x%08x [STATUS MASKS:    ZERO, ERROR, CHAINED BUFFER TRANSFER COMPLETED, BUFFER TRANSFER COMPLETED]\n", dma_readl(atmelDmaPointer, EBCISR));
// 	printdbg( KERN_ALERT "ak5702: dma0chsr:      0x%08x [CHANNEL HANDLER STATUS]\n", dma_readl(atmelDmaPointer, CHSR));
// 	printdbg( KERN_ALERT "ak5702: channel saddr: 0x%08x [SOURCE ADDRESS]\n", channel_readl( atmelChannelPointer, SADDR));
// 	printdbg( KERN_ALERT "ak5702: channel daddr: 0x%08x [DESTINATION ADDRESS]\n", channel_readl( atmelChannelPointer, DADDR));
// 	printdbg( KERN_ALERT "ak5702: channel ctrla: 0x%08x []\n", channel_readl( atmelChannelPointer, CTRLA));
// 	printdbg( KERN_ALERT "ak5702: channel ctrlb: 0x%08x [AUTO, IEN, DST_INCR(2), X(2), SRC_INCR(2); FC(3), DST_DSCR, X(3), SRC_DSCR...]\n", channel_readl( atmelChannelPointer, CTRLB));
// 	printdbg( KERN_ALERT "ak5702: channel cfg:   0x%08x []\n", channel_readl( atmelChannelPointer, CFG));
// 	printdbg( KERN_ALERT "ak5702: channel dscr:  0x%08x [DESCRIPTOR ADDRESS]\n", channel_readl( atmelChannelPointer, DSCR));
// }

/* -----------------------------------------------------------------------
 * dmac start is just some variables setup plus a change buffer
 */
void ak5702_dmac_callback( void *parameter)
{
	_number_of_dmac_callbacks++;

	spin_lock( & _indexes_locker);
	{
		_number_of_available_splits++;
	}
	spin_unlock( & _indexes_locker);
}

/* -----------------------------------------------------------------------
 * dmac start is just some variables setup plus a change buffer
 */
static void ak5702_start_dmac( void)
{
	struct dma_slave_config configuration;

	/* set or keep the dmac enabled */
	dma_writel( _at_dma, EN, 1);

	/* put garbage inside the allocated memory */
	memset( (char *)(_dma_buffer_pointer_for_driver), 0x5a, AK5702_DMA_BUFFER_SIZE);

	/* Reset internal counters */
	_split_index_to_read = 0;
	_number_of_available_splits = 0;

	/* prepare the slave configuration */
	configuration.device_fc = true;
	configuration.direction = DMA_DEV_TO_MEM;
	configuration.dst_addr = _dma_buffer_pointer_for_configurations;
	configuration.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
	configuration.dst_maxburst = 1;
	configuration.slave_id = 0xe;
	configuration.src_addr = _ssc_dev->phybase + SSC_RHR;
	configuration.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
	configuration.src_maxburst = 1;

	/* this looks like to be useful to setup a few details */
	dmaengine_slave_config( _rx_channel, & configuration);

	/* this is the real setup of what we need inside the tracer */
	_rx_async_descriptor = _at_dma->dma_common.device_prep_dma_cyclic( _rx_channel,
																																		 (dma_addr_t) _dma_buffer_pointer_for_configurations,
																																		 AK5702_DMA_BUFFER_SIZE,
																																		 AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT,
																																		 DMA_DEV_TO_MEM,
																																		 DMA_PREP_INTERRUPT | DMA_CTRL_ACK,
																																		 0
																																	);

	if( _rx_async_descriptor == 0 )
	{
		printdbg(KERN_ALERT "[!] ak5702_start_dmac(): error: channel configuration FAILED\n");
	}
	else
	{
		/* we need the callback to update the split indexes */
		_rx_async_descriptor->callback = & ak5702_dmac_callback;
		_rx_async_descriptor->callback_param = 0;

		/* ask to start the dma operation */
		dmaengine_submit( _rx_async_descriptor);
		dma_async_issue_pending( _rx_channel);
	}
}

/* -----------------------------------------------------------------------
 * dmac shall stop after the ssc stops
 */
static void ak5702_stop_dmac(void)
{
	dmaengine_terminate_all( _rx_channel);
}

/* -----------------------------------------------------------------------
 *
 * ssc shall start after the dmac setup
 */
static void ak5702_start_ssc( void)
{
	u32 tempore;

/* SSC IS IN SLAVE MODE */
	spin_lock( &_ssc_lock);
	{
		/* clear status (maybe again...) */
		ssc_writel( _ssc_dev->regs, CR, SSC_BIT( CR_SWRST));

		/* Set divider in SSC device. */
		ssc_writel( _ssc_dev->regs, CMR, 0); /* Generate no Clock */

		/* SSC_RCMR: Setting SSC Receive Clock Mode Register in SLAVE MODE */
		tempore = SSC_BF( RCMR_PERIOD, 0) |
							SSC_BF( RCMR_STTDLY, 0) |
							SSC_BF( RCMR_STOP, 0) |
							SSC_BF( RCMR_START, SSC_START_RISING_RF) |
							SSC_BF( RCMR_CKG, SSC_CKG_CONTINUOUS) |
							SSC_BF( RCMR_CKI, SSC_CKI_RISING) |
							SSC_BF( RCMR_CKO, SSC_CKO_NONE) |
							SSC_BF( RCMR_CKS, SSC_CKS_PIN);

		ssc_writel( _ssc_dev->regs, RCMR, tempore);

		/* SSC_RFMR: Setting SSC Receive Frame Mode Register */
		tempore = SSC_BF( RFMR_FSLEN_EXT, 0) |
							SSC_BF( RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) |
							SSC_BF( RFMR_FSOS, SSC_FSOS_NONE) |
							SSC_BF( RFMR_FSLEN, 0) |
							SSC_BF( RFMR_MSBF, 1) |
							SSC_BF( RFMR_DATNB, (AK5702_NUMBER_OF_CHANNELS) - 1) |
							SSC_BF( RFMR_DATLEN, (AK5702_SAMPLE_SIZE_IN_BYTES * 8) - 1);

		ssc_writel( _ssc_dev->regs, RFMR, tempore);

		/* Disable ALL irqs to avoid troubles */
		tempore = SSC_BIT( IDR_RXSYN) |
							SSC_BIT( IDR_TXSYN) |
							SSC_BIT( IDR_CP1) |
							SSC_BIT( IDR_CP0) |
							SSC_BIT( IDR_OVRUN) |
							SSC_BIT( IDR_RXRDY) |
							SSC_BIT( IDR_TXEMPTY) |
							SSC_BIT( IDR_TXRDY);
		ssc_writel( _ssc_dev->regs, IDR, tempore);

		/* Enable rx ovrun irq to catch troubles */
		tempore = SSC_BIT( IER_OVRUN);
		ssc_writel( _ssc_dev->regs, IER, tempore);

		/* Start capture */
		ssc_writel( _ssc_dev->regs, CR, SSC_BIT( CR_RXEN));
	}
	spin_unlock( &_ssc_lock);
}

/* -----------------------------------------------------------------------
 *
 * ssc shall stop before the dmac stop
 */
static void ak5702_stop_ssc(void)
{
	u32 tempore;

	spin_lock( &_ssc_lock);
	{
		/* clear status */
		ssc_writel( _ssc_dev->regs, CR, SSC_BIT(CR_SWRST));

		/* Stop Capture anyway */
		tempore = SSC_BIT(CR_RXDIS) |
							SSC_BIT(CR_TXDIS);
		ssc_writel( _ssc_dev->regs, CR, tempore);

		/* Disable ALL irqs to avoid troubles */
		tempore = SSC_BIT(IDR_RXSYN) |
							SSC_BIT(IDR_TXSYN) |
							SSC_BIT(IDR_CP1) |
							SSC_BIT(IDR_CP0) |
							SSC_BIT(IDR_OVRUN) |
							SSC_BIT(IDR_RXRDY) |
							SSC_BIT(IDR_TXEMPTY) |
							SSC_BIT(IDR_TXRDY);
		ssc_writel( _ssc_dev->regs, IDR, tempore);
	}
	spin_unlock( &_ssc_lock);
}

/** ----------------------------------------------------------------------
 * ssc irq handler routine
 */
static irqreturn_t ak5702_interrupt_ssc(int irq, void *dev_id)
{
	u32 status, imr, pending;
	int retval = IRQ_NONE;

	spin_lock( &_ssc_lock);
	{
		imr = ssc_readl( _ssc_dev->regs, IMR);
		status = ssc_readl( _ssc_dev->regs, SR);
		pending = status & imr;

		if( pending & SSC_BIT(IMR_OVRUN) )
		{
			//u32 trashedValue;

			_number_of_ssc_overrun_interrupts++;
			//trashedValue = ssc_readl( _ssc_dev->regs, RHR);

			retval = IRQ_HANDLED;
		}
	}
	spin_unlock( &_ssc_lock);

	return retval;
}

/** ----------------------------------------------------------------------
 *
 */
static bool ak5702_dma_channel_filter( struct dma_chan *chan, void *pdata)
{
// 	struct at_dma_chan *atDmaChannel;
// 	int                eitherSourceOrDestination;
//
// 	atDmaChannel = to_at_dma_chan( _tx_channel);
// 	eitherSourceOrDestination = *((int *) pdata);
//
// 	if( (atDmaChannel->dma_sconfig.src_addr == eitherSourceOrDestination) ||
// 			(atDmaChannel->dma_sconfig.dst_addr == eitherSourceOrDestination)
// 		)
// 	{
// 		printdbg( KERN_ALERT "ak5702_dma_channel_filter(): recognized usage of register %d\n", eitherSourceOrDestination);
// 		return true;
// 	}
//
// 	// who cares...I just need two channels from the first dma
// 	// and they are the chosen ones I could even ignore the device tree
// 	return true;
	struct at_dma_chan *atDmaChannel;
	int                sourceRegisterAddress;

	atDmaChannel = to_at_dma_chan( _rx_channel);
	sourceRegisterAddress = *((int *) pdata);

	if( atDmaChannel->dma_sconfig.src_addr == sourceRegisterAddress )
	{
		printdbg( KERN_ALERT "ak5702_dma_channel_filter(): recognized usage of source register %d\n", sourceRegisterAddress);
		return true;
	}

	return false;
}

/** ----------------------------------------------------------------------
 *
 */
static void free_module_resources(void)
{
	//dmac_dump_channel( _tx_channel);
	//dmac_dump_channel( _rx_channel);

	if( _ssc_irq_number >= 0 )
	{
		free_irq( _ssc_irq_number, 0);
		_ssc_irq_number = -1;
	}

	if( _ssc_dev != 0 )
	{
		ssc_free( _ssc_dev);
		_ssc_dev = 0;
	}

// 	if( _tx_channel != 0 )
// 	{
// 		dma_release_channel( _tx_channel);
// 		_tx_channel = 0;
// 	}

	if( _rx_channel != 0 )
	{
		dma_release_channel( _rx_channel);
		_rx_channel = 0;
	}

	printdbg(KERN_ALERT "ak5702: Free Buffer DMA\n");
	dma_free_coherent( NULL, AK5702_DMA_BUFFER_SIZE, _dma_buffer_pointer_for_driver, _dma_buffer_pointer_for_configurations);
}

/** ----------------------------------------------------------------------
 *
 */
static int alloc_module_resources(void)
{
	int            retval;
	dma_cap_mask_t mask;
// 	int            txRegister;
	int            rxRegister;

	/* Alloc dma Buffer */
// 	printdbg( KERN_ALERT "ak5702: allocating dma memory: %d bytes\n", AK5702_DMA_BUFFER_SIZE);
	_dma_buffer_pointer_for_driver = dma_alloc_coherent( NULL, AK5702_DMA_BUFFER_SIZE, & _dma_buffer_pointer_for_configurations, GFP_KERNEL);
	if( _dma_buffer_pointer_for_driver == NULL )
	{
		printdbg( KERN_ALERT "[!] ak5702 error: unable to allocate %d bytes for dma \n", AK5702_DMA_BUFFER_SIZE);
		return -ENOMEM;
	}

// 	printdbg(KERN_ALERT "ak5702: dma driver side pointer = 0x%08x; real memory pointer = 0x%08x\n", (u32)_dma_buffer_pointer_for_driver, (u32)_dma_buffer_pointer_for_configurations);

/* SSC */
	_ssc_dev = ssc_request( AK5702_SSC_ID_DEVICE);

	if( IS_ERR(_ssc_dev) )
	{
		printdbg( KERN_ALERT "[!] ak5702: could not get ssc%d device\n", AK5702_SSC_ID_DEVICE);
		free_module_resources();
		return PTR_ERR( _ssc_dev);
	}

/* dma channels from dmac */
	dma_cap_zero(mask);
	dma_cap_set(DMA_SLAVE, mask);
// 	txRegister = _ssc_dev->phybase + SSC_THR;
	rxRegister = _ssc_dev->phybase + SSC_RHR;

// 	_tx_channel = dma_request_slave_channel_compat( mask, ak5702_dma_channel_filter, & txRegister, & _ssc_dev->pdev->dev, "tx");
// 	if( _tx_channel == 0 )
// 	{
// 		dev_info( & _ssc_dev->pdev->dev, "ak5702: can't get a dma channel for tx\n");
// 		return -EBUSY;
// 	}
	_rx_channel = dma_request_slave_channel_compat( mask, ak5702_dma_channel_filter, & rxRegister, & _ssc_dev->pdev->dev, "rx");
	if( _rx_channel == 0 )
	{
		dev_info( & _ssc_dev->pdev->dev, "[!] ak5702: can't get a dma channel for rx\n");
		free_module_resources();
		return -EBUSY;
	}

// 	_at_tx_channel = to_at_dma_chan( _tx_channel);
// 	_at_rx_channel = to_at_dma_chan( _rx_channel);
	_at_dma        = to_at_dma( _rx_channel->device);

// 	dev_info( & _ssc_dev->pdev->dev, "ak5702: using %s (tx) for dma transfers; _tx_channel->chan_id: %d\n", dma_chan_name( _tx_channel), _tx_channel->chan_id);
	dev_info( & _ssc_dev->pdev->dev, "ak5702: using %s (rx) for dma transfers; _rx_channel->chan_id: %d\n", dma_chan_name( _rx_channel), _rx_channel->chan_id);

/* SSC irq registration */
	if( _ssc_dev->irq < 0 )
	{
		retval = _ssc_dev->irq;
		printdbg( KERN_ALERT "[!] ak5702: Unable to find irq %d\n", _ssc_dev->irq);
		free_module_resources();
		return retval;
	}

	retval = request_irq( _ssc_dev->irq, ak5702_interrupt_ssc, 0, "ak5702", 0);
	if( retval )
	{
		printdbg( KERN_ALERT "[!] ak5702: Unable to request irq %d\n", _ssc_dev->irq);
		free_module_resources();
		return retval;
	}
	_ssc_irq_number = _ssc_dev->irq;

	/* @patch clear the ssc status to avoid spurious data */
	ssc_writel( _ssc_dev->regs, CR, SSC_BIT( CR_SWRST));

	//dmac_dump_channel( _tx_channel);
	//dmac_dump_channel( _rx_channel);

	printdbg( KERN_ALERT "ak5702: registered to SSC irq %d\n", _ssc_dev->irq);

	return SUCCESS;
}

/** ----------------------------------------------------------------------
 *
 * Called when a process tries to open the device file, like
 * "cat /dev/mycharfile"
 */
static int ak5702_open_device_file( struct inode *inode, struct file *file)
{
	if( _is_device_open )
	{
		printdbg( KERN_ALERT "ak5702_open_device_file(): error\n");
		return -EBUSY;
	}
	_is_device_open++;
	printdbg( KERN_ALERT "ak5702_open_device_file() [_is_device_open: %d]\n", _is_device_open);
	try_module_get( THIS_MODULE);

	return SUCCESS;
}

/** ----------------------------------------------------------------------
 *
 * Called when a process closes the device file.
 */
static int ak5702_release( struct inode *inode, struct file *file)
{
	printdbg( KERN_ALERT "ak5702_release\n");

	ak5702_stop_ssc();
	ak5702_stop_dmac();

	_is_device_open--;
	module_put( THIS_MODULE);

	return 0;
}


/** ----------------------------------------------------------------------
 *
 * Called when a process, which already opened the dev file, attempts to
 * read from it.
 */
static ssize_t ak5702_read( struct file *filp, /* see include/linux/fs.h   */
														char *userBufferPointer,
														size_t userBufferSize,
														loff_t * offset
													)
{
	int  numberOfAvailableSplits;
	bool isOverrunning;
	DEFINE_WAIT(waiter);

	// The read function is in BLOCKING mode
	// check if we have data - if not, sleep
	// wake up in interrupt_handler

	isOverrunning = false;
	spin_lock( &_indexes_locker);
	{
		numberOfAvailableSplits = _number_of_available_splits;
	}
	spin_unlock( &_indexes_locker);

// 	printdbg( KERN_ALERT "ak5702: nsplits: %d; #dmac_calls: %d; #ssc_ovrrun: %d\n",
// 					 (int) numberOfAvailableSplits,
// 					 _number_of_dmac_callbacks,
// 					 _number_of_ssc_overrun_interrupts
// 					);

	if( numberOfAvailableSplits <= 0 )
	{
		// @patch for kernel 3.x
		//interruptible_sleep_on_timeout( &ak5702_wait, 1 * HZ);
		prepare_to_wait( & ak5702_wait, & waiter, TASK_INTERRUPTIBLE);
		schedule_timeout( (1 * HZ) / 2);
		finish_wait( & ak5702_wait, & waiter);

		spin_lock( &_indexes_locker);
		{
			numberOfAvailableSplits = _number_of_available_splits;
		}
		spin_unlock( &_indexes_locker);

		if( numberOfAvailableSplits <= 0 )
		{
			return 0;
		}
	}

	if( userBufferSize < AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT )
	{
		printdbg( KERN_ALERT "ak5702: the user buffer is too small, please use at least %d bytes\n", AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT);
		return -EFAULT;
	}

	if( copy_to_user( userBufferPointer, (char *)(_dma_buffer_pointer_for_driver) + (_split_index_to_read * AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT), AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT) )
	{
		printdbg( KERN_ALERT "ak5702: Copy to user error\n");
		return -EFAULT;
	}

	spin_lock( &_indexes_locker);
	{
		_number_of_available_splits--;
	}
	spin_unlock( &_indexes_locker);

	_split_index_to_read++;
	if( _split_index_to_read >= AK5702_NUMBER_OF_SPLITS_OF_DMA_BUFFER )
	{
		_split_index_to_read -= AK5702_NUMBER_OF_SPLITS_OF_DMA_BUFFER;
	}

	return AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT;
}

//////////////////////////////////////////////////////////////////////////
//
// ioctl
//
static long ak5702_ioctl( struct file *filp, u_int cmd, u_long arg)
{
	int retval = -1;

	switch( cmd )
	{
		case AK5702_IOCTL_STATUS:
		case AK5702_IOCTL_POWER_ON:
		case AK5702_IOCTL_POWER_OFF:
		case AK5702_IOCTL_INIT_SSC:
		{
			retval = -EFAULT;
			break;
		}
		case AK5702_IOCTL_START:
		{
			ak5702_start_dmac();
			ak5702_start_ssc();
			retval = SUCCESS;
			break;
		}
		case AK5702_IOCTL_STOP:
		{
			ak5702_stop_ssc();
			ak5702_stop_dmac();
			retval = SUCCESS;
			break;
		}
		default:
		{
			retval = -EINVAL;
			break;
		}
	}

	return retval;
}

//////////////////////////////////////////////////////////////////////////
//
static const struct file_operations fops = {
	.owner = THIS_MODULE,
	.read = ak5702_read,
	/*.write = device_write,*/
	.open = ak5702_open_device_file,
	.unlocked_ioctl = ak5702_ioctl,
	.release = ak5702_release
};

int ak5702_read_procmem( char *buf,
												 char **start,
												 off_t offset,
												 int count,
												 int *eof,
												 void *data
											)
{
	//int limit = count - 80;
	int len = 0;
	len += sprintf( buf + len, "\nak5702: driver information\n\n");

	if( _ssc_dev == 0 )
	{
		len += sprintf( buf + len, "WARNING!!! SSC device not found!!\n");
	}
	else
	{
		len += sprintf( buf + len, "\n-------------------\n");
		len += sprintf( buf + len, "Register SR=[0x%08X]\n", ssc_readl(_ssc_dev->regs, SR));
		len += sprintf( buf + len, "Register RFMR=[0x%08X]\n", ssc_readl(_ssc_dev->regs, RFMR));
		len += sprintf( buf + len, "Register RCMR=[0x%08X]\n", ssc_readl(_ssc_dev->regs, RCMR));
		len += sprintf( buf + len, "-------------------\n");
		//n_data = ssc_readl( _ssc_dev->regs, PDC_RPR) - _dma_buffer_device
	}

	*eof = 1;

	return len;
}

//////////////////////////////////////////////////////////////////////////
//
// This function is called when the module is loaded
//
static int __init ak5702_init(void)
{
	int retval;

	spin_lock_init( &_ssc_lock);
	spin_lock_init( &_indexes_locker);

	_major = register_chrdev( 0, AK5702_DEVICE_NAME, &fops);

	printdbg( KERN_ALERT "Registering chrdev ak5702: with major number %d\n", _major);

	if( _major < 0 )
	{
		printdbg( KERN_ALERT "Registering char device failed with %d\n", _major);
		return _major;
	}

	retval = alloc_module_resources();
	if( retval )
	{
		free_module_resources();
		unregister_chrdev( _major, AK5702_DEVICE_NAME);
		return retval;
	}

#if 0
	create_proc_read_entry( "ak5702", 0444, NULL, ak5702_read_procmem, NULL);
#endif

	return retval;
}

module_init( ak5702_init);

//////////////////////////////////////////////////////////////////////////
//
// This function is called when the module is unloaded
//
static void __exit ak5702_exit(void)
{
	/*
	 * Unregister the device
	 */
	printdbg(KERN_ALERT "Unregister chrdev AK5702\n");

	/* Forzo l'uscita dalla read */
	wake_up_interruptible( &ak5702_wait);

	free_module_resources();

#if 0
	remove_proc_entry( "ak5702", NULL);
#endif

	unregister_chrdev( _major, AK5702_DEVICE_NAME);
}

module_exit( ak5702_exit);

//////////////////////////////////////////////////////////////////////////
//
MODULE_DESCRIPTION( "Driver for ak5702: with Atmel SSC and DMAC data reads");
MODULE_AUTHOR( "Nemo <nemo@mail.com>");
MODULE_LICENSE( "GPL");
both this code and the header file are a bit limited by our case, but the ideas are all there...here is the header ak5702.h:

Code: Select all

/*
 * Driver for the AK5702 16-bit ADC Multichannel on Atmel
 */

#ifndef _SND_AK5702_H
#define _SND_AK5702_H

/* IOCTL */
#define AK5702_IOCTL_STATUS     1
#define AK5702_IOCTL_POWER_ON   2
#define AK5702_IOCTL_POWER_OFF  3
#define AK5702_IOCTL_INIT_SSC   4
#define AK5702_IOCTL_START      5
#define AK5702_IOCTL_STOP       6

/* constants */
#define AK5702_DEVICE_NAME                          "ak5702" // Dev name as it appears in /proc/devices
#define AK5702_SSC_ID_DEVICE                        0

#define AK5702_MAX_SAMPLE_FREQUENCY                 32000   // ak5702 could reach 48kHz

#define AK5702_SAMPLE_SIZE_IN_BYTES                 2       // 2 bytes
#define AK5702_NUMBER_OF_CHANNELS                   4       // 4 channels data trasmissino
#define AK5702_FRAME_SIZE_IN_BYTES                  8       // 8 btyes = 4 channels x 2 bytes incoming from the ak5702
#define AK5702_DMA_BUFFER_SIZE                      (32000 * 4 * 2)
#define AK5702_NUMBER_OF_SPLITS_OF_DMA_BUFFER       4

#define AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT (AK5702_DMA_BUFFER_SIZE / AK5702_NUMBER_OF_SPLITS_OF_DMA_BUFFER)

#endif /* _SND_AK5702_H */
at last here is a small sample of the user space code:

Code: Select all

int handle;
uint8_t data[ AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT];

system( "modprobe ak5702");
handle = open("/dev/ak5702", O_RDWR);
ioctl( handle, AK5702_IOCTL_START, NULL);
/* shame: AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT is the only size supported */
available = read( handle, data, AK5702_NUMBER_OF_BYTES_PER_DMA_BUFFER_SPLIT);
...
ioctl( handle, AK5702_IOCTL_STOP, NULL);
close( handle);
system( "modprobe -r ak5702");
bye
aretra4
Posts: 4
Joined: Mon Dec 15, 2014 1:46 am

Re: Non-ALSA SSC/DMA support?

Thu May 07, 2015 10:50 am

note that I had to add some very simple extra defines somewhere inside the included atmel headers of the kernel, plus the code is for 3.16.1 kernel.
Something changes in case of the 3.18.12: you just need to remove the last "0" argument to the call _at_dma->dma_common.device_prep_dma_cyclic

Return to “SAMA5D Cortex-A5 MPU”

Who is online

Users browsing this forum: No registered users and 2 guests