From: Ryan Mallon <rmallon@gmail.com>
To: Michail Kurachkin <michail.kurachkin@promwad.com>
Cc: "linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Kuten Ivan <Ivan.Kuten@promwad.com>,
"benavi@marvell.com" <benavi@marvell.com>,
Palstsiuk Viktar <Viktar.Palstsiuk@promwad.com>,
Dmitriy Gorokh <dmitriy.gorokh@promwad.com>,
Oliver Neukum <oneukum@suse.de>
Subject: Re: [PATCH v2 02/11] staging: Initial commit of Kirkwood TDM driver
Date: Sat, 02 Mar 2013 10:54:47 +1100 [thread overview]
Message-ID: <51313FC7.2010802@gmail.com> (raw)
In-Reply-To: <b621699e601f0db529226913bad132d7704a0b31.1362133319.git.michail.kurachkin@promwad.com>
On 01/03/13 21:52, Michail Kurachkin wrote:
> From: Michail Kurochkin <michail.kurachkin@promwad.com>
>
> Signed-off-by: Michail Kurochkin <michail.kurachkin@promwad.com>
> ---
> drivers/staging/tdm/kirkwood_tdm.c | 932 ++++++++++++++++++++++++++++++++++++
> drivers/staging/tdm/kirkwood_tdm.h | 110 +++++
> 2 files changed, 1042 insertions(+), 0 deletions(-)
> create mode 100644 drivers/staging/tdm/kirkwood_tdm.c
> create mode 100644 drivers/staging/tdm/kirkwood_tdm.h
>
> diff --git a/drivers/staging/tdm/kirkwood_tdm.c b/drivers/staging/tdm/kirkwood_tdm.c
> new file mode 100644
> index 0000000..4366d38
> --- /dev/null
> +++ b/drivers/staging/tdm/kirkwood_tdm.c
> @@ -0,0 +1,932 @@
> +/**********************************************************************
> + * Author: Michail Kurachkin
> + *
> + * Contact: michail.kurachkin@promwad.com
> + *
> + * Copyright (c) 2013 Promwad Inc.
> + *
> + * This file is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License, Version 2, as
> + * published by the Free Software Foundation.
> + *
> + * This file is distributed in the hope that it will be useful, but
> + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty
> + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
> + * NONINFRINGEMENT. See the GNU General Public License for more
> + * details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this file; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
> + * or visit http://www.gnu.org/licenses/.
> +**********************************************************************/
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/sched.h>
> +#include <linux/interrupt.h>
> +#include <linux/types.h>
> +#include <linux/gpio.h>
> +#include <linux/init.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/in.h>
> +#include <linux/ip.h>
> +#include <linux/tcp.h>
> +#include <linux/udp.h>
> +#include <linux/etherdevice.h>
> +#include <linux/delay.h>
> +#include <linux/ethtool.h>
> +#include <linux/platform_device.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +#include <linux/phy.h>
> +#include <linux/io.h>
> +#include <linux/types.h>
> +#include <linux/inet_lro.h>
> +#include <linux/slab.h>
> +#include <asm/system.h>
This file is marked on arm as to be deleted.
> +#include <linux/io.h>
> +#include <asm/uaccess.h>
Use <linux/uaccess.h>
> +#include <asm/mach-types.h>
> +#include <asm/mach/arch.h>
Drivers should really avoid including <asm/mach-types.h> if they
can avoid it.
> +#include <linux/mbus.h>
> +#include <linux/miscdevice.h>
> +#include <linux/platform_device.h>
> +
> +#include "tdm.h"
> +#include "kirkwood_tdm.h"
> +
> +
> +/**
> + * Init hardware tdm controller
> + * @param tdm - tdm_controller descriptor
> + * @return
> + */
> +static int kirkwood_tdm_hw_init(struct tdm_controller *tdm)
> +{
> + struct kirkwood_tdm *onchip_tdm =
> + (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
Don't need the cast.
> + struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> + struct tdm_controller_hw_settings *hw = tdm->settings;
> + u32 control_val = 0;
> + u32 pclk_freq;
> + unsigned long flags;
> + spinlock_t lock;
> + spin_lock_init(&lock);
What use is a local spin lock? Any concurrent callers will be
using different locks. If you just need to disable interrupts
you can use local_irq_save and local_irq_restore (or
local_irq_disable/enable if you know that interrupts are
already enabled).
> +
> +
> + // Errata GL-CODEC-10: resolve problem witch FS
> + spin_lock_irqsave(&lock, flags);
> + writel(0x03ffff00, ®s->pcm_ctrl_reg + 0x1000);
> + writel(5, ®s->pcm_ctrl_reg + 0x101C);
> + spin_unlock_irqrestore(&lock, flags);
> +
> +
> + writel(0, ®s->int_reset_sel);
> + writel(0x3ffff, ®s->int_event_mask);
> + writel(0, ®s->int_status_mask);
> + writel(0, ®s->int_status);
> +
> + writeb(0x41, ®s->pcm_ctrl_reg + 0xC41);
> + writeb(0x10, ®s->pcm_ctrl_reg + 0xC40);
> +
> + // Configuring PCLK and FS frequency
> + pclk_freq = hw->count_time_slots * 8 * hw->fs_freq;
> + switch(pclk_freq) {
> + case 256:
> + writel(0x1, ®s->tdm_pcm_clock_div);
> + writel(0x0, ®s->dummy_rx_write);
> + writel(0x4, ®s->num_time_slot);
> + break;
> +
> + case 512:
> + writel(0x2, ®s->tdm_pcm_clock_div);
> + writel(0x0, ®s->dummy_rx_write);
> + writel(0x8, ®s->num_time_slot);
> + break;
> +
> + case 1024:
> + writel(0x4, ®s->tdm_pcm_clock_div);
> + writel(0x0, ®s->dummy_rx_write);
> + writel(0x10, ®s->num_time_slot);
> + break;
> +
> + case 2048:
> + writel(0x8, ®s->tdm_pcm_clock_div);
> + writel(0x0, ®s->dummy_rx_write);
> + writel(0x20, ®s->num_time_slot);
> + break;
> +
> + case 4096:
> + writel(0x10, ®s->tdm_pcm_clock_div);
> + writel(0x0, ®s->dummy_rx_write);
> + writel(0x40, ®s->num_time_slot);
> + break;
> +
> + case 8192:
> + writel(0x20, ®s->tdm_pcm_clock_div);
> + writel(0x0, ®s->dummy_rx_write);
> + writel(0x80, ®s->num_time_slot);
> + break;
> +
> + default:
> + dev_err(&tdm->dev, "Incorrect count of time slots parameter\n");
> + return -EINVAL;
> + }
Same #defines for all the magic values above would be good.
Same elsewhere, there seem to be a lot of magic values in this
file.
> +
> + control_val = readl(®s->pcm_ctrl_reg);
> +
> + if(hw->clock_direction == TDM_CLOCK_OUTPUT)
> + control_val &= (u32)~(1 << 0);
You don't need the cast here. Same elsewhere.
> + else
> + control_val |= (1 << 0);
> +
> +
> + if(hw->fs_clock_direction == TDM_CLOCK_OUTPUT)
> + control_val &= (u32) ~(1 << 1);
> + else
> + control_val |= (1 << 1);
> +
> +
> + if(hw->fs_polarity == TDM_POLAR_POSITIV)
> + control_val &= (u32) ~(1 << 4);
> + else
> + control_val |= (1 << 4);
> +
> +
> + if(hw->data_polarity == TDM_POLAR_POSITIV)
> + control_val &= (u32) ~(1 << 2);
> + else
> + control_val |= (1 << 2);
> +
> +
> + if(hw->channel_size == 1)
> + control_val &= (u32) ~(1 << 6);
> + else
> + control_val |= (1 << 6);
> +
> + writel(control_val, ®s->pcm_ctrl_reg);
> + writel(0, ®s->chan_time_slot_ctrl);
> + writel(0, ®s->chan0_enable_disable);
> + writel(0, ®s->chan0_enable_disable + 4);
> +
> + return 0;
> +}
> +
> +/**
> + * deInitialization hardware tdm controller
> + * @param tdm - tdm_controller descriptor
> + */
> +static void kirkwood_tdm_hw_deinit(struct tdm_controller *tdm)
> +{
> + struct kirkwood_tdm *onchip_tdm =
> + (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
> + struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> +
> + writel(0, ®s->pcm_ctrl_reg);
> + writel(0, ®s->chan_time_slot_ctrl);
> +}
> +
> +/**
> + * Setup hardware voice channel
> + * @param ch - voice hardware channel
> + * @return 0 - OK
> + */
> +int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
static?
> +{
> + struct tdm_device *tdm_dev = to_tdm_device(ch->dev);
> + struct tdm_controller *tdm = tdm_dev->controller;
> + struct tdm_controller_hw_settings *hw = tdm->settings;
> + struct kirkwood_tdm *onchip_tdm =
> + (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
> + struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> + struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> + unsigned long timeout;
> + u32 state;
> + int i;
> +
> + u8 voice_num = ch->channel_num;
> +
> + /* calculate buffer size */
> + ch->buffer_len = tdm_dev->buffer_sample_count * hw->channel_size;
> +
> + /* Request timeslot for voice channel */
> + writeb(ch->tdm_channel, (u8*)®s->chan_time_slot_ctrl + 2 * voice_num);
> + writeb(ch->tdm_channel, (u8*)®s->chan_time_slot_ctrl + 1 + 2 * voice_num);
> +
> + /* FIXME: move coherent_dma_mask to board specific */
> + tdm_dev->dev.coherent_dma_mask = 0xffffffff;
> +
> + /* Allocate rx and tx buffers */
> + for(i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++) {
> + /* allocate memory for DMA receiver */
> + onchip_ch->rx_buf[i] =
> + dma_alloc_coherent(&tdm_dev->dev,
> + ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_DMA);
> +
> + if (onchip_ch->rx_buf == NULL) {
> + dev_err(ch->dev, "Can't allocate memory for TDM receiver DMA buffer\n");
> + return -ENOMEM;
> + }
> + memset(onchip_ch->rx_buf[i], 0, ch->buffer_len);
> +
> +
> + /* allocate memory for DMA transmitter */
> + onchip_ch->tx_buf[i] =
> + dma_alloc_coherent(&tdm_dev->dev,
> + ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_DMA);
> +
> + if (onchip_ch->tx_buf == NULL) {
> + dev_err(ch->dev, "Can't allocate memory for TDM transmitter DMA buffer\n");
> + return -ENOMEM;
> + }
> + memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
> + }
> +
> + atomic_set(&onchip_ch->write_rx_buf_num, 0);
> + atomic_set(&onchip_ch->write_tx_buf_num, 0);
> + atomic_set(&onchip_ch->read_rx_buf_num, 0);
> + atomic_set(&onchip_ch->read_tx_buf_num, 0);
> +
> + /* Set length for DMA */
> + writel(((tdm_dev->buffer_sample_count - 32) << 8) | tdm_dev->buffer_sample_count,
> + ®s->chan0_total_sample + voice_num);
> +
> + /* Waiting for program transmit DMA */
> + timeout = jiffies + HZ;
> + do {
> + state = readl(®s->chan0_buff_ownership + 4 * voice_num) & 0x100;
> + if(time_after(jiffies, timeout)) {
> + dev_err(ch->dev, "Can`t program DMA tx buffer\n");
> + return -EBUSY;
> + }
> + } while(state);
> +
> + /* Set DMA buffers fo transmitter */
> + writel((u32)onchip_ch->tx_buff_phy[0],
> + ®s->chan0_transmit_start_addr + 4 * voice_num);
> + writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * voice_num) + 1);
> +
> + /* Waiting for program received DMA */
> + timeout = jiffies + HZ;
> + do {
> + state = readl(®s->chan0_buff_ownership + 4 * voice_num) & 1;
> + if(time_after(jiffies, timeout)) {
> + dev_err(ch->dev, "Can`t program DMA rx buffer\n");
> + return -EBUSY;
> + }
> + } while(state);
Can this be wrapped up as a helper function somehow so it doesn't
need to be open coded everywhere?
> +
> + /* Set DMA buffers for receiver */
> + writel((u32)onchip_ch->rx_buff_phy[0],
> + ®s->chan0_receive_start_addr + 4 * voice_num);
> + writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * voice_num) );
> +
> + return 0;
> +}
> +
> +
> +/**
> + * Run tdm transmitter and receiver
> + * @param tdm_dev - tdm device
> + * @return 0 - ok
> + */
> +int kirkwood_tdm_run_audio(struct tdm_device *tdm_dev)
> +{
> + struct tdm_controller *tdm = tdm_dev->controller;
> + struct kirkwood_tdm *onchip_tdm =
> + (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
> + struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> + struct tdm_voice_channel *ch = tdm_dev->ch;
> + struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +
> + memset(onchip_ch->tx_buf[0], 0, ch->buffer_len);
> + memset(onchip_ch->tx_buf[1], 0, ch->buffer_len);
> +
> + writeb(0x1, (u8 *)(®s->chan0_enable_disable + 4 * ch->channel_num) + 1);
> + writeb(0x1, (u8 *)(®s->chan0_enable_disable + 4 * ch->channel_num) );
> +
> + /* enable Tx interrupts */
> + writel(0, ®s->int_status);
> + writel((readl(®s->int_status_mask) | TDM_INT_TX(ch->channel_num)),
> + ®s->int_status_mask);
> +
> + /* enable Rx interrupts */
> + writel(0, ®s->int_status);
> + writel((readl(®s->int_status_mask) | TDM_INT_RX(ch->channel_num)),
> + ®s->int_status_mask);
> +
> + writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * ch->channel_num));
> + writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * ch->channel_num) + 1);
> +
> + return 0;
> +}
> +
> +
> +/**
> + * Stop tdm transmitter and receiver
> + * @param tdm_dev - tdm device
> + * @return 0 - ok
> + */
> +int kirkwood_tdm_stop_audio(struct tdm_device *tdm_dev)
> +{
> + struct tdm_controller *tdm = tdm_dev->controller;
> + struct kirkwood_tdm *onchip_tdm =
> + (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
> + struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> + struct tdm_voice_channel *ch = tdm_dev->ch;
> +
> + /* disable Tx interrupts */
> + writel((readl(®s->int_status_mask) & (~TDM_INT_TX(ch->channel_num))),
> + ®s->int_status_mask);
> + writel(0, ®s->int_status);
> +
> + /* disable Rx interrupts */
> + writel((readl(®s->int_status_mask) & (~TDM_INT_RX(ch->channel_num))),
> + ®s->int_status_mask);
> + writel(0, ®s->int_status);
> +
> + writeb(0x0, (u8 *)(®s->chan0_enable_disable + 4 * ch->channel_num) + 1);
> + writeb(0x0, (u8 *)(®s->chan0_enable_disable + 4 * ch->channel_num) );
This gets done a lot. Maybe have a function to get a channel register,
which takes the base channel register and the channel number, rather
than repeating the calculation everywhere.
> + return 0;
> +}
> +
> +
> +/**
> + * Get DMA tx buffers latency
> + * @param onchip_ch - kirkwood based voice channel
> + * @return latency in buffers
> + */
> +int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
> +{
> + if (atomic_read(&onchip_ch->read_tx_buf_num) <= atomic_read(&onchip_ch->write_tx_buf_num))
> + return atomic_read(&onchip_ch->write_tx_buf_num) - atomic_read(&onchip_ch->read_tx_buf_num);
The values of read/write_tx_buf_num change potentially change between
doing the if check, and returning the value. they can also change
between the individual atomic reads. Is that all safe? I think maybe
you want to replace all of the atomics with a mutex/spinlock which
protects these variables.
> + else
> + return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_tx_buf_num)
> + + atomic_read(&onchip_ch->write_tx_buf_num);
> +}
> +
> +
> +/**
> + * Get DMA rx buffers latency
> + * @param onchip_ch - kirkwood based voice channel
> + * @return latency in buffers
> + */
> +int get_rx_latency(struct kirkwood_tdm_voice *onchip_ch)
> +{
> + if (atomic_read(&onchip_ch->read_rx_buf_num) <= atomic_read(&onchip_ch->write_rx_buf_num))
> + return atomic_read(&onchip_ch->write_rx_buf_num) - atomic_read(&onchip_ch->read_rx_buf_num);
> + else
> + return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_rx_buf_num)
> + + atomic_read(&onchip_ch->write_rx_buf_num);
> +}
> +
> +
> +/**
> + * Check rx audio buffer for exist new data
> + * @param tdm_dev - tdm device registered on TDM bus
> + * @return 0 - not enought data, 1 - data exist
> + */
> +int kirkwood_poll_rx(struct tdm_device *tdm_dev)
> +{
> + struct tdm_voice_channel *ch = tdm_dev->ch;
> + struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +
> + return get_rx_latency(onchip_ch) > 1;
Again, the values of the atomics may have changed since you called
get_rx_latency, so the return value here could be wrong.
> +}
> +
> +
> +/**
> + * Check tx audio buffer for free space
> + * @param tdm_dev - tdm device registered on TDM bus
> + * @return 0 - not enought free space, 1 - exist free space
> + */
> +int kirkwood_poll_tx(struct tdm_device *tdm_dev)
> +{
> + struct tdm_voice_channel *ch = tdm_dev->ch;
> + struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +
> + return get_tx_latency(onchip_ch) > 1;
> +}
> +
> +
> +
> +/**
> + * Get next dma buffer number
> + * @param num - current buffer number
> + * @return next buffer number
> + */
> +static int inc_next_dma_buf_num(int num)
> +{
> + num ++;
> + if (num >= COUNT_DMA_BUFFERS_PER_CHANNEL)
> + num = 0;
> +
> + return num;
> +}
> +
> +
> +
> +/**
> + * Send voice data block to tdm voice channel controller.
> + * @param ch - voice channel attendant to transmit data in TDM frame
> + * @param data - data to be transmit. Length of data must be equal to
> + * value returned by get_voice_block_size()
> + *
> + * Context: can sleep
> + * @return 0 on success; negative errno on failure
> + */
> +static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
> +{
> + struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> + int rc = 0;
> +
> + rc = wait_event_interruptible(ch->tx_queue,
> + get_tx_latency(onchip_ch) > 1);
> +
> + if (rc)
> + return rc;
> +
> + memcpy(onchip_ch->tx_buf[atomic_read(&onchip_ch->read_tx_buf_num)], data,
> + ch->buffer_len);
> + atomic_set(&onchip_ch->read_tx_buf_num,
> + inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
> + return rc;
> +}
> +
> +
> +/**
> + * Receive voice data block from TDM voice channel controller.
> + * @param ch - voice channel attendant to transmit data in TDM frame
> + * @param data - pointer to read data received by DMA.
> + Length data for read equal to value returned by get_tdm_voice_block_size()
> + *
> + * Context: can sleep
> + * @return 0 on success; negative errno on failure
> + */
> +static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
> +{
> + struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> + int rc = 0;
> +
> + rc = wait_event_interruptible(ch->rx_queue,
> + get_rx_latency(onchip_ch) > 1);
> +
> + if (rc)
> + return rc;
> +
> + memcpy(data, onchip_ch->rx_buf[atomic_read(&onchip_ch->read_rx_buf_num)],
> + ch->buffer_len);
> + atomic_set(&onchip_ch->read_rx_buf_num,
> + inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
> + return rc;
> +}
> +
> +
> +
> +/**
> + * kirkwood_tdm_irq - IRQ handler for Kirkwood TDM
> + * @irq: IRQ number for this TDM controller
> + * @context_data: structure for TDM controller kirkwood_tdm
> + * Context: can not sleep
> + */
> +static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
int irq.
> +{
> + struct kirkwood_tdm *onchip_tdm = context_data;
> + struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> + struct tdm_controller *tdm = to_tdm_controller(onchip_tdm->controller_dev);
> + struct tdm_voice_channel *ch;
> + struct kirkwood_tdm_voice *onchip_ch;
> + struct tdm_device *tdm_dev;
> +
> + irqreturn_t ret = IRQ_NONE;
> + u32 status;
> + u8 i;
> +
> + int voice_num; /* current voice channel */
> + int next_buf_num; /* number of next buffer */
> + int mode; /* irq event mode: */
> + int overflow = 0;
> + int full = 0;
> +
> + enum irq_event_mode {
> + IRQ_RECEIVE,
> + IRQ_TRANSMIT,
> + };
> +
> + status = readl(®s->int_status);
> +
> + if ((status & 0xFF) == 0)
> + return ret;
> +
> + /* Check first 8 bit in status mask register for detect event type */
> + for(i = 0; i < 8; i++) {
> + if((status & (1 << i)) == 0)
> + continue;
> +
> + writel(status & ~(1 << i), ®s->int_status);
> +
> + switch(i) {
> + case 0:
> + mode = IRQ_RECEIVE;
> + voice_num = 0;
> + overflow = 1;
> + break;
> +
> + case 1:
> + mode = IRQ_TRANSMIT;
> + voice_num = 0;
> + overflow = 1;
> + break;
> +
> + case 2:
> + mode = IRQ_RECEIVE;
> + voice_num = 1;
> + overflow = 1;
> + break;
> +
> + case 3:
> + mode = IRQ_TRANSMIT;
> + voice_num = 1;
> + overflow = 1;
> + break;
> +
> + case 4:
> + mode = IRQ_RECEIVE;
> + voice_num = 0;
> + overflow = 0;
> + full = 0;
> + break;
> +
> + case 5:
> + mode = IRQ_TRANSMIT;
> + voice_num = 0;
> + overflow = 0;
> + full = 0;
> + break;
> +
> + case 6:
> + mode = IRQ_RECEIVE;
> + voice_num = 1;
> + overflow = 0;
> + full = 0;
> + break;
> +
> + case 7:
> + mode = IRQ_TRANSMIT;
> + voice_num = 1;
> + overflow = 0;
> + full = 0;
> + break;
> + }
> +
> + /* �urrent voice channel struct */
> + ch = get_voice_channel_by_num(tdm, voice_num);
> + onchip_ch = ch->private_data;
> +
> + /* TDM device attached to current voice channel */
> + tdm_dev = to_tdm_device(ch->dev);
> +
> + switch(mode) {
> + case IRQ_RECEIVE: {
> + /* get next buffer number, and move write/read pointer */
> + next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_rx_buf_num));
> + atomic_set(&onchip_ch->write_rx_buf_num, next_buf_num);
> + if(next_buf_num == atomic_read(&onchip_ch->read_rx_buf_num))
> + atomic_set(&onchip_ch->read_rx_buf_num,
> + inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
> +
> + /* if receive overflow event */
> + if (overflow) {
> + /* set next buffer address */
> + writel((u32)onchip_ch->rx_buff_phy[next_buf_num],
> + ®s->chan0_receive_start_addr + 4 * voice_num);
> + writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * voice_num));
> +
> + /* enable receiver */
> + writeb(0x1, (u8 *)(®s->chan0_enable_disable + 4 * voice_num));
> +
> + ret = IRQ_HANDLED;
> + break;
> + }
> +
> + /* waiting while dma providing access to buffer */
> + while(readl(®s->chan0_buff_ownership + 4 * voice_num) & 1);
> +
> + /* set next buffer address */
> + writel((u32)onchip_ch->rx_buff_phy[next_buf_num],
> + ®s->chan0_receive_start_addr + 4 * voice_num);
> + writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * voice_num));
> +
> + wake_up_interruptible(&ch->rx_queue);
> +
> + ret = IRQ_HANDLED;
> + }
> + break;
> +
> + case IRQ_TRANSMIT: {
> + /* get next buffer number, and move write/read pointer */
> + next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_tx_buf_num));
> + atomic_set(&onchip_ch->write_tx_buf_num, next_buf_num);
> + if(next_buf_num == atomic_read(&onchip_ch->read_tx_buf_num))
> + atomic_set(&onchip_ch->read_tx_buf_num,
> + inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
> +
> + /* if transmit overflow event */
> + if (overflow) {
> + /* set next buffer address */
> + writel((u32)onchip_ch->tx_buff_phy[next_buf_num],
> + ®s->chan0_transmit_start_addr + 4 * voice_num);
> + writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * voice_num) + 1);
> +
> + /* enable transmitter */
> + writeb(0x1, (u8 *)(®s->chan0_enable_disable + 4 * voice_num) + 1);
> +
> + ret = IRQ_HANDLED;
> + break;
> + }
> +
> + /* waiting while dma providing access to buffer */
> + while(readl(®s->chan0_buff_ownership + 4 * voice_num) & 0x100);
> +
> + /* set next buffer address */
> + writel((u32)onchip_ch->tx_buff_phy[next_buf_num],
> + ®s->chan0_transmit_start_addr + 4 * voice_num);
> + writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * voice_num) + 1);
> +
> + wake_up_interruptible(&ch->tx_queue);
> +
> + ret = IRQ_HANDLED;
> + }
> + break;
> + }
> + }
> +
> + return ret;
> +}
> +
> +
> +/**
> + * Configuring mbus windows for correct access to DMA memory
> + * @param base_regs - base address for tdm registers
> + * @param dram - dram settings
> + */
> +static void kirkwood_tdm_mbus_windows(void *base_regs,
> + struct mbus_dram_target_info *dram)
> +{
> + int i;
> +
> + for (i = 0; i < 4; i++) {
> + writel(0, (u8 *)base_regs + TDM_WINDOW_CTRL(i));
> + writel(0, (u8 *)base_regs + TDM_WINDOW_BASE(i));
> + }
> +
> + for (i = 0; i < dram->num_cs; i++) {
> + struct mbus_dram_window *cs = dram->cs + i;
> +
> + writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) |
> + (dram->mbus_dram_target_id << 4) | 1,
> + (u8 *)base_regs + TDM_WINDOW_CTRL(i));
> +
> + writel(cs->base, (u8 *)base_regs + TDM_WINDOW_BASE(i));
> + }
> +}
> +
> +
> +
> +static int __init kirkwood_tdm_probe(struct platform_device *pdev)
probe functions should not be marked __init.
> +{
> + int err = 0;
> + struct tdm_controller *tdm;
> + struct kirkwood_tdm *onchip_tdm = NULL;
> + struct resource *res;
> + struct tdm_controller_hw_settings *hw = pdev->dev.platform_data;
> + struct tdm_voice_channel *ch;
> + int i;
> +
> + tdm = tdm_alloc_controller(&pdev->dev, sizeof(struct kirkwood_tdm));
> + if (tdm == NULL) {
> + dev_err(&pdev->dev, "Can`t alloc memory\n");
Don't need the error message, kalloc failures give a stack trace.
> + err = -ENOMEM;
> + goto out0;
> + }
> +
> + platform_set_drvdata(pdev, tdm);
> + onchip_tdm = tdm_controller_get_devdata(tdm);
> +
> + if (pdev->id != -1)
> + tdm->bus_num = pdev->id;
> +
> + /* Check hardware settings */
> + if (hw->fs_freq != 8 && hw->fs_freq != 16) {
> + dev_err(&pdev->dev, "Fs frequency may be 8kHz o 16kHz. "
> + "Frequency %d is not incorrect\n", hw->fs_freq);
> + err = -EINVAL;
> + goto out1;
> + }
> +
> + if (hw->count_time_slots > 64) {
> + dev_err(&pdev->dev, "Incorrect count time slots. "
> + "No more than 64 timeslots per one FS. "
> + "Current set %d\n", hw->count_time_slots);
Don't split string over multiple lines, they are an exception to the
80 column rule for easy grepability.
> + err = -EINVAL;
> + goto out1;
> + }
> +
> + if (hw->channel_size > 2 || hw->channel_size == 0) {
> + dev_err(&pdev->dev, "Incorrect count time slots. "
> + "No more than 64 timeslots per one FS. "
> + "Current set %d\n", hw->count_time_slots);
Error message looks wrong.
> + err = -EINVAL;
> + goto out1;
> + }
> +
> + if (hw->clock_direction != TDM_CLOCK_INPUT &&
> + hw->clock_direction != TDM_CLOCK_OUTPUT) {
> + dev_err(&pdev->dev, "Incorrect PCLK clock direction value\n");
> + err = -EINVAL;
> + goto out1;
> + }
Using a bool for this value, as suggested in the other patch would
remove the need for this check.
> +
> + if (hw->fs_clock_direction != TDM_CLOCK_INPUT &&
> + hw->fs_clock_direction != TDM_CLOCK_OUTPUT) {
> + dev_err(&pdev->dev, "Incorrect FS clock direction value\n");
> + err = -EINVAL;
> + goto out1;
> + }
> +
> + if (hw->fs_polarity != TDM_POLAR_NEGATIVE &&
> + hw->fs_polarity != TDM_POLAR_POSITIV) {
> + dev_err(&pdev->dev, "Incorrect FS polarity value\n");
> + err = -EINVAL;
> + goto out1;
> + }
> +
> + if (hw->data_polarity != TDM_POLAR_NEGATIVE &&
> + hw->data_polarity != TDM_POLAR_POSITIV) {
> + dev_err(&pdev->dev, "Incorrect data polarity value\n");
> + err = -EINVAL;
> + goto out1;
> + }
> +
> + /* Set controller data */
> + tdm->bus_num = pdev->id;
> + tdm->settings = hw;
> + tdm->setup_voice_channel = kirkwood_setup_voice_channel;
> + tdm->recv = kirkwood_recv;
> + tdm->send = kirkwood_send;
> + tdm->run_audio = kirkwood_tdm_run_audio;
> + tdm->stop_audio = kirkwood_tdm_stop_audio;
> + tdm->poll_rx = kirkwood_poll_rx;
> + tdm->poll_tx = kirkwood_poll_tx;
> +
> + for (i = 0; i < KIRKWOOD_MAX_VOICE_CHANNELS; i++)
> + {
> + ch = tdm_alloc_voice_channel();
> + if (ch == NULL) {
> + dev_err(&pdev->dev, "Can`t alloc voice channel %d\n", i);
> + goto out1;
> + }
If you get partway through this loop before failing, then you
will leak a bunch of voice channels.
> +
> + tdm_register_new_voice_channel(tdm, ch, (void *)(onchip_tdm->voice_channels + i));
> + }
> +
> +
> + /* Get resources(memory, IRQ) associated with the device */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (res == NULL) {
> + dev_err(&pdev->dev, "Can`t request registers memory\n");
> + err = -ENODEV;
> + goto out2;
> + }
> +
> + onchip_tdm->regs = ioremap(res->start, resource_size(res));
> + if (!onchip_tdm->regs) {
> + dev_err(&pdev->dev, "Incorrect setup registers memory area\n");
> + err = -EINVAL;
> + goto out2;
> + }
> +
> + /* If need remap mbus windows */
> + if (hw->dram != NULL)
> + kirkwood_tdm_mbus_windows(onchip_tdm->regs, hw->dram);
> +
> + /* Get IRQ number for all controllers events */
> + onchip_tdm->irq = platform_get_irq(pdev, 0);
> + if (onchip_tdm->irq < 0) {
> + dev_err(&pdev->dev, "Can`t request IRQ\n");
> + err = onchip_tdm->irq;
> + goto out3;
> + }
> +
> + /* Set interrupt callback kirkwood_tdm_irq */
> + err = request_irq(onchip_tdm->irq, kirkwood_tdm_irq, IRQF_DISABLED,
> + dev_name(&pdev->dev), onchip_tdm);
> + if (err) {
> + dev_err(&pdev->dev, "Can`t setup IRQ callback\n");
> + err = -EINVAL;
> + goto out4;
> + }
At this point you can now receive interrupts, but you do
kirkwood_tdm_hw_init below. Is it safe to get interrupted at this
point?
> +
> + onchip_tdm->controller_dev = &tdm->dev;
> +
> + /* Initialization TDM controller */
> + err = kirkwood_tdm_hw_init(tdm);
> + if (err < 0) {
> + dev_err(&pdev->dev, "Can't initialization tdm controller, %d\n",
> + err);
> + goto out4;
> + }
> +
> + err = tdm_controller_register(tdm);
> + if(err) {
> + dev_err(&pdev->dev, "cannot register tdm controller, %d\n", err);
> + goto out5;
> + }
> +
> + printk("Init ok");
Remove.
> +
> + dev_dbg(&tdm->dev, "tdm controller registred sucessfully\n");
> + return 0;
> +
> +
> +out5:
> + kirkwood_tdm_hw_deinit(tdm);
> +
> +out4:
> + free_irq(onchip_tdm->irq, onchip_tdm);
> +
> +out3:
> + if (onchip_tdm->regs)
> + iounmap(onchip_tdm->regs);
> +
> +out2:
> + tdm_free_voice_channels(tdm);
> +
> +out1:
> + tdm_free_controller(tdm);
> +
> +out0:
> + return err;
> +}
> +
> +
> +static int __exit kirkwood_tdm_remove(struct platform_device *pdev)
Don't mark as __exit.
> +{
> + struct tdm_controller *tdm = NULL;
> + struct kirkwood_tdm *onchip_tdm = NULL;
> +
> + tdm = platform_get_drvdata(pdev);
> + if (tdm == NULL)
> + goto out0;
> +
> + onchip_tdm = tdm_controller_get_devdata(tdm);
> +
> + kirkwood_tdm_hw_deinit(tdm);
> + free_irq(onchip_tdm->irq, onchip_tdm);
> +
> + tdm_free_voice_channels(tdm);
> + tdm_controller_unregister(tdm);
> + if (onchip_tdm->regs)
> + iounmap(onchip_tdm->regs);
> +
> + tdm_free_controller(tdm);
> +out0:
> + return 0;
> +}
> +
> +
> +static struct platform_driver kirkwood_tdm_driver = {
> + .probe = kirkwood_tdm_probe,
> + .remove = __exit_p(kirkwood_tdm_remove),
> + .driver = {
> + .name = "kirkwood_tdm",
> + .owner = THIS_MODULE,
> + },
> + .suspend = NULL,
> + .resume = NULL,
> +};
> +
> +
> +static int __init kirkwood_init_tdm(void)
> +{
> + return platform_driver_register(&kirkwood_tdm_driver);
> +}
> +
> +static void __exit kirkwood_exit_tdm(void)
> +{
> + platform_driver_unregister(&kirkwood_tdm_driver);
> +}
> +
> +module_init(kirkwood_init_tdm);
> +module_exit(kirkwood_exit_tdm);
> +MODULE_AUTHOR("Michail Kurochkin <stelhs@yandex.ru>");
> +MODULE_DESCRIPTION("TDM controller driver for Marvel kirkwood arch.");
> +MODULE_LICENSE("GPL");
> +
> +
> diff --git a/drivers/staging/tdm/kirkwood_tdm.h b/drivers/staging/tdm/kirkwood_tdm.h
> new file mode 100644
> index 0000000..6c7f34d
> --- /dev/null
> +++ b/drivers/staging/tdm/kirkwood_tdm.h
> @@ -0,0 +1,110 @@
> +/*
> + * kirkwood_tdm.h
> + *
> + * Created on: 25.01.2012
> + * Author: Michail Kurochkin
> + */
> +
> +#ifndef KIRKWOOD_TDM_H_
> +#define KIRKWOOD_TDM_H_
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +
> +struct kirkwood_tdm_regs {
> + u32 pcm_ctrl_reg; /* 0x0 PCM control register */
> + u32 chan_time_slot_ctrl; /* 0x4 Channel Time Slot Control Register */
> + u32 chan0_delay_ctrl; /* 0x8 Channel 0 Delay Control Register */
> + u32 chan1_delay_ctrl; /* 0xC Channel 1 Delay Control Register */
> + u32 chan0_enable_disable; /* 0x10 Channel 0 Enable and Disable Register */
> + u32 chan0_buff_ownership; /* 0x14 Channel 0 Buffer Ownership Register */
> + u32 chan0_transmit_start_addr; /* 0x18 Channel 0 Transmit Data Start Address Register */
> + u32 chan0_receive_start_addr; /* 0x1C Channel 0 Receive Data Start Address Register */
> + u32 chan1_enable_disable; /* 0x20 Channel 1 Enable and Disable Register */
> + u32 chan1_buff_ownership; /* 0x24 Channel 1 Buffer Ownership Register */
> + u32 chan1_transmit_start_addr; /* 0x28 Channel 1 Transmit Data Start Address Register */
> + u32 chan1_receive_start_addr; /* 0x2C Channel 1 Receive Data Start Address Register */
> + u32 chan0_total_sample; /* 0x30 Channel 0 Total Sample Count Register */
> + u32 chan1_total_sample; /* 0x34 Channel 1 Total Sample Count Register */
> + u32 num_time_slot; /* 0x38 Number of Time Slot Register */
> + u32 tdm_pcm_clock_div; /* 0x3C TDM PCM Clock Rate Divisor Register */
> + u32 int_event_mask; /* 0x40 Interrupt Event Mask Register */
> + u32 reserved_44h; /* 0x44 */
> + u32 int_status_mask; /* 0x48 Interrupt Status Mask Register */
> + u32 int_reset_sel; /* 0x4C Interrupt Reset Selection Register */
> + u32 int_status; /* 0x50 Interrupt Status Register */
> + u32 dummy_rx_write;/* 0x54 Dummy Data for Dummy RX Write Register */
> + u32 misc_control; /* 0x58 Miscellaneous Control Register */
> + u32 reserved_5Ch; /* 0x5C */
> + u32 chan0_current_tx_addr; /* 0x60 Channel 0 Transmit Data Current Address Register (for DMA) */
> + u32 chan0_current_rx_addr; /* 0x64 Channel 0 Receive Data Current Address Register (for DMA) */
> + u32 chan1_current_tx_addr; /* 0x68 Channel 1 Transmit Data Current Address Register (for DMA) */
> + u32 chan1_current_rx_addr; /* 0x6C Channel 1 Receive Data Current Address Register (for DMA) */
> + u32 curr_time_slot; /* 0x70 Current Time Slot Register */
> + u32 revision; /* 0x74 TDM Revision Register */
> + u32 chan0_debug; /* 0x78 TDM Channel 0 Debug Register */
> + u32 chan1_debug; /* 0x7C TDM Channel 1 Debug Register */
> + u32 tdm_dma_abort_1; /* 0x80 TDM DMA Abort Register 1 */
> + u32 tdm_dma_abort_2; /* 0x84 TDM DMA Abort Register 2 */
> + u32 chan0_wideband_delay_ctrl; /* 0x88 TDM Channel 0 Wideband Delay Control Register */
> + u32 chan1_wideband_delay_ctrl; /* 0x8C TDM Channel 1 Wideband Delay Control Register */
> +};
> +
> +
> +#define KIRKWOOD_MAX_VOICE_CHANNELS 2 /* Max count hardware channels */
> +#define COUNT_DMA_BUFFERS_PER_CHANNEL 6 /* Count of dma buffers for tx or rx path by one channel */
> +
> +/*
> + * Data specified for kirkwood hardware voice channel
> + */
> +struct kirkwood_tdm_voice {
> + /* Transmitter and receiver buffers split to half.
> + * While first half buffer is filling by DMA controller,
> + * second half buffer is used by consumer and etc.
> + */
> + u8 *tx_buf[COUNT_DMA_BUFFERS_PER_CHANNEL]; /* transmitter voice buffers */
> + u8 *rx_buf[COUNT_DMA_BUFFERS_PER_CHANNEL]; /* receiver voice buffers pointer */
> +
> + dma_addr_t tx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /* two physical pointers to tx_buf */
> + dma_addr_t rx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /* two physical pointers to rx_buf */
> + atomic_t write_tx_buf_num; /* current writing transmit buffer number */
> + atomic_t read_tx_buf_num; /* current reading transmit buffer number */
> + atomic_t write_rx_buf_num; /* current writing receive buffer number */
> + atomic_t read_rx_buf_num; /* current reading receive buffer number */
> +};
> +
> +
> +/*
> + * Data specified for kirkwood tdm controller
> + */
> +struct kirkwood_tdm {
> + struct kirkwood_tdm_regs *regs; /* Registers for hardware TDM */
> + u32 irq; /* Irq number for all TDM operations */
> +
> + struct kirkwood_tdm_voice voice_channels[KIRKWOOD_MAX_VOICE_CHANNELS];
> + struct device *controller_dev;
> +};
> +
> +
> +#define TDM_WINDOW_CTRL(i) (0x4030 + ((i) << 4))
> +#define TDM_WINDOW_BASE(i) (0x4034 + ((i) << 4))
> +
> +
> +/* INT_STATUS_REG bits */
> +#define RX_OVERFLOW_BIT(ch) (1<<(0+(ch)*2))
> +#define TX_UNDERFLOW_BIT(ch) (1<<(1+((ch)*2)))
> +#define RX_BIT(ch) (1<<(4+((ch)*2)))
> +#define TX_BIT(ch) (1<<(5+((ch)*2)))
> +#define RX_IDLE_BIT(ch) (1<<(8+((ch)*2)))
> +#define TX_IDLE_BIT(ch) (1<<(9+((ch)*2)))
> +#define RX_FIFO_FULL(ch) (1<<(12+((ch)*2)))
> +#define TX_FIFO_EMPTY(ch) (1<<(13+((ch)*2)))
> +#define DMA_ABORT_BIT (1<<16)
> +#define SLIC_INT_BIT (1<<17)
> +#define TDM_INT_TX(ch) (TX_UNDERFLOW_BIT(ch) | TX_BIT(ch))
> +#define TDM_INT_RX(ch) (RX_OVERFLOW_BIT(ch) | RX_BIT(ch))
> +
> +
> +#endif /* KIRKWOOD_TDM_H_ */
> +
> +
next prev parent reply other threads:[~2013-03-01 23:49 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-02-27 16:22 [PATCH 0/9] Support of TDM bus, TDM driver for Marvell Kirkwood and SLIC driver for Silabs Si3226x Michail Kurachkin
2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
2013-03-01 10:50 ` [PATCH v2 01/11] staging: Initial commit of TDM core Michail Kurachkin
2013-03-01 22:54 ` Ryan Mallon
2013-03-11 17:17 ` Greg Kroah-Hartman
2013-03-01 10:52 ` [PATCH v2 02/11] staging: Initial commit of Kirkwood TDM driver Michail Kurachkin
2013-03-01 23:54 ` Ryan Mallon [this message]
2013-03-01 10:54 ` [PATCH v2 03/11] staging: Initial commit of SLIC si3226x driver Michail Kurachkin
2013-03-01 10:56 ` [PATCH v2 04/11] staging: added TODO file for si3226x Michail Kurachkin
2013-03-01 10:57 ` [PATCH v2 05/11] staging: refactoring request/free voice channels Michail Kurachkin
2013-03-02 22:56 ` Ryan Mallon
2013-03-01 10:58 ` [PATCH v2 06/11] staging: remove device_attribute Michail Kurachkin
2013-03-01 11:00 ` [PATCH v2 07/11] staging: added issues description in TODO file Michail Kurachkin
2013-03-01 11:00 ` [PATCH v2 08/11] staging: removing of buffer filling flag and also reverting old buffer related fix which is not really effective Michail Kurachkin
2013-03-01 11:02 ` [PATCH v2 09/11] staging: fixed e-mail address Michail Kurachkin
2013-03-01 11:03 ` [PATCH v2 10/11] staging: add issuses in TODO Michail Kurachkin
2013-03-01 11:04 ` [PATCH v2 11/11] " Michail Kurachkin
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=51313FC7.2010802@gmail.com \
--to=rmallon@gmail.com \
--cc=Ivan.Kuten@promwad.com \
--cc=Viktar.Palstsiuk@promwad.com \
--cc=benavi@marvell.com \
--cc=dmitriy.gorokh@promwad.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=michail.kurachkin@promwad.com \
--cc=oneukum@suse.de \
/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.