blob: 660ad432f8477e1962fcb2af748b65e9db2132b5 [file] [log] [blame]
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include "avi_compat.h"
#include "avi_segment.h"
#include "avi_dma.h"
#include "avi_debug.h"
#include "avi_scaler.h"
#include "avi_pixfmt.h"
#ifdef DEBUG
#define dprintk(format_, args...) \
pr_debug("avi segment: " format_, ##args)
#define dprintk_s(s_, format_, args...) \
dev_dbg((s_)->owner, "[%s] " format_, (s_)->id, ##args)
#else /* DEBUG */
#define dprintk(format_, args...) do {} while(0)
#define dprintk_s(s_, format_, args...) (void)s_
#endif /* DEBUG */
struct avi_segment_index {
struct mutex lock;
struct list_head segment_list;
};
static struct avi_segment_index avi_segment_index = {
.lock = __MUTEX_INITIALIZER(avi_segment_index.lock),
.segment_list = LIST_HEAD_INIT(avi_segment_index.segment_list),
};
/*
* This function contains the heuristic used to build the segments. Hopefully it
* should handle all the cases and the client application won't have to bother
* tweaking the order of the nodes within a segment.
*
* This function is called with avi_segment_index.lock held.
*/
static int avi_segment_satisfy_caps(struct avi_segment *s)
{
unsigned long caps = s->caps;
unsigned long missing = 0;
int ret = 0;
int i = 0;
unsigned long flags;
struct avi_node *n;
/* First some basic (and probably non-exhaustive) sanity checks on the
* capabilities. Some combinations make no sense (like having both a
* DMA_OUT and an LCD or multiple LCDs for instance) */
BUG_ON(!caps);
BUG_ON(avi_cap_check(caps));
/* At the moment each capability has an associated node. This might
* change later on.
*/
/* Play nicely with legacy code. We don't want it to allocate nodes in
* our back. */
spin_lock_irqsave(&avi_ctrl.cfg_lck, flags);
/* Planar special case:
* If requested caps contain DMA_IN, SCALER and PLANAR
* we try to find a "PLANAR" node (DMA_IN)
*/
s->dma_in_planar = NULL;
if (caps & AVI_CAP_PLANAR) {
n = avi_cap_get_node(AVI_CAP_PLANAR);
if (n) {
n->busy = 1;
n->assigned_caps = AVI_CAP_PLANAR;
}
#ifdef AVI_BACKWARD_COMPAT
else if ((avi_get_revision() < AVI_REVISION_3))
/* for AVI revision 3, DMA IN FIFO works with
* semi-planar pixel formats instead of revision 1 and 2
* where they are bugged. So in this case, a second DMA
* IN FIFO is mandatory for managing semi-planar */
missing |= (AVI_CAP_PLANAR);
#endif /* AVI_BACKWARD_COMPAT */
s->dma_in_planar = n;
caps &= ~(AVI_CAP_PLANAR);
}
/* Unlock CFG to allow kzalloc() below with GFP_KERNEL. */
spin_unlock_irqrestore(&avi_ctrl.cfg_lck, flags);
/*
* The Hamming weight is the number of bits set in the int.
*/
s->nodes_nr = hweight_long(caps);
s->nodes = kzalloc(s->nodes_nr * sizeof(*s->nodes), GFP_KERNEL);
if (!s->nodes) {
if (s->dma_in_planar) {
s->dma_in_planar->assigned_caps = 0;
s->dma_in_planar->busy = 0;
}
ret = -ENOMEM;
return ret;
}
/* Play nicely with legacy code. We don't want it to allocate nodes in
* our back. */
spin_lock_irqsave(&avi_ctrl.cfg_lck, flags);
#define AVI_SEGMENT_FIND_NODE(cap) do { \
if (caps & (cap)) { \
n = avi_cap_get_node(cap); \
if (!n) { \
missing |= (cap); \
} else { \
n->busy = 1; \
n->assigned_caps = cap; \
} \
s->nodes[i++] = n; \
caps &= ~(cap); \
} \
} while(0)
AVI_SEGMENT_FIND_NODE(AVI_CAP_CAM_0);
AVI_SEGMENT_FIND_NODE(AVI_CAP_CAM_1);
AVI_SEGMENT_FIND_NODE(AVI_CAP_CAM_2);
AVI_SEGMENT_FIND_NODE(AVI_CAP_CAM_3);
AVI_SEGMENT_FIND_NODE(AVI_CAP_CAM_4);
AVI_SEGMENT_FIND_NODE(AVI_CAP_CAM_5);
AVI_SEGMENT_FIND_NODE(AVI_CAP_DMA_IN);
AVI_SEGMENT_FIND_NODE(AVI_CAP_STATS_BAYER_0);
AVI_SEGMENT_FIND_NODE(AVI_CAP_STATS_BAYER_1);
/* Selecting one part of ISP (Bayer or YUV)
* implies select of all modules
* It does not make sense to take only a part of the ISP-chain
* See AVI_CAPS_ISP in avi_cap.h
* For the ISP chain, the order is fixed
*/
if (caps & (AVI_CAP_ISP_CHAIN_BAYER|AVI_CAP_ISP_CHAIN_YUV)) {
AVI_SEGMENT_FIND_NODE(AVI_CAP_ISP_CHAIN_BAYER);
AVI_SEGMENT_FIND_NODE(AVI_CAP_GAM);
AVI_SEGMENT_FIND_NODE(AVI_CAP_CONV);
AVI_SEGMENT_FIND_NODE(AVI_CAP_STATS_YUV);
AVI_SEGMENT_FIND_NODE(AVI_CAP_ISP_CHAIN_YUV);
}
AVI_SEGMENT_FIND_NODE(AVI_CAP_SCAL);
AVI_SEGMENT_FIND_NODE(AVI_CAP_BUFFER);
AVI_SEGMENT_FIND_NODE(AVI_CAP_CONV);
/* In P7R1 the gamma and the LCD are directly connected in hardware,
* therefore we have to put the gamma just before the LCD here if we
* want to remain compatible with this version */
AVI_SEGMENT_FIND_NODE(AVI_CAP_GAM);
AVI_SEGMENT_FIND_NODE(AVI_CAP_LCD_0);
AVI_SEGMENT_FIND_NODE(AVI_CAP_LCD_1);
AVI_SEGMENT_FIND_NODE(AVI_CAP_DMA_OUT);
/* We shouldn't have unhandled capabilities at that point. If we have it
* means our code is incomplete or the caller gave us garbage (undefined
* caps in the bitfield) */
BUG_ON(caps);
if (missing) {
int j;
for (j = 0; j < s->nodes_nr; j++)
if (s->nodes[j]) {
s->nodes[j]->assigned_caps = 0;
s->nodes[j]->busy = 0;
}
kfree(s->nodes);
if (s->dma_in_planar) {
s->dma_in_planar->assigned_caps = 0;
s->dma_in_planar->busy = 0;
}
/* update s->caps with the unsatisfied capabilities */
s->caps = missing;
ret = -ENODEV;
goto unlock;
}
/* We should have filled the entire array by that point */
BUG_ON(i != s->nodes_nr);
unlock:
spin_unlock_irqrestore(&avi_ctrl.cfg_lck, flags);
return ret;
}
/* Set sink's source to src_id in the AVI interconnect. If src_id is 0 it
* disconnects the node. */
static void avi_segment_set_node_source(struct avi_node *inter,
unsigned sink_id,
unsigned src_id)
{
unsigned base;
unsigned shift;
u32 reg;
BUG_ON(src_id == AVI_SRCSINK_NONE);
/* We have 4 src_ids per 32 bit registers. */
base = avi_node_base(inter) + (sink_id & ~(3UL));
/* Now that we have the base we need to find the layout within
* the register */
shift = (sink_id % 4) * 8;
reg = AVI_READ(base);
/* We mask the previous value of the src id */
reg &= ~(0xffUL << shift);
reg |= src_id << shift;
AVI_WRITE(reg, base);
}
static inline void avi_segment_set_blender_src_id(struct avi_node *inter,
struct avi_node *blender,
unsigned input,
unsigned src_id)
{
unsigned sink_id = blender->sink_id + input;
BUG_ON(input >= 4);
avi_segment_set_node_source(inter, sink_id, src_id);
}
/* Connect src to a blender input. If src is NULL it disconnects the input. */
static inline void avi_segment_set_blender_source(struct avi_node *inter,
struct avi_node *blender,
unsigned input,
struct avi_segment *src)
{
unsigned src_id = 0;
if (src)
src_id = src->nodes[src->nodes_nr - 1]->src_id;
if (!src || src->layout.hidden) {
/* hide plane */
avi_setup_blender_input(blender, input,
0xffff, 0xffff,
0);
} else {
unsigned blender_y = src->layout.y;
if (src->output_format.interlaced)
/* If the stream is interlaced we have to use the offset
* in the field, not the full frame */
blender_y /= 2;
avi_setup_blender_input(blender, input,
src->layout.x, blender_y,
src->layout.alpha);
}
avi_segment_set_blender_src_id(inter, blender, input, src_id);
}
/* must be called with avi_ctrl.cfg_lck held */
static void avi_segment_connect_nodes(struct avi_segment *s)
{
struct avi_node *inter = avi_get_node(AVI_INTER_NODE);
struct avi_node *scaler;
int i;
avi_lock_node(inter);
for (i = 1; i < s->nodes_nr; i++)
avi_segment_set_node_source(inter,
s->nodes[i]->sink_id,
s->nodes[i - 1]->src_id);
if (s->dma_in_planar) {
scaler = avi_segment_get_node(s, AVI_CAP_SCAL);
BUG_ON(!scaler);
avi_segment_set_node_source(inter,
scaler->sink_id + 1,
s->dma_in_planar->src_id);
}
avi_unlock_node(inter);
}
/* Must be called with avi_ctrl.cfg_lck held */
static void avi_segment_disconnect_nodes(struct avi_segment *s)
{
struct avi_node *inter = avi_get_node(AVI_INTER_NODE);
int i;
avi_lock_node(inter);
if (s->nodes_nr)
for (i = 0; i < s->nodes_nr; i++)
avi_segment_set_node_source(inter,
s->nodes[i]->sink_id,
0);
if (s->dma_in_planar) {
struct avi_node *scaler = avi_segment_get_node(s, AVI_CAP_SCAL);
BUG_ON(!scaler);
avi_segment_set_node_source(inter,
scaler->sink_id + 1,
0);
}
avi_unlock_node(inter);
/* The node disconnection will only take place at the next clear, but
* since we're in the process of destroying the segment it won't happen
* any time soon. Therefore we force the interconnect change right
* here. */
avi_apply_node(inter);
}
/* Must be called with the index lock held */
struct avi_segment *avi_segment_find(const char *id)
{
struct avi_segment *s;
BUG_ON(!id || !*id);
list_for_each_entry(s, &avi_segment_index.segment_list, list)
if (strncmp(s->id, id, ARRAY_SIZE(s->id)) == 0)
return s;
/* not found */
return NULL;
}
EXPORT_SYMBOL(avi_segment_find);
/* Reconfigure all nodes in the segment to sane default values.
*
* Must be called with s->lock held. */
static void avi_segment_reset(struct avi_segment *s)
{
struct avi_node *n;
int i;
for (i = 0; i < s->nodes_nr; i++) {
n = s->nodes[i];
switch (n->type) {
case AVI_FIFO_NODE_TYPE:
{
struct avi_fifo_registers fifo_regs;
memset(&fifo_regs, 0, sizeof(fifo_regs));
if ((s->caps & AVI_CAP_DMA_IN) && i == 0)
/* DMA IN fifo */
fifo_regs.cfg.srctype = AVI_FIFO_CFG_DMA_TYPE;
else
fifo_regs.cfg.srctype = AVI_FIFO_CFG_VL_TYPE;
if ((s->caps & AVI_CAP_DMA_OUT) &&
i == (s->nodes_nr - 1))
/* DMA OUT fifo */
fifo_regs.cfg.dsttype = AVI_FIFO_CFG_DMA_TYPE;
else
fifo_regs.cfg.dsttype = AVI_FIFO_CFG_VL_TYPE;
fifo_regs.cfg.dstbitenc = 0x8; /* 8888 */
fifo_regs.cfg.srcbitenc = 0x8; /* 8888 */
fifo_regs.cfg.itline = 0xf;
fifo_regs.cfg.dithering = 1; /* MSB filling */
fifo_regs.swap0._register = AVI_FIFO_NULL_SWAP;
fifo_regs.swap1._register = AVI_FIFO_NULL_SWAP;
fifo_regs.timeout.pincr = 1;
fifo_regs.timeout.issuing_capability = 7;
fifo_regs.dmafxycfg.dmafxnb = 1;
fifo_regs.dmafxycfg.dmafynb = 1;
avi_fifo_set_registers(n, &fifo_regs);
if (s->dma_in_planar && i == 0)
avi_fifo_set_registers(s->dma_in_planar, &fifo_regs);
}
break;
case AVI_CONV_NODE_TYPE:
/* Put the identity matrix in the converter */
avi_setup_conv_colorspace(n,
AVI_NULL_CSPACE,
AVI_NULL_CSPACE);
break;
case AVI_GAM_NODE_TYPE:
/* Bypass the gamma */
avi_gam_setup(n, 1, 0, 0);
break;
case AVI_LCD_NODE_TYPE:
{
struct avi_lcd_regs lcd_regs;
memset(&lcd_regs, 0, sizeof(lcd_regs));
/* Default pixel colour. Ugly yellow. */
lcd_regs.dpd.dpd = 0xd4e523,
avi_lcd_set_registers(n, &lcd_regs);
}
break;
case AVI_CAM_NODE_TYPE:
{
struct avi_cam_registers cam_regs;
memset(&cam_regs, 0, sizeof(cam_regs));
avi_cam_set_registers(n, &cam_regs);
}
break;
case AVI_SCALROT_NODE_TYPE:
switch(n->node_id)
{
case AVI_SCAL00_NODE:/* scaler */
case AVI_SCAL10_NODE:
/* Setup scaler in bypass by default */
avi_scal_bypass(n);
break;
case AVI_ROT0_NODE:/* rotator */
case AVI_ROT1_NODE:
BUG();
break;
default:
BUG();
break;
}
break;
case AVI_ISP_CHAIN_BAYER_NODE_TYPE:
avi_isp_chain_bayer_bypass(n);
break;
case AVI_STATS_BAYER_NODE_TYPE:
/* Nop */
break;
case AVI_STATS_YUV_NODE_TYPE:
/* Nothing to be done, the module always forward the
* stream */
break;
case AVI_ISP_CHAIN_YUV_NODE_TYPE:
avi_isp_chain_yuv_bypass(n);
break;
default:
BUG();
}
}
}
/* Handle FIFO start address reconfiguration for interlaced video */
static inline void avi_segment_update_dma_field(struct avi_segment *s)
{
if (s->input_format.interlaced && (s->caps & AVI_CAP_DMA_IN)) {
struct avi_node *fifo0;
struct avi_node *fifo1;
dma_addr_t start0 = s->plane0_base;
dma_addr_t start1 = s->plane1_base;
if (s->cur_field == AVI_BOT_FIELD) {
/* Offset the beginning of the video buffer by one line
* to get the bottom field. Since line_size must already
* contain the offset to skip one line we must divide it
* by 2
*
* XXX I'm not really satisfied with this method, maybe
* the client driver should tell us where the bottom
* field is? The problem of course is that if we need to
* use the scaler to drop the useless field we *must* be
* in INTERLACED mode, ALTERNATE can't work. That
* constrains what we can do here. */
start0 += s->input_format.plane0.line_size / 2;
start1 += s->input_format.plane1.line_size / 2;
}
fifo0 = avi_segment_get_node(s, AVI_CAP_DMA_IN);
BUG_ON(fifo0 == NULL);
avi_fifo_set_dmasa(fifo0, start0, start1);
fifo1 = avi_segment_get_node(s, AVI_CAP_PLANAR);
if (fifo1)
avi_fifo_set_dmasa(fifo1, start0, start1);
}
}
static void avi_segment_update_field(struct avi_segment *s, enum avi_field *field)
{
s->cur_field = *field;
if (*field == AVI_NONE_FIELD)
/* Nothing left to do here */
return;
BUG_ON(*field != AVI_TOP_FIELD && *field != AVI_BOT_FIELD);
/* If the segment is displayed on an odd line we have to inverse the
* field for interlaced streams*/
if (s->layout.y % 2)
s->cur_field = (*field == AVI_BOT_FIELD) ?
AVI_TOP_FIELD : AVI_BOT_FIELD;
if (!s->input_format.interlaced && s->output_format.interlaced) {
/* We use the scaler to drop the useless field*/
struct avi_node *scal = avi_segment_get_node(s, AVI_CAP_SCAL);
BUG_ON(!scal);
avi_scal_set_field(scal,
s->input_format.height,
s->output_format.height,
s->cur_field);
}
avi_segment_update_dma_field(s);
}
/* I couldn't find a way to factorize these two function simply, so I use a
* macro instead... */
#define AVI_SEGMENT_PROPAGATE_IRQ(_dir, _count, _member) \
static void avi_segment_propagate_irq_##_dir(struct avi_segment *s, \
enum avi_field field) \
{ \
int i; \
\
for (i = 0; i < _count; i++) { \
struct avi_segment *next = s->_member[i]; \
\
if (next) { \
/* Each branch can have its own field it can \
* manipulate at will without impacting the \
* other branches */ \
enum avi_field nfield = field; \
unsigned status = 0; \
\
avi_segment_update_field(next, &nfield); \
\
if (next->active == AVI_SEGMENT_ACTIVE_ONESHOT)\
avi_segment_deactivate(next); \
\
if (next->irq_handler_f) \
status = next->irq_handler_f(next, \
&nfield); \
\
if (!(status & AVI_IRQ_HANDLED)) \
avi_segment_propagate_irq_##_dir( \
next, \
nfield); \
\
} \
} \
}
AVI_SEGMENT_PROPAGATE_IRQ(forward, AVI_SEGMENT_MAX_SINK, sink)
AVI_SEGMENT_PROPAGATE_IRQ(backward, AVI_SEGMENT_MAX_SRC, source)
static irqreturn_t avi_segment_irq_handler(int irq, void *priv)
{
struct avi_segment *s = priv;
enum avi_field field = AVI_NONE_FIELD;
int status = 0;
if (!avi_node_get_irq_flag(s->irq_node)) {
printk("stray IRQ\n");
return IRQ_NONE;
}
if (s->active == AVI_SEGMENT_ACTIVE_ONESHOT)
avi_segment_deactivate(s);
if (s->irq_handler_f)
status = s->irq_handler_f(s, &field);
if (!(status & AVI_IRQ_ACKED))
avi_ack_node_irq(s->irq_node);
if (!(status & AVI_IRQ_HANDLED)) {
avi_segment_update_field(s, &field);
avi_segment_propagate_irq_forward (s, field);
avi_segment_propagate_irq_backward(s, field);
}
return IRQ_HANDLED;
}
static int avi_segment_setup_irq(struct avi_segment *s)
{
BUG_ON(!s->irq_node);
avi_disable_node_irq(s->irq_node);
avi_ack_node_irq(s->irq_node);
return request_irq(avi_node_irq(s->irq_node),
&avi_segment_irq_handler,
0,
s->id, s);
}
static void avi_segment_free_irq(struct avi_segment *s)
{
BUG_ON(!s->irq_node);
avi_disable_node_irq(s->irq_node);
free_irq(avi_node_irq(s->irq_node), s);
}
void avi_segment_enable_irq(struct avi_segment *s)
{
BUG_ON(!s->irq_node);
avi_enable_node_irq(s->irq_node);
}
EXPORT_SYMBOL(avi_segment_enable_irq);
void avi_segment_disable_irq(struct avi_segment *s)
{
BUG_ON(!s->irq_node);
avi_disable_node_irq(s->irq_node);
}
EXPORT_SYMBOL(avi_segment_disable_irq);
void avi_segment_register_irq(struct avi_segment *s,
avi_segment_irq_handler_t f)
{
s->irq_handler_f = f;
}
EXPORT_SYMBOL(avi_segment_register_irq);
static const struct avi_segment_format avi_segment_default_format = {
.width = 0,
.height = 0,
.colorspace = AVI_NULL_CSPACE,
.pix_fmt = AVI_PIXFMT_INVALID,
};
static void avi_segment_free_nodes(struct avi_segment *s)
{
int i;
/* Mark the nodes as unused */
for (i = 0; i < s->nodes_nr; i++) {
struct avi_node *n = s->nodes[i];
n->assigned_caps = 0;
n->busy = 0;
}
if (s->dma_in_planar) {
s->dma_in_planar->assigned_caps = 0;
s->dma_in_planar->busy = 0;
}
}
static struct avi_segment *avi_segment_do_build(unsigned long *caps,
const char *id,
struct device *owner)
{
struct avi_segment_index *index = &avi_segment_index;
struct avi_segment *new;
struct avi_node *out_node;
int ret;
/* Make sure the AVI has been succesfully probed, otherwise we will
* crash when we attempt to access the registers. */
BUG_ON(!avi_probed());
mutex_lock(&index->lock);
if (avi_segment_find(id)) {
dprintk("duplicate segment %s\n", id);
ret = -EBUSY;
goto unlock;
}
new = kzalloc(sizeof(*new), GFP_KERNEL);
if (!new) {
dprintk("can't allocate segment struct for %s", id);
ret = -ENOMEM;
goto unlock;
}
new->caps = *caps;
new->owner = owner;
new->input_format = avi_segment_default_format;
new->layout.alpha = 0xff;
new->cur_field = AVI_NONE_FIELD;
/* if the provided id is less than ARRAY_SIZE(new->id) it feels the
* remaining bytes with \0. If it's bigger however the buffer will not
* be \0 terminated. Since the buffer has a static size we could live
* with it but in order to save us some trouble later I force the last
* byte to \0. It means we effectively lose one usable byte for the
* id. */
strncpy(new->id, id, ARRAY_SIZE(new->id));
new->id[ARRAY_SIZE(new->id) - 1] = '\0';
mutex_init(&new->lock);
ret = avi_segment_satisfy_caps(new);
if (ret) {
if (ret == -ENODEV) {
#ifdef DEBUG
char str[64];
avi_debug_format_caps(new->caps, str, sizeof(str));
dprintk_s(new, "can't satisfy caps: %s\n", str);
#endif /* DEBUG */
/* Copy the unsatisfied capabilities back to caps */
*caps = new->caps;
} else {
dprintk_s (new, "satisfy caps failed [%d]\n", ret);
}
goto free;
}
/* If we have a LCD, CAM or a DMA_OUT, let's setup its interrupt */
out_node = avi_segment_get_node(new,
AVI_CAPS_LCD_ALL |
AVI_CAPS_CAM_ALL |
AVI_CAP_DMA_OUT);
if (out_node) {
new->irq_node = out_node;
ret = avi_segment_setup_irq(new);
if (ret) {
dprintk_s(new,
"can't setup interrupt for node %s\n",
new->irq_node->name);
new->irq_node = NULL;
goto free_nodes;
}
}
/* Reconfigure all nodes to sane (and hopefully harmless) default values */
avi_segment_reset(new);
avi_segment_connect_nodes(new);
list_add_tail(&new->list, &index->segment_list);
mutex_unlock(&index->lock);
return new;
free_nodes:
avi_segment_free_nodes(new);
free:
kfree(new);
unlock:
mutex_unlock(&index->lock);
return ERR_PTR(ret);
}
struct avi_segment *avi_segment_build(unsigned long *caps,
const char *type,
int major,
int minor,
struct device *owner)
{
char id[AVI_SEGMENT_ID_LEN];
if (minor >= 0)
snprintf(id, sizeof(id), "%s.%d:%d",
type, major, minor);
else
snprintf(id, sizeof(id), "%s.%d",
type, major);
return avi_segment_do_build(caps, id, owner);
}
EXPORT_SYMBOL(avi_segment_build);
void avi_segment_gen_id(const char *base_id,
const char *nonce,
char gen_id[AVI_SEGMENT_ID_LEN])
{
size_t base_len = strlen(base_id);
/* We need space for two more chars: the dash and the trailing \0 */
size_t nonce_len = strlen(nonce) + 2;
size_t dst_len;
char *p;
/* limit the size of the nonce */
if (nonce_len > (AVI_SEGMENT_ID_LEN / 2))
nonce_len = (AVI_SEGMENT_ID_LEN / 2);
dst_len = base_len + nonce_len;
if (dst_len > AVI_SEGMENT_ID_LEN)
dst_len = AVI_SEGMENT_ID_LEN;
memset(gen_id, 0, AVI_SEGMENT_ID_LEN);
p = gen_id;
strncpy(p, base_id, AVI_SEGMENT_ID_LEN);
p += dst_len - nonce_len;
*p++ = '-';
memcpy(p, nonce, nonce_len - 2);
}
EXPORT_SYMBOL(avi_segment_gen_id);
int avi_segment_connected(struct avi_segment *s)
{
int i;
for (i = 0; i < AVI_SEGMENT_MAX_SRC; i++)
if (s->source[i])
return 1;
for (i = 0; i < AVI_SEGMENT_MAX_SINK; i++)
if (s->sink[i])
return 1;
return 0;
}
EXPORT_SYMBOL(avi_segment_connected);
int avi_segment_teardown(struct avi_segment *s)
{
int ret = 0;
/* Make sure the index is locked since we want to remove an element */
mutex_lock(&avi_segment_index.lock);
mutex_lock(&s->lock);
if (avi_segment_connected(s)) {
ret = -EBUSY;
goto unlock;
}
avi_segment_deactivate(s);
if (s->irq_node)
avi_segment_free_irq(s);
/* Reconfigure all nodes to sane (and hopefully harmless) default values */
avi_segment_reset(s);
spin_lock(&avi_ctrl.cfg_lck);
avi_segment_disconnect_nodes(s);
/* Mark the nodes as unused */
avi_segment_free_nodes(s);
list_del(&s->list);
spin_unlock(&avi_ctrl.cfg_lck);
mutex_unlock(&s->lock);
mutex_unlock(&avi_segment_index.lock);
kfree(s);
return 0;
unlock:
mutex_unlock(&s->lock);
mutex_unlock(&avi_segment_index.lock);
return ret;
}
EXPORT_SYMBOL(avi_segment_teardown);
int avi_take_segment(struct avi_segment *s, struct device *owner)
{
mutex_lock(&s->lock);
if (s->owner) {
mutex_unlock(&s->lock);
return -EBUSY;
}
s->owner = owner;
mutex_unlock(&s->lock);
return 0;
}
EXPORT_SYMBOL(avi_take_segment);
int avi_release_segment(struct avi_segment *s)
{
mutex_lock(&s->lock);
if (s->owner == NULL) {
mutex_unlock(&s->lock);
return -EINVAL;
}
/* Disown the segment and cleanup the old client context for good
* measure. */
s->owner = NULL;
s->priv = NULL;
s->irq_handler_f = NULL;
mutex_unlock(&s->lock);
return 0;
}
EXPORT_SYMBOL(avi_release_segment);
/* This function attempt to reconnect all sources of s, adding and removing
* blenders if necessary.
*
* Must be called with the segment's lock held and interrupts disabled */
static int avi_segment_reconfigure_connection(struct avi_segment *s)
{
int force_blender = 0;
int nsources = 0;
int ret = 0;
struct avi_node *inter = avi_get_node(AVI_INTER_NODE);
unsigned sink_id;
int nblenders = 0;
int i;
unsigned long cfg_flags;
struct avi_segment *dense_sources[AVI_SEGMENT_MAX_SRC] = { NULL };
BUG_ON(s->nodes_nr == 0);
sink_id = s->nodes[0]->sink_id;
/* count number of connected sources,
* compress segment sources into dense_sources */
for (i = 0; i < AVI_SEGMENT_MAX_SRC; i++) {
struct avi_segment *tmp;
tmp = s->source[i];
if (tmp != NULL) {
dense_sources[nsources] = tmp;
nsources ++;
}
}
if ((nsources == 1) && (dense_sources[0] != NULL)) {
/* Even if we only have one source we might need a
* blender if it's not fullscreen or needs to be moved
* around. */
if (dense_sources[0]->layout.x != 0 ||
dense_sources[0]->layout.y != 0 ||
dense_sources[0]->layout.hidden ||
dense_sources[0]->layout.alpha != 0xff ||
dense_sources[0]->output_format.width !=
s->input_format.width ||
dense_sources[0]->output_format.height !=
s->input_format.height)
force_blender = 1;
}
if (nsources <= 1)
nblenders = force_blender;
else if (nsources <= 4)
nblenders = 1;
else
nblenders = 2;
spin_lock_irqsave(&avi_ctrl.cfg_lck, cfg_flags);
for (i = 0; i < nblenders; i++)
if (s->blender[i] == NULL) {
/* We need a new blender node */
s->blender[i] = avi_cap_get_blender();
if (s->blender[i] == NULL) {
/* no more blenders available! */
ret = -ENODEV;
break;
}
/* We mark a different busy state in order to
* distinguish freshly obtained blenders and old ones.
* This is used to revert to previous state if an error
* occured. */
s->blender[i]->busy = 2;
}
if (ret) {
/* We couldn't get the blenders we wanted, rollback and make
* sure we revert to the initial state. */
while (i--)
if (s->blender[i]->busy == 2) {
s->blender[i]->busy = 0;
s->blender[i] = NULL;
}
goto unlock;
}
else {
/* All needed blenders are requested, so we no longer need to
* distinguish blenders */
for (i = 0; i < nblenders; i++)
if (s->blender[i]->busy == 2)
s->blender[i]->busy = 1;
}
avi_lock_node(inter);
if (nblenders == 0) {
unsigned src_id = 0;
if (dense_sources[0])
src_id = dense_sources[0]->
nodes[dense_sources[0]->nodes_nr - 1]->
src_id;
avi_segment_set_node_source(inter, sink_id, src_id);
} else if (nblenders == 1) {
/* Connect the blender */
avi_segment_set_node_source(inter, sink_id, s->blender[0]->src_id);
for (i = 0; i < 4; i++)
/* The blender's input are ordered from bottom to top
* (input 0 is under input 1 etc...). Our zorder is the
* other way around so we connect them starting from 3
* downwards. */
avi_segment_set_blender_source(inter,
s->blender[0],
3 - i,
dense_sources[i]);
} else if (nblenders == 2) {
/* Connect the blenders */
avi_segment_set_node_source(inter,
sink_id,
s->blender[0]->src_id);
/* the 2nd blender's output is the first input of the other
* one */
avi_segment_set_blender_src_id(inter,
s->blender[0],
0,
s->blender[1]->src_id);
/* Connect the first 3 planes on the first blender */
for (i = 0; i < 3; i++)
/* Same as above, we connect the planes in reverse
* order. */
avi_segment_set_blender_source(inter,
s->blender[0],
3 - i,
dense_sources[i]);
/* Connect the last 4 planes on the 2nd blender */
for (i = 3; i < 7; i++) {
avi_segment_set_blender_source(inter,
s->blender[1],
6 - i,
dense_sources[i]);
}
} else
BUG();
for (i = 0; i < nblenders; i++) {
avi_setup_blend(s->blender[i],
s->background,
s->input_format.width,
s->input_format.height);
avi_enable_node(s->blender[i]);
}
/* Free the no-longer needed blenders (if any) */
for (i = nblenders; i < 2; i++)
if (s->blender[i]) {
int j;
for (j = 0; j < 4; j++)
avi_segment_set_blender_src_id(inter,
s->blender[i],
j,
0);
avi_disable_node(s->blender[i]);
s->blender[i]->busy = 0;
s->blender[i]->assigned_caps = 0;
s->blender[i] = NULL;
}
/* Reconfiguration done! */
avi_unlock_node(inter);
unlock:
spin_unlock_irqrestore(&avi_ctrl.cfg_lck, cfg_flags);
return ret;
}
int avi_segment_connect(struct avi_segment *src,
struct avi_segment *dst,
int zorder)
{
int ret;
mutex_lock(&src->lock);
mutex_lock(&dst->lock);
if (src->sink[0]) {
/* Forks are not yet supported */
ret = -EBUSY;
goto unlock;
}
if (zorder == -1) {
/* Look for an available zorder */
for (zorder = 0; zorder < AVI_SEGMENT_MAX_SRC; zorder++)
if (dst->source[zorder] == NULL)
/* We found an available source */
break;
if (zorder >= AVI_SEGMENT_MAX_SRC) {
/* No available sources remain */
ret = -EBUSY;
goto unlock;
}
} else if (dst->source[zorder] != NULL) {
/* The requested zorder is already in use */
ret = -EBUSY;
goto unlock;
}
dst->source[zorder] = src;
ret = avi_segment_reconfigure_connection(dst);
if (ret) {
/* reconfiguration failed, rollback */
dst->source[zorder] = NULL;
goto unlock;
}
src->sink[0] = dst;
ret = 0;
unlock:
mutex_unlock(&dst->lock);
mutex_unlock(&src->lock);
return ret;
}
EXPORT_SYMBOL(avi_segment_connect);
int avi_segment_disconnect(struct avi_segment *src,
struct avi_segment *dst)
{
int ret;
int zorder;
mutex_lock(&src->lock);
mutex_lock(&dst->lock);
for (zorder = 0; zorder < AVI_SEGMENT_MAX_SRC; zorder++)
if (dst->source[zorder] == src)
break;
if (zorder >= AVI_SEGMENT_MAX_SRC) {
ret = -ENODEV;
goto unlock;
}
dst->source[zorder] = NULL;
ret = avi_segment_reconfigure_connection(dst);
if (ret) {
/* reconfiguration failed, rollback */
dst->source[zorder] = src;
goto unlock;
}
/* XXX no fork support */
src->sink[0] = NULL;
ret = 0;
unlock:
mutex_unlock(&dst->lock);
mutex_unlock(&src->lock);
return ret;
}
EXPORT_SYMBOL(avi_segment_disconnect);
void avi_segment_set_background(struct avi_segment *s, u32 rgb)
{
/* Convert the rgb color to the destination colorspace */
s->background = avi_conv_convert_color(AVI_RGB_CSPACE,
s->output_format.colorspace,
rgb);
if (s->blender[0])
avi_blend_set_bg(s->blender[0], s->background);
if (s->blender[1])
avi_blend_set_bg(s->blender[1], s->background);
}
EXPORT_SYMBOL(avi_segment_set_background);
u32 avi_segment_get_background(struct avi_segment *s)
{
/* Convert the rgb color to the destination colorspace */
return avi_conv_convert_color(s->output_format.colorspace,
AVI_RGB_CSPACE,
s->background);
}
EXPORT_SYMBOL(avi_segment_get_background);
static int avi_segment_do_activate(struct avi_segment *s, bool oneshot)
{
struct avi_node *out_node;
unsigned i;
if (s->active != AVI_SEGMENT_DISABLED)
return -EBUSY;
for (i = 0; i < s->nodes_nr; i++) {
if (s->nodes[i]->type == AVI_FIFO_NODE_TYPE)
avi_fifo_set_oneshot(s->nodes[i], oneshot);
avi_enable_node(s->nodes[i]);
}
if (s->dma_in_planar) {
avi_fifo_set_oneshot(s->dma_in_planar, oneshot);
avi_enable_node(s->dma_in_planar);
}
out_node = avi_segment_get_node(s, AVI_CAPS_LCD_ALL | AVI_CAP_DMA_OUT);
if (out_node)
avi_apply_node(out_node);
#ifdef AVI_BACKWARD_COMPAT
if ((avi_get_revision() == AVI_REVISION_1) && oneshot) {
/* Pre-disable FIFO nodes to stop processing after one shot */
for (i = 0; i < s->nodes_nr; i++) {
if (s->nodes[i]->type == AVI_FIFO_NODE_TYPE)
avi_disable_node(s->nodes[i]);
}
if (s->dma_in_planar) {
avi_disable_node(s->dma_in_planar);
}
}
#endif /* AVI_BACKWARD_COMPAT */
s->active = oneshot ? AVI_SEGMENT_ACTIVE_ONESHOT : AVI_SEGMENT_ACTIVE;
return 0;
}
int avi_segment_activate(struct avi_segment *s) {
return avi_segment_do_activate(s, 0);
}
EXPORT_SYMBOL(avi_segment_activate);
int avi_segment_activate_oneshot(struct avi_segment *s) {
return avi_segment_do_activate(s, 1);
}
EXPORT_SYMBOL(avi_segment_activate_oneshot);
int avi_segment_deactivate(struct avi_segment *s)
{
unsigned i;
if (s->active == AVI_SEGMENT_DISABLED)
return -EINVAL;
for (i = 0; i < s->nodes_nr; i++)
avi_disable_node(s->nodes[i]);
if (s->dma_in_planar)
avi_disable_node(s->dma_in_planar);
s->active = AVI_SEGMENT_DISABLED;
return 0;
}
EXPORT_SYMBOL(avi_segment_deactivate);
/* Called with the segment's lock held */
static int avi_segment_check_transform(struct avi_segment *s,
const struct avi_segment_format *in,
const struct avi_segment_format *out)
{
dprintk_s(s,
"check_transform: %ux%u%c %s [%s %u %u] ->"
" %ux%u%c %s [%s %u %u]\n",
in->width, in->height, in->interlaced ? 'i' : 'p',
avi_debug_colorspace_to_str(in->colorspace),
avi_debug_pixfmt_to_str(in->pix_fmt),
in->plane0.line_size,
in->plane1.line_size,
out->width, out->height, out->interlaced ? 'i' : 'p',
avi_debug_colorspace_to_str(out->colorspace),
avi_debug_pixfmt_to_str(out->pix_fmt),
out->plane0.line_size,
out->plane1.line_size);
#define INVALID_TRANSFORM(msg_) do { \
dprintk_s(s, "bad transform: %s\n", msg_); \
return -EINVAL; \
} while (0)
if (in->width == 0 || out->width == 0 ||
in->height == 0 || out->height == 0)
INVALID_TRANSFORM("null dimension in transform");
if ((in->width != out->width || in->height != out->height)
&& !(s->caps & AVI_CAP_SCAL))
INVALID_TRANSFORM("can't rescale without scaler");
if (in->interlaced && !out->interlaced)
INVALID_TRANSFORM("can't deinterlace");
if (!in->interlaced && out->interlaced && !(s->caps & AVI_CAP_SCAL))
INVALID_TRANSFORM("can't drop field without scaler");
/* For RAW input, need to check that the whole ISP chain is
* present and the dimensions */
if (in->pix_fmt.raw) {
if (!out->pix_fmt.raw &&
(s->caps & AVI_CAPS_ISP) != AVI_CAPS_ISP)
INVALID_TRANSFORM("can't do ISP conversion");
if (in->width % 2)
INVALID_TRANSFORM("bad width for RAW colorspace");
if (in->height % 2)
INVALID_TRANSFORM("bad height for RAW colorspace");
}
if (in->colorspace != out->colorspace && !(s->caps & AVI_CAP_CONV))
INVALID_TRANSFORM("can't do chroma conversion");
if (in->pix_fmt.id != AVI_INVALID_FMTID && !(s->caps & AVI_CAP_DMA_IN))
INVALID_TRANSFORM("pixel_format without DMA_IN");
if (in->pix_fmt.id != AVI_INVALID_FMTID && in->plane0.line_size == 0)
INVALID_TRANSFORM("bad line_size for DMA_IN");
if (avi_pixfmt_get_packing(in->pix_fmt) != AVI_INTERLEAVED_444_PACKING
&& (in->width % 2))
INVALID_TRANSFORM("bad width for DMA_IN");
if (avi_pixfmt_get_packing(in->pix_fmt) == AVI_SEMIPLANAR_YUV_420_PACKING
&& (in->height % 2))
INVALID_TRANSFORM("bad height for DMA_IN");
if (out->pix_fmt.id != AVI_INVALID_FMTID &&
!(s->caps & AVI_CAP_DMA_OUT))
INVALID_TRANSFORM("pixel_format without DMA_OUT");
if (out->pix_fmt.id != AVI_INVALID_FMTID && out->plane0.line_size == 0)
INVALID_TRANSFORM("bad line_size for DMA_OUT");
if (avi_pixfmt_get_packing(out->pix_fmt) != AVI_INTERLEAVED_444_PACKING
&& (out->width % 2))
INVALID_TRANSFORM("bad width for DMA_OUT");
if (avi_pixfmt_get_packing(out->pix_fmt) == AVI_SEMIPLANAR_YUV_420_PACKING
&& (out->height % 2))
INVALID_TRANSFORM("bad height for DMA_OUT");
if ((avi_get_revision() < AVI_REVISION_3))
if (avi_pixfmt_is_planar(in->pix_fmt) &&
!(s->caps & AVI_CAP_PLANAR))
INVALID_TRANSFORM("can't handle planar without CAP_PLANAR");
#undef INVALID_TRANSFORM
/* All is good */
return 0;
}
int avi_segment_try_format(struct avi_segment *s,
const struct avi_segment_format *in,
const struct avi_segment_format *out,
const struct avi_segment_layout *layout)
{
unsigned out_width;
unsigned out_height;
int ret;
int i;
ret = avi_segment_check_transform(s, in, out);
if (ret)
return ret;
/* How big the sinks will have to be to accomodate the current
* format. */
out_width = layout->x + out->width;
out_height = layout->y + out->height;
for (i = 0; i < AVI_SEGMENT_MAX_SINK; i++) {
struct avi_segment *sink = s->sink[i];
if (sink &&
(sink->input_format.width < out_width ||
sink->input_format.height < out_height)) {
dprintk_s(s, "segment position is out "
"of the sink window: %ux%u @%u,%u -> %ux%u\n",
out->width, out->height,
layout->x, layout->y,
sink->input_format.width,
sink->input_format.height);
return -ERANGE;
}
}
return 0;
}
EXPORT_SYMBOL(avi_segment_try_format);
/* Must be called with interrupts disabled */
static inline int avi_segment_reconfigure_sinks(struct avi_segment *s)
{
/* XXX no fork support */
struct avi_segment *sink = s->sink[0];
if (sink)
return avi_segment_reconfigure_connection(s->sink[0]);
return 0;
}
/* Configure the segment's node to match the current input and output
* formats. This function cannot fail as avi_segment_try_format must have been
* called beforehand to validate */
static int avi_segment_apply_format(struct avi_segment *s)
{
const struct avi_segment_format *in = &s->input_format;
const struct avi_segment_format *out = &s->output_format;
struct avi_node *n;
unsigned in_height;
unsigned out_height;
int ret;
in_height = in->height;
out_height = out->height;
if (in->interlaced)
in_height /= 2;
if (out->interlaced)
out_height /= 2;
ret = avi_segment_reconfigure_sinks(s);
if (ret)
return ret;
if ((n = avi_segment_get_node(s, AVI_CAP_SCAL))) {
/* We need to configure the scaler in planar if we have a planar
* pixel format and we're using two FIFOs */
avi_scal_setup_oneshot(n,
in->width, out->width,
in_height, out_height,
in->pix_fmt,
(s->dma_in_planar != NULL));
}
/* configure only chroma converter if ISP CHAIN BAYER is not present
* in this case chroma converter is configured in userland
*/
if (!avi_segment_get_node(s, AVI_CAP_ISP_CHAIN_BAYER))
if ((n = avi_segment_get_node(s, AVI_CAP_CONV)))
avi_setup_conv_colorspace(n,
in->colorspace,
out->colorspace);
if ((n = avi_segment_get_node(s, AVI_CAP_DMA_IN))) {
struct avi_node *planar_fifo;
planar_fifo = avi_segment_get_node(s, AVI_CAP_PLANAR);
avi_dma_setup_input(n, planar_fifo, in);
}
if ((n = avi_segment_get_node(s, AVI_CAP_BUFFER))) {
struct avi_fifo_registers fifo_reg = {
.cfg = {{
.srctype = AVI_FIFO_CFG_VL_TYPE,
.dsttype = AVI_FIFO_CFG_VL_TYPE,
.srcbitenc = AVI_FIFO_CFG_8888_BENC,
.dstbitenc = AVI_FIFO_CFG_8888_BENC,
}},
.framesize = {{
.width = out->width - 1,
.height = out_height - 1,
}},
.dmafxycfg = {{
.dmafxnb = 1,
.dmafynb = 1,
.dmafxymode = 0,
}},
};
avi_fifo_set_registers(n, &fifo_reg);
}
if ((n = avi_segment_get_node(s, AVI_CAP_DMA_OUT)))
avi_dma_setup_output(n, out);
avi_segment_reconfigure_connection(s);
return 0;
}
int avi_segment_set_format_and_layout(struct avi_segment *s,
const struct avi_segment_format *in_fmt,
const struct avi_segment_format *out_fmt,
const struct avi_segment_layout *layout)
{
struct avi_segment_format old_in_fmt;
struct avi_segment_format old_out_fmt;
struct avi_segment_layout old_layout;
int ret;
ret = avi_segment_try_format(s, in_fmt, out_fmt, layout);
if (ret)
return ret;
/* Roll the new configuration in */
old_in_fmt = s->input_format;
old_out_fmt = s->output_format;
old_layout = s->layout;
s->input_format = *in_fmt;
s->output_format = *out_fmt;
s->layout = *layout;
ret = avi_segment_apply_format(s);
if (ret) {
/* Rollback */
s->input_format = old_in_fmt;
s->output_format = old_out_fmt;
s->layout = old_layout;
return ret;
}
return 0;
}
EXPORT_SYMBOL(avi_segment_set_format_and_layout);
int avi_segment_set_format(struct avi_segment *s,
const struct avi_segment_format *in_fmt,
const struct avi_segment_format *out_fmt)
{
return avi_segment_set_format_and_layout(s,
in_fmt,
out_fmt,
&s->layout);
}
EXPORT_SYMBOL(avi_segment_set_format);
int avi_segment_set_input_format(struct avi_segment *s,
const struct avi_segment_format *fmt)
{
return avi_segment_set_format(s, fmt, &s->output_format);
}
EXPORT_SYMBOL(avi_segment_set_input_format);
int avi_segment_set_output_format(struct avi_segment *s,
const struct avi_segment_format *fmt)
{
return avi_segment_set_format(s, &s->input_format, fmt);
}
EXPORT_SYMBOL(avi_segment_set_output_format);
int avi_segment_hide(struct avi_segment *s)
{
int ret = 0;
if (!s->layout.hidden) {
s->layout.hidden = 1;
ret = avi_segment_reconfigure_sinks(s);
if (ret)
s->layout.hidden = 0;
}
return ret;
}
EXPORT_SYMBOL(avi_segment_hide);
int avi_segment_unhide(struct avi_segment *s)
{
int ret = 0;
if (s->layout.hidden) {
s->layout.hidden = 0;
ret = avi_segment_reconfigure_sinks(s);
if (ret)
s->layout.hidden = 1;
}
return ret;
}
EXPORT_SYMBOL(avi_segment_unhide);
inline int avi_segment_set_position(struct avi_segment *s,
unsigned x,
unsigned y)
{
struct avi_segment *sink = s->sink[0];
struct avi_segment_layout layout;
int ret = 0;
if (s->layout.x == x && s->layout.y == y)
/* Nothing to do */
return 0;
layout = s->layout;
layout.x = x;
layout.y = y;
ret = avi_segment_try_format(s,
&s->input_format,
&s->output_format,
&layout);
if (ret)
return ret;
s->layout = layout;
if (sink)
return avi_segment_reconfigure_connection(sink);
return 0;
}
EXPORT_SYMBOL(avi_segment_set_position);
inline int avi_segment_set_alpha(struct avi_segment *s, int alpha)
{
int ret = 0;
if (s->layout.alpha != alpha) {
struct avi_segment *sink = s->sink[0];
s->layout.alpha = alpha;
if (sink)
ret = avi_segment_reconfigure_connection(sink);
}
return ret;
}
EXPORT_SYMBOL(avi_segment_set_alpha);
void avi_segment_get_input_format(struct avi_segment *s,
struct avi_segment_format *fmt)
{
*fmt = s->input_format;
}
EXPORT_SYMBOL(avi_segment_get_input_format);
void avi_segment_get_output_format(struct avi_segment *s,
struct avi_segment_format *fmt)
{
*fmt = s->output_format;
}
EXPORT_SYMBOL(avi_segment_get_output_format);
void avi_segment_get_layout(struct avi_segment *s,
struct avi_segment_layout *layout)
{
*layout = s->layout;
}
EXPORT_SYMBOL(avi_segment_get_layout);
struct avi_node *avi_segment_get_node(struct avi_segment *s,
const unsigned long cap)
{
int pos;
if ((s->caps & cap)) {
for (pos = 0; pos < s->nodes_nr; pos++)
if ((s->nodes[pos]->assigned_caps & cap))
return s->nodes[pos];
if (s->dma_in_planar && (s->dma_in_planar->assigned_caps & cap))
return s->dma_in_planar;
}
/* Not found */
return NULL;
}
EXPORT_SYMBOL(avi_segment_get_node);
void avi_segment_set_input_buffer(struct avi_segment *segment,
const struct avi_dma_buffer *buff)
{
struct avi_node *fifo_plane0;
struct avi_node *fifo_plane1;
const struct avi_segment_format *in = &segment->input_format;
fifo_plane0 = avi_segment_get_node(segment, AVI_CAP_DMA_IN);
BUG_ON(fifo_plane0 == NULL);
BUG_ON(buff->status != AVI_BUFFER_READY);
BUG_ON(buff->plane0.dma_addr == 0);
BUG_ON(avi_pixfmt_is_planar(in->pix_fmt) &&
buff->plane1.dma_addr == 0);
segment->plane0_base = buff->plane0.dma_addr;
segment->plane1_base = buff->plane1.dma_addr;
fifo_plane1 = avi_segment_get_node(segment, AVI_CAP_PLANAR);
avi_dma_set_input_buffer(fifo_plane0, fifo_plane1, in, buff);
avi_segment_update_dma_field(segment);
}
EXPORT_SYMBOL(avi_segment_set_input_buffer);
void avi_segment_set_output_buffer(struct avi_segment *segment,
const struct avi_dma_buffer *buff)
{
struct avi_node *fifo;
const struct avi_segment_format *out = &segment->output_format;
fifo = avi_segment_get_node(segment, AVI_CAP_DMA_OUT);
BUG_ON(fifo == NULL);
BUG_ON(buff->status != AVI_BUFFER_READY);
avi_dma_set_output_buffer(fifo, out, buff);
}
EXPORT_SYMBOL(avi_segment_set_output_buffer);
int avi_segment_foreach(int (*cback)(struct avi_segment *, void *), void *priv)
{
struct avi_segment *s;
int ret = 0;
mutex_lock(&avi_segment_index.lock);
list_for_each_entry(s, &avi_segment_index.segment_list, list) {
ret = cback(s, priv);
if (ret)
break;
}
mutex_unlock(&avi_segment_index.lock);
return ret;
}
EXPORT_SYMBOL(avi_segment_foreach);
unsigned avi_segment_count(void)
{
struct avi_segment *s;
unsigned count = 0;
mutex_lock(&avi_segment_index.lock);
list_for_each_entry(s, &avi_segment_index.segment_list, list) {
count++;
}
mutex_unlock(&avi_segment_index.lock);
return count;
}
EXPORT_SYMBOL(avi_segment_count);
static int avi_segment_reconfigure(struct avi_segment *s, void *unused)
{
avi_segment_reset(s);
avi_segment_connect_nodes(s);
BUG_ON(avi_segment_apply_format(s));
BUG_ON(avi_segment_reconfigure_connection(s));
/* It's up to each individual driver (fb, cam...) to actually reactivate
* the segments (enable, apply and IRQ) as well as reconfiguring the
* DMA. */
s->active = AVI_SEGMENT_DISABLED;
return 0;
}
void avi_segment_resume(void)
{
/* Iterate over each segment, reconfiguring and reconnecting
* everything. */
avi_segment_foreach(avi_segment_reconfigure, NULL);
}
MODULE_AUTHOR("Lionel Flandrin <lionel.flandrin@parrot.com>");
MODULE_DESCRIPTION("Parrot Advanced Video Interface Segment layer");
MODULE_LICENSE("GPL");