提交 85756a06 编写于 作者: H Hans Verkuil 提交者: 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: NHans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: NMauro Carvalho Chehab <mchehab@osg.samsung.com>
上级 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;
}
/*
* 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.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <media/v4l2-device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "cobalt-driver.h"
#include "cobalt-alsa.h"
#include "cobalt-alsa-pcm.h"
static unsigned int pcm_debug;
module_param(pcm_debug, int, 0644);
MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm");
#define dprintk(fmt, arg...) \
do { \
if (pcm_debug) \
pr_info("cobalt-alsa-pcm %s: " fmt, __func__, ##arg); \
} while (0)
static struct snd_pcm_hardware snd_cobalt_hdmi_capture = {
.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = 4 * 240 * 8 * 4, /* 5 ms of data */
.period_bytes_min = 1920, /* 1 sample = 8 * 4 bytes */
.period_bytes_max = 240 * 8 * 4, /* 5 ms of 8 channel data */
.periods_min = 1,
.periods_max = 4,
};
static struct snd_pcm_hardware snd_cobalt_playback = {
.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = 4 * 240 * 8 * 4, /* 5 ms of data */
.period_bytes_min = 1920, /* 1 sample = 8 * 4 bytes */
.period_bytes_max = 240 * 8 * 4, /* 5 ms of 8 channel data */
.periods_min = 1,
.periods_max = 4,
};
static void sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32)
{
static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 };
unsigned idx = 0;
while (len >= (is_s32 ? 4 : 2)) {
unsigned offset = map[idx] * 4;
u32 val = src[offset + 1] + (src[offset + 2] << 8) +
(src[offset + 3] << 16);
if (is_s32) {
*dst++ = 0;
*dst++ = val & 0xff;
}
*dst++ = (val >> 8) & 0xff;
*dst++ = (val >> 16) & 0xff;
len -= is_s32 ? 4 : 2;
idx++;
}
}
static void cobalt_alsa_announce_pcm_data(struct snd_cobalt_card *cobsc,
u8 *pcm_data,
size_t skip,
size_t samples)
{
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
unsigned long flags;
unsigned int oldptr;
unsigned int stride;
int length = samples;
int period_elapsed = 0;
bool is_s32;
dprintk("cobalt alsa announce ptr=%p data=%p num_bytes=%zd\n", cobsc,
pcm_data, samples);
substream = cobsc->capture_pcm_substream;
if (substream == NULL) {
dprintk("substream was NULL\n");
return;
}
runtime = substream->runtime;
if (runtime == NULL) {
dprintk("runtime was NULL\n");
return;
}
is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE;
stride = runtime->frame_bits >> 3;
if (stride == 0) {
dprintk("stride is zero\n");
return;
}
if (length == 0) {
dprintk("%s: length was zero\n", __func__);
return;
}
if (runtime->dma_area == NULL) {
dprintk("dma area was NULL - ignoring\n");
return;
}
oldptr = cobsc->hwptr_done_capture;
if (oldptr + length >= runtime->buffer_size) {
unsigned int cnt = runtime->buffer_size - oldptr;
unsigned i;
for (i = 0; i < cnt; i++)
sample_cpy(runtime->dma_area + (oldptr + i) * stride,
pcm_data + i * skip,
stride, is_s32);
for (i = cnt; i < length; i++)
sample_cpy(runtime->dma_area + (i - cnt) * stride,
pcm_data + i * skip, stride, is_s32);
} else {
unsigned i;
for (i = 0; i < length; i++)
sample_cpy(runtime->dma_area + (oldptr + i) * stride,
pcm_data + i * skip,
stride, is_s32);
}
snd_pcm_stream_lock_irqsave(substream, flags);
cobsc->hwptr_done_capture += length;
if (cobsc->hwptr_done_capture >=
runtime->buffer_size)
cobsc->hwptr_done_capture -=
runtime->buffer_size;
cobsc->capture_transfer_done += length;
if (cobsc->capture_transfer_done >=
runtime->period_size) {
cobsc->capture_transfer_done -=
runtime->period_size;
period_elapsed = 1;
}
snd_pcm_stream_unlock_irqrestore(substream, flags);
if (period_elapsed)
snd_pcm_period_elapsed(substream);
}
static int alsa_fnc(struct vb2_buffer *vb, void *priv)
{
struct cobalt_stream *s = priv;
unsigned char *p = vb2_plane_vaddr(vb, 0);
int i;
if (pcm_debug) {
pr_info("alsa: ");
for (i = 0; i < 8 * 4; i++) {
if (!(i & 3))
pr_cont(" ");
pr_cont("%02x", p[i]);
}
pr_cont("\n");
}
cobalt_alsa_announce_pcm_data(s->alsa,
vb2_plane_vaddr(vb, 0),
8 * 4,
vb2_get_plane_payload(vb, 0) / (8 * 4));
return 0;
}
static int snd_cobalt_pcm_capture_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
struct cobalt_stream *s = cobsc->s;
runtime->hw = snd_cobalt_hdmi_capture;
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
cobsc->capture_pcm_substream = substream;
runtime->private_data = s;
cobsc->alsa_record_cnt++;
if (cobsc->alsa_record_cnt == 1) {
int rc;
rc = vb2_thread_start(&s->q, alsa_fnc, s, s->vdev.name);
if (rc) {
cobsc->alsa_record_cnt--;
return rc;
}
}
return 0;
}
static int snd_cobalt_pcm_capture_close(struct snd_pcm_substream *substream)
{
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
struct cobalt_stream *s = cobsc->s;
cobsc->alsa_record_cnt--;
if (cobsc->alsa_record_cnt == 0)
vb2_thread_stop(&s->q);
return 0;
}
static int snd_cobalt_pcm_ioctl(struct snd_pcm_substream *substream,
unsigned int cmd, void *arg)
{
return snd_pcm_lib_ioctl(substream, cmd, arg);
}
static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,
size_t size)
{
struct snd_pcm_runtime *runtime = subs->runtime;
dprintk("Allocating vbuffer\n");
if (runtime->dma_area) {
if (runtime->dma_bytes > size)
return 0;
vfree(runtime->dma_area);
}
runtime->dma_area = vmalloc(size);
if (!runtime->dma_area)
return -ENOMEM;
runtime->dma_bytes = size;
return 0;
}
static int snd_cobalt_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
dprintk("%s called\n", __func__);
return snd_pcm_alloc_vmalloc_buffer(substream,
params_buffer_bytes(params));
}
static int snd_cobalt_pcm_hw_free(struct snd_pcm_substream *substream)
{
if (substream->runtime->dma_area) {
dprintk("freeing pcm capture region\n");
vfree(substream->runtime->dma_area);
substream->runtime->dma_area = NULL;
}
return 0;
}
static int snd_cobalt_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
cobsc->hwptr_done_capture = 0;
cobsc->capture_transfer_done = 0;
return 0;
}
static int snd_cobalt_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_STOP:
return 0;
default:
return -EINVAL;
}
return 0;
}
static
snd_pcm_uframes_t snd_cobalt_pcm_pointer(struct snd_pcm_substream *substream)
{
snd_pcm_uframes_t hwptr_done;
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
hwptr_done = cobsc->hwptr_done_capture;
return hwptr_done;
}
static void pb_sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32)
{
static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 };
unsigned idx = 0;
while (len >= (is_s32 ? 4 : 2)) {
unsigned offset = map[idx] * 4;
u8 *out = dst + offset;
*out++ = 0;
if (is_s32) {
src++;
*out++ = *src++;
} else {
*out++ = 0;
}
*out++ = *src++;
*out = *src++;
len -= is_s32 ? 4 : 2;
idx++;
}
}
static void cobalt_alsa_pb_pcm_data(struct snd_cobalt_card *cobsc,
u8 *pcm_data,
size_t skip,
size_t samples)
{
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
unsigned long flags;
unsigned int pos;
unsigned int stride;
bool is_s32;
unsigned i;
dprintk("cobalt alsa pb ptr=%p data=%p samples=%zd\n", cobsc,
pcm_data, samples);
substream = cobsc->playback_pcm_substream;
if (substream == NULL) {
dprintk("substream was NULL\n");
return;
}
runtime = substream->runtime;
if (runtime == NULL) {
dprintk("runtime was NULL\n");
return;
}
is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE;
stride = runtime->frame_bits >> 3;
if (stride == 0) {
dprintk("stride is zero\n");
return;
}
if (samples == 0) {
dprintk("%s: samples was zero\n", __func__);
return;
}
if (runtime->dma_area == NULL) {
dprintk("dma area was NULL - ignoring\n");
return;
}
pos = cobsc->pb_pos % cobsc->pb_size;
for (i = 0; i < cobsc->pb_count / (8 * 4); i++)
pb_sample_cpy(pcm_data + i * skip,
runtime->dma_area + pos + i * stride,
stride, is_s32);
snd_pcm_stream_lock_irqsave(substream, flags);
cobsc->pb_pos += i * stride;
snd_pcm_stream_unlock_irqrestore(substream, flags);
if (cobsc->pb_pos % cobsc->pb_count == 0)
snd_pcm_period_elapsed(substream);
}
static int alsa_pb_fnc(struct vb2_buffer *vb, void *priv)
{
struct cobalt_stream *s = priv;
if (s->alsa->alsa_pb_channel)
cobalt_alsa_pb_pcm_data(s->alsa,
vb2_plane_vaddr(vb, 0),
8 * 4,
vb2_get_plane_payload(vb, 0) / (8 * 4));
return 0;
}
static int snd_cobalt_pcm_playback_open(struct snd_pcm_substream *substream)
{
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct cobalt_stream *s = cobsc->s;
runtime->hw = snd_cobalt_playback;
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
cobsc->playback_pcm_substream = substream;
runtime->private_data = s;
cobsc->alsa_playback_cnt++;
if (cobsc->alsa_playback_cnt == 1) {
int rc;
rc = vb2_thread_start(&s->q, alsa_pb_fnc, s, s->vdev.name);
if (rc) {
cobsc->alsa_playback_cnt--;
return rc;
}
}
return 0;
}
static int snd_cobalt_pcm_playback_close(struct snd_pcm_substream *substream)
{
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
struct cobalt_stream *s = cobsc->s;
cobsc->alsa_playback_cnt--;
if (cobsc->alsa_playback_cnt == 0)
vb2_thread_stop(&s->q);
return 0;
}
static int snd_cobalt_pcm_pb_prepare(struct snd_pcm_substream *substream)
{
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
cobsc->pb_size = snd_pcm_lib_buffer_bytes(substream);
cobsc->pb_count = snd_pcm_lib_period_bytes(substream);
cobsc->pb_pos = 0;
return 0;
}
static int snd_cobalt_pcm_pb_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if (cobsc->alsa_pb_channel)
return -EBUSY;
cobsc->alsa_pb_channel = true;
return 0;
case SNDRV_PCM_TRIGGER_STOP:
cobsc->alsa_pb_channel = false;
return 0;
default:
return -EINVAL;
}
}
static
snd_pcm_uframes_t snd_cobalt_pcm_pb_pointer(struct snd_pcm_substream *substream)
{
struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
size_t ptr;
ptr = cobsc->pb_pos;
return bytes_to_frames(substream->runtime, ptr) %
substream->runtime->buffer_size;
}
static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
unsigned long offset)
{
void *pageptr = subs->runtime->dma_area + offset;
return vmalloc_to_page(pageptr);
}
static struct snd_pcm_ops snd_cobalt_pcm_capture_ops = {
.open = snd_cobalt_pcm_capture_open,
.close = snd_cobalt_pcm_capture_close,
.ioctl = snd_cobalt_pcm_ioctl,
.hw_params = snd_cobalt_pcm_hw_params,
.hw_free = snd_cobalt_pcm_hw_free,
.prepare = snd_cobalt_pcm_prepare,
.trigger = snd_cobalt_pcm_trigger,
.pointer = snd_cobalt_pcm_pointer,
.page = snd_pcm_get_vmalloc_page,
};
static struct snd_pcm_ops snd_cobalt_pcm_playback_ops = {
.open = snd_cobalt_pcm_playback_open,
.close = snd_cobalt_pcm_playback_close,
.ioctl = snd_cobalt_pcm_ioctl,
.hw_params = snd_cobalt_pcm_hw_params,
.hw_free = snd_cobalt_pcm_hw_free,
.prepare = snd_cobalt_pcm_pb_prepare,
.trigger = snd_cobalt_pcm_pb_trigger,
.pointer = snd_cobalt_pcm_pb_pointer,
.page = snd_pcm_get_vmalloc_page,
};
int snd_cobalt_pcm_create(struct snd_cobalt_card *cobsc)
{
struct snd_pcm *sp;
struct snd_card *sc = cobsc->sc;
struct cobalt_stream *s = cobsc->s;
struct cobalt *cobalt = s->cobalt;
int ret;
s->q.gfp_flags |= __GFP_ZERO;
if (!s->is_output) {
cobalt_s_bit_sysctrl(cobalt,
COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel),
0);
mdelay(2);
cobalt_s_bit_sysctrl(cobalt,
COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel),
1);
mdelay(1);
ret = snd_pcm_new(sc, "Cobalt PCM-In HDMI",
0, /* PCM device 0, the only one for this card */
0, /* 0 playback substreams */
1, /* 1 capture substream */
&sp);
if (ret) {
cobalt_err("snd_cobalt_pcm_create() failed for input with err %d\n",
ret);
goto err_exit;
}
snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE,
&snd_cobalt_pcm_capture_ops);
sp->info_flags = 0;
sp->private_data = cobsc;
strlcpy(sp->name, "cobalt", sizeof(sp->name));
} else {
cobalt_s_bit_sysctrl(cobalt,
COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, 0);
mdelay(2);
cobalt_s_bit_sysctrl(cobalt,
COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, 1);
mdelay(1);
ret = snd_pcm_new(sc, "Cobalt PCM-Out HDMI",
0, /* PCM device 0, the only one for this card */
1, /* 0 playback substreams */
0, /* 1 capture substream */
&sp);
if (ret) {
cobalt_err("snd_cobalt_pcm_create() failed for output with err %d\n",
ret);
goto err_exit;
}
snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_PLAYBACK,
&snd_cobalt_pcm_playback_ops);
sp->info_flags = 0;
sp->private_data = cobsc;
strlcpy(sp->name, "cobalt", sizeof(sp->name));
}
return 0;
err_exit:
return ret;
}
/*
* 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);
/*
* 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.
*/
#include <linux/delay.h>
#include "cobalt-driver.h"
#define ADRS(offset) (COBALT_BUS_CPLD_BASE + offset)
static u16 cpld_read(struct cobalt *cobalt, u32 offset)
{
return cobalt_bus_read32(cobalt, ADRS(offset));
}
static void cpld_write(struct cobalt *cobalt, u32 offset, u16 val)
{
return cobalt_bus_write32(cobalt, ADRS(offset), val);
}
static void cpld_info_ver3(struct cobalt *cobalt)
{
u32 rd;
u32 tmp;
cobalt_info("CPLD System control register (read/write)\n");
cobalt_info("\t\tSystem control: 0x%04x (0x0f00)\n",
cpld_read(cobalt, 0));
cobalt_info("CPLD Clock control register (read/write)\n");
cobalt_info("\t\tClock control: 0x%04x (0x0000)\n",
cpld_read(cobalt, 0x04));
cobalt_info("CPLD HSMA Clk Osc register (read/write) - Must set wr trigger to load default values\n");
cobalt_info("\t\tRegister #7:\t0x%04x (0x0022)\n",
cpld_read(cobalt, 0x08));
cobalt_info("\t\tRegister #8:\t0x%04x (0x0047)\n",
cpld_read(cobalt, 0x0c));
cobalt_info("\t\tRegister #9:\t0x%04x (0x00fa)\n",
cpld_read(cobalt, 0x10));
cobalt_info("\t\tRegister #10:\t0x%04x (0x0061)\n",
cpld_read(cobalt, 0x14));
cobalt_info("\t\tRegister #11:\t0x%04x (0x001e)\n",
cpld_read(cobalt, 0x18));
cobalt_info("\t\tRegister #12:\t0x%04x (0x0045)\n",
cpld_read(cobalt, 0x1c));
cobalt_info("\t\tRegister #135:\t0x%04x\n",
cpld_read(cobalt, 0x20));
cobalt_info("\t\tRegister #137:\t0x%04x\n",
cpld_read(cobalt, 0x24));
cobalt_info("CPLD System status register (read only)\n");
cobalt_info("\t\tSystem status: 0x%04x\n",
cpld_read(cobalt, 0x28));
cobalt_info("CPLD MAXII info register (read only)\n");
cobalt_info("\t\tBoard serial number: 0x%04x\n",
cpld_read(cobalt, 0x2c));
cobalt_info("\t\tMAXII program revision: 0x%04x\n",
cpld_read(cobalt, 0x30));
cobalt_info("CPLD temp and voltage ADT7411 registers (read only)\n");
cobalt_info("\t\tBoard temperature: %u Celcius\n",
cpld_read(cobalt, 0x34) / 4);
cobalt_info("\t\tFPGA temperature: %u Celcius\n",
cpld_read(cobalt, 0x38) / 4);
rd = cpld_read(cobalt, 0x3c);
tmp = (rd * 33 * 1000) / (483 * 10);
cobalt_info("\t\tVDD 3V3: %u,%03uV\n", tmp / 1000, tmp % 1000);
rd = cpld_read(cobalt, 0x40);
tmp = (rd * 74 * 2197) / (27 * 1000);
cobalt_info("\t\tADC ch3 5V: %u,%03uV\n", tmp / 1000, tmp % 1000);
rd = cpld_read(cobalt, 0x44);
tmp = (rd * 74 * 2197) / (47 * 1000);
cobalt_info("\t\tADC ch4 3V: %u,%03uV\n", tmp / 1000, tmp % 1000);
rd = cpld_read(cobalt, 0x48);
tmp = (rd * 57 * 2197) / (47 * 1000);
cobalt_info("\t\tADC ch5 2V5: %u,%03uV\n", tmp / 1000, tmp % 1000);
rd = cpld_read(cobalt, 0x4c);
tmp = (rd * 2197) / 1000;
cobalt_info("\t\tADC ch6 1V8: %u,%03uV\n", tmp / 1000, tmp % 1000);
rd = cpld_read(cobalt, 0x50);
tmp = (rd * 2197) / 1000;
cobalt_info("\t\tADC ch7 1V5: %u,%03uV\n", tmp / 1000, tmp % 1000);
rd = cpld_read(cobalt, 0x54);
tmp = (rd * 2197) / 1000;
cobalt_info("\t\tADC ch8 0V9: %u,%03uV\n", tmp / 1000, tmp % 1000);
}
void cobalt_cpld_status(struct cobalt *cobalt)
{
u32 rev = cpld_read(cobalt, 0x30);
switch (rev) {
case 3:
case 4:
case 5:
cpld_info_ver3(cobalt);
break;
default:
cobalt_info("CPLD revision %u is not supported!\n", rev);
break;
}
}
#define DCO_MIN 4850000000ULL
#define DCO_MAX 5670000000ULL
#define SI570_CLOCK_CTRL 0x04
#define S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_WR_TRIGGER 0x200
#define S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_RST_TRIGGER 0x100
#define S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL 0x80
#define S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN 0x40
#define SI570_REG7 0x08
#define SI570_REG8 0x0c
#define SI570_REG9 0x10
#define SI570_REG10 0x14
#define SI570_REG11 0x18
#define SI570_REG12 0x1c
#define SI570_REG135 0x20
#define SI570_REG137 0x24
struct multiplier {
unsigned mult, hsdiv, n1;
};
/* List all possible multipliers (= hsdiv * n1). There are lots of duplicates,
which are all removed in this list to keep the list as short as possible.
The values for hsdiv and n1 are the actual values, not the register values.
*/
static const struct multiplier multipliers[] = {
{ 4, 4, 1 }, { 5, 5, 1 }, { 6, 6, 1 },
{ 7, 7, 1 }, { 8, 4, 2 }, { 9, 9, 1 },
{ 10, 5, 2 }, { 11, 11, 1 }, { 12, 6, 2 },
{ 14, 7, 2 }, { 16, 4, 4 }, { 18, 9, 2 },
{ 20, 5, 4 }, { 22, 11, 2 }, { 24, 4, 6 },
{ 28, 7, 4 }, { 30, 5, 6 }, { 32, 4, 8 },
{ 36, 6, 6 }, { 40, 4, 10 }, { 42, 7, 6 },
{ 44, 11, 4 }, { 48, 4, 12 }, { 50, 5, 10 },
{ 54, 9, 6 }, { 56, 4, 14 }, { 60, 5, 12 },
{ 64, 4, 16 }, { 66, 11, 6 }, { 70, 5, 14 },
{ 72, 4, 18 }, { 80, 4, 20 }, { 84, 6, 14 },
{ 88, 11, 8 }, { 90, 5, 18 }, { 96, 4, 24 },
{ 98, 7, 14 }, { 100, 5, 20 }, { 104, 4, 26 },
{ 108, 6, 18 }, { 110, 11, 10 }, { 112, 4, 28 },
{ 120, 4, 30 }, { 126, 7, 18 }, { 128, 4, 32 },
{ 130, 5, 26 }, { 132, 11, 12 }, { 136, 4, 34 },
{ 140, 5, 28 }, { 144, 4, 36 }, { 150, 5, 30 },
{ 152, 4, 38 }, { 154, 11, 14 }, { 156, 6, 26 },
{ 160, 4, 40 }, { 162, 9, 18 }, { 168, 4, 42 },
{ 170, 5, 34 }, { 176, 11, 16 }, { 180, 5, 36 },
{ 182, 7, 26 }, { 184, 4, 46 }, { 190, 5, 38 },
{ 192, 4, 48 }, { 196, 7, 28 }, { 198, 11, 18 },
{ 198, 9, 22 }, { 200, 4, 50 }, { 204, 6, 34 },
{ 208, 4, 52 }, { 210, 5, 42 }, { 216, 4, 54 },
{ 220, 11, 20 }, { 224, 4, 56 }, { 228, 6, 38 },
{ 230, 5, 46 }, { 232, 4, 58 }, { 234, 9, 26 },
{ 238, 7, 34 }, { 240, 4, 60 }, { 242, 11, 22 },
{ 248, 4, 62 }, { 250, 5, 50 }, { 252, 6, 42 },
{ 256, 4, 64 }, { 260, 5, 52 }, { 264, 11, 24 },
{ 266, 7, 38 }, { 270, 5, 54 }, { 272, 4, 68 },
{ 276, 6, 46 }, { 280, 4, 70 }, { 286, 11, 26 },
{ 288, 4, 72 }, { 290, 5, 58 }, { 294, 7, 42 },
{ 296, 4, 74 }, { 300, 5, 60 }, { 304, 4, 76 },
{ 306, 9, 34 }, { 308, 11, 28 }, { 310, 5, 62 },
{ 312, 4, 78 }, { 320, 4, 80 }, { 322, 7, 46 },
{ 324, 6, 54 }, { 328, 4, 82 }, { 330, 11, 30 },
{ 336, 4, 84 }, { 340, 5, 68 }, { 342, 9, 38 },
{ 344, 4, 86 }, { 348, 6, 58 }, { 350, 5, 70 },
{ 352, 11, 32 }, { 360, 4, 90 }, { 364, 7, 52 },
{ 368, 4, 92 }, { 370, 5, 74 }, { 372, 6, 62 },
{ 374, 11, 34 }, { 376, 4, 94 }, { 378, 7, 54 },
{ 380, 5, 76 }, { 384, 4, 96 }, { 390, 5, 78 },
{ 392, 4, 98 }, { 396, 11, 36 }, { 400, 4, 100 },
{ 406, 7, 58 }, { 408, 4, 102 }, { 410, 5, 82 },
{ 414, 9, 46 }, { 416, 4, 104 }, { 418, 11, 38 },
{ 420, 5, 84 }, { 424, 4, 106 }, { 430, 5, 86 },
{ 432, 4, 108 }, { 434, 7, 62 }, { 440, 11, 40 },
{ 444, 6, 74 }, { 448, 4, 112 }, { 450, 5, 90 },
{ 456, 4, 114 }, { 460, 5, 92 }, { 462, 11, 42 },
{ 464, 4, 116 }, { 468, 6, 78 }, { 470, 5, 94 },
{ 472, 4, 118 }, { 476, 7, 68 }, { 480, 4, 120 },
{ 484, 11, 44 }, { 486, 9, 54 }, { 488, 4, 122 },
{ 490, 5, 98 }, { 492, 6, 82 }, { 496, 4, 124 },
{ 500, 5, 100 }, { 504, 4, 126 }, { 506, 11, 46 },
{ 510, 5, 102 }, { 512, 4, 128 }, { 516, 6, 86 },
{ 518, 7, 74 }, { 520, 5, 104 }, { 522, 9, 58 },
{ 528, 11, 48 }, { 530, 5, 106 }, { 532, 7, 76 },
{ 540, 5, 108 }, { 546, 7, 78 }, { 550, 11, 50 },
{ 552, 6, 92 }, { 558, 9, 62 }, { 560, 5, 112 },
{ 564, 6, 94 }, { 570, 5, 114 }, { 572, 11, 52 },
{ 574, 7, 82 }, { 576, 6, 96 }, { 580, 5, 116 },
{ 588, 6, 98 }, { 590, 5, 118 }, { 594, 11, 54 },
{ 600, 5, 120 }, { 602, 7, 86 }, { 610, 5, 122 },
{ 612, 6, 102 }, { 616, 11, 56 }, { 620, 5, 124 },
{ 624, 6, 104 }, { 630, 5, 126 }, { 636, 6, 106 },
{ 638, 11, 58 }, { 640, 5, 128 }, { 644, 7, 92 },
{ 648, 6, 108 }, { 658, 7, 94 }, { 660, 11, 60 },
{ 666, 9, 74 }, { 672, 6, 112 }, { 682, 11, 62 },
{ 684, 6, 114 }, { 686, 7, 98 }, { 696, 6, 116 },
{ 700, 7, 100 }, { 702, 9, 78 }, { 704, 11, 64 },
{ 708, 6, 118 }, { 714, 7, 102 }, { 720, 6, 120 },
{ 726, 11, 66 }, { 728, 7, 104 }, { 732, 6, 122 },
{ 738, 9, 82 }, { 742, 7, 106 }, { 744, 6, 124 },
{ 748, 11, 68 }, { 756, 6, 126 }, { 768, 6, 128 },
{ 770, 11, 70 }, { 774, 9, 86 }, { 784, 7, 112 },
{ 792, 11, 72 }, { 798, 7, 114 }, { 810, 9, 90 },
{ 812, 7, 116 }, { 814, 11, 74 }, { 826, 7, 118 },
{ 828, 9, 92 }, { 836, 11, 76 }, { 840, 7, 120 },
{ 846, 9, 94 }, { 854, 7, 122 }, { 858, 11, 78 },
{ 864, 9, 96 }, { 868, 7, 124 }, { 880, 11, 80 },
{ 882, 7, 126 }, { 896, 7, 128 }, { 900, 9, 100 },
{ 902, 11, 82 }, { 918, 9, 102 }, { 924, 11, 84 },
{ 936, 9, 104 }, { 946, 11, 86 }, { 954, 9, 106 },
{ 968, 11, 88 }, { 972, 9, 108 }, { 990, 11, 90 },
{ 1008, 9, 112 }, { 1012, 11, 92 }, { 1026, 9, 114 },
{ 1034, 11, 94 }, { 1044, 9, 116 }, { 1056, 11, 96 },
{ 1062, 9, 118 }, { 1078, 11, 98 }, { 1080, 9, 120 },
{ 1098, 9, 122 }, { 1100, 11, 100 }, { 1116, 9, 124 },
{ 1122, 11, 102 }, { 1134, 9, 126 }, { 1144, 11, 104 },
{ 1152, 9, 128 }, { 1166, 11, 106 }, { 1188, 11, 108 },
{ 1210, 11, 110 }, { 1232, 11, 112 }, { 1254, 11, 114 },
{ 1276, 11, 116 }, { 1298, 11, 118 }, { 1320, 11, 120 },
{ 1342, 11, 122 }, { 1364, 11, 124 }, { 1386, 11, 126 },
{ 1408, 11, 128 },
};
bool cobalt_cpld_set_freq(struct cobalt *cobalt, unsigned f_out)
{
const unsigned f_xtal = 39170000; /* xtal for si598 */
unsigned long long dco;
unsigned long long rfreq;
unsigned delta = 0xffffffff;
unsigned i_best = 0;
unsigned i;
u8 n1, hsdiv;
u8 regs[6];
int found = 0;
u16 clock_ctrl;
int retries = 3;
for (i = 0; i < ARRAY_SIZE(multipliers); i++) {
unsigned mult = multipliers[i].mult;
unsigned d;
dco = (unsigned long long)f_out * mult;
if (dco < DCO_MIN || dco > DCO_MAX)
continue;
d = ((dco << 28) + f_xtal / 2) % f_xtal;
if (d < delta) {
found = 1;
i_best = i;
delta = d;
}
}
if (!found)
return false;
dco = (unsigned long long)f_out * multipliers[i_best].mult;
n1 = multipliers[i_best].n1 - 1;
hsdiv = multipliers[i_best].hsdiv - 4;
rfreq = (dco << 28) / f_xtal;
clock_ctrl = cpld_read(cobalt, SI570_CLOCK_CTRL);
clock_ctrl |= S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL;
clock_ctrl |= S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN;
regs[0] = (hsdiv << 5) | (n1 >> 2);
regs[1] = ((n1 & 0x3) << 6) | (rfreq >> 32);
regs[2] = (rfreq >> 24) & 0xff;
regs[3] = (rfreq >> 16) & 0xff;
regs[4] = (rfreq >> 8) & 0xff;
regs[5] = rfreq & 0xff;
/* The sequence of clock_ctrl flags to set is very weird. It looks
like I have to reset it, then set the new frequency and reset it
again. It shouldn't be necessary to do a reset, but if I don't,
then a strange frequency is set (156.412034 MHz, or register values
0x01, 0xc7, 0xfc, 0x7f, 0x53, 0x62).
*/
cobalt_dbg(1, "%u: %02x %02x %02x %02x %02x %02x\n", f_out,
regs[0], regs[1], regs[2], regs[3], regs[4], regs[5]);
while (retries--) {
u8 read_regs[6];
cpld_write(cobalt, SI570_CLOCK_CTRL,
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN |
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL);
usleep_range(10000, 15000);
cpld_write(cobalt, SI570_REG7, regs[0]);
cpld_write(cobalt, SI570_REG8, regs[1]);
cpld_write(cobalt, SI570_REG9, regs[2]);
cpld_write(cobalt, SI570_REG10, regs[3]);
cpld_write(cobalt, SI570_REG11, regs[4]);
cpld_write(cobalt, SI570_REG12, regs[5]);
cpld_write(cobalt, SI570_CLOCK_CTRL,
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN |
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_WR_TRIGGER);
usleep_range(10000, 15000);
cpld_write(cobalt, SI570_CLOCK_CTRL,
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN |
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL);
usleep_range(10000, 15000);
read_regs[0] = cpld_read(cobalt, SI570_REG7);
read_regs[1] = cpld_read(cobalt, SI570_REG8);
read_regs[2] = cpld_read(cobalt, SI570_REG9);
read_regs[3] = cpld_read(cobalt, SI570_REG10);
read_regs[4] = cpld_read(cobalt, SI570_REG11);
read_regs[5] = cpld_read(cobalt, SI570_REG12);
cpld_write(cobalt, SI570_CLOCK_CTRL,
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN |
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL |
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_RST_TRIGGER);
usleep_range(10000, 15000);
cpld_write(cobalt, SI570_CLOCK_CTRL,
S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN);
usleep_range(10000, 15000);
if (!memcmp(read_regs, regs, sizeof(read_regs)))
break;
cobalt_dbg(1, "retry: %02x %02x %02x %02x %02x %02x\n",
read_regs[0], read_regs[1], read_regs[2],
read_regs[3], read_regs[4], read_regs[5]);
}
if (2 - retries)
cobalt_info("Needed %d retries\n", 2 - retries);
return true;
}
/*
* 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
此差异已折叠。
/*
* cobalt driver internal defines and structures
*
* Derived from cx18-driver.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.
*/
#ifndef COBALT_DRIVER_H
#define COBALT_DRIVER_H
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <linux/i2c.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fh.h>
#include <media/videobuf2-dma-sg.h>
#include "m00233_video_measure_memmap_package.h"
#include "m00235_fdma_packer_memmap_package.h"
#include "m00389_cvi_memmap_package.h"
#include "m00460_evcnt_memmap_package.h"
#include "m00473_freewheel_memmap_package.h"
#include "m00479_clk_loss_detector_memmap_package.h"
#include "m00514_syncgen_flow_evcnt_memmap_package.h"
/* System device ID */
#define PCI_DEVICE_ID_COBALT 0x2732
/* Number of cobalt device nodes. */
#define COBALT_NUM_INPUTS 4
#define COBALT_NUM_NODES 6
/* Number of cobalt device streams. */
#define COBALT_NUM_STREAMS 12
#define COBALT_HSMA_IN_NODE 4
#define COBALT_HSMA_OUT_NODE 5
/* Cobalt audio streams */
#define COBALT_AUDIO_IN_STREAM 6
#define COBALT_AUDIO_OUT_STREAM 11
/* DMA stuff */
#define DMA_CHANNELS_MAX 16
/* i2c stuff */
#define I2C_CLIENTS_MAX 16
#define COBALT_NUM_ADAPTERS 5
#define COBALT_CLK 50000000
/* System status register */
#define COBALT_SYSSTAT_DIP0_MSK (1 << 0)
#define COBALT_SYSSTAT_DIP1_MSK (1 << 1)
#define COBALT_SYSSTAT_HSMA_PRSNTN_MSK (1 << 2)
#define COBALT_SYSSTAT_FLASH_RDYBSYN_MSK (1 << 3)
#define COBALT_SYSSTAT_VI0_5V_MSK (1 << 4)
#define COBALT_SYSSTAT_VI0_INT1_MSK (1 << 5)
#define COBALT_SYSSTAT_VI0_INT2_MSK (1 << 6)
#define COBALT_SYSSTAT_VI0_LOST_DATA_MSK (1 << 7)
#define COBALT_SYSSTAT_VI1_5V_MSK (1 << 8)
#define COBALT_SYSSTAT_VI1_INT1_MSK (1 << 9)
#define COBALT_SYSSTAT_VI1_INT2_MSK (1 << 10)
#define COBALT_SYSSTAT_VI1_LOST_DATA_MSK (1 << 11)
#define COBALT_SYSSTAT_VI2_5V_MSK (1 << 12)
#define COBALT_SYSSTAT_VI2_INT1_MSK (1 << 13)
#define COBALT_SYSSTAT_VI2_INT2_MSK (1 << 14)
#define COBALT_SYSSTAT_VI2_LOST_DATA_MSK (1 << 15)
#define COBALT_SYSSTAT_VI3_5V_MSK (1 << 16)
#define COBALT_SYSSTAT_VI3_INT1_MSK (1 << 17)
#define COBALT_SYSSTAT_VI3_INT2_MSK (1 << 18)
#define COBALT_SYSSTAT_VI3_LOST_DATA_MSK (1 << 19)
#define COBALT_SYSSTAT_VIHSMA_5V_MSK (1 << 20)
#define COBALT_SYSSTAT_VIHSMA_INT1_MSK (1 << 21)
#define COBALT_SYSSTAT_VIHSMA_INT2_MSK (1 << 22)
#define COBALT_SYSSTAT_VIHSMA_LOST_DATA_MSK (1 << 23)
#define COBALT_SYSSTAT_VOHSMA_INT1_MSK (1 << 25)
#define COBALT_SYSSTAT_VOHSMA_PLL_LOCKED_MSK (1 << 26)
#define COBALT_SYSSTAT_VOHSMA_LOST_DATA_MSK (1 << 27)
#define COBALT_SYSSTAT_AUD_PLL_LOCKED_MSK (1 << 28)
#define COBALT_SYSSTAT_AUD_IN_LOST_DATA_MSK (1 << 29)
#define COBALT_SYSSTAT_AUD_OUT_LOST_DATA_MSK (1 << 30)
#define COBALT_SYSSTAT_PCIE_SMBCLK_MSK (1 << 31)
/* Cobalt memory map */
#define COBALT_I2C_0_BASE 0x0
#define COBALT_I2C_1_BASE 0x080
#define COBALT_I2C_2_BASE 0x100
#define COBALT_I2C_3_BASE 0x180
#define COBALT_I2C_HSMA_BASE 0x200
#define COBALT_SYS_CTRL_BASE 0x400
#define COBALT_SYS_CTRL_HSMA_TX_ENABLE_BIT 1
#define COBALT_SYS_CTRL_VIDEO_RX_RESETN_BIT(n) (4 + 4 * (n))
#define COBALT_SYS_CTRL_NRESET_TO_HDMI_BIT(n) (5 + 4 * (n))
#define COBALT_SYS_CTRL_HPD_TO_CONNECTOR_BIT(n) (6 + 4 * (n))
#define COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(n) (7 + 4 * (n))
#define COBALT_SYS_CTRL_PWRDN0_TO_HSMA_TX_BIT 24
#define COBALT_SYS_CTRL_VIDEO_TX_RESETN_BIT 25
#define COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT 27
#define COBALT_SYS_STAT_BASE 0x500
#define COBALT_SYS_STAT_MASK (COBALT_SYS_STAT_BASE + 0x08)
#define COBALT_SYS_STAT_EDGE (COBALT_SYS_STAT_BASE + 0x0c)
#define COBALT_HDL_INFO_BASE 0x4800
#define COBALT_HDL_INFO_SIZE 0x200
#define COBALT_VID_BASE 0x10000
#define COBALT_VID_SIZE 0x1000
#define COBALT_CVI(cobalt, c) \
(cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE)
#define COBALT_CVI_VMR(cobalt, c) \
(cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x100)
#define COBALT_CVI_EVCNT(cobalt, c) \
(cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x200)
#define COBALT_CVI_FREEWHEEL(cobalt, c) \
(cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x300)
#define COBALT_CVI_CLK_LOSS(cobalt, c) \
(cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x400)
#define COBALT_CVI_PACKER(cobalt, c) \
(cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x500)
#define COBALT_TX_BASE(cobalt) (cobalt->bar1 + COBALT_VID_BASE + 0x5000)
#define DMA_INTERRUPT_STATUS_REG 0x08
#define COBALT_HDL_SEARCH_STR "** HDL version info **"
/* Cobalt CPU bus interface */
#define COBALT_BUS_BAR1_BASE 0x600
#define COBALT_BUS_SRAM_BASE 0x0
#define COBALT_BUS_CPLD_BASE 0x00600000
#define COBALT_BUS_FLASH_BASE 0x08000000
/* FDMA to PCIe packing */
#define COBALT_BYTES_PER_PIXEL_YUYV 2
#define COBALT_BYTES_PER_PIXEL_RGB24 3
#define COBALT_BYTES_PER_PIXEL_RGB32 4
/* debugging */
extern int cobalt_debug;
extern int cobalt_ignore_err;
#define cobalt_err(fmt, arg...) v4l2_err(&cobalt->v4l2_dev, fmt, ## arg)
#define cobalt_warn(fmt, arg...) v4l2_warn(&cobalt->v4l2_dev, fmt, ## arg)
#define cobalt_info(fmt, arg...) v4l2_info(&cobalt->v4l2_dev, fmt, ## arg)
#define cobalt_dbg(level, fmt, arg...) \
v4l2_dbg(level, cobalt_debug, &cobalt->v4l2_dev, fmt, ## arg)
struct cobalt;
struct cobalt_i2c_regs;
/* Per I2C bus private algo callback data */
struct cobalt_i2c_data {
struct cobalt *cobalt;
volatile struct cobalt_i2c_regs __iomem *regs;
};
struct pci_consistent_buffer {
void *virt;
dma_addr_t bus;
size_t bytes;
};
struct sg_dma_desc_info {
void *virt;
dma_addr_t bus;
unsigned size;
void *last_desc_virt;
struct device *dev;
};
#define COBALT_MAX_WIDTH 1920
#define COBALT_MAX_HEIGHT 1200
#define COBALT_MAX_BPP 3
#define COBALT_MAX_FRAMESZ \
(COBALT_MAX_WIDTH * COBALT_MAX_HEIGHT * COBALT_MAX_BPP)
#define NR_BUFS VIDEO_MAX_FRAME
#define COBALT_STREAM_FL_DMA_IRQ 0
#define COBALT_STREAM_FL_ADV_IRQ 1
struct cobalt_buffer {
struct vb2_buffer vb;
struct list_head list;
};
static inline struct cobalt_buffer *to_cobalt_buffer(struct vb2_buffer *vb2)
{
return container_of(vb2, struct cobalt_buffer, vb);
}
struct cobalt_stream {
struct video_device vdev;
struct vb2_queue q;
struct list_head bufs;
struct i2c_adapter *i2c_adap;
struct v4l2_subdev *sd;
struct mutex lock;
spinlock_t irqlock;
struct v4l2_dv_timings timings;
u32 input;
u32 pad_source;
u32 width, height, bpp;
u32 stride;
u32 pixfmt;
u32 sequence;
u32 colorspace;
u32 ycbcr_enc;
u32 quantization;
u8 dma_channel;
int video_channel;
struct sg_dma_desc_info dma_desc_info[NR_BUFS];
unsigned long flags;
bool unstable_frame;
bool enable_cvi;
bool enable_freewheel;
unsigned skip_first_frames;
bool is_output;
bool is_audio;
bool is_dummy;
struct cobalt *cobalt;
struct snd_cobalt_card *alsa;
};
struct snd_cobalt_card;
/* Struct to hold info about cobalt cards */
struct cobalt {
int instance;
struct pci_dev *pci_dev;
struct v4l2_device v4l2_dev;
void *alloc_ctx;
void __iomem *bar0, *bar1;
u8 card_rev;
u16 device_id;
/* device nodes */
struct cobalt_stream streams[DMA_CHANNELS_MAX];
struct i2c_adapter i2c_adap[COBALT_NUM_ADAPTERS];
struct cobalt_i2c_data i2c_data[COBALT_NUM_ADAPTERS];
bool have_hsma_rx;
bool have_hsma_tx;
/* irq */
struct workqueue_struct *irq_work_queues;
struct work_struct irq_work_queue; /* work entry */
/* irq counters */
u32 irq_adv1;
u32 irq_adv2;
u32 irq_advout;
u32 irq_dma_tot;
u32 irq_dma[COBALT_NUM_STREAMS];
u32 irq_none;
u32 irq_full_fifo;
bool msi_enabled;
/* omnitek dma */
int dma_channels;
int first_fifo_channel;
bool pci_32_bit;
char hdl_info[COBALT_HDL_INFO_SIZE];
/* NOR flash */
struct mtd_info *mtd;
};
static inline struct cobalt *to_cobalt(struct v4l2_device *v4l2_dev)
{
return container_of(v4l2_dev, struct cobalt, v4l2_dev);
}
static inline void cobalt_write_bar0(struct cobalt *cobalt, u32 reg, u32 val)
{
iowrite32(val, cobalt->bar0 + reg);
}
static inline u32 cobalt_read_bar0(struct cobalt *cobalt, u32 reg)
{
return ioread32(cobalt->bar0 + reg);
}
static inline void cobalt_write_bar1(struct cobalt *cobalt, u32 reg, u32 val)
{
iowrite32(val, cobalt->bar1 + reg);
}
static inline u32 cobalt_read_bar1(struct cobalt *cobalt, u32 reg)
{
return ioread32(cobalt->bar1 + reg);
}
static inline u32 cobalt_g_sysctrl(struct cobalt *cobalt)
{
return cobalt_read_bar1(cobalt, COBALT_SYS_CTRL_BASE);
}
static inline void cobalt_s_bit_sysctrl(struct cobalt *cobalt,
int bit, int val)
{
u32 ctrl = cobalt_read_bar1(cobalt, COBALT_SYS_CTRL_BASE);
cobalt_write_bar1(cobalt, COBALT_SYS_CTRL_BASE,
(ctrl & ~(1UL << bit)) | (val << bit));
}
static inline u32 cobalt_g_sysstat(struct cobalt *cobalt)
{
return cobalt_read_bar1(cobalt, COBALT_SYS_STAT_BASE);
}
#define ADRS_REG (cobalt->bar1 + COBALT_BUS_BAR1_BASE + 0)
#define LOWER_DATA (cobalt->bar1 + COBALT_BUS_BAR1_BASE + 4)
#define UPPER_DATA (cobalt->bar1 + COBALT_BUS_BAR1_BASE + 6)
static inline u32 cobalt_bus_read32(struct cobalt *cobalt, u32 bus_adrs)
{
iowrite32(bus_adrs, ADRS_REG);
return ioread32(LOWER_DATA);
}
static inline void cobalt_bus_write16(struct cobalt *cobalt,
u32 bus_adrs, u16 data)
{
iowrite32(bus_adrs, ADRS_REG);
if (bus_adrs & 2)
iowrite16(data, UPPER_DATA);
else
iowrite16(data, LOWER_DATA);
}
static inline void cobalt_bus_write32(struct cobalt *cobalt,
u32 bus_adrs, u16 data)
{
iowrite32(bus_adrs, ADRS_REG);
if (bus_adrs & 2)
iowrite32(data, UPPER_DATA);
else
iowrite32(data, LOWER_DATA);
}
/*==============Prototypes==================*/
void cobalt_pcie_status_show(struct cobalt *cobalt);
#endif
/*
* 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
此差异已折叠。
/*
* 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);
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册