[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1414495589-8579-7-git-send-email-qais.yousef@imgtec.com>
Date: Tue, 28 Oct 2014 11:26:24 +0000
From: Qais Yousef <qais.yousef@...tec.com>
To: <linux-kernel@...r.kernel.org>
CC: Qais Yousef <qais.yousef@...tec.com>,
Arnd Bergmann <arnd@...db.de>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
<alsa-devel@...a-project.org>
Subject: [PATCH 06/11] drivers: char: axd: add basic files for sending/receiving axd cmds
these files do the important part of talking with AXD to send and
receive data buffers
Signed-off-by: Qais Yousef <qais.yousef@...tec.com>
Cc: Arnd Bergmann <arnd@...db.de>
Cc: Greg Kroah-Hartman <gregkh@...uxfoundation.org>
Cc: <alsa-devel@...a-project.org>
---
drivers/char/axd/axd_cmds.c | 98 +++
drivers/char/axd/axd_cmds.h | 531 ++++++++++++++
drivers/char/axd/axd_cmds_pipes.c | 1368 +++++++++++++++++++++++++++++++++++++
3 files changed, 1997 insertions(+)
create mode 100644 drivers/char/axd/axd_cmds.c
create mode 100644 drivers/char/axd/axd_cmds.h
create mode 100644 drivers/char/axd/axd_cmds_pipes.c
diff --git a/drivers/char/axd/axd_cmds.c b/drivers/char/axd/axd_cmds.c
new file mode 100644
index 000000000000..229a0cd85d52
--- /dev/null
+++ b/drivers/char/axd/axd_cmds.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2011-2014 Imagination Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * AXD Commands API - generic setup functions.
+ */
+#include "axd_api.h"
+#include "axd_cmds.h"
+#include "axd_cmds_internal.h"
+#include "axd_module.h"
+
+static unsigned long __io_address;
+static unsigned long __phys_address;
+
+void axd_cmd_init(struct axd_cmd *cmd, unsigned long cmd_address,
+ unsigned long io_address, unsigned long phys_address)
+{
+ int i;
+
+ cmd->message = (struct axd_memory_map __iomem *)cmd_address;
+ mutex_init(&cmd->cm_lock);
+ init_waitqueue_head(&cmd->wait);
+ axd_set_flag(&cmd->response_flg, 0);
+ axd_set_flag(&cmd->fw_stopped_flg, 0);
+ for (i = 0; i < AXD_MAX_PIPES; i++) {
+ axd_cmd_inpipe_init(cmd, i);
+ axd_cmd_outpipe_init(cmd, i);
+ }
+ __io_address = io_address;
+ __phys_address = phys_address;
+ cmd->watchdogenabled = 1;
+ /*
+ * By default, always discard any pending buffers if an output device is
+ * closed before EOS is reached.
+ * This behaviour can be changed through sysfs. If discard is disabled,
+ * then upon closing an output device before EOS is reached, it'll
+ * resume from where it stopped.
+ */
+ axd_set_flag(&cmd->discard_flg, 1);
+ axd_set_flag(&cmd->ctrlbuf_active_flg, 0);
+}
+
+int axd_cmd_set_pc(struct axd_cmd *cmd, unsigned int thread, unsigned long pc)
+{
+ if (thread >= THREAD_COUNT)
+ return -1;
+ iowrite32(pc, &cmd->message->pc[thread]);
+ return 0;
+}
+
+unsigned long axd_cmd_get_datain_address(struct axd_cmd *cmd)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+
+ return (unsigned long) axd->buf_base_m;
+}
+
+unsigned long axd_cmd_get_datain_size(struct axd_cmd *cmd)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+
+ return axd->inbuf_size;
+}
+
+unsigned long axd_cmd_get_dataout_address(struct axd_cmd *cmd)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+
+ return ((unsigned long) axd->buf_base_m) + axd->inbuf_size;
+}
+
+unsigned long axd_cmd_get_dataout_size(struct axd_cmd *cmd)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+
+ return axd->outbuf_size;
+}
+
+/*
+ * The driver understands IO address, while f/w understands physical addresses.
+ * A couple of helper functions to aid in converting when exchanging buffers.
+ *
+ * NOTE:
+ * buf must NOT be NULL - we want this as fast as possible, so omit the check
+ * for NULLl
+ */
+inline char *axd_io_2_phys(const char *buf)
+{
+ return (char *)(buf - __io_address + __phys_address);
+}
+inline char *axd_phys_2_io(const char *buf)
+{
+ return (char *)(buf - __phys_address + __io_address);
+}
diff --git a/drivers/char/axd/axd_cmds.h b/drivers/char/axd/axd_cmds.h
new file mode 100644
index 000000000000..150098a99d92
--- /dev/null
+++ b/drivers/char/axd/axd_cmds.h
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2011-2014 Imagination Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * AXD API commands Helper functions.
+ */
+#ifndef AXD_CMDS_H_
+#define AXD_CMDS_H_
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/semaphore.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include "linux/workqueue.h"
+
+#include "axd_api.h"
+#include "axd_buffers.h"
+
+/**
+ * struct axd_desc_ctrl - axd desctriptors control structure
+ * @rd_idx: read index of next available descriptor
+ * @wr_idx: write index of empty slot ot return a descriptor to
+ * @rd_sem: semaphore to block when no more descriptors are available
+ * @wr_sem: semaphore to block when all descriptors are available
+ * @rd_lock: smp critical section protection for reads
+ * @wr_lock: smp critical section protection for writes
+ * @buf_desc: pointer to iomem where the descriptors are
+ * @num_desc: total number of descriptors provided by axd
+ *
+ * axd has a number of input and output descriptors to pass buffers around, this
+ * structure provides a mean for the driver to manage access to these
+ * descriptors.
+ */
+struct axd_desc_ctrl {
+ unsigned int rd_idx;
+ unsigned int wr_idx;
+ struct semaphore rd_sem;
+ struct semaphore wr_sem;
+ spinlock_t rd_lock;
+ spinlock_t wr_lock;
+ struct axd_buffer_desc __iomem *buf_desc;
+ unsigned int num_desc;
+};
+
+struct axd_cmd;
+
+/**
+ * struct axd_pipe - axd pipe management structure
+ * @work: work for top half of the interrupt
+ * @desc_ctrl: axd_desc_ctrl structure to manage this pipe's
+ * descriptors
+ * @desc_bufferq: buffer queue send through the descriptors
+ * @user_bufferq: buffer queue of buffers to be read by the user. only
+ * makes sense for an output pipe where the user doesn't
+ * have to read the returned buffers synchronously when we
+ * get an interrupt
+ * @cur_buf: pointer to the current user_bufferq being read
+ * @cur_buf_offset: offset of the current user_bufferq to start reading from
+ * @cur_buf_size: remaining size of data in current user_bufferq
+ * @discard_flg: a flag to indicate we should discard the remaining data
+ * if the user closed output node before reading all data,
+ * default to true.
+ * @enabled_flg: a flag indicates that this pipe is actively handling a
+ * stream
+ * @eos_flg: a flag indicates that eos was reached and we should do
+ * clean up as soon as possible
+ * @eos_mutex: for input pipes we need to protect against possible
+ * simulataneous sending of eos
+ * @intcount: number of interrupts received since last service.
+ * indicates the number of buffers services by axd.
+ * used by top half workqueue to know how many interrupts
+ * it needs to service in one go
+ * @id: pipe number or id
+ * @tsk: the userland task that opened this pipe
+ * @buf_size: the size of the buffer this pipe is configured to use
+ * @current_ts_low: lower half of the 64-bit timestamp for current buffer
+ * @current_ts_high: top half of the 64-bit timestamp for current buffer
+ * @cmd: pointer to axd_cmd struct for quick access
+ * @buf_desc: pointer to axd_buffer_desc struct for quick access
+ *
+ * axd could provide a number of pipes each of which handles a separate stream.
+ * this structure manages descriptors, buffers and other control bits associated
+ * with each input/output pipe.
+ */
+struct axd_pipe {
+ struct work_struct work;
+ struct axd_desc_ctrl desc_ctrl;
+ struct axd_bufferq desc_bufferq;
+ struct axd_bufferq user_bufferq;
+ char *cur_buf;
+ unsigned int cur_buf_offset;
+ unsigned int cur_buf_size;
+ unsigned int discard_flg;
+ unsigned int enabled_flg;
+ unsigned int eos_flg;
+ struct mutex eos_mutex;
+ atomic_t intcount;
+ unsigned int id;
+ struct task_struct *tsk;
+ unsigned int buf_size;
+ u32 current_ts_low;
+ u32 current_ts_high;
+ struct axd_cmd *cmd;
+ struct axd_buffer_desc __iomem *buf_desc;
+};
+
+/**
+ * struct axd_cmd - axd command structure
+ * @message: iomapped axd massage region, see axd_memory_map struct
+ * @cm_lock: mutex to control access to the message region
+ * @wait: wait for ControlCommand response, or command completion
+ * @response_flg: condition variable to wait on for response
+ * @in_workq: array of workqueues for input pipes
+ * @out_workq: array of workqueues for output pipes
+ * @in_pipes: array of input axd_pipe structs
+ * @out_pipes: array of output axd_pipe structs
+ * @watchdogenabled: software watchdog switch
+ * @discard_flg: master flag to control whether to discard data when user
+ * closes output node
+ * @nonblock: operate in nonblocking mode
+ * @fw_stopped_flg: this flag indicates that software watchdog detected that
+ * the firmware is not responding
+ * @num_inputs: number of input pipes
+ * @num_outputs: number of output pipes
+ * @ctrlbuf_active_flg: this flag indicates ctrl buffer mechanism is in use
+ * @dcpp_channel_ctrl_cache: dcpp channel configuration cache
+ * @dcpp_band_ctrl_cache: dcpp band configuration cache
+ *
+ * manage the iomapped area to exchange messages/commands with axd
+ */
+struct axd_cmd {
+ struct axd_memory_map __iomem *message;
+ struct mutex cm_lock;
+ wait_queue_head_t wait;
+ unsigned int response_flg;
+ struct workqueue_struct *in_workq;
+ struct workqueue_struct *out_workq;
+ struct axd_pipe in_pipes[AXD_MAX_PIPES];
+ struct axd_pipe out_pipes[AXD_MAX_PIPES];
+ int watchdogenabled;
+ unsigned int discard_flg;
+ unsigned int nonblock;
+ unsigned int fw_stopped_flg;
+ int num_inputs;
+ int num_outputs;
+ unsigned int ctrlbuf_active_flg;
+ int dcpp_channel_ctrl_cache[AXD_MAX_PIPES];
+ int dcpp_band_ctrl_cache[AXD_MAX_PIPES];
+ unsigned int started_flg;
+};
+
+static inline void axd_set_flag(unsigned int *flag, unsigned int value)
+{
+ *flag = value;
+ smp_wmb(); /* guarantee smp ordering */
+}
+
+static inline unsigned int axd_get_flag(unsigned int *flag)
+{
+ smp_rmb(); /* guarantee smp ordering */
+ return *flag;
+}
+
+#define CMD_TIMEOUT (1*HZ)
+
+/* Generic setup API */
+void axd_cmd_init(struct axd_cmd *cmd, unsigned long cmd_address,
+ unsigned long io_address, unsigned long phys_address);
+int axd_cmd_set_pc(struct axd_cmd *cmd, unsigned int thread, unsigned long pc);
+unsigned long axd_cmd_get_datain_address(struct axd_cmd *cmd);
+unsigned long axd_cmd_get_datain_size(struct axd_cmd *cmd);
+unsigned long axd_cmd_get_dataout_address(struct axd_cmd *cmd);
+unsigned long axd_cmd_get_dataout_size(struct axd_cmd *cmd);
+
+/* Info API */
+void axd_cmd_get_version(struct axd_cmd *cmd, int *major,
+ int *minor, int *patch);
+int axd_cmd_get_num_pipes(struct axd_cmd *cmd, unsigned int *inpipes,
+ unsigned int *outpipes);
+void axd_cmd_get_decoders(struct axd_cmd *cmd, char *codecs);
+void axd_cmd_get_encoders(struct axd_cmd *cmd, char *codecs);
+int axd_cmd_xbar_present(struct axd_cmd *cmd);
+int axd_cmd_mixer_get_eqenabled(struct axd_cmd *cmd, unsigned int pipe);
+void axd_cmd_mixer_get_eqmastergain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_mixer_get_eqband0gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_mixer_get_eqband1gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_mixer_get_eqband2gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_mixer_get_eqband3gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_mixer_get_eqband4gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_mixer_get_mux(struct axd_cmd *cmd, unsigned int pipe,
+ char *mux);
+int axd_cmd_input_get_enabled(struct axd_cmd *cmd, unsigned int pipe);
+void axd_cmd_input_get_source(struct axd_cmd *cmd, unsigned int pipe,
+ char *source);
+void axd_cmd_input_get_codec(struct axd_cmd *cmd, unsigned int pipe,
+ char *codec);
+void axd_cmd_input_get_gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_input_get_mute(struct axd_cmd *cmd, unsigned int pipe,
+ int *muted);
+void axd_cmd_input_get_upmix(struct axd_cmd *cmd, unsigned int pipe,
+ char *upmix);
+void axd_cmd_input_get_decoder_config(struct axd_cmd *cmd, unsigned int pipe,
+ char *config);
+int axd_cmd_output_get_enabled(struct axd_cmd *cmd, unsigned int pipe);
+void axd_cmd_output_get_codec(struct axd_cmd *cmd, unsigned int pipe,
+ char *codec);
+void axd_cmd_output_get_sink(struct axd_cmd *cmd, unsigned int pipe,
+ char *sink);
+void axd_cmd_output_get_downmix(struct axd_cmd *cmd, unsigned int pipe,
+ char *downmix);
+int axd_cmd_output_get_eqenabled(struct axd_cmd *cmd, unsigned int pipe);
+void axd_cmd_output_get_eqmastergain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_output_get_eqband0gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_output_get_eqband1gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_output_get_eqband2gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_output_get_eqband3gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_output_get_eqband4gain(struct axd_cmd *cmd, unsigned int pipe,
+ int *gain);
+void axd_cmd_output_get_encoder_config(struct axd_cmd *cmd, unsigned int pipe,
+ char *config);
+void axd_cmd_output_get_geq_power(struct axd_cmd *cmd, unsigned int pipe,
+ char *buf, int channel);
+/* DCPP */
+int axd_cmd_output_dcpp_select_channel(struct axd_cmd *cmd, unsigned int pipe,
+ bool subband, unsigned int channel);
+int axd_cmd_output_dcpp_select_band(struct axd_cmd *cmd, unsigned int pipe,
+ unsigned int band);
+
+unsigned int axd_cmd_output_get_dcpp_enabled(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_mode(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_channels(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_eq_mode(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_eq_bands(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_max_delay_samples(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_channel_delay_samples(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_output_volume(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_passthrough_gain(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_inverse_passthrough_gain(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_bass_shelf_shift(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_bass_shelf_a0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_bass_shelf_a1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_bass_shelf_a2(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_bass_shelf_b0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_bass_shelf_b1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_treble_shelf_shift(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_treble_shelf_a0(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_treble_shelf_a1(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_treble_shelf_a2(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_treble_shelf_b0(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_treble_shelf_b1(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int channel);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_gain(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_a0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_a1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_a2(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_b0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_b1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_channel_eq_shift(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_bands(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_enabled(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_input_channel_mask(
+ struct axd_cmd *cmd, unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_delay_samples(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_output_volume(
+ struct axd_cmd *cmd, unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_passthrough_gain(
+ struct axd_cmd *cmd, unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_inverse_passthrough_gain(
+ struct axd_cmd *cmd, unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_gain(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_a0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_a1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_a2(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_b0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_b1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_subband_eq_shift(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band);
+unsigned int axd_cmd_output_get_dcpp_subband_low_pass_filter_a0(
+ struct axd_cmd *cmd, unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_low_pass_filter_a1(
+ struct axd_cmd *cmd, unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_low_pass_filter_a2(
+ struct axd_cmd *cmd, unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_low_pass_filter_b0(
+ struct axd_cmd *cmd, unsigned int pipe);
+unsigned int axd_cmd_output_get_dcpp_subband_low_pass_filter_b1(
+ struct axd_cmd *cmd, unsigned int pipe);
+/* Config API */
+void axd_cmd_mixer_set_eqenabled(struct axd_cmd *cmd, unsigned int pipe,
+ int enable);
+void axd_cmd_mixer_set_eqmastergain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_mixer_set_eqband0gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_mixer_set_eqband1gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_mixer_set_eqband2gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_mixer_set_eqband3gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_mixer_set_eqband4gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_mixer_set_mux(struct axd_cmd *cmd, unsigned int pipe,
+ int mux);
+int axd_cmd_input_set_enabled(struct axd_cmd *cmd, unsigned int pipe,
+ int enable);
+void axd_cmd_input_set_source(struct axd_cmd *cmd, unsigned int pipe,
+ int source);
+void axd_cmd_input_set_codec(struct axd_cmd *cmd, unsigned int pipe,
+ int codec);
+void axd_cmd_input_set_gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_input_set_mute(struct axd_cmd *cmd, unsigned int pipe,
+ int mute);
+void axd_cmd_input_set_upmix(struct axd_cmd *cmd, unsigned int pipe,
+ int upmix);
+void axd_cmd_input_set_decoder_config(struct axd_cmd *cmd, unsigned int pipe,
+ const char *config);
+int axd_cmd_output_set_enabled(struct axd_cmd *cmd, unsigned int pipe,
+ int enable);
+void axd_cmd_output_set_codec(struct axd_cmd *cmd, unsigned int pipe,
+ int codec);
+void axd_cmd_output_set_sink(struct axd_cmd *cmd, unsigned int pipe,
+ int sink);
+void axd_cmd_output_set_downmix(struct axd_cmd *cmd, unsigned int pipe,
+ int downmix);
+void axd_cmd_output_set_eqenabled(struct axd_cmd *cmd, unsigned int pipe,
+ int enable);
+void axd_cmd_output_set_eqmastergain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_output_set_eqband0gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_output_set_eqband1gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_output_set_eqband2gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_output_set_eqband3gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+void axd_cmd_output_set_eqband4gain(struct axd_cmd *cmd, unsigned int pipe,
+ int gain);
+/* DCPP */
+int axd_cmd_output_set_dcpp_enabled(struct axd_cmd *cmd, unsigned int pipe,
+ int enable);
+int axd_cmd_output_set_dcpp_mode(struct axd_cmd *cmd, unsigned int pipe,
+ unsigned int mode);
+int axd_cmd_output_set_dcpp_eq_mode(struct axd_cmd *cmd, unsigned int pipe,
+ unsigned int mode);
+int axd_cmd_output_set_dcpp_channel_delay_samples(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_output_volume(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_passthrough_gain(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_inverse_passthrough_gain(
+ struct axd_cmd *cmd, unsigned int pipe,
+ unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_bass_shelf_shift(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_bass_shelf_a0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_bass_shelf_a1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_bass_shelf_a2(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_bass_shelf_b0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_bass_shelf_b1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_treble_shelf_shift(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_treble_shelf_a0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_treble_shelf_a1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_treble_shelf_a2(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_treble_shelf_b0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_treble_shelf_b1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_gain(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel,
+ unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_a0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel,
+ unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_a1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel,
+ unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_a2(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel,
+ unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_b0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel,
+ unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_b1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel,
+ unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_channel_eq_shift(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int channel,
+ unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_enabled(struct axd_cmd *cmd,
+ unsigned int pipe, int enable);
+int axd_cmd_output_set_dcpp_subband_input_channel_mask(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_delay_samples(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_output_volume(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_passthrough_gain(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_inverse_passthrough_gain(
+ struct axd_cmd *cmd, unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_gain(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_a0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_a1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_a2(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_b0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_b1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_eq_shift(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int band, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_low_pass_filter_a0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_low_pass_filter_a1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_low_pass_filter_a2(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_low_pass_filter_b0(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+int axd_cmd_output_set_dcpp_subband_low_pass_filter_b1(struct axd_cmd *cmd,
+ unsigned int pipe, unsigned int data);
+void axd_cmd_output_set_encoder_config(struct axd_cmd *cmd, unsigned int pipe,
+ const char *config);
+unsigned int axd_cmd_info_get_resampler_fin(struct axd_cmd *cmd,
+ unsigned int pipe);
+unsigned int axd_cmd_info_get_resampler_fout(struct axd_cmd *cmd,
+ unsigned int pipe);
+void axd_cmd_info_set_resampler_fout(struct axd_cmd *cmd, unsigned int pipe,
+ unsigned int fout);
+unsigned int axd_cmd_input_get_buffer_occupancy(struct axd_cmd *cmd,
+ unsigned int pipe);
+void axd_cmd_input_set_buffer_occupancy(struct axd_cmd *cmd, unsigned int pipe,
+ unsigned int bo);
+
+/* Channel setup and access API */
+int axd_cmd_inpipe_start(struct axd_cmd *cmd, unsigned int pipe);
+void axd_cmd_inpipe_stop(struct axd_cmd *cmd, unsigned int pipe);
+void axd_cmd_inpipe_reset(struct axd_cmd *cmd, unsigned int pipe);
+int axd_cmd_inpipe_active(struct axd_cmd *cmd, unsigned int pipe);
+int axd_cmd_outpipe_start(struct axd_cmd *cmd, unsigned int pipe);
+void axd_cmd_outpipe_stop(struct axd_cmd *cmd, unsigned int pipe);
+void axd_cmd_outpipe_reset(struct axd_cmd *cmd, unsigned int pipe);
+int axd_cmd_send_buffer(struct axd_cmd *cmd, unsigned int pipe,
+ const char __user *buf, unsigned int size);
+void axd_cmd_send_buffer_abort(struct axd_cmd *cmd, unsigned int pipe);
+int axd_cmd_recv_buffer(struct axd_cmd *cmd, unsigned int pipe,
+ char __user *buf, unsigned int size);
+void axd_cmd_recv_buffer_abort(struct axd_cmd *cmd, unsigned int pipe);
+int axd_cmd_install_irq(struct axd_cmd *cmd, unsigned int irqnum);
+void axd_cmd_free_irq(struct axd_cmd *cmd, unsigned int irqnum);
+int axd_cmd_reset_pipe(struct axd_cmd *cmd, unsigned int pipe);
+
+/* generic helper function required in several places */
+char *axd_io_2_phys(const char *buf);
+char *axd_phys_2_io(const char *buf);
+
+/* Register write buffer */
+int axd_flush_reg_buf(struct axd_cmd *cmd);
+
+#endif /* AXD_CMDS_H_ */
diff --git a/drivers/char/axd/axd_cmds_pipes.c b/drivers/char/axd/axd_cmds_pipes.c
new file mode 100644
index 000000000000..2e3ca7443647
--- /dev/null
+++ b/drivers/char/axd/axd_cmds_pipes.c
@@ -0,0 +1,1368 @@
+/*
+ * Copyright (C) 2011-2014 Imagination Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * AXD Commands API - Pipes/Buffers Accessing functions.
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+#include "axd_api.h"
+#include "axd_cmds.h"
+#include "axd_cmds_internal.h"
+#include "axd_hdr.h"
+#include "axd_module.h"
+#include "axd_platform.h"
+
+/*
+ * axd_pipe->eos_flg for output pipes is overloaded to mean two things:
+ *
+ * - EOS_REACHED: indicates that firmware has processed all input buffers
+ * including EOS but userland hasn't read them all yet.
+ *
+ * - EOF_REACHED: indicates that firmware sent EOS back to us AND userland read
+ * all the data till EOS.
+ */
+#define EOS_REACHED 1
+#define EOF_REACHED 2
+
+/*
+ * axd_pipe->enabled_flg for output pipes is overloaded to mean two things:
+ *
+ * - PIPE_STARTED: indicates that pipe was opened but no buffers were passed.
+ * When stopping the pipes, we know that we don't need to discard anything if
+ * the discard_flg is set in cmd struct. Which allows us to terminate easily
+ * and quickly.
+ *
+ * - PIPE_RUNNING: indicates that pipe has processed some buffers, so we should
+ * discard if user terminates early (and discard_flg is set in cmd struct).
+ */
+#define PIPE_STARTED 1
+#define PIPE_RUNNING 2
+
+#ifdef DEBUG_DIAG
+static unsigned int inSentCount[AXD_MAX_PIPES];
+static unsigned int inRecvCount[AXD_MAX_PIPES];
+static unsigned int outSentCount[AXD_MAX_PIPES];
+static unsigned int outRecvCount[AXD_MAX_PIPES];
+static unsigned int primeupCount[AXD_MAX_PIPES];
+static unsigned int read_size[AXD_MAX_PIPES];
+static unsigned int write_size[AXD_MAX_PIPES];
+static unsigned int recv_size[AXD_MAX_PIPES];
+#define debugdiag printk
+#else
+#define debugdiag(format, ...)
+#endif
+
+static void axd_cmd_inpipe_clear(struct axd_cmd *cmd, unsigned int pipe);
+static void axd_cmd_outpipe_clear(struct axd_cmd *cmd, unsigned int pipe);
+static void axd_cmd_send_eos(struct axd_pipe *axd_pipe);
+static void axd_output_prime_up(struct axd_pipe *axd_pipe);
+static void axd_cmd_output_eos_reached(struct axd_cmd *cmd, unsigned int pipe);
+
+/*
+ * Send/Clear data{in, out} kicks.
+ *
+ * NOTE:
+ * Must acquire axd_platform_lock() before accessing kick and interrupt status
+ * registers as the AXD firmware might be accessing them at the same time.
+ */
+static inline void axd_datain_kick(struct axd_pipe *axd_pipe)
+{
+ unsigned long flags;
+ struct axd_memory_map __iomem *message = axd_pipe->cmd->message;
+ unsigned int pipe = axd_pipe->id;
+ unsigned int temp;
+
+#ifdef DEBUG_DIAG
+ inSentCount[pipe]++;
+#endif
+ debugbuf("----> Send datain kick\n");
+ flags = axd_platform_lock();
+ temp = ioread32(&message->kick) |
+ AXD_ANY_KICK_BIT | AXD_KICK_DATA_IN_BIT;
+ iowrite32(temp, &message->kick);
+ temp = ioread32(&message->in_kick_count[pipe]) + 1;
+ iowrite32(temp, &message->in_kick_count[pipe]);
+ axd_platform_unlock(flags);
+ axd_platform_kick();
+}
+
+static inline void axd_dataout_kick(struct axd_pipe *axd_pipe)
+{
+ unsigned long flags;
+ struct axd_memory_map __iomem *message = axd_pipe->cmd->message;
+ unsigned int pipe = axd_pipe->id;
+ unsigned int temp;
+
+#ifdef DEBUG_DIAG
+ outSentCount[pipe]++;
+#endif
+ debugbuf("----> Send dataout kick\n");
+ flags = axd_platform_lock();
+ temp = ioread32(&message->kick) |
+ AXD_ANY_KICK_BIT | AXD_KICK_DATA_OUT_BIT;
+ iowrite32(temp, &message->kick);
+ temp = ioread32(&message->out_kick_count[pipe]) + 1;
+ iowrite32(temp, &message->out_kick_count[pipe]);
+ axd_platform_unlock(flags);
+ axd_platform_kick();
+}
+
+/* Assumes axd_platform_lock() is already acquired before calling this */
+static inline int axd_datain_status_clear(struct axd_pipe *axd_pipe)
+{
+ struct axd_memory_map __iomem *message = axd_pipe->cmd->message;
+ unsigned int pipe = axd_pipe->id;
+ unsigned int intcount = ioread32(&message->in_int_count[pipe]);
+
+ debugbuf("Clearing in_int_count[%u] = %u\n", pipe, intcount);
+ if (intcount == 0)
+ return -1;
+ atomic_add(intcount, &axd_pipe->intcount);
+ iowrite32(0, &message->in_int_count[pipe]);
+ return 0;
+}
+
+/* Assumes axd_platform_lock() is already acquired before calling this */
+static inline int axd_dataout_status_clear(struct axd_pipe *axd_pipe)
+{
+ struct axd_memory_map __iomem *message = axd_pipe->cmd->message;
+ unsigned int pipe = axd_pipe->id;
+ unsigned int intcount = ioread32(&message->out_int_count[pipe]);
+
+ debugbuf("Clearing out_int_count[%u] = %u\n", pipe, intcount);
+ if (intcount == 0)
+ return -1;
+ atomic_add(intcount, &axd_pipe->intcount);
+ iowrite32(0, &message->out_int_count[pipe]);
+ return 0;
+}
+
+/* IRQ Handler */
+static irqreturn_t axd_irq(int irq, void *data)
+{
+ struct axd_cmd *cmd = data;
+ unsigned int int_status;
+ unsigned long flags;
+ int i, ret;
+
+ axd_platform_irq_ack();
+ flags = axd_platform_lock();
+ int_status = ioread32(&cmd->message->int_status);
+ iowrite32(0, &cmd->message->int_status);
+
+ if (!int_status)
+ goto out;
+
+ debugbuf("<---- Received int_status = 0x%08X\n", int_status);
+ if (int_status & AXD_INT_KICK_DONE)
+ debugbuf("<---- Received kick done interrupt\n");
+ if (int_status & AXD_INT_DATAIN) {
+ debugbuf("<---- Received datain interrupt\n");
+ for (i = 0; i < AXD_MAX_PIPES; i++) {
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[i];
+
+ if (axd_get_flag(&axd_pipe->enabled_flg)) {
+ ret = axd_datain_status_clear(axd_pipe);
+ if (!ret)
+ queue_work(cmd->in_workq, &axd_pipe->work);
+ }
+ }
+ }
+ if (int_status & AXD_INT_DATAOUT) {
+ debugbuf("<---- Received dataout interrupt\n");
+ for (i = 0; i < AXD_MAX_PIPES; i++) {
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[i];
+
+ if (axd_get_flag(&axd_pipe->enabled_flg)) {
+ ret = axd_dataout_status_clear(axd_pipe);
+ if (!ret && !axd_get_flag(&axd_pipe->eos_flg))
+ queue_work(cmd->out_workq, &axd_pipe->work);
+ }
+ }
+ }
+ if (int_status & AXD_INT_CTRL) {
+ debugbuf("<---- Received ctrl interrupt\n");
+ axd_set_flag(&cmd->response_flg, 1);
+ wake_up(&cmd->wait);
+ }
+ if (int_status & AXD_INT_ERROR) {
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ int error = ioread32(&cmd->message->error);
+
+ debugbuf("<---- Received error interrupt\n");
+ switch (error) {
+ default:
+ case 0:
+ break;
+ case 1:
+ dev_err(axd->dev, "Fatal error received...\n");
+ axd_schedule_reset(cmd);
+ break;
+ case 2:
+ dev_warn(axd->dev, "Failed to set last configuration command\n");
+ break;
+ }
+
+ iowrite32(0, &cmd->message->error);
+ }
+out:
+ /*
+ * ensure all writes to uncached shared memory are visible to AXD
+ * before releasing axd_platform_lock()
+ */
+ wmb();
+ axd_platform_unlock(flags);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Initialize the drivers descriptors control structre.
+ * @desc_ctrl: the desc control structure to initialize.
+ * @buf_desc: pointer to the buffer descriptor to control.
+ * @num_desc: total number of descriptors inside @buf_desc.
+ */
+static void desc_init(struct axd_desc_ctrl *desc_ctrl,
+ struct axd_buffer_desc __iomem *buf_desc, unsigned int num_desc)
+{
+ /* Reset ctrl desc struct */
+ desc_ctrl->rd_idx = 0;
+ desc_ctrl->wr_idx = 0;
+ sema_init(&desc_ctrl->rd_sem, num_desc);
+ sema_init(&desc_ctrl->wr_sem, 0);
+ spin_lock_init(&desc_ctrl->rd_lock);
+ spin_lock_init(&desc_ctrl->wr_lock);
+ desc_ctrl->buf_desc = buf_desc;
+ desc_ctrl->num_desc = num_desc;
+}
+
+/*
+ * Prepare a descriptor to be sent to firmware.
+ * @desc_ctrl: the control structure of the descriptor.
+ * @buf: physical address of the buffer to enqueue.
+ * @size: size of the buffer.
+ * @last: non-zero of this is the last buffer ie: EOS.
+ */
+static int desc_enqueue(struct axd_desc_ctrl *desc_ctrl, char *buf,
+ unsigned int size, int last, struct axd_pipe *chan)
+{
+ struct axd_buffer_desc __iomem *buf_desc = desc_ctrl->buf_desc;
+ unsigned int num_desc = desc_ctrl->num_desc;
+ unsigned int status_size = size | AXD_DESCRIPTOR_READY_BIT;
+ int ret;
+
+ debugbuf("Enqueuing a descriptor, pipe[%u]... ", chan->id);
+ /* only proceed if we're not full */
+ ret = down_trylock(&desc_ctrl->rd_sem);
+ if (ret) {
+ debugbuf("FAILED - full\n");
+ return -1;
+ }
+ debugbuf("SUCCEEDED\n");
+
+ if (last)
+ status_size |= AXD_DESCRIPTOR_EOS_BIT;
+
+ /*
+ * if we could lock the semaphore, then we're guaranteed that the
+ * current rd_idx is valid and ready to be used. So no need to verify
+ * that the status of the descriptor at rd_idx is valid.
+ */
+ spin_lock(&desc_ctrl->rd_lock);
+ iowrite32(status_size, &buf_desc[desc_ctrl->rd_idx].status_size);
+ iowrite32((unsigned int)axd_io_2_phys(buf),
+ &buf_desc[desc_ctrl->rd_idx].data_ptr);
+ iowrite32(chan->current_ts_high, &buf_desc[desc_ctrl->rd_idx].pts_high);
+ iowrite32(chan->current_ts_low, &buf_desc[desc_ctrl->rd_idx].pts_low);
+ desc_ctrl->rd_idx++;
+ if (desc_ctrl->rd_idx >= num_desc)
+ desc_ctrl->rd_idx = 0;
+ spin_unlock(&desc_ctrl->rd_lock);
+ up(&desc_ctrl->wr_sem); /* we can dequeue 1 more item */
+ return 0;
+}
+
+/*
+ * Takes a buffer out of the descriptor queue.
+ * @desc_ctrl: the control structure of the descriptor.
+ * @size: sets it tot he size of the buffer returned if not NULL.
+ * @last: sets it to non-zero of this is the last buffer ie: EOS (if last is not
+ * NULL)
+ *
+ * On success, a valid pointer is received. NULL otherwise.
+ */
+static char *desc_dequeue(struct axd_desc_ctrl *desc_ctrl, unsigned int *size,
+ int *last, struct axd_pipe *chan, int is_out)
+{
+ struct axd_buffer_desc __iomem *buf_desc = desc_ctrl->buf_desc;
+ unsigned int num_desc = desc_ctrl->num_desc;
+ unsigned int status_size;
+ char *buf;
+ int ret;
+
+ debugbuf("Dequeuing a descriptor, pipe[%u]... ", chan->id);
+ /* only proceed if we're not empty */
+ ret = down_trylock(&desc_ctrl->wr_sem);
+ if (ret) {
+ debugbuf("FAILED - empty\n");
+ return NULL;
+ }
+ spin_lock(&desc_ctrl->wr_lock);
+ status_size = ioread32(&buf_desc[desc_ctrl->wr_idx].status_size);
+ /*
+ * if ready and in_use bit are set, then the rest of the buffers are
+ * still owned by the AXD fw, we can't dequeue them then. exit.
+ */
+ if ((status_size & AXD_DESCRIPTOR_INUSE_BIT) &&
+ !(status_size & AXD_DESCRIPTOR_READY_BIT)) {
+
+ debugbuf("SUCCEEDED\n");
+ /* clear the in_use bit */
+ iowrite32(status_size & ~AXD_DESCRIPTOR_INUSE_BIT,
+ &buf_desc[desc_ctrl->wr_idx].status_size);
+
+ /*
+ * Return the pointer to the buffer and its size to caller.
+ * The caller might need to read it or return it to the pool.
+ */
+ buf = (char *)ioread32(&buf_desc[desc_ctrl->wr_idx].data_ptr);
+ if (size)
+ *size = status_size & AXD_DESCRIPTOR_SIZE_MASK;
+ if (last)
+ *last = status_size & AXD_DESCRIPTOR_EOS_BIT;
+
+ if (is_out) {
+ /* update any timestamps if is use */
+ chan->current_ts_high =
+ ioread32(&buf_desc[desc_ctrl->wr_idx].pts_high);
+ chan->current_ts_low =
+ ioread32(&buf_desc[desc_ctrl->wr_idx].pts_low);
+ }
+
+ desc_ctrl->wr_idx++;
+ if (desc_ctrl->wr_idx >= num_desc)
+ desc_ctrl->wr_idx = 0;
+
+ spin_unlock(&desc_ctrl->wr_lock);
+ up(&desc_ctrl->rd_sem); /* we can enqueue 1 more item */
+ return axd_phys_2_io(buf);
+ }
+ debugbuf("FAILED - AXD holds the rest of the descriptors\n");
+ /*
+ * failed due to busy buffer, return writer locks
+ * as we haven't dequeued
+ */
+ spin_unlock(&desc_ctrl->wr_lock);
+ up(&desc_ctrl->wr_sem);
+ return NULL;
+}
+
+/*
+ * This is the function executed by the workqueue to process return input
+ * pipes descriptors.
+ * Each pipe will have its own version of this function executed when datain
+ * interrupt is received.
+ */
+static void in_desc_workq(struct work_struct *work)
+{
+ struct axd_pipe *axd_pipe = container_of(work, struct axd_pipe, work);
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+ struct axd_desc_ctrl *desc_ctrl = &axd_pipe->desc_ctrl;
+ struct axd_cmd *cmd = axd_pipe->cmd;
+ unsigned int pipe = axd_pipe->id;
+ char *ret_buf;
+ int ret, last;
+
+ debugbuf("*** Processing datain[%u] buffer ***\n", pipe);
+ do { /* we should have at least 1 desc to process */
+ ret_buf = desc_dequeue(desc_ctrl, NULL, &last, axd_pipe, 0);
+ if (!ret_buf)
+ /*
+ * This could happen if an interrupt occurs while this
+ * work is already running, causing us to run twice in a
+ * row unnecessarily. Not harmful, so just return.
+ */
+ return;
+#ifdef DEBUG_DIAG
+ inRecvCount[pipe]++;
+#endif
+ ret = axd_bufferq_put(desc_bufferq, ret_buf, -1);
+ if (ret)
+ return;
+ if (last) {
+ debugbuf("Received input[%u] EOS\n", pipe);
+ debugdiag("inSentCount[%u]= %u, inRecvCount[%u]= %u, write_size[%u]= %u\n",
+ pipe, inSentCount[pipe],
+ pipe, inRecvCount[pipe],
+ pipe, write_size[pipe]);
+ axd_cmd_inpipe_clear(cmd, pipe);
+ }
+ } while (!atomic_dec_and_test(&axd_pipe->intcount));
+
+ /* Do we need to send EOS? */
+ if (axd_get_flag(&axd_pipe->eos_flg))
+ axd_cmd_send_eos(axd_pipe);
+}
+
+/*
+ * This is the function executed by the workqueue to process return output
+ * pipes descriptors.
+ * Each pipe will have its own version of this function executed when dataout
+ * interrupt is received.
+ */
+static void out_desc_workq(struct work_struct *work)
+{
+ struct axd_pipe *axd_pipe = container_of(work, struct axd_pipe, work);
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+ struct axd_bufferq *user_bufferq = &axd_pipe->user_bufferq;
+ struct axd_desc_ctrl *desc_ctrl = &axd_pipe->desc_ctrl;
+ char *ret_buf;
+ unsigned int buf_size;
+ int ret, last;
+
+ debugbuf("*** Processing dataout[%u] buffer ***\n", axd_pipe->id);
+ do { /* we should have at least 1 desc to process */
+ ret_buf = desc_dequeue(desc_ctrl,
+ &buf_size, &last, axd_pipe, 1);
+ if (!ret_buf || axd_get_flag(&axd_pipe->eos_flg)) {
+ /*
+ * This could happen if an interrupt occurs while this
+ * work is already running, causing us to run twice in a
+ * row unnecessarily. Not harmful, so just return.
+ *
+ * OR if we prime up the output bufferq a tad too much
+ * we could end up with extra buffers after eos is
+ * reached, in this case we shouldn't process these
+ * extra buffers and just return.
+ */
+ return;
+ }
+#ifdef DEBUG_DIAG
+ outRecvCount[axd_pipe->id]++;
+ recv_size[axd_pipe->id] += buf_size;
+#endif
+ if (likely(!axd_get_flag(&axd_pipe->discard_flg))) {
+ if (last) {
+ debugbuf("Received output[%u] EOS\n",
+ axd_pipe->id);
+
+ axd_set_flag(&axd_pipe->eos_flg, EOS_REACHED);
+ }
+ ret = axd_bufferq_put(user_bufferq, ret_buf, buf_size);
+ if (ret)
+ return;
+ } else { /* drop all buffers until EOS is reached */
+ if (last) {
+ debugbuf("Received output[%u] EOS - discard\n",
+ axd_pipe->id);
+ axd_set_flag(&axd_pipe->eos_flg, EOS_REACHED);
+ axd_cmd_output_eos_reached(axd_pipe->cmd,
+ axd_pipe->id);
+ return;
+ }
+ ret = axd_bufferq_put(desc_bufferq, ret_buf, -1);
+ if (ret)
+ return;
+ axd_output_prime_up(axd_pipe);
+ }
+ } while (!atomic_dec_and_test(&axd_pipe->intcount));
+}
+
+/* Send a stream flush command to firmware */
+static int axd_flush_input_stream(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ struct axd_memory_map __iomem *message = cmd->message;
+ struct mutex *cm_lock = &cmd->cm_lock;
+ int ret;
+
+ mutex_lock(cm_lock);
+ if (axd_get_flag(&cmd->fw_stopped_flg)) {
+ mutex_unlock(cm_lock);
+ return -1;
+ }
+ axd_set_flag(&cmd->response_flg, 0);
+ iowrite32(AXD_CTRL_CMD_FLUSH, &message->control_command);
+ iowrite32(pipe, &message->control_data);
+ axd_ctrl_kick(message);
+ ret = wait_event_timeout(cmd->wait,
+ axd_get_flag(&cmd->response_flg) != 0, CMD_TIMEOUT);
+ mutex_unlock(cm_lock);
+ if (!ret) {
+ dev_warn(axd->inputdev[pipe], "failed to flush input stream\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int axd_flush_output_stream(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ struct axd_memory_map __iomem *message = cmd->message;
+ struct mutex *cm_lock = &cmd->cm_lock;
+ int ret;
+
+ mutex_lock(cm_lock);
+ if (axd_get_flag(&cmd->fw_stopped_flg)) {
+ mutex_unlock(cm_lock);
+ return -1;
+ }
+ axd_set_flag(&cmd->response_flg, 0);
+ iowrite32(AXD_CTRL_CMD_FLUSH, &message->control_command);
+ iowrite32(pipe + AXD_MAX_PIPES, &message->control_data);
+ axd_ctrl_kick(message);
+ ret = wait_event_timeout(cmd->wait,
+ axd_get_flag(&cmd->response_flg) != 0, CMD_TIMEOUT);
+ mutex_unlock(cm_lock);
+ if (!ret) {
+ dev_warn(axd->outputdev[pipe], "failed to flush output stream\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* Send a reset buffer descriptor commands to firmware - input */
+static int axd_reset_input_bd(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ struct axd_memory_map __iomem *message = cmd->message;
+ struct mutex *cm_lock = &cmd->cm_lock;
+ int ret;
+
+ mutex_lock(cm_lock);
+ if (axd_get_flag(&cmd->fw_stopped_flg)) {
+ mutex_unlock(cm_lock);
+ return -1;
+ }
+ axd_set_flag(&cmd->response_flg, 0);
+ iowrite32(AXD_CTRL_CMD_RESET_BD, &message->control_command);
+ iowrite32(pipe, &message->control_data);
+ axd_ctrl_kick(message);
+ ret = wait_event_timeout(cmd->wait,
+ axd_get_flag(&cmd->response_flg) != 0, CMD_TIMEOUT);
+ mutex_unlock(cm_lock);
+ if (!ret) {
+ dev_warn(axd->inputdev[pipe], "failed to reset input buffer descriptors\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* Send a reset buffer descriptor commands to firmware - output */
+static int axd_reset_output_bd(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ struct axd_memory_map __iomem *message = cmd->message;
+ struct mutex *cm_lock = &cmd->cm_lock;
+ int ret;
+
+ mutex_lock(cm_lock);
+ if (axd_get_flag(&cmd->fw_stopped_flg)) {
+ mutex_unlock(cm_lock);
+ return -1;
+ }
+ axd_set_flag(&cmd->response_flg, 0);
+ iowrite32(AXD_CTRL_CMD_RESET_BD, &message->control_command);
+ iowrite32(pipe + AXD_MAX_PIPES, &message->control_data);
+ axd_ctrl_kick(message);
+ ret = wait_event_timeout(cmd->wait,
+ axd_get_flag(&cmd->response_flg) != 0, CMD_TIMEOUT);
+ mutex_unlock(cm_lock);
+ if (!ret) {
+ dev_warn(axd->outputdev[pipe], "failed to reset output buffer descriptors\n");
+ return -1;
+ }
+ return 0;
+}
+/* Send a reset pipe command to the firmware */
+int axd_cmd_reset_pipe(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ struct axd_memory_map __iomem *message = cmd->message;
+ struct mutex *cm_lock = &cmd->cm_lock;
+ int ret;
+
+ mutex_lock(cm_lock);
+ if (axd_get_flag(&cmd->fw_stopped_flg)) {
+ mutex_unlock(cm_lock);
+ return -1;
+ }
+ axd_set_flag(&cmd->response_flg, 0);
+ iowrite32(AXD_CTRL_CMD_RESET_PIPE, &message->control_command);
+ iowrite32(pipe, &message->control_data);
+ axd_ctrl_kick(message);
+ ret = wait_event_timeout(cmd->wait,
+ axd_get_flag(&cmd->response_flg) != 0, CMD_TIMEOUT);
+ mutex_unlock(cm_lock);
+ if (!ret) {
+ dev_warn(axd->ctrldev[0], "failed to reset pipe%d", pipe);
+ return -1;
+ }
+ return 0;
+}
+
+/* Sends a dummy buffer indicating EOS to a pipe */
+static void axd_cmd_send_eos(struct axd_pipe *axd_pipe)
+{
+ struct axd_dev *axd = container_of(axd_pipe->cmd, struct axd_dev, cmd);
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+ struct axd_desc_ctrl *desc_ctrl = &axd_pipe->desc_ctrl;
+ int ret;
+ char *p;
+
+ mutex_lock(&axd_pipe->eos_mutex);
+ /*
+ * If eos is cleared, then a previous call successfully sent it, nothing
+ * to do then, so exit.
+ */
+ if (!axd_get_flag(&axd_pipe->eos_flg))
+ goto out;
+
+ /* Only proceed if we know a buffer is available, don't block */
+ if (axd_bufferq_is_empty(desc_bufferq))
+ goto out;
+ p = axd_bufferq_take(desc_bufferq, NULL);
+ if (unlikely(IS_ERR(p)))
+ goto out;
+ ret = desc_enqueue(desc_ctrl, p, 0, 1, axd_pipe);
+ if (unlikely(ret)) {
+ /* shouldn't happen, print a warning */
+ dev_warn(axd->inputdev[axd_pipe->id], "Warning, failed to enqueue buffer\n");
+ goto out;
+ }
+ /* enqueued successfully, inform the axd firmware */
+ axd_datain_kick(axd_pipe);
+ debugbuf("Sent input[%u] EOS\n", axd_pipe->id);
+ /*
+ * clear if eos sent successfully
+ */
+ axd_set_flag(&axd_pipe->eos_flg, 0);
+out:
+ mutex_unlock(&axd_pipe->eos_mutex);
+}
+
+/*
+ * Send as many buffers to the output pipe as possible.
+ * Keeping the firmware output buffer primed up prevents the firmware from
+ * getting deprived of buffers to fill.
+ */
+static void axd_output_prime_up(struct axd_pipe *axd_pipe)
+{
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+ struct axd_desc_ctrl *desc_ctrl = &axd_pipe->desc_ctrl;
+ unsigned int stride;
+ char *p;
+ int ret;
+
+ /*
+ * Try not to send too much. Make sure to stop as soon as we receive
+ * EOS.
+ */
+ if (axd_get_flag(&axd_pipe->eos_flg))
+ return;
+
+ /* prime up the output buffer as much as we can */
+ while (!axd_bufferq_is_empty(desc_bufferq)) {
+#ifdef DEBUG_DIAG
+ primeupCount[axd_pipe->id]++;
+#endif
+ p = axd_bufferq_take(desc_bufferq, &stride);
+ if (IS_ERR(p))
+ break;
+ ret = desc_enqueue(desc_ctrl, p, stride, 0, axd_pipe);
+ if (ret) {
+ /*
+ * error, return the buffer to the pool
+ */
+ axd_bufferq_put(desc_bufferq, p, -1);
+ break;
+ }
+ /* inform axd firmware */
+ axd_dataout_kick(axd_pipe);
+ }
+}
+
+/* Exported functions */
+/* Initialize the input pipe structure */
+void axd_cmd_inpipe_init(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+
+ axd_pipe->cmd = cmd;
+ axd_pipe->buf_desc = cmd->message->input[pipe].descriptors;
+ axd_pipe->id = pipe;
+
+ axd_set_flag(&axd_pipe->enabled_flg, 0);
+ axd_set_flag(&axd_pipe->eos_flg, 0);
+ mutex_init(&axd_pipe->eos_mutex);
+ atomic_set(&axd_pipe->intcount, 0);
+
+ /* default buffer size, could be changed through sysfs */
+ axd_pipe->buf_size = 1024*2;
+}
+
+/* Initialize the output pipe structure */
+void axd_cmd_outpipe_init(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+
+ axd_pipe->cmd = cmd;
+ axd_pipe->buf_desc = cmd->message->output[pipe].descriptors;
+ axd_pipe->id = pipe;
+
+ axd_set_flag(&axd_pipe->discard_flg, 0);
+ axd_set_flag(&axd_pipe->enabled_flg, 0);
+ axd_set_flag(&axd_pipe->eos_flg, 0);
+ atomic_set(&axd_pipe->intcount, 0);
+
+ /* default buffer size, could be changed through sysfs */
+ axd_pipe->buf_size = 1024*16;
+}
+
+/* Set up the IRQ handler and workqueues */
+int axd_cmd_install_irq(struct axd_cmd *cmd, unsigned int irqnum)
+{
+ int i;
+
+ cmd->in_workq = create_workqueue("axd_din_q");
+ if (!cmd->in_workq)
+ return -ENOMEM;
+ for (i = 0; i < AXD_MAX_PIPES; i++)
+ INIT_WORK(&cmd->in_pipes[i].work, in_desc_workq);
+ cmd->out_workq = create_workqueue("axd_dout_q");
+ if (!cmd->out_workq) {
+ destroy_workqueue(cmd->in_workq);
+ return -ENOMEM;
+ }
+ for (i = 0; i < AXD_MAX_PIPES; i++)
+ INIT_WORK(&cmd->out_pipes[i].work, out_desc_workq);
+ iowrite32(AXD_INT_KICK_DONE, &cmd->message->int_mask);
+ return request_irq(irqnum, axd_irq, 0, "axd_irq", cmd);
+}
+
+void axd_cmd_free_irq(struct axd_cmd *cmd, unsigned int irqnum)
+{
+ flush_workqueue(cmd->in_workq);
+ destroy_workqueue(cmd->in_workq);
+ flush_workqueue(cmd->out_workq);
+ destroy_workqueue(cmd->out_workq);
+ free_irq(irqnum, cmd);
+}
+
+/*
+ * Calculate the starting address of input pipe's buffers based on the
+ * information provided in firmware's header
+ */
+static char *axd_inpipe_datain_address(struct axd_cmd *cmd, unsigned int pipe,
+ unsigned int *num_avail_buffers)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+ unsigned long base_address = axd_cmd_get_datain_address(cmd);
+ unsigned long total_size = axd_cmd_get_datain_size(cmd);
+ unsigned long num_desc, offset;
+
+ /*
+ * Based on the defined axd_pipe->buf_size and number of input pipes
+ * supported by the firmware, we calculate the number of descriptors we
+ * need to use using this formula:
+ *
+ * axd_pipe->buf_size * num_desc = total_size / num_inputs
+ */
+ num_desc = total_size / (cmd->num_inputs * axd_pipe->buf_size);
+ if (num_desc > AXD_INPUT_DESCRIPTORS) {
+ num_desc = AXD_INPUT_DESCRIPTORS;
+ } else if (num_desc == 0) {
+ dev_err(axd->inputdev[pipe],
+ "Error: input buffer element size is too large\n");
+ return NULL;
+ }
+ offset = (total_size / cmd->num_inputs) * pipe;
+ if (num_avail_buffers)
+ *num_avail_buffers = num_desc;
+
+ return (char *)(base_address + offset);
+}
+
+static int axd_cmd_inpipe_buffers_init(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+ struct axd_desc_ctrl *desc_ctrl = &axd_pipe->desc_ctrl;
+ struct axd_buffer_desc __iomem *in_buf_desc = axd_pipe->buf_desc;
+ unsigned int num_avail_buffers;
+ char bufname[16];
+ int ret;
+
+ char *buf_address = axd_inpipe_datain_address(cmd, pipe,
+ &num_avail_buffers);
+ if (!buf_address)
+ return -EIO;
+
+ /* initialize descriptors & control semaphores/locks */
+ desc_init(desc_ctrl, in_buf_desc, AXD_INPUT_DESCRIPTORS);
+
+ /* initialize buffers */
+ sprintf(bufname, "in_bufferq[%u]", pipe);
+ ret = axd_bufferq_init(&axd_pipe->desc_bufferq, bufname, buf_address,
+ num_avail_buffers, axd_pipe->buf_size, cmd->nonblock);
+ return ret;
+}
+
+/* prepare inpipe for processing data */
+static int axd_cmd_inpipe_prepare(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+ int ret;
+
+ ret = axd_cmd_inpipe_buffers_init(cmd, pipe);
+ if (ret)
+ return ret;
+
+ atomic_set(&axd_pipe->intcount, 0);
+ axd_set_flag(&axd_pipe->enabled_flg, PIPE_STARTED);
+ if (axd_reset_input_bd(cmd, pipe))
+ goto out;
+ if (axd_flush_input_stream(cmd, pipe))
+ goto out;
+ if (axd_cmd_input_set_enabled(cmd, pipe, 1))
+ goto out;
+
+ /* Set PTS values for streams received without sync data */
+ axd_pipe->current_ts_high = -1;
+ axd_pipe->current_ts_low = -1;
+
+ return 0;
+out:
+ axd_set_flag(&axd_pipe->enabled_flg, 0);
+ return -EIO;
+}
+
+/* Start processing data on input pipe @pipe */
+int axd_cmd_inpipe_start(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+ int ret;
+
+ /*
+ * If enabled is locked, it means that the firmware is still busy
+ * processing buffers until EOS is reached. Tell to try again shortly.
+ */
+ if (axd_get_flag(&axd_pipe->enabled_flg))
+ return -EAGAIN;
+
+ debugbuf("Starting input[%u]\n", pipe);
+ ret = axd_cmd_inpipe_prepare(cmd, pipe);
+ if (ret)
+ return ret;
+ axd_pipe->tsk = current;
+#ifdef DEBUG_DIAG
+ inSentCount[pipe] = 0;
+ inRecvCount[pipe] = 0;
+ write_size[pipe] = 0;
+#endif
+ return 0;
+}
+
+/* Stop processing data on input pipe @pipe */
+void axd_cmd_inpipe_stop(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+
+ debugbuf("Stopping input[%u]\n", pipe);
+ /*
+ * If we haven't sent any data to the firmware, then clear ourselves
+ * immediately without having to send EOS which could never return.
+ */
+ if (axd_get_flag(&axd_pipe->discard_flg)) {
+ /*
+ * Setting eos indicates that an eos buffer need to be sent. In
+ * some cases (ie: error occurs in the application), the buffer
+ * queue would be full and eos would fail to send. When an
+ * interrupt is received then and a buffer becomes free, we
+ * send eos buffer if the eos flag is set.
+ */
+ axd_set_flag(&axd_pipe->eos_flg, EOS_REACHED);
+ axd_cmd_send_eos(axd_pipe);
+ } else {
+ axd_cmd_inpipe_clear(cmd, pipe);
+ }
+ axd_pipe->tsk = NULL;
+}
+
+/* clears input pipe so that it can be prepared to start again */
+static void axd_cmd_inpipe_clear(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+
+ /* disable input and clear buffers */
+ axd_cmd_input_set_enabled(cmd, pipe, 0);
+ axd_bufferq_clear(&axd_pipe->desc_bufferq);
+ /*
+ * NOTE: disabling the enabled flag must be done at the end to make sure
+ * that the input device can't be opened again before everything else is
+ * cleared up properly. There was a race where setting enabled to 0
+ * before clearing bufferq caused a crash as the device could be opened
+ * after the flag is disabled but before the bufferq is cleared so the
+ * bufferq would be setup then cleared again causing wrong memory access
+ * later when reading.
+ */
+ axd_set_flag(&axd_pipe->enabled_flg, 0);
+ axd_set_flag(&axd_pipe->discard_flg, 0);
+}
+
+/* Reset input pipe to starting state - for error recovery */
+void axd_cmd_inpipe_reset(struct axd_cmd *cmd, unsigned int pipe)
+{
+ axd_cmd_inpipe_clear(cmd, pipe);
+}
+
+/* Is the input pipe active? */
+int axd_cmd_inpipe_active(struct axd_cmd *cmd, unsigned int pipe)
+{
+ int state = axd_get_flag(&cmd->in_pipes[pipe].enabled_flg);
+ return state == PIPE_STARTED || state == PIPE_RUNNING;
+}
+
+/*
+ * Calculate the starting address of output pipe's buffers based on the
+ * information provided in firmware's header
+ */
+static char *axd_outpipe_dataout_address(struct axd_cmd *cmd, unsigned int pipe,
+ unsigned int *num_avail_buffers)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+ unsigned long base_address = axd_cmd_get_dataout_address(cmd);
+ unsigned long total_size = axd_cmd_get_dataout_size(cmd);
+ unsigned long num_desc, offset;
+
+ /*
+ * Based on the defined axd_pipe->buf_size and number of output pipes
+ * supported by the firmware, we calculate the number of descriptors we
+ * need to use using this formula:
+ *
+ * axd_pipe->buf_size * num_desc = total_size / num_outputs
+ */
+ num_desc = total_size / (cmd->num_outputs * axd_pipe->buf_size);
+ if (num_desc > AXD_OUTPUT_DESCRIPTORS) {
+ num_desc = AXD_OUTPUT_DESCRIPTORS;
+ } else if (num_desc == 0) {
+ dev_err(axd->outputdev[pipe], "Error: output buffer element size is too large\n");
+ return NULL;
+ }
+ offset = (total_size / cmd->num_outputs) * pipe;
+ if (num_avail_buffers)
+ *num_avail_buffers = num_desc;
+
+ return (char *)(base_address + offset);
+}
+
+static int axd_cmd_outpipe_buffers_init(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+ struct axd_desc_ctrl *desc_ctrl = &axd_pipe->desc_ctrl;
+ struct axd_buffer_desc __iomem *out_buf_desc = axd_pipe->buf_desc;
+ unsigned int num_avail_buffers;
+ char bufname[16];
+ int ret;
+
+ char *buf_address = axd_outpipe_dataout_address(cmd, pipe,
+ &num_avail_buffers);
+ if (!buf_address)
+ return -EIO;
+
+ /* initialise descriptors & control semaphores/locks */
+ desc_init(desc_ctrl, out_buf_desc, AXD_OUTPUT_DESCRIPTORS);
+ /* intialise buffers */
+ sprintf(bufname, "out_bufferq[%u]", pipe);
+ ret = axd_bufferq_init(&axd_pipe->desc_bufferq,
+ bufname, buf_address,
+ num_avail_buffers, axd_pipe->buf_size,
+ cmd->nonblock);
+ if (ret)
+ return ret;
+ sprintf(bufname, "user_bufferq[%u]", pipe);
+ ret = axd_bufferq_init_empty(&axd_pipe->user_bufferq,
+ bufname, num_avail_buffers,
+ axd_pipe->buf_size, cmd->nonblock);
+ if (ret) {
+ axd_bufferq_clear(&axd_pipe->desc_bufferq);
+ return ret;
+ }
+
+ return ret;
+}
+
+/* prepare outpipe for processing data */
+static int axd_cmd_outpipe_prepare(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+ int ret;
+
+ ret = axd_cmd_outpipe_buffers_init(cmd, pipe);
+ if (ret)
+ return ret;
+
+ atomic_set(&axd_pipe->intcount, 0);
+ axd_set_flag(&axd_pipe->enabled_flg, PIPE_STARTED);
+ axd_set_flag(&axd_pipe->eos_flg, 0);
+ if (axd_reset_output_bd(cmd, pipe))
+ goto out;
+ if (axd_cmd_output_set_enabled(cmd, pipe, 1))
+ goto out;
+ return 0;
+out:
+ axd_set_flag(&axd_pipe->enabled_flg, 0);
+ axd_set_flag(&axd_pipe->eos_flg, EOF_REACHED);
+ return -EIO;
+}
+
+/* Start processing data on output pipe @pipe */
+int axd_cmd_outpipe_start(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+ int ret;
+
+ debugbuf("Starting output[%u]\n", pipe);
+ /*
+ * Fully initialise only if enabled is unlocked.
+ * If enabled is locked, it means someone opened the device then
+ * closed it before reaching EOS. In this case, re-enable output to
+ * continue reading from where we stopped.
+ */
+ if (!axd_get_flag(&axd_pipe->enabled_flg)) {
+ ret = axd_cmd_outpipe_prepare(cmd, pipe);
+ if (ret)
+ return ret;
+ } else if (axd_get_flag(&axd_pipe->discard_flg)) {
+ /*
+ * we're still discarding some data from a previous call to
+ * stop, tell the user to try again shortly
+ */
+ return -EAGAIN;
+ }
+ axd_pipe->tsk = current;
+#ifdef DEBUG_DIAG
+ outSentCount[pipe] = 0;
+ outRecvCount[pipe] = 0;
+ primeupCount[pipe] = 0;
+ read_size[pipe] = 0;
+ recv_size[pipe] = 0;
+#endif
+ return 0;
+}
+
+/* Stop processing data on output pipe @pipe */
+void axd_cmd_outpipe_stop(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+ struct axd_bufferq *user_bufferq = &axd_pipe->user_bufferq;
+ char *ret_buf;
+
+ debugbuf("Stopping output[%u]\n", pipe);
+ axd_pipe->tsk = NULL;
+ if (axd_get_flag(&cmd->discard_flg) &&
+ axd_get_flag(&axd_pipe->enabled_flg)) {
+ /* Is there anything to discard? */
+ if (axd_get_flag(&axd_pipe->enabled_flg) == PIPE_STARTED) {
+ /*
+ * nothing to clear up too, just disable the input so
+ * we'd initialise ourselves properly again on next
+ * start.
+ */
+ axd_set_flag(&axd_pipe->enabled_flg, 0);
+ return;
+ }
+ axd_set_flag(&axd_pipe->discard_flg, 1);
+
+ if (axd_pipe->cur_buf)
+ axd_bufferq_put(desc_bufferq, axd_pipe->cur_buf, -1);
+
+ while (!axd_bufferq_is_empty(user_bufferq)) {
+ ret_buf = axd_bufferq_take(user_bufferq, NULL);
+ axd_bufferq_put(desc_bufferq, ret_buf, -1);
+ }
+
+ if (axd_get_flag(&axd_pipe->eos_flg) == EOS_REACHED) {
+ axd_cmd_output_eos_reached(cmd, pipe);
+ return;
+ }
+
+ axd_output_prime_up(axd_pipe);
+
+ }
+
+}
+
+static void axd_cmd_outpipe_clear(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+ /*
+ * unlock enabled to fully intialise next time we're
+ * opened.
+ */
+ axd_flush_output_stream(cmd, pipe);
+ axd_bufferq_clear(&axd_pipe->desc_bufferq);
+ axd_bufferq_clear(&axd_pipe->user_bufferq);
+ axd_cmd_output_set_enabled(cmd, pipe, 0);
+ axd_set_flag(&axd_pipe->enabled_flg, 0);
+ axd_set_flag(&axd_pipe->discard_flg, 0);
+ axd_pipe->cur_buf = NULL;
+ axd_pipe->cur_buf_size = 0;
+ axd_pipe->cur_buf_offset = 0;
+}
+
+/* Reset output pipe to starting state - for error recovery */
+void axd_cmd_outpipe_reset(struct axd_cmd *cmd, unsigned int pipe)
+{
+ axd_cmd_outpipe_clear(cmd, pipe);
+}
+
+/*
+ * Send a buffer to input @pipe
+ *
+ * Returns number of bytes sent, or negative error number.
+ */
+int axd_cmd_send_buffer(struct axd_cmd *cmd, unsigned int pipe,
+ const char __user *buf, unsigned int size)
+{
+ struct axd_dev *axd = container_of(cmd, struct axd_dev, cmd);
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+ struct axd_desc_ctrl *desc_ctrl = &axd_pipe->desc_ctrl;
+ unsigned int stride;
+ int ret = 0;
+ int written = 0;
+ int diff;
+ unsigned int cp_size;
+ char *p;
+
+ /*
+ * Before if we had no data buffer sent to the firmware EOS flag was
+ * sent perfect through, but now we shouldn't send EOS flag if
+ * no data was sent to the firmware. We use the discard variable to
+ * flag if we need to send the EOS at stop or not.
+ * see axd_cmd_inpipe_stop()
+ * NOTE: discard_flg for input pipe is different than discard_flg for
+ * output pipe.
+ */
+ if (unlikely(!axd_get_flag(&axd_pipe->discard_flg)))
+ axd_set_flag(&axd_pipe->discard_flg, 1);
+
+ debugbuf("Writing %u bytes [%u]\n", size, pipe);
+ while (written < size) {
+ /*
+ * There's a one to one mapping between the desc buffers and the
+ * descriptors owned by the driver. If the descriptors are
+ * empty, we'll sleep in here and when we wake up/proceed we are
+ * guaranteed that we will enqueue a descriptor successfully
+ */
+ p = axd_bufferq_take(desc_bufferq, &stride);
+ if (IS_ERR(p)) {
+ ret = PTR_ERR(p);
+ goto out;
+ }
+ diff = size - written;
+ cp_size = diff < stride ? diff : stride;
+ ret = copy_from_user(p, buf, cp_size);
+ if (ret) {
+ ret = -EFAULT;
+ goto out;
+ }
+ ret = desc_enqueue(desc_ctrl, p, cp_size, 0, axd_pipe);
+ if (unlikely(ret)) {
+ /* shouldn't happen, print a warning */
+ dev_warn(axd->inputdev[pipe], "Warning, failed to enqueue buffer\n");
+ goto out;
+ }
+ /* enqueued successfully, inform the axd firmware */
+ axd_datain_kick(axd_pipe);
+ written += cp_size;
+ buf += cp_size;
+
+ /*
+ * A time-based stream frame with PTS might have to be split
+ * over multiple buffers. We should only provide the PTS for
+ * the first buffer. The rest should have the PTS invalidated.
+ */
+ axd->cmd.in_pipes[pipe].current_ts_high = -1;
+ axd->cmd.in_pipes[pipe].current_ts_low = -1;
+ }
+out:
+ if (written) {
+#ifdef DEBUG_DIAG
+ write_size[pipe] += written;
+#endif
+ return written;
+ }
+ return ret;
+}
+
+void axd_cmd_send_buffer_abort(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->in_pipes[pipe];
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+
+ axd_bufferq_abort_take(desc_bufferq);
+}
+
+static void axd_cmd_output_eos_reached(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+
+ /* display diag info only if chan is enabled */
+ if (axd_get_flag(&axd_pipe->enabled_flg)) {
+ debugbuf("Output[%u] EOS reached\n", pipe);
+ debugdiag("outSentCount[%u]= %u, outRecvCount[%u]= %u, read_size[%u]= %u\n",
+ pipe, outSentCount[pipe], pipe, outRecvCount[pipe],
+ pipe, read_size[pipe]);
+ debugdiag("primeupCount[%u]= %u, recv_size[%u]= %u\n",
+ pipe, primeupCount[pipe], pipe, recv_size[pipe]);
+
+ /* All buffers are read, clear them. */
+ axd_cmd_outpipe_clear(cmd, pipe);
+ }
+}
+
+/*
+ * Receive a buffer from output @pipe
+ *
+ * The logic in here is that buffers we can copy from are in user_bufferq which
+ * is filled when we get an interrupt that the axd firmware filled them up.
+ * desc_bufferq holds the buffers are yet to be serviced by the firmware.
+ *
+ * Returns number of bytes received, or negative error number.
+ */
+int axd_cmd_recv_buffer(struct axd_cmd *cmd, unsigned int pipe,
+ char __user *buf, unsigned int size)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+ struct axd_bufferq *user_bufferq = &axd_pipe->user_bufferq;
+ int ret = 0;
+ int read = 0;
+ int diff;
+ unsigned int cp_size;
+ unsigned int cur_buf_size, cur_buf_offset;
+ char *cur_buf = axd_pipe->cur_buf;
+
+ if (axd_get_flag(&axd_pipe->eos_flg) == EOF_REACHED) {
+ axd_cmd_output_eos_reached(cmd, pipe);
+ return 0;
+ }
+
+ axd_output_prime_up(axd_pipe);
+
+ debugbuf("Reading %u bytes [%u]\n", size, pipe);
+ while (read < size) {
+ cur_buf_size = axd_pipe->cur_buf_size;
+ cur_buf_offset = axd_pipe->cur_buf_offset;
+ if (cur_buf_size) {
+ /*
+ * Current buffer points to the current user buffer
+ * we're holding and reading from. We keep hold into it
+ * until it is completely read. The logic is done in
+ * this way because the likelihood of this buffer to be
+ * larger than the read count is quite high if not the
+ * normal case everytime a read is issued.
+ */
+ diff = size - read;
+ cp_size = diff < cur_buf_size ? diff : cur_buf_size;
+ ret = copy_to_user(buf, cur_buf+cur_buf_offset,
+ cp_size);
+ if (ret)
+ goto out;
+ read += cp_size;
+ buf += cp_size;
+ axd_pipe->cur_buf_offset += cp_size;
+ axd_pipe->cur_buf_size -= cp_size;
+#ifdef DEBUG_DIAG
+ read_size[pipe] += cp_size;
+#endif
+ } else {
+ /*
+ * Current user buffer is completely read, return it to
+ * the desc_bufferq and take another user buffer.
+ * Note that we will sleep on either putting or taking
+ * from the buffer if we're full/empty. ISR should
+ * fill our user buffer once more are available.
+ */
+ if (cur_buf) {
+ ret = axd_bufferq_put(desc_bufferq, cur_buf, -1);
+ if (ret)
+ goto out;
+ if (axd_bufferq_is_empty(user_bufferq) &&
+ axd_get_flag(&axd_pipe->eos_flg)) {
+ /* send EOF on next read */
+ axd_set_flag(&axd_pipe->eos_flg,
+ EOF_REACHED);
+ /*
+ * Normally, we only need to clear up
+ * if read is 0. But, if the application
+ * is keeping track of where the stream
+ * ends, it might try to close the
+ * output pipe before the EOF is read.
+ * In this case, then the driver would
+ * lock up. Instead, we always clear up
+ * here to avoid this.
+ */
+ axd_cmd_output_eos_reached(cmd, pipe);
+ goto out;
+ }
+ axd_output_prime_up(axd_pipe);
+ }
+ cur_buf = axd_bufferq_take(user_bufferq, &cp_size);
+ if (IS_ERR(cur_buf)) {
+ axd_pipe->cur_buf = NULL;
+ axd_pipe->cur_buf_offset = 0;
+ axd_pipe->cur_buf_size = 0;
+ /*
+ * if EOS is set and we get an error from
+ * bufferq_take then it is because we received a
+ * zero byte buffer with a EOS flag set (From
+ * the firmware), in this instance we just
+ * return EOF instead of the error code
+ * (ERESTARTSYS)
+ */
+ if (axd_get_flag(&axd_pipe->eos_flg)) {
+ axd_set_flag(&axd_pipe->eos_flg,
+ EOF_REACHED);
+ ret = 0;
+ axd_cmd_output_eos_reached(cmd, pipe);
+ } else {
+ ret = PTR_ERR(cur_buf);
+ }
+ goto out;
+ }
+ axd_pipe->cur_buf_offset = 0;
+ axd_pipe->cur_buf_size = cp_size;
+ axd_pipe->cur_buf = cur_buf;
+ }
+ }
+out:
+ if (read) {
+ axd_set_flag(&axd_pipe->enabled_flg, PIPE_RUNNING);
+ return read;
+ }
+ return ret;
+}
+
+void axd_cmd_recv_buffer_abort(struct axd_cmd *cmd, unsigned int pipe)
+{
+ struct axd_pipe *axd_pipe = &cmd->out_pipes[pipe];
+ struct axd_bufferq *desc_bufferq = &axd_pipe->desc_bufferq;
+ struct axd_bufferq *user_bufferq = &axd_pipe->user_bufferq;
+
+ axd_bufferq_abort_put(desc_bufferq);
+ axd_bufferq_abort_take(user_bufferq);
+}
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists