Commit 85756a06 authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab

[media] cobalt: add new driver

The cobalt device is a PCIe card with 4 HDMI inputs (adv7604) and a
connector that can be used to hook up an adv7511 transmitter or an
adv7842 receiver daughterboard.

This device is used within Cisco but is sadly not available outside
of Cisco. Nevertheless it is a very interesting driver that can serve
as an example of how to support HDMI hardware and how to use the popular
adv devices.
Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@osg.samsung.com>
parent 48519838
......@@ -2602,6 +2602,14 @@ L: platform-driver-x86@vger.kernel.org
S: Supported
F: drivers/platform/x86/classmate-laptop.c
COBALT MEDIA DRIVER
M: Hans Verkuil <hans.verkuil@cisco.com>
L: linux-media@vger.kernel.org
T: git git://linuxtv.org/media_tree.git
W: http://linuxtv.org
S: Supported
F: drivers/media/pci/cobalt/
COCCINELLE/Semantic Patches (SmPL)
M: Julia Lawall <Julia.Lawall@lip6.fr>
M: Gilles Muller <Gilles.Muller@lip6.fr>
......
......@@ -33,6 +33,7 @@ source "drivers/media/pci/cx88/Kconfig"
source "drivers/media/pci/bt8xx/Kconfig"
source "drivers/media/pci/saa7134/Kconfig"
source "drivers/media/pci/saa7164/Kconfig"
source "drivers/media/pci/cobalt/Kconfig"
endif
......
......@@ -28,3 +28,4 @@ obj-$(CONFIG_VIDEO_DT3155) += dt3155/
obj-$(CONFIG_VIDEO_MEYE) += meye/
obj-$(CONFIG_STA2X11_VIP) += sta2x11/
obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/
obj-$(CONFIG_VIDEO_COBALT) += cobalt/
config VIDEO_COBALT
tristate "Cisco Cobalt support"
depends on VIDEO_V4L2 && I2C && MEDIA_CONTROLLER
depends on PCI_MSI && MTD_COMPLEX_MAPPINGS
select I2C_ALGOBIT
select VIDEO_ADV7604
select VIDEO_ADV7511
select VIDEO_ADV7842
select VIDEOBUF2_DMA_SG
---help---
This is a video4linux driver for the Cisco PCIe Cobalt card.
This board is sadly not available outside of Cisco, but it is
very useful as an example of a real driver that uses all the
latest frameworks and APIs.
To compile this driver as a module, choose M here: the
module will be called cobalt.
cobalt-objs := cobalt-driver.o cobalt-irq.o cobalt-v4l2.o \
cobalt-i2c.o cobalt-omnitek.o cobalt-flash.o cobalt-cpld.o \
cobalt-alsa-main.o cobalt-alsa-pcm.o
obj-$(CONFIG_VIDEO_COBALT) += cobalt.o
/*
* ALSA interface to cobalt PCM capture streams
*
* Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <media/v4l2-device.h>
#include <sound/core.h>
#include <sound/initval.h>
#include "cobalt-driver.h"
#include "cobalt-alsa.h"
#include "cobalt-alsa-pcm.h"
static void snd_cobalt_card_free(struct snd_cobalt_card *cobsc)
{
if (cobsc == NULL)
return;
cobsc->s->alsa = NULL;
kfree(cobsc);
}
static void snd_cobalt_card_private_free(struct snd_card *sc)
{
if (sc == NULL)
return;
snd_cobalt_card_free(sc->private_data);
sc->private_data = NULL;
sc->private_free = NULL;
}
static int snd_cobalt_card_create(struct cobalt_stream *s,
struct snd_card *sc,
struct snd_cobalt_card **cobsc)
{
*cobsc = kzalloc(sizeof(struct snd_cobalt_card), GFP_KERNEL);
if (*cobsc == NULL)
return -ENOMEM;
(*cobsc)->s = s;
(*cobsc)->sc = sc;
sc->private_data = *cobsc;
sc->private_free = snd_cobalt_card_private_free;
return 0;
}
static int snd_cobalt_card_set_names(struct snd_cobalt_card *cobsc)
{
struct cobalt_stream *s = cobsc->s;
struct cobalt *cobalt = s->cobalt;
struct snd_card *sc = cobsc->sc;
/* sc->driver is used by alsa-lib's configurator: simple, unique */
strlcpy(sc->driver, "cobalt", sizeof(sc->driver));
/* sc->shortname is a symlink in /proc/asound: COBALT-M -> cardN */
snprintf(sc->shortname, sizeof(sc->shortname), "cobalt-%d-%d",
cobalt->instance, s->video_channel);
/* sc->longname is read from /proc/asound/cards */
snprintf(sc->longname, sizeof(sc->longname),
"Cobalt %d HDMI %d",
cobalt->instance, s->video_channel);
return 0;
}
int cobalt_alsa_init(struct cobalt_stream *s)
{
struct cobalt *cobalt = s->cobalt;
struct snd_card *sc = NULL;
struct snd_cobalt_card *cobsc;
int ret;
/* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */
/* (1) Check and increment the device index */
/* This is a no-op for us. We'll use the cobalt->instance */
/* (2) Create a card instance */
ret = snd_card_new(&cobalt->pci_dev->dev, SNDRV_DEFAULT_IDX1,
SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &sc);
if (ret) {
cobalt_err("snd_card_new() failed with err %d\n", ret);
goto err_exit;
}
/* (3) Create a main component */
ret = snd_cobalt_card_create(s, sc, &cobsc);
if (ret) {
cobalt_err("snd_cobalt_card_create() failed with err %d\n",
ret);
goto err_exit_free;
}
/* (4) Set the driver ID and name strings */
snd_cobalt_card_set_names(cobsc);
ret = snd_cobalt_pcm_create(cobsc);
if (ret) {
cobalt_err("snd_cobalt_pcm_create() failed with err %d\n",
ret);
goto err_exit_free;
}
/* FIXME - proc files */
/* (7) Set the driver data and return 0 */
/* We do this out of normal order for PCI drivers to avoid races */
s->alsa = cobsc;
/* (6) Register the card instance */
ret = snd_card_register(sc);
if (ret) {
s->alsa = NULL;
cobalt_err("snd_card_register() failed with err %d\n", ret);
goto err_exit_free;
}
return 0;
err_exit_free:
if (sc != NULL)
snd_card_free(sc);
kfree(cobsc);
err_exit:
return ret;
}
void cobalt_alsa_exit(struct cobalt_stream *s)
{
struct snd_cobalt_card *cobsc = s->alsa;
if (cobsc)
snd_card_free(cobsc->sc);
s->alsa = NULL;
}
This diff is collapsed.
/*
* ALSA PCM device for the
* ALSA interface to cobalt PCM capture streams
*
* Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
int snd_cobalt_pcm_create(struct snd_cobalt_card *cobsc);
/*
* ALSA interface to cobalt PCM capture streams
*
* Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
struct snd_card;
struct snd_cobalt_card {
struct cobalt_stream *s;
struct snd_card *sc;
unsigned int capture_transfer_done;
unsigned int hwptr_done_capture;
unsigned alsa_record_cnt;
struct snd_pcm_substream *capture_pcm_substream;
unsigned int pb_size;
unsigned int pb_count;
unsigned int pb_pos;
unsigned pb_filled;
bool alsa_pb_channel;
unsigned alsa_playback_cnt;
struct snd_pcm_substream *playback_pcm_substream;
};
int cobalt_alsa_init(struct cobalt_stream *s);
void cobalt_alsa_exit(struct cobalt_stream *s);
This diff is collapsed.
/*
* Cobalt CPLD functions
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef COBALT_CPLD_H
#define COBALT_CPLD_H
#include "cobalt-driver.h"
void cobalt_cpld_status(struct cobalt *cobalt);
bool cobalt_cpld_set_freq(struct cobalt *cobalt, unsigned freq);
#endif
This diff is collapsed.
This diff is collapsed.
/*
* Cobalt NOR flash functions
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/cfi.h>
#include <linux/time.h>
#include "cobalt-driver.h"
#define ADRS(offset) (COBALT_BUS_FLASH_BASE + offset)
static struct map_info cobalt_flash_map = {
.name = "cobalt-flash",
.bankwidth = 2, /* 16 bits */
.size = 0x4000000, /* 64MB */
.phys = 0, /* offset */
};
static map_word flash_read16(struct map_info *map, unsigned long offset)
{
struct cobalt *cobalt = map->virt;
map_word r;
r.x[0] = cobalt_bus_read32(cobalt, ADRS(offset));
if (offset & 0x2)
r.x[0] >>= 16;
else
r.x[0] &= 0x0000ffff;
return r;
}
static void flash_write16(struct map_info *map, const map_word datum,
unsigned long offset)
{
struct cobalt *cobalt = map->virt;
u16 data = (u16)datum.x[0];
cobalt_bus_write16(cobalt, ADRS(offset), data);
}
static void flash_copy_from(struct map_info *map, void *to,
unsigned long from, ssize_t len)
{
struct cobalt *cobalt = map->virt;
u32 src = from;
u8 *dest = to;
u32 data;
while (len) {
data = cobalt_bus_read32(cobalt, ADRS(src));
do {
*dest = data >> (8 * (src & 3));
src++;
dest++;
len--;
} while (len && (src % 4));
}
}
static void flash_copy_to(struct map_info *map, unsigned long to,
const void *from, ssize_t len)
{
struct cobalt *cobalt = map->virt;
const u8 *src = from;
u32 dest = to;
cobalt_info("%s: offset 0x%x: length %zu\n", __func__, dest, len);
while (len) {
u16 data = 0xffff;
do {
data = *src << (8 * (dest & 1));
src++;
dest++;
len--;
} while (len && (dest % 2));
cobalt_bus_write16(cobalt, ADRS(dest - 2), data);
}
}
int cobalt_flash_probe(struct cobalt *cobalt)
{
struct map_info *map = &cobalt_flash_map;
struct mtd_info *mtd;
BUG_ON(!map_bankwidth_supported(map->bankwidth));
map->virt = cobalt;
map->read = flash_read16;
map->write = flash_write16;
map->copy_from = flash_copy_from;
map->copy_to = flash_copy_to;
mtd = do_map_probe("cfi_probe", map);
cobalt->mtd = mtd;
if (!mtd) {
cobalt_err("Probe CFI flash failed!\n");
return -1;
}
mtd->owner = THIS_MODULE;
mtd->dev.parent = &cobalt->pci_dev->dev;
mtd_device_register(mtd, NULL, 0);
return 0;
}
void cobalt_flash_remove(struct cobalt *cobalt)
{
if (cobalt->mtd) {
mtd_device_unregister(cobalt->mtd);
map_destroy(cobalt->mtd);
}
}
/*
* Cobalt NOR flash functions
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef COBALT_FLASH_H
#define COBALT_FLASH_H
#include "cobalt-driver.h"
int cobalt_flash_probe(struct cobalt *cobalt);
void cobalt_flash_remove(struct cobalt *cobalt);
#endif
/*
* cobalt I2C functions
*
* Derived from cx18-i2c.c
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "cobalt-driver.h"
#include "cobalt-i2c.h"
struct cobalt_i2c_regs {
/* Clock prescaler register lo-byte */
u8 prerlo;
u8 dummy0[3];
/* Clock prescaler register high-byte */
u8 prerhi;
u8 dummy1[3];
/* Control register */
u8 ctr;
u8 dummy2[3];
/* Transmit/Receive register */
u8 txr_rxr;
u8 dummy3[3];
/* Command and Status register */
u8 cr_sr;
u8 dummy4[3];
};
/* CTR[7:0] - Control register */
/* I2C Core enable bit */
#define M00018_CTR_BITMAP_EN_MSK (1 << 7)
/* I2C Core interrupt enable bit */
#define M00018_CTR_BITMAP_IEN_MSK (1 << 6)
/* CR[7:0] - Command register */
/* I2C start condition */
#define M00018_CR_BITMAP_STA_MSK (1 << 7)
/* I2C stop condition */
#define M00018_CR_BITMAP_STO_MSK (1 << 6)
/* I2C read from slave */
#define M00018_CR_BITMAP_RD_MSK (1 << 5)
/* I2C write to slave */
#define M00018_CR_BITMAP_WR_MSK (1 << 4)
/* I2C ack */
#define M00018_CR_BITMAP_ACK_MSK (1 << 3)
/* I2C Interrupt ack */
#define M00018_CR_BITMAP_IACK_MSK (1 << 0)
/* SR[7:0] - Status register */
/* Receive acknowledge from slave */
#define M00018_SR_BITMAP_RXACK_MSK (1 << 7)
/* Busy, I2C bus busy (as defined by start / stop bits) */
#define M00018_SR_BITMAP_BUSY_MSK (1 << 6)
/* Arbitration lost - core lost arbitration */
#define M00018_SR_BITMAP_AL_MSK (1 << 5)
/* Transfer in progress */
#define M00018_SR_BITMAP_TIP_MSK (1 << 1)
/* Interrupt flag */
#define M00018_SR_BITMAP_IF_MSK (1 << 0)
/* Frequency, in Hz */
#define I2C_FREQUENCY 400000
#define ALT_CPU_FREQ 83333333
static volatile struct cobalt_i2c_regs __iomem *
cobalt_i2c_regs(struct cobalt *cobalt, unsigned idx)
{
switch (idx) {
case 0:
default:
return (volatile struct cobalt_i2c_regs __iomem *)
(cobalt->bar1 + COBALT_I2C_0_BASE);
case 1:
return (volatile struct cobalt_i2c_regs __iomem *)
(cobalt->bar1 + COBALT_I2C_1_BASE);
case 2:
return (volatile struct cobalt_i2c_regs __iomem *)
(cobalt->bar1 + COBALT_I2C_2_BASE);
case 3:
return (volatile struct cobalt_i2c_regs __iomem *)
(cobalt->bar1 + COBALT_I2C_3_BASE);
case 4:
return (volatile struct cobalt_i2c_regs __iomem *)
(cobalt->bar1 + COBALT_I2C_HSMA_BASE);
}
}
/* Do low-level i2c byte transfer.
* Returns -1 in case of an error or 0 otherwise.
*/
static int cobalt_tx_bytes(volatile struct cobalt_i2c_regs __iomem *regs,
struct i2c_adapter *adap, bool start, bool stop,
u8 *data, u16 len)
{
unsigned long start_time;
int status;
int cmd;
int i;
for (i = 0; i < len; i++) {
/* Setup data */
regs->txr_rxr = data[i];
/* Setup command */
if (i == 0 && start != 0) {
/* Write + Start */
cmd = M00018_CR_BITMAP_WR_MSK |
M00018_CR_BITMAP_STA_MSK;
} else if (i == len - 1 && stop != 0) {
/* Write + Stop */
cmd = M00018_CR_BITMAP_WR_MSK |
M00018_CR_BITMAP_STO_MSK;
} else {
/* Write only */
cmd = M00018_CR_BITMAP_WR_MSK;
}
/* Execute command */
regs->cr_sr = cmd;
/* Wait for transfer to complete (TIP = 0) */
start_time = jiffies;
status = regs->cr_sr;
while (status & M00018_SR_BITMAP_TIP_MSK) {
if (time_after(jiffies, start_time + adap->timeout))
return -ETIMEDOUT;
cond_resched();
status = regs->cr_sr;
}
/* Verify ACK */
if (status & M00018_SR_BITMAP_RXACK_MSK) {
/* NO ACK! */
return -EIO;
}
/* Verify arbitration */
if (status & M00018_SR_BITMAP_AL_MSK) {
/* Arbitration lost! */
return -EIO;
}
}
return 0;
}
/* Do low-level i2c byte read.
* Returns -1 in case of an error or 0 otherwise.
*/
static int cobalt_rx_bytes(volatile struct cobalt_i2c_regs __iomem *regs,
struct i2c_adapter *adap, bool start, bool stop,
u8 *data, u16 len)
{
unsigned long start_time;
int status;
int cmd;
int i;
for (i = 0; i < len; i++) {
/* Setup command */
if (i == 0 && start != 0) {
/* Read + Start */
cmd = M00018_CR_BITMAP_RD_MSK |
M00018_CR_BITMAP_STA_MSK;
} else if (i == len - 1 && stop != 0) {
/* Read + Stop */
cmd = M00018_CR_BITMAP_RD_MSK |
M00018_CR_BITMAP_STO_MSK;
} else {
/* Read only */
cmd = M00018_CR_BITMAP_RD_MSK;
}
/* Last byte to read, no ACK */
if (i == len - 1)
cmd |= M00018_CR_BITMAP_ACK_MSK;
/* Execute command */
regs->cr_sr = cmd;
/* Wait for transfer to complete (TIP = 0) */
start_time = jiffies;
status = regs->cr_sr;
while (status & M00018_SR_BITMAP_TIP_MSK) {
if (time_after(jiffies, start_time + adap->timeout))
return -ETIMEDOUT;
cond_resched();
status = regs->cr_sr;
}
/* Verify arbitration */
if (status & M00018_SR_BITMAP_AL_MSK) {
/* Arbitration lost! */
return -EIO;
}
/* Store data */
data[i] = regs->txr_rxr;
}
return 0;
}
/* Generate stop condition on i2c bus.
* The m00018 stop isn't doing the right thing (wrong timing).
* So instead send a start condition, 8 zeroes and a stop condition.
*/
static int cobalt_stop(volatile struct cobalt_i2c_regs __iomem *regs,
struct i2c_adapter *adap)
{
u8 data = 0;
return cobalt_tx_bytes(regs, adap, true, true, &data, 1);
}
static int cobalt_xfer(struct i2c_adapter *adap,
struct i2c_msg msgs[], int num)
{
struct cobalt_i2c_data *data = adap->algo_data;
volatile struct cobalt_i2c_regs __iomem *regs = data->regs;
struct i2c_msg *pmsg;
unsigned short flags;
int ret = 0;
int i, j;
for (i = 0; i < num; i++) {
int stop = (i == num - 1);
pmsg = &msgs[i];
flags = pmsg->flags;
if (!(pmsg->flags & I2C_M_NOSTART)) {
u8 addr = pmsg->addr << 1;
if (flags & I2C_M_RD)
addr |= 1;
if (flags & I2C_M_REV_DIR_ADDR)
addr ^= 1;
for (j = 0; j < adap->retries; j++) {
ret = cobalt_tx_bytes(regs, adap, true, false,
&addr, 1);
if (!ret)
break;
cobalt_stop(regs, adap);
}
if (ret < 0)
return ret;
ret = 0;
}
if (pmsg->flags & I2C_M_RD) {
/* read bytes into buffer */
ret = cobalt_rx_bytes(regs, adap, false, stop,
pmsg->buf, pmsg->len);
if (ret < 0)
goto bailout;
} else {
/* write bytes from buffer */
ret = cobalt_tx_bytes(regs, adap, false, stop,
pmsg->buf, pmsg->len);
if (ret < 0)
goto bailout;
}
}
ret = i;
bailout:
if (ret < 0)
cobalt_stop(regs, adap);
return ret;
}
static u32 cobalt_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
/* template for i2c-bit-algo */
static struct i2c_adapter cobalt_i2c_adap_template = {
.name = "cobalt i2c driver",
.algo = NULL, /* set by i2c-algo-bit */
.algo_data = NULL, /* filled from template */
.owner = THIS_MODULE,
};
static const struct i2c_algorithm cobalt_algo = {
.master_xfer = cobalt_xfer,
.functionality = cobalt_func,
};
/* init + register i2c algo-bit adapter */
int cobalt_i2c_init(struct cobalt *cobalt)
{
int i, err;
int status;
int prescale;
unsigned long start_time;
cobalt_dbg(1, "i2c init\n");
/* Define I2C clock prescaler */
prescale = ((ALT_CPU_FREQ) / (5 * I2C_FREQUENCY)) - 1;
for (i = 0; i < COBALT_NUM_ADAPTERS; i++) {
volatile struct cobalt_i2c_regs __iomem *regs =
cobalt_i2c_regs(cobalt, i);
struct i2c_adapter *adap = &cobalt->i2c_adap[i];
/* Disable I2C */
regs->cr_sr = M00018_CTR_BITMAP_EN_MSK;
regs->ctr = 0;
regs->cr_sr = 0;
start_time = jiffies;
do {
if (time_after(jiffies, start_time + HZ)) {
if (cobalt_ignore_err) {
adap->dev.parent = NULL;
return 0;
}
return -ETIMEDOUT;
}
status = regs->cr_sr;
} while (status & M00018_SR_BITMAP_TIP_MSK);
/* Disable I2C */
regs->ctr = 0;
regs->cr_sr = 0;
/* Calculate i2c prescaler */
regs->prerlo = prescale & 0xff;
regs->prerhi = (prescale >> 8) & 0xff;
/* Enable I2C, interrupts disabled */
regs->ctr = M00018_CTR_BITMAP_EN_MSK;
/* Setup algorithm for adapter */
cobalt->i2c_data[i].cobalt = cobalt;
cobalt->i2c_data[i].regs = regs;
*adap = cobalt_i2c_adap_template;
adap->algo = &cobalt_algo;
adap->algo_data = &cobalt->i2c_data[i];
adap->retries = 3;
sprintf(adap->name + strlen(adap->name),
" #%d-%d", cobalt->instance, i);
i2c_set_adapdata(adap, &cobalt->v4l2_dev);
adap->dev.parent = &cobalt->pci_dev->dev;
err = i2c_add_adapter(adap);
if (err) {
if (cobalt_ignore_err) {
adap->dev.parent = NULL;
return 0;
}
while (i--)
i2c_del_adapter(&cobalt->i2c_adap[i]);
return err;
}
cobalt_info("registered bus %s\n", adap->name);
}
return 0;
}
void cobalt_i2c_exit(struct cobalt *cobalt)
{
int i;
cobalt_dbg(1, "i2c exit\n");
for (i = 0; i < COBALT_NUM_ADAPTERS; i++) {
cobalt_err("unregistered bus %s\n", cobalt->i2c_adap[i].name);
i2c_del_adapter(&cobalt->i2c_adap[i]);
}
}
/*
* cobalt I2C functions
*
* Derived from cx18-i2c.h
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/* init + register i2c algo-bit adapter */
int cobalt_i2c_init(struct cobalt *cobalt);
void cobalt_i2c_exit(struct cobalt *cobalt);
/*
* cobalt interrupt handling
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <media/adv7604.h>
#include "cobalt-driver.h"
#include "cobalt-irq.h"
#include "cobalt-omnitek.h"
static void cobalt_dma_stream_queue_handler(struct cobalt_stream *s)
{
struct cobalt *cobalt = s->cobalt;
int rx = s->video_channel;
volatile struct m00473_freewheel_regmap __iomem *fw =
COBALT_CVI_FREEWHEEL(s->cobalt, rx);
volatile struct m00233_video_measure_regmap __iomem *vmr =
COBALT_CVI_VMR(s->cobalt, rx);
volatile struct m00389_cvi_regmap __iomem *cvi =
COBALT_CVI(s->cobalt, rx);
volatile struct m00479_clk_loss_detector_regmap __iomem *clkloss =
COBALT_CVI_CLK_LOSS(s->cobalt, rx);
struct cobalt_buffer *cb;
bool skip = false;
spin_lock(&s->irqlock);
if (list_empty(&s->bufs)) {
pr_err("no buffers!\n");
spin_unlock(&s->irqlock);
return;
}
/* Give the fresh filled up buffer to the user.
* Note that the interrupt is only sent if the DMA can continue
* with a new buffer, so it is always safe to return this buffer
* to userspace. */
cb = list_first_entry(&s->bufs, struct cobalt_buffer, list);
list_del(&cb->list);
spin_unlock(&s->irqlock);
if (s->is_audio || s->is_output)
goto done;
if (s->unstable_frame) {
uint32_t stat = vmr->irq_status;
vmr->irq_status = stat;
if (!(vmr->status & M00233_STATUS_BITMAP_INIT_DONE_MSK)) {
cobalt_dbg(1, "!init_done\n");
if (s->enable_freewheel)
goto restart_fw;
goto done;
}
if (clkloss->status & M00479_STATUS_BITMAP_CLOCK_MISSING_MSK) {
clkloss->ctrl = 0;
clkloss->ctrl = M00479_CTRL_BITMAP_ENABLE_MSK;
cobalt_dbg(1, "no clock\n");
if (s->enable_freewheel)
goto restart_fw;
goto done;
}
if ((stat & (M00233_IRQ_STATUS_BITMAP_VACTIVE_AREA_MSK |
M00233_IRQ_STATUS_BITMAP_HACTIVE_AREA_MSK)) ||
vmr->vactive_area != s->timings.bt.height ||
vmr->hactive_area != s->timings.bt.width) {
cobalt_dbg(1, "unstable\n");
if (s->enable_freewheel)
goto restart_fw;
goto done;
}
if (!s->enable_cvi) {
s->enable_cvi = true;
cvi->control = M00389_CONTROL_BITMAP_ENABLE_MSK;
goto done;
}
if (!(cvi->status & M00389_STATUS_BITMAP_LOCK_MSK)) {
cobalt_dbg(1, "cvi no lock\n");
if (s->enable_freewheel)
goto restart_fw;
goto done;
}
if (!s->enable_freewheel) {
cobalt_dbg(1, "stable\n");
s->enable_freewheel = true;
fw->ctrl = 0;
goto done;
}
cobalt_dbg(1, "enabled fw\n");
vmr->control = M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK |
M00233_CONTROL_BITMAP_ENABLE_INTERRUPT_MSK;
fw->ctrl = M00473_CTRL_BITMAP_ENABLE_MSK;
s->enable_freewheel = false;
s->unstable_frame = false;
s->skip_first_frames = 2;
skip = true;
goto done;
}
if (fw->status & M00473_STATUS_BITMAP_FREEWHEEL_MODE_MSK) {
restart_fw:
cobalt_dbg(1, "lost lock\n");
vmr->control = M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK;
fw->ctrl = M00473_CTRL_BITMAP_ENABLE_MSK |
M00473_CTRL_BITMAP_FORCE_FREEWHEEL_MODE_MSK;
cvi->control = 0;
s->unstable_frame = true;
s->enable_freewheel = false;
s->enable_cvi = false;
}
done:
if (s->skip_first_frames) {
skip = true;
s->skip_first_frames--;
}
v4l2_get_timestamp(&cb->vb.v4l2_buf.timestamp);
/* TODO: the sequence number should be read from the FPGA so we
also know about dropped frames. */
cb->vb.v4l2_buf.sequence = s->sequence++;
vb2_buffer_done(&cb->vb, (skip || s->unstable_frame) ?
VB2_BUF_STATE_QUEUED : VB2_BUF_STATE_DONE);
}
irqreturn_t cobalt_irq_handler(int irq, void *dev_id)
{
struct cobalt *cobalt = (struct cobalt *)dev_id;
u32 dma_interrupt =
cobalt_read_bar0(cobalt, DMA_INTERRUPT_STATUS_REG) & 0xffff;
u32 mask = cobalt_read_bar1(cobalt, COBALT_SYS_STAT_MASK);
u32 edge = cobalt_read_bar1(cobalt, COBALT_SYS_STAT_EDGE);
int i;
/* Clear DMA interrupt */
cobalt_write_bar0(cobalt, DMA_INTERRUPT_STATUS_REG, dma_interrupt);
cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK, mask & ~edge);
cobalt_write_bar1(cobalt, COBALT_SYS_STAT_EDGE, edge);
for (i = 0; i < COBALT_NUM_STREAMS; i++) {
struct cobalt_stream *s = &cobalt->streams[i];
unsigned dma_fifo_mask =
COBALT_SYSSTAT_VI0_LOST_DATA_MSK << (4 * s->video_channel);
if (dma_interrupt & (1 << s->dma_channel)) {
cobalt->irq_dma[i]++;
/* Give fresh buffer to user and chain newly
* queued buffers */
cobalt_dma_stream_queue_handler(s);
if (!s->is_audio) {
edge &= ~dma_fifo_mask;
cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK,
mask & ~edge);
}
}
if (s->is_audio)
continue;
if (edge & (0x20 << (4 * s->video_channel)))
set_bit(COBALT_STREAM_FL_ADV_IRQ, &s->flags);
if ((edge & mask & dma_fifo_mask) && vb2_is_streaming(&s->q)) {
cobalt_info("full rx FIFO %d\n", i);
cobalt->irq_full_fifo++;
}
}
queue_work(cobalt->irq_work_queues, &cobalt->irq_work_queue);
if (edge & mask & (COBALT_SYSSTAT_VI0_INT1_MSK |
COBALT_SYSSTAT_VI1_INT1_MSK |
COBALT_SYSSTAT_VI2_INT1_MSK |
COBALT_SYSSTAT_VI3_INT1_MSK |
COBALT_SYSSTAT_VIHSMA_INT1_MSK |
COBALT_SYSSTAT_VOHSMA_INT1_MSK))
cobalt->irq_adv1++;
if (edge & mask & (COBALT_SYSSTAT_VI0_INT2_MSK |
COBALT_SYSSTAT_VI1_INT2_MSK |
COBALT_SYSSTAT_VI2_INT2_MSK |
COBALT_SYSSTAT_VI3_INT2_MSK |
COBALT_SYSSTAT_VIHSMA_INT2_MSK))
cobalt->irq_adv2++;
if (edge & mask & COBALT_SYSSTAT_VOHSMA_INT1_MSK)
cobalt->irq_advout++;
if (dma_interrupt)
cobalt->irq_dma_tot++;
if (!(edge & mask) && !dma_interrupt)
cobalt->irq_none++;
dma_interrupt = cobalt_read_bar0(cobalt, DMA_INTERRUPT_STATUS_REG);
return IRQ_HANDLED;
}
void cobalt_irq_work_handler(struct work_struct *work)
{
struct cobalt *cobalt =
container_of(work, struct cobalt, irq_work_queue);
int i;
for (i = 0; i < COBALT_NUM_NODES; i++) {
struct cobalt_stream *s = &cobalt->streams[i];
if (test_and_clear_bit(COBALT_STREAM_FL_ADV_IRQ, &s->flags)) {
u32 mask;
v4l2_subdev_call(cobalt->streams[i].sd, core,
interrupt_service_routine, 0, NULL);
mask = cobalt_read_bar1(cobalt, COBALT_SYS_STAT_MASK);
cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK,
mask | (0x20 << (4 * s->video_channel)));
}
}
}
void cobalt_irq_log_status(struct cobalt *cobalt)
{
u32 mask;
int i;
cobalt_info("irq: adv1=%u adv2=%u advout=%u none=%u full=%u\n",
cobalt->irq_adv1, cobalt->irq_adv2, cobalt->irq_advout,
cobalt->irq_none, cobalt->irq_full_fifo);
cobalt_info("irq: dma_tot=%u (", cobalt->irq_dma_tot);
for (i = 0; i < COBALT_NUM_STREAMS; i++)
pr_cont("%s%u", i ? "/" : "", cobalt->irq_dma[i]);
pr_cont(")\n");
cobalt->irq_dma_tot = cobalt->irq_adv1 = cobalt->irq_adv2 = 0;
cobalt->irq_advout = cobalt->irq_none = cobalt->irq_full_fifo = 0;
memset(cobalt->irq_dma, 0, sizeof(cobalt->irq_dma));
mask = cobalt_read_bar1(cobalt, COBALT_SYS_STAT_MASK);
cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK,
mask |
COBALT_SYSSTAT_VI0_LOST_DATA_MSK |
COBALT_SYSSTAT_VI1_LOST_DATA_MSK |
COBALT_SYSSTAT_VI2_LOST_DATA_MSK |
COBALT_SYSSTAT_VI3_LOST_DATA_MSK |
COBALT_SYSSTAT_VIHSMA_LOST_DATA_MSK |
COBALT_SYSSTAT_VOHSMA_LOST_DATA_MSK |
COBALT_SYSSTAT_AUD_IN_LOST_DATA_MSK |
COBALT_SYSSTAT_AUD_OUT_LOST_DATA_MSK);
}
/*
* cobalt interrupt handling
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <linux/interrupt.h>
irqreturn_t cobalt_irq_handler(int irq, void *dev_id);
void cobalt_irq_work_handler(struct work_struct *work);
void cobalt_irq_log_status(struct cobalt *cobalt);
/*
* Omnitek Scatter-Gather DMA Controller
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <linux/string.h>
#include <linux/io.h>
#include <linux/pci_regs.h>
#include <linux/spinlock.h>
#include "cobalt-driver.h"
#include "cobalt-omnitek.h"
/* descriptor */
#define END_OF_CHAIN (1 << 1)
#define INTERRUPT_ENABLE (1 << 2)
#define WRITE_TO_PCI (1 << 3)
#define READ_FROM_PCI (0 << 3)
#define DESCRIPTOR_FLAG_MSK (END_OF_CHAIN | INTERRUPT_ENABLE | WRITE_TO_PCI)
#define NEXT_ADRS_MSK 0xffffffe0
/* control/status register */
#define ENABLE (1 << 0)
#define START (1 << 1)
#define ABORT (1 << 2)
#define DONE (1 << 4)
#define SG_INTERRUPT (1 << 5)
#define EVENT_INTERRUPT (1 << 6)
#define SCATTER_GATHER_MODE (1 << 8)
#define DISABLE_VIDEO_RESYNC (1 << 9)
#define EVENT_INTERRUPT_ENABLE (1 << 10)
#define DIRECTIONAL_MSK (3 << 16)
#define INPUT_ONLY (0 << 16)
#define OUTPUT_ONLY (1 << 16)
#define BIDIRECTIONAL (2 << 16)
#define DMA_TYPE_MEMORY (0 << 18)
#define DMA_TYPE_FIFO (1 << 18)
#define BASE (cobalt->bar0)
#define CAPABILITY_HEADER (BASE)
#define CAPABILITY_REGISTER (BASE + 0x04)
#define PCI_64BIT (1 << 8)
#define LOCAL_64BIT (1 << 9)
#define INTERRUPT_STATUS (BASE + 0x08)
#define PCI(c) (BASE + 0x40 + ((c) * 0x40))
#define SIZE(c) (BASE + 0x58 + ((c) * 0x40))
#define DESCRIPTOR(c) (BASE + 0x50 + ((c) * 0x40))
#define CS_REG(c) (BASE + 0x60 + ((c) * 0x40))
#define BYTES_TRANSFERRED(c) (BASE + 0x64 + ((c) * 0x40))
static char *get_dma_direction(u32 status)
{
switch (status & DIRECTIONAL_MSK) {
case INPUT_ONLY: return "Input";
case OUTPUT_ONLY: return "Output";
case BIDIRECTIONAL: return "Bidirectional";
}
return "";
}
static void show_dma_capability(struct cobalt *cobalt)
{
u32 header = ioread32(CAPABILITY_HEADER);
u32 capa = ioread32(CAPABILITY_REGISTER);
u32 i;
cobalt_info("Omnitek DMA capability: ID 0x%02x Version 0x%02x Next 0x%x Size 0x%x\n",
header & 0xff, (header >> 8) & 0xff,
(header >> 16) & 0xffff, (capa >> 24) & 0xff);
switch ((capa >> 8) & 0x3) {
case 0:
cobalt_info("Omnitek DMA: 32 bits PCIe and Local\n");
break;
case 1:
cobalt_info("Omnitek DMA: 64 bits PCIe, 32 bits Local\n");
break;
case 3:
cobalt_info("Omnitek DMA: 64 bits PCIe and Local\n");
break;
}
for (i = 0; i < (capa & 0xf); i++) {
u32 status = ioread32(CS_REG(i));
cobalt_info("Omnitek DMA channel #%d: %s %s\n", i,
status & DMA_TYPE_FIFO ? "FIFO" : "MEMORY",
get_dma_direction(status));
}
}
void omni_sg_dma_start(struct cobalt_stream *s, struct sg_dma_desc_info *desc)
{
struct cobalt *cobalt = s->cobalt;
iowrite32((u32)(desc->bus >> 32), DESCRIPTOR(s->dma_channel) + 4);
iowrite32((u32)desc->bus & NEXT_ADRS_MSK, DESCRIPTOR(s->dma_channel));
iowrite32(ENABLE | SCATTER_GATHER_MODE | START, CS_REG(s->dma_channel));
}
bool is_dma_done(struct cobalt_stream *s)
{
struct cobalt *cobalt = s->cobalt;
if (ioread32(CS_REG(s->dma_channel)) & DONE)
return true;
return false;
}
void omni_sg_dma_abort_channel(struct cobalt_stream *s)
{
struct cobalt *cobalt = s->cobalt;
if (is_dma_done(s) == false)
iowrite32(ABORT, CS_REG(s->dma_channel));
}
int omni_sg_dma_init(struct cobalt *cobalt)
{
u32 capa = ioread32(CAPABILITY_REGISTER);
int i;
cobalt->first_fifo_channel = 0;
cobalt->dma_channels = capa & 0xf;
if (capa & PCI_64BIT)
cobalt->pci_32_bit = false;
else
cobalt->pci_32_bit = true;
for (i = 0; i < cobalt->dma_channels; i++) {
u32 status = ioread32(CS_REG(i));
u32 ctrl = ioread32(CS_REG(i));
if (!(ctrl & DONE))
iowrite32(ABORT, CS_REG(i));
if (!(status & DMA_TYPE_FIFO))
cobalt->first_fifo_channel++;
}
show_dma_capability(cobalt);
return 0;
}
int descriptor_list_create(struct cobalt *cobalt,
struct scatterlist *scatter_list, bool to_pci, unsigned sglen,
unsigned size, unsigned width, unsigned stride,
struct sg_dma_desc_info *desc)
{
struct sg_dma_descriptor *d = (struct sg_dma_descriptor *)desc->virt;
dma_addr_t next = desc->bus;
unsigned offset = 0;
unsigned copy_bytes = width;
unsigned copied = 0;
bool first = true;
/* Must be 4-byte aligned */
WARN_ON(sg_dma_address(scatter_list) & 3);
WARN_ON(size & 3);
WARN_ON(next & 3);
WARN_ON(stride & 3);
WARN_ON(stride < width);
if (width >= stride)
copy_bytes = stride = size;
while (size) {
dma_addr_t addr = sg_dma_address(scatter_list) + offset;
unsigned bytes;
if (addr == 0)
return -EFAULT;
if (cobalt->pci_32_bit) {
WARN_ON((u64)addr >> 32);
if ((u64)addr >> 32)
return -EFAULT;
}
/* PCIe address */
d->pci_l = addr & 0xffffffff;
/* If dma_addr_t is 32 bits, then addr >> 32 is actually the
equivalent of addr >> 0 in gcc. So must cast to u64. */
d->pci_h = (u64)addr >> 32;
/* Sync to start of streaming frame */
d->local = 0;
d->reserved0 = 0;
/* Transfer bytes */
bytes = min(sg_dma_len(scatter_list) - offset,
copy_bytes - copied);
if (first) {
if (to_pci)
d->local = 0x11111111;
first = false;
if (sglen == 1) {
/* Make sure there are always at least two
* descriptors */
d->bytes = (bytes / 2) & ~3;
d->reserved1 = 0;
size -= d->bytes;
copied += d->bytes;
offset += d->bytes;
addr += d->bytes;
next += sizeof(struct sg_dma_descriptor);
d->next_h = (u32)(next >> 32);
d->next_l = (u32)next |
(to_pci ? WRITE_TO_PCI : 0);
bytes -= d->bytes;
d++;
/* PCIe address */
d->pci_l = addr & 0xffffffff;
/* If dma_addr_t is 32 bits, then addr >> 32
* is actually the equivalent of addr >> 0 in
* gcc. So must cast to u64. */
d->pci_h = (u64)addr >> 32;
/* Sync to start of streaming frame */
d->local = 0;
d->reserved0 = 0;
}
}
d->bytes = bytes;
d->reserved1 = 0;
size -= bytes;
copied += bytes;
offset += bytes;
if (copied == copy_bytes) {
while (copied < stride) {
bytes = min(sg_dma_len(scatter_list) - offset,
stride - copied);
copied += bytes;
offset += bytes;
size -= bytes;
if (sg_dma_len(scatter_list) == offset) {
offset = 0;
scatter_list = sg_next(scatter_list);
}
}
copied = 0;
} else {
offset = 0;
scatter_list = sg_next(scatter_list);
}
/* Next descriptor + control bits */
next += sizeof(struct sg_dma_descriptor);
if (size == 0) {
/* Loopback to the first descriptor */
d->next_h = (u32)(desc->bus >> 32);
d->next_l = (u32)desc->bus |
(to_pci ? WRITE_TO_PCI : 0) | INTERRUPT_ENABLE;
if (!to_pci)
d->local = 0x22222222;
desc->last_desc_virt = d;
} else {
d->next_h = (u32)(next >> 32);
d->next_l = (u32)next | (to_pci ? WRITE_TO_PCI : 0);
}
d++;
}
return 0;
}
void descriptor_list_chain(struct sg_dma_desc_info *this,
struct sg_dma_desc_info *next)
{
struct sg_dma_descriptor *d = this->last_desc_virt;
u32 direction = d->next_l & WRITE_TO_PCI;
if (next == NULL) {
d->next_h = 0;
d->next_l = direction | INTERRUPT_ENABLE | END_OF_CHAIN;
} else {
d->next_h = (u32)(next->bus >> 32);
d->next_l = (u32)next->bus | direction | INTERRUPT_ENABLE;
}
}
void *descriptor_list_allocate(struct sg_dma_desc_info *desc, size_t bytes)
{
desc->size = bytes;
desc->virt = dma_alloc_coherent(desc->dev, bytes,
&desc->bus, GFP_KERNEL);
return desc->virt;
}
void descriptor_list_free(struct sg_dma_desc_info *desc)
{
if (desc->virt)
dma_free_coherent(desc->dev, desc->size,
desc->virt, desc->bus);
desc->virt = NULL;
}
void descriptor_list_interrupt_enable(struct sg_dma_desc_info *desc)
{
struct sg_dma_descriptor *d = desc->last_desc_virt;
d->next_l |= INTERRUPT_ENABLE;
}
void descriptor_list_interrupt_disable(struct sg_dma_desc_info *desc)
{
struct sg_dma_descriptor *d = desc->last_desc_virt;
d->next_l &= ~INTERRUPT_ENABLE;
}
void descriptor_list_loopback(struct sg_dma_desc_info *desc)
{
struct sg_dma_descriptor *d = desc->last_desc_virt;
d->next_h = (u32)(desc->bus >> 32);
d->next_l = (u32)desc->bus | (d->next_l & DESCRIPTOR_FLAG_MSK);
}
void descriptor_list_end_of_chain(struct sg_dma_desc_info *desc)
{
struct sg_dma_descriptor *d = desc->last_desc_virt;
d->next_l |= END_OF_CHAIN;
}
/*
* Omnitek Scatter-Gather DMA Controller
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef COBALT_OMNITEK_H
#define COBALT_OMNITEK_H
#include <linux/scatterlist.h>
#include "cobalt-driver.h"
struct sg_dma_descriptor {
u32 pci_l;
u32 pci_h;
u32 local;
u32 reserved0;
u32 next_l;
u32 next_h;
u32 bytes;
u32 reserved1;
};
int omni_sg_dma_init(struct cobalt *cobalt);
void omni_sg_dma_abort_channel(struct cobalt_stream *s);
void omni_sg_dma_start(struct cobalt_stream *s, struct sg_dma_desc_info *desc);
bool is_dma_done(struct cobalt_stream *s);
int descriptor_list_create(struct cobalt *cobalt,
struct scatterlist *scatter_list, bool to_pci, unsigned sglen,
unsigned size, unsigned width, unsigned stride,
struct sg_dma_desc_info *desc);
void descriptor_list_chain(struct sg_dma_desc_info *this,
struct sg_dma_desc_info *next);
void descriptor_list_loopback(struct sg_dma_desc_info *desc);
void descriptor_list_end_of_chain(struct sg_dma_desc_info *desc);
void *descriptor_list_allocate(struct sg_dma_desc_info *desc, size_t bytes);
void descriptor_list_free(struct sg_dma_desc_info *desc);
void descriptor_list_interrupt_enable(struct sg_dma_desc_info *desc);
void descriptor_list_interrupt_disable(struct sg_dma_desc_info *desc);
#endif
This diff is collapsed.
/*
* cobalt V4L2 API
*
* Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
int cobalt_nodes_register(struct cobalt *cobalt);
void cobalt_nodes_unregister(struct cobalt *cobalt);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment