* Re: [patch 5/5] saa7121 driver for s6000 data port
@ 2009-03-26 15:08 Hans Verkuil
2009-03-30 9:56 ` Daniel Glöckner
0 siblings, 1 reply; 8+ messages in thread
From: Hans Verkuil @ 2009-03-26 15:08 UTC (permalink / raw)
To: Daniel Glöckner
Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media,
Daniel Glöckner
> This patch adds a driver to support the saa7121 PAL/NTSC video encoder
> in combination with the s6000 data port driver.
>
> The chip is configured for embedded BT.656 syncs as this mode should
> be supported on all devices.
>
> The driver presents two outputs to applications and while it is true
> that the device has these two outputs, both of them are always active.
> The only difference on the "Y/C" output is that it disables the luma
> notch filter.
Hi Daniel,
The big problem with this driver and the micron driver is that this
implementation ties it directly to the s6000 data port driver. Thus making
it impossible to reuse in another system.
I've been working on a new framework for devices like this and almost all
i2c v4l drivers are now converted to v4l2_subdev in our v4l-dvb tree. It
will also be merged in 2.6.30. Please take a look at v4l2-framework.txt in
the v4l-dvb repository for more information.
I'm sure you will have questions later, please don't hesitate to ask! It's
a recent development but very much needed. Otherwise we will end up with a
lot of duplicate i2c drivers, each tied to their own platform or
framework. That's clearly something we do not want.
Regards,
Hans
>
> Signed-off-by: Daniel Glöckner <dg@emlix.com>
> ---
> drivers/media/video/s6dp/Kconfig | 7 +
> drivers/media/video/s6dp/Makefile | 1 +
> drivers/media/video/s6dp/s6dp-saa7121.c | 478
> +++++++++++++++++++++++++++++++
> 3 files changed, 486 insertions(+), 0 deletions(-)
> create mode 100644 drivers/media/video/s6dp/s6dp-saa7121.c
>
> diff --git a/drivers/media/video/s6dp/Kconfig
> b/drivers/media/video/s6dp/Kconfig
> index 853e6b1..c95904c 100644
> --- a/drivers/media/video/s6dp/Kconfig
> +++ b/drivers/media/video/s6dp/Kconfig
> @@ -20,3 +20,10 @@ config VIDEO_S6DP_MT9D131
> default n
> help
> Enables the MT9D131 camera driver.
> +
> +config VIDEO_S6DP_SAA7121
> + tristate "SAA7121 video encoder"
> + depends on VIDEO_S6000
> + default n
> + help
> + Enables the SAA7121 video encoder driver.
> diff --git a/drivers/media/video/s6dp/Makefile
> b/drivers/media/video/s6dp/Makefile
> index af0bc0f..61d86c9 100644
> --- a/drivers/media/video/s6dp/Makefile
> +++ b/drivers/media/video/s6dp/Makefile
> @@ -1,2 +1,3 @@
> obj-$(CONFIG_VIDEO_S6000) += s6dp.o
> obj-$(CONFIG_VIDEO_S6DP_MT9D131) += s6dp-mt9d131.o
> +obj-$(CONFIG_VIDEO_S6DP_SAA7121) += s6dp-saa7121.o
> diff --git a/drivers/media/video/s6dp/s6dp-saa7121.c
> b/drivers/media/video/s6dp/s6dp-saa7121.c
> new file mode 100644
> index 0000000..70799cd
> --- /dev/null
> +++ b/drivers/media/video/s6dp/s6dp-saa7121.c
> @@ -0,0 +1,478 @@
> +/*
> + * drivers/media/video/s6dp/s6dp-saa7121.c
> + *
> + * Description: Driver for SAA7121 chips hooked up to a S6000 family data
> port
> + * (c) 2009 emlix GmbH <info@emlix.com>
> + *
> + * Author: Daniel Gloeckner <dg@emlix.com>
> + */
> +
> +#include <media/s6dp-link.h>
> +#include "s6dp.h"
> +#include <linux/i2c.h>
> +
> +static const u8 initial_setup[][2] = {
> + {0x3a, 0x13}
> +};
> +
> +static const u8 pal_values[][2] = {
> + {0x28, 33}, {0x29, 29}, {0x5a, 0x00}, {0x5b, 125},
> + {0x5c, 175}, {0x5d, 35}, {0x5e, 53}, {0x5f, 0x40+53},
> + {0x61, 0x06}, {0x62, 47}, {0x63, 0xcb}, {0x64, 0x8a},
> + {0x65, 0x09}, {0x66, 0x2a}, {0x6c, 0x05}, {0x6d, 0x20},
> + {0x6e, 0xa0}
> +};
> +
> +static const u8 pal_nc_values[][2] = {
> + {0x28, 33}, {0x29, 37}, {0x5a, 0x00}, {0x5b, 125},
> + {0x5c, 175}, {0x5d, 35}, {0x5e, 53}, {0x5f, 0xc0+53},
> + {0x61, 0x06}, {0x62, 47}, {0x63, 0x46}, {0x64, 0x94},
> + {0x65, 0xf6}, {0x66, 0x21}, {0x6c, 0x05}, {0x6d, 0x20},
> + {0x6e, 0xa0}
> +};
> +
> +static const u8 pal_m_values[][2] = {
> + {0x28, 25}, {0x29, 29}, {0x5a, 0x00}, {0x5b, 118},
> + {0x5c, 165}, {0x5d, 45}, {0x5e, 49}, {0x5f, 0xc0+59},
> + {0x61, 0x17}, {0x62, 45}, {0x63, 0xe3}, {0x64, 0xef},
> + {0x65, 0xe6}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
> + {0x6e, 0xa0}
> +};
> +
> +static const u8 ntsc_values[][2] = {
> + {0x28, 25}, {0x29, 29}, {0x5a, 0x88}, {0x5b, 118},
> + {0x5c, 165}, {0x5d, 42}, {0x5e, 46}, {0x5f, 0xc0+46},
> + {0x61, 0x15}, {0x62, 63}, {0x63, 0x1f}, {0x64, 0x7c},
> + {0x65, 0xf0}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
> + {0x6e, 0x80}
> +};
> +
> +static const u8 ntsc_jp_values[][2] = {
> + {0x28, 25}, {0x29, 29}, {0x5a, 0x88}, {0x5b, 118},
> + {0x5c, 165}, {0x5d, 19}, {0x5e, 46}, {0x5f, 0xc0+46},
> + {0x61, 0x05}, {0x62, 62}, {0x63, 0x1f}, {0x64, 0x7c},
> + {0x65, 0xf0}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
> + {0x6e, 0x80}
> +};
> +
> +struct saa7121 {
> + int std;
> + int yc;
> + struct v4l2_pix_format fmt;
> + struct v4l2_rect crop;
> + u8 regs[128];
> +};
> +
> +static int saa7121_write_regs(struct i2c_client *client)
> +{
> + struct saa7121 *me = i2c_get_clientdata(client);
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(me->regs); i++) {
> + int ret = i2c_smbus_write_byte_data(client, i, me->regs[i]);
> + if (ret < 0)
> + return ret;
> + }
> + return 0;
> +}
> +
> +static void saa7121_change_regs(struct saa7121 *me, const u8 (*regs)[2],
> + int num)
> +{
> + int i;
> + for (i = 0; i < num; i++)
> + me->regs[regs[i][0]] = regs[i][1];
> +}
> +
> +static const struct {
> + v4l2_std_id mask;
> + const char *name;
> + const u8 (*regs)[2];
> + int num;
> +} standards[] = {
> + {
> + V4L2_STD_PAL | V4L2_STD_PAL_N,
> + "PAL",
> + pal_values,
> + ARRAY_SIZE(pal_values)
> + },
> + {
> + V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR,
> + "NTSC",
> + ntsc_values,
> + ARRAY_SIZE(ntsc_values)
> + },
> + {
> + V4L2_STD_NTSC_M_JP,
> + "NTSC-JP",
> + ntsc_jp_values,
> + ARRAY_SIZE(ntsc_jp_values)
> + },
> + {
> + V4L2_STD_PAL_M,
> + "PAL-M",
> + pal_m_values,
> + ARRAY_SIZE(pal_nc_values)
> + },
> + {
> + V4L2_STD_PAL_Nc,
> + "PAL-Nc",
> + pal_nc_values,
> + ARRAY_SIZE(pal_nc_values)
> + },
> +};
> +
> +static int saa7121_e_std(void *ctx, struct v4l2_standard *std)
> +{
> + if (std->index >= ARRAY_SIZE(standards))
> + return -EINVAL;
> + std->id = standards[std->index].mask;
> + strcpy(std->name, standards[std->index].name);
> + if (std->id & V4L2_STD_625_50) {
> + std->frameperiod.numerator = 1;
> + std->frameperiod.denominator = 25;
> + std->framelines = 625;
> + } else {
> + std->frameperiod.numerator = 1001;
> + std->frameperiod.denominator = 30000;
> + std->framelines = 525;
> + }
> + return 0;
> +}
> +
> +static int saa7121_cropcap(void *ctx, struct v4l2_cropcap *cap)
> +{
> + struct i2c_client *client = ctx;
> + struct saa7121 *me = i2c_get_clientdata(client);
> +
> + if (standards[me->std].mask & V4L2_STD_625_50) {
> + cap->bounds.top = 23 * 2;
> + cap->bounds.left = 132;
> + cap->bounds.width = 720;
> + cap->bounds.height = 576;
> + cap->defrect.left = 140;
> + cap->defrect.top = 23 * 2;
> + cap->defrect.width = 702;
> + cap->defrect.height = 576;
> + cap->pixelaspect.numerator = 54;
> + cap->pixelaspect.denominator = 59;
> + } else {
> + cap->bounds.top = 20 * 2;
> + cap->bounds.left = 122;
> + cap->bounds.width = 720;
> + cap->bounds.height = 487;
> + cap->defrect.left = 130;
> + cap->defrect.top = 22 * 2;
> + cap->defrect.width = 704;
> + cap->defrect.height = 480;
> + cap->pixelaspect.numerator = 11;
> + cap->pixelaspect.denominator = 10;
> + }
> + return 0;
> +}
> +
> +static int saa7121_s_crop(void *ctx, struct v4l2_crop *crop, int busy)
> +{
> + struct i2c_client *client = ctx;
> + struct saa7121 *me = i2c_get_clientdata(client);
> + struct v4l2_cropcap cap;
> +
> + if (busy)
> + return -EBUSY;
> +
> + saa7121_cropcap(ctx, &cap);
> + me->crop = crop->c;
> +
> + if (me->crop.width > cap.bounds.width)
> + me->crop.width = cap.bounds.width;
> + me->crop.left += me->crop.width & 1;
> + me->crop.width &= ~1;
> + if (me->crop.height > cap.bounds.height)
> + me->crop.height = cap.bounds.height;
> + if (me->crop.left < cap.bounds.left)
> + me->crop.left = cap.bounds.left;
> + if (me->crop.left > cap.bounds.left + cap.bounds.width - me->crop.width)
> + me->crop.left = cap.bounds.left + cap.bounds.width
> + - me->crop.width;
> + me->crop.left &= ~1;
> + if (me->crop.top < cap.bounds.top)
> + me->crop.top = cap.bounds.top;
> + if (me->crop.top > cap.bounds.top + cap.bounds.height - me->crop.height)
> + me->crop.top = cap.bounds.top + cap.bounds.height
> + - me->crop.height;
> + me->crop.top &= ~1;
> + me->fmt.width = me->crop.width;
> + me->fmt.height = me->crop.height;
> + return 0;
> +}
> +
> +static int saa7121_g_crop(void *ctx, struct v4l2_crop *crop)
> +{
> + struct i2c_client *client = ctx;
> + struct saa7121 *me = i2c_get_clientdata(client);
> +
> + crop->c = me->crop;
> + return 0;
> +}
> +
> +static int saa7121_s_fmt(void *ctx, int try_fmt, struct v4l2_pix_format
> *fmt,
> + int busy)
> +{
> + struct i2c_client *client = ctx;
> + struct saa7121 *me = i2c_get_clientdata(client);
> + struct v4l2_cropcap cap;
> +
> + if (!try_fmt && busy)
> + return -EBUSY;
> +
> + saa7121_cropcap(ctx, &cap);
> + fmt->pixelformat = V4L2_PIX_FMT_UYVY;
> + fmt->field = V4L2_FIELD_ALTERNATE;
> +
> + if (standards[me->std].mask & V4L2_STD_625_50)
> + fmt->colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
> + else if (standards[me->std].mask == V4L2_STD_PAL_M)
> + fmt->colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
> + else
> + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
> +
> + fmt->width &= ~1;
> + if (fmt->width > cap.bounds.width)
> + fmt->width = cap.bounds.width;
> + if (fmt->height > cap.bounds.height)
> + fmt->height = cap.bounds.height;
> +
> + if (!try_fmt) {
> + me->fmt = *fmt;
> + me->crop.width = fmt->width;
> + me->crop.height = fmt->height;
> + if (me->crop.left > cap.bounds.left + cap.bounds.width
> + - fmt->width) {
> + me->crop.left = cap.bounds.left + cap.bounds.width
> + - fmt->width;
> + me->crop.left &= ~1;
> + }
> + if (me->crop.top > cap.bounds.top + cap.bounds.height
> + - fmt->height) {
> + me->crop.top = cap.bounds.top + cap.bounds.height
> + - fmt->height;
> + me->crop.top &= ~1;
> + }
> + }
> + return 0;
> +}
> +
> +static int saa7121_g_fmt(void *ctx, struct v4l2_pix_format *fmt)
> +{
> + struct i2c_client *client = ctx;
> + struct saa7121 *me = i2c_get_clientdata(client);
> + *fmt = me->fmt;
> + return 0;
> +}
> +
> +static void saa7121_g_mode(void *ctx, struct s6dp_mode *mode)
> +{
> + struct i2c_client *client = ctx;
> + struct saa7121 *me = i2c_get_clientdata(client);
> + struct v4l2_cropcap cap;
> +
> + saa7121_cropcap(ctx, &cap);
> + mode->type = S6_DP_VIDEO_CFG_MODE_422_SERIAL;
> + mode->progressive = 0;
> + mode->embedded_sync = 1;
> + mode->relaxed_framing = 0;
> + mode->ten_bit = 0;
> + mode->line_and_crc = 0;
> + mode->micron_mode = 0;
> + if (standards[me->std].mask & V4L2_STD_625_50) {
> + mode->pixel_total = 864;
> + mode->framelines = 625;
> + mode->odd_vsync_offset = 623;
> + mode->odd_vsync_len = 24;
> + mode->odd_first = 22;
> + mode->odd_total = 312;
> + mode->even_vsync_offset = 310;
> + mode->even_vsync_len = 25;
> + mode->even_first = 335;
> + mode->hsync_offset = 0;
> + mode->hsync_len = 4;
> + } else {
> + mode->pixel_total = 858;
> + mode->framelines = 525;
> + mode->odd_vsync_offset = 522;
> + mode->odd_vsync_len = 19;
> + mode->odd_first = 16;
> + mode->odd_total = 262;
> + mode->even_vsync_offset = 260;
> + mode->even_vsync_len = 19;
> + mode->even_first = 279;
> + mode->hsync_offset = 0;
> + mode->hsync_len = 4;
> + }
> + mode->even_active = me->crop.height / 2;
> + mode->odd_active = me->crop.height - mode->even_active;
> + mode->odd_first += (me->crop.top - cap.bounds.top) / 2;
> + mode->even_first += (me->crop.top - cap.bounds.top) / 2;
> + mode->pixel_active = me->crop.width;
> + mode->pixel_offset = me->crop.left - cap.bounds.left;
> + mode->pixel_padding = 720 - mode->pixel_active - mode->pixel_offset;
> +}
> +
> +static void saa7121_reconfigure(struct i2c_client *client)
> +{
> + struct saa7121 *me = i2c_get_clientdata(client);
> +
> + saa7121_change_regs(me, standards[me->std].regs,
> + standards[me->std].num);
> + if (me->yc)
> + me->regs[0x5f] &= 0x3f;
> +
> + saa7121_write_regs(client);
> +}
> +
> +static int saa7121_s_std(void *ctx, v4l2_std_id *mask, int busy)
> +{
> + struct i2c_client *client = ctx;
> + struct saa7121 *me = i2c_get_clientdata(client);
> + int i;
> +
> + if (busy && !(standards[me->std].mask & V4L2_STD_625_50)
> + == !(*mask & V4L2_STD_625_50))
> + return -EBUSY;
> +
> + for (i = 0; i < ARRAY_SIZE(standards); i++) {
> + if (standards[i].mask & *mask) {
> + me->std = i;
> + saa7121_reconfigure(client);
> + *mask = standards[i].mask;
> + saa7121_s_fmt(ctx, 0, &me->fmt, 0);
> + return 0;
> + }
> + }
> + return -EINVAL;
> +}
> +
> +static int saa7121_e_outp(void *ctx, struct v4l2_output *outp)
> +{
> + int i;
> + if (outp->index > 1)
> + return -EINVAL;
> +
> + outp->type = V4L2_OUTPUT_TYPE_ANALOG;
> + for (i = 0; i < ARRAY_SIZE(standards); i++)
> + outp->std |= standards[i].mask;
> +
> + strcpy(outp->name, outp->index ? "Y/C" : "CVBS");
> + return 0;
> +}
> +
> +static int saa7121_s_outp(void *ctx, unsigned int nr, int busy)
> +{
> + struct i2c_client *client = ctx;
> + struct saa7121 *me = i2c_get_clientdata(client);
> +
> + if (nr > 1)
> + return -EINVAL;
> +
> + /*
> + * both outputs are always active
> + * we just disable the cross color filter for Y/C
> + */
> + me->yc = nr;
> + saa7121_reconfigure(client);
> + return 0;
> +}
> +
> +static int saa7121_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct saa7121 *me;
> + struct s6dp_link *link;
> + s32 val;
> +
> + if (!client->dev.platform_data)
> + return -EINVAL;
> + link = client->dev.platform_data;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE
> + | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
> + return -ENODEV;
> +
> + val = i2c_smbus_read_byte(client);
> + if (val < 0) {
> + printk(KERN_ERR "saa7121: can't read status byte\n");
> + return -EIO;
> + }
> + if ((val & 0xE0) != 0x20) {
> + printk(KERN_ERR "saa7121: unsupported chip version %i"
> + " (status = 0x%02x)\n", val >> 5, val);
> + return -ENODEV;
> + }
> + me = kzalloc(sizeof(*me), GFP_KERNEL);
> + if (!me)
> + return -ENOMEM;
> + me->std = V4L2_STD_PAL;
> + i2c_set_clientdata(client, me);
> +
> + saa7121_change_regs(me, initial_setup, ARRAY_SIZE(initial_setup));
> + saa7121_change_regs(me, pal_values, ARRAY_SIZE(pal_values));
> + if (saa7121_write_regs(client) < 0) {
> + printk(KERN_ERR "saa7121: can't write registers\n");
> + kfree(me);
> + return -EIO;
> + }
> +
> + link->g_mode = saa7121_g_mode;
> + link->e_std = saa7121_e_std;
> + link->s_std = saa7121_s_std;
> + link->s_fmt = saa7121_s_fmt;
> + link->g_fmt = saa7121_g_fmt;
> + link->cropcap = saa7121_cropcap;
> + link->s_crop = saa7121_s_crop;
> + link->g_crop = saa7121_g_crop;
> + link->dir.egress.e_outp = saa7121_e_outp;
> + link->dir.egress.s_outp = saa7121_s_outp;
> + link->context = client;
> + printk(KERN_INFO "saa7121 probed successfully\n");
> + return 0;
> +}
> +
> +static int saa7121_remove(struct i2c_client *client)
> +{
> + struct saa7121_data *data;
> + data = i2c_get_clientdata(client);
> + i2c_set_clientdata(client, NULL);
> + kfree(data);
> + return 0;
> +}
> +
> +static const struct i2c_device_id saa7121_id[] = {
> + { "saa7121", 0 },
> + { }
> +};
> +
> +static struct i2c_driver saa7121_driver = {
> + .driver = {
> + .name = "s6dp-saa7121",
> + },
> + .probe = saa7121_probe,
> + .remove = saa7121_remove,
> + .id_table = saa7121_id,
> +};
> +
> +static int __init saa7121_init(void)
> +{
> + return i2c_add_driver(&saa7121_driver);
> +}
> +
> +static void __exit saa7121_exit(void)
> +{
> + i2c_del_driver(&saa7121_driver);
> +}
> +
> +MODULE_AUTHOR("Daniel Gloeckner <dg@emlix.com>");
> +MODULE_DESCRIPTION("SAA7121 driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(saa7121_init);
> +module_exit(saa7121_exit);
> --
> 1.6.2.107.ge47ee
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
--
Hans Verkuil - video4linux developer - sponsored by TANDBERG
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [patch 5/5] saa7121 driver for s6000 data port
2009-03-26 15:08 [patch 5/5] saa7121 driver for s6000 data port Hans Verkuil
@ 2009-03-30 9:56 ` Daniel Glöckner
2009-03-30 10:03 ` Hans Verkuil
0 siblings, 1 reply; 8+ messages in thread
From: Daniel Glöckner @ 2009-03-30 9:56 UTC (permalink / raw)
To: Hans Verkuil; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media
On 03/26/2009 04:08 PM, Hans Verkuil wrote:
> I've been working on a new framework for devices like this and almost all
> i2c v4l drivers are now converted to v4l2_subdev in our v4l-dvb tree. It
> will also be merged in 2.6.30. Please take a look at v4l2-framework.txt in
> the v4l-dvb repository for more information.
>
> I'm sure you will have questions later, please don't hesitate to ask! It's
> a recent development but very much needed. Otherwise we will end up with a
> lot of duplicate i2c drivers, each tied to their own platform or
> framework. That's clearly something we do not want.
Hi Hans,
the problem I see with the v4l2-framework in this case is that in its current
state it does not allow to exchange information regarding the bus parameters
between the sub device and the controller.
It seems the soc-camera framework is a better choice here, but to make it work
with the saa7121 one would first have to implement support for video output.
What do you recommend?
Daniel
--
Dipl.-Math. Daniel Glöckner, emlix GmbH, http://www.emlix.com
Fon +49 551 30664-0, Fax -11, Bahnhofsallee 1b, 37081 Göttingen, Germany
Geschäftsführung: Dr. Uwe Kracke, Dr. Cord Seele, Ust-IdNr.: DE 205 198 055
Sitz der Gesellschaft: Göttingen, Amtsgericht Göttingen HR B 3160
emlix - your embedded linux partner
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 5/5] saa7121 driver for s6000 data port
2009-03-30 9:56 ` Daniel Glöckner
@ 2009-03-30 10:03 ` Hans Verkuil
2009-03-30 12:12 ` Daniel Glöckner
0 siblings, 1 reply; 8+ messages in thread
From: Hans Verkuil @ 2009-03-30 10:03 UTC (permalink / raw)
To: Daniel Glöckner; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media
On Monday 30 March 2009 11:56:25 Daniel Glöckner wrote:
> On 03/26/2009 04:08 PM, Hans Verkuil wrote:
> > I've been working on a new framework for devices like this and almost
> > all i2c v4l drivers are now converted to v4l2_subdev in our v4l-dvb
> > tree. It will also be merged in 2.6.30. Please take a look at
> > v4l2-framework.txt in the v4l-dvb repository for more information.
> >
> > I'm sure you will have questions later, please don't hesitate to ask!
> > It's a recent development but very much needed. Otherwise we will end
> > up with a lot of duplicate i2c drivers, each tied to their own platform
> > or framework. That's clearly something we do not want.
>
> Hi Hans,
>
> the problem I see with the v4l2-framework in this case is that in its
> current state it does not allow to exchange information regarding the bus
> parameters between the sub device and the controller.
What exactly do you need? If there is something missing, then it should be
added. But my guess is that you can pass such information via the s_routing
callback. That's what all other drivers that use v4l2_subdev do.
> It seems the soc-camera framework is a better choice here, but to make it
> work with the saa7121 one would first have to implement support for video
> output.
This framework will also be converted to use v4l2_subdev for the
communication with i2c drivers.
> What do you recommend?
Actually, I recommend that you first look at the existing saa7127.c source.
I don't know how many differences there are between the saa7121 and
saa7127, but perhaps support for the saa7121 can be added there rather than
introducing a new driver. Of course, that only works if the differences are
not too big.
Regards,
Hans
--
Hans Verkuil - video4linux developer - sponsored by TANDBERG
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 5/5] saa7121 driver for s6000 data port
2009-03-30 10:03 ` Hans Verkuil
@ 2009-03-30 12:12 ` Daniel Glöckner
2009-03-30 12:50 ` Hans Verkuil
0 siblings, 1 reply; 8+ messages in thread
From: Daniel Glöckner @ 2009-03-30 12:12 UTC (permalink / raw)
To: Hans Verkuil; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media
On 03/30/2009 12:03 PM, Hans Verkuil wrote:
> What exactly do you need? If there is something missing, then it should be
> added. But my guess is that you can pass such information via the s_routing
> callback. That's what all other drivers that use v4l2_subdev do.
The s_routing callback looks very limited. One can pass only two u32 values.
The parameters that have to be negotiated are:
- What is the on-wire video format?
- how many data lines are connected?
- synchronization using embedded SAV/EAV codes or using dedicated pins?
- polarity of sync lines?
- valid CRC and line number in digital blanking?
- what is the layout of the digital image?
- how many odd lines are there? how many even? (including blanking)
- how many horizontal pixels? (incl. blanking)
- where is the active region?
- on which pixels/lines do we start/end horizontal/vertical sync?
>> It seems the soc-camera framework is a better choice here, but to make it
>> work with the saa7121 one would first have to implement support for video
>> output.
>
> This framework will also be converted to use v4l2_subdev for the
> communication with i2c drivers.
So it shouldn't matter which one I chose?
> Actually, I recommend that you first look at the existing saa7127.c source.
> I don't know how many differences there are between the saa7121 and
> saa7127, but perhaps support for the saa7121 can be added there rather than
> introducing a new driver. Of course, that only works if the differences are
> not too big.
The chips appear to be very similar, sharing most of the registers. However, the
aforementioned problem still exists with this driver. A driver connecting this
sub device must know beforehand that it has to send standard BT.656 video frames
with SAV/EAV codes.
Daniel
--
Dipl.-Math. Daniel Glöckner, emlix GmbH, http://www.emlix.com
Fon +49 551 30664-0, Fax -11, Bahnhofsallee 1b, 37081 Göttingen, Germany
Geschäftsführung: Dr. Uwe Kracke, Dr. Cord Seele, Ust-IdNr.: DE 205 198 055
Sitz der Gesellschaft: Göttingen, Amtsgericht Göttingen HR B 3160
emlix - your embedded linux partner
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 5/5] saa7121 driver for s6000 data port
2009-03-30 12:12 ` Daniel Glöckner
@ 2009-03-30 12:50 ` Hans Verkuil
2009-03-30 13:36 ` Daniel Glöckner
0 siblings, 1 reply; 8+ messages in thread
From: Hans Verkuil @ 2009-03-30 12:50 UTC (permalink / raw)
To: Daniel Glöckner; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media
On Monday 30 March 2009 14:12:10 Daniel Glöckner wrote:
> On 03/30/2009 12:03 PM, Hans Verkuil wrote:
> > What exactly do you need? If there is something missing, then it should be
> > added. But my guess is that you can pass such information via the s_routing
> > callback. That's what all other drivers that use v4l2_subdev do.
>
> The s_routing callback looks very limited. One can pass only two u32 values.
If a driver needs it, it can be extended. In particular I always thought that
a third config value would be useful.
> The parameters that have to be negotiated are:
> - What is the on-wire video format?
That might go to such a config value.
> - how many data lines are connected?
routing
> - synchronization using embedded SAV/EAV codes or using dedicated pins?
config value and/or routing.
> - polarity of sync lines?
config
> - valid CRC and line number in digital blanking?
Do you really need to control these?
> - what is the layout of the digital image?
> - how many odd lines are there? how many even? (including blanking)
> - how many horizontal pixels? (incl. blanking)
> - where is the active region?
> - on which pixels/lines do we start/end horizontal/vertical sync?
It's a PAL/NTSC encoder, so the standard specified with s_std_output will
map to the corresponding values that you need to put in. This is knowledge
that the i2c driver implements.
>
> >> It seems the soc-camera framework is a better choice here, but to make it
> >> work with the saa7121 one would first have to implement support for video
> >> output.
> >
> > This framework will also be converted to use v4l2_subdev for the
> > communication with i2c drivers.
>
> So it shouldn't matter which one I chose?
You will have to do the work anyway. Better to go with the new framework then
having to do the work twice.
> > Actually, I recommend that you first look at the existing saa7127.c source.
> > I don't know how many differences there are between the saa7121 and
> > saa7127, but perhaps support for the saa7121 can be added there rather than
> > introducing a new driver. Of course, that only works if the differences are
> > not too big.
>
> The chips appear to be very similar, sharing most of the registers. However, the
> aforementioned problem still exists with this driver. A driver connecting this
> sub device must know beforehand that it has to send standard BT.656 video frames
> with SAV/EAV codes.
So? If some future driver wants to do this differently, then we add the
necessary code to the i2c driver. It's not fixed in stone, you know :-)
Basically a driver only implements what can be tested. There is little point
in adding a full feature set for a device if you are unable to test the code
as well. So if a newer board appears in the future that needs to use
something new, then we add support for that to the i2c driver.
Looking at the datasheets I don't think you should make a new driver for
this. Unless something crops up that makes it hard to use saa7127.c I think
you should extend that driver to support saa7121 and add support for the
missing functionality. But only what is necessary for your setup.
Regards,
Hans
--
Hans Verkuil - video4linux developer - sponsored by TANDBERG
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 5/5] saa7121 driver for s6000 data port
2009-03-30 12:50 ` Hans Verkuil
@ 2009-03-30 13:36 ` Daniel Glöckner
2009-03-30 13:41 ` Hans Verkuil
0 siblings, 1 reply; 8+ messages in thread
From: Daniel Glöckner @ 2009-03-30 13:36 UTC (permalink / raw)
To: Hans Verkuil; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media
On 03/30/2009 02:50 PM, Hans Verkuil wrote:
> On Monday 30 March 2009 14:12:10 Daniel Glöckner wrote:
>> - valid CRC and line number in digital blanking?
>
> Do you really need to control these?
Not on this board..
> It's a PAL/NTSC encoder, so the standard specified with s_std_output will
> map to the corresponding values that you need to put in. This is knowledge
> that the i2c driver implements.
There is a micron camera connected to the controller that can output any
resolution up to 1600x1200 and we don't have standard ids for all those HD
formats supported by encoders like the ADV7197. It would be really nice to
have an interface that covers all this while being symmetric for input and output.
Daniel
--
Dipl.-Math. Daniel Glöckner, emlix GmbH, http://www.emlix.com
Fon +49 551 30664-0, Fax -11, Bahnhofsallee 1b, 37081 Göttingen, Germany
Geschäftsführung: Dr. Uwe Kracke, Dr. Cord Seele, Ust-IdNr.: DE 205 198 055
Sitz der Gesellschaft: Göttingen, Amtsgericht Göttingen HR B 3160
emlix - your embedded linux partner
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [patch 5/5] saa7121 driver for s6000 data port
2009-03-30 13:36 ` Daniel Glöckner
@ 2009-03-30 13:41 ` Hans Verkuil
0 siblings, 0 replies; 8+ messages in thread
From: Hans Verkuil @ 2009-03-30 13:41 UTC (permalink / raw)
To: Daniel Glöckner; +Cc: Mauro Carvalho Chehab, Chris Zankel, linux-media
On Monday 30 March 2009 15:36:42 Daniel Glöckner wrote:
> On 03/30/2009 02:50 PM, Hans Verkuil wrote:
> > On Monday 30 March 2009 14:12:10 Daniel Glöckner wrote:
> >> - valid CRC and line number in digital blanking?
> >
> > Do you really need to control these?
>
> Not on this board..
>
> > It's a PAL/NTSC encoder, so the standard specified with s_std_output will
> > map to the corresponding values that you need to put in. This is knowledge
> > that the i2c driver implements.
>
> There is a micron camera connected to the controller that can output any
> resolution up to 1600x1200 and we don't have standard ids for all those HD
> formats supported by encoders like the ADV7197. It would be really nice to
> have an interface that covers all this while being symmetric for input and output.
This is a known issue. I expect to see some proposals for this in the near
future since Texas Instruments need this as well for their davinci architecture.
Regards,
Hans
--
Hans Verkuil - video4linux developer - sponsored by TANDBERG
^ permalink raw reply [flat|nested] 8+ messages in thread
* [patch 1/5] s6000 data port driver
@ 2009-03-26 14:36 Daniel Glöckner
2009-03-26 14:36 ` [patch 5/5] saa7121 driver for s6000 data port Daniel Glöckner
0 siblings, 1 reply; 8+ messages in thread
From: Daniel Glöckner @ 2009-03-26 14:36 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Chris Zankel, linux-media, Oskar Schirmer, Fabian Godehardt,
Johannes Weiner, Daniel Glöckner
From: Oskar Schirmer <os@emlix.com>
Support for the s6000 on-chip video input/output engine.
Depending on external wiring it supports up to four video devices.
Signed-off-by: Fabian Godehardt <fg@emlix.com>
Signed-off-by: Oskar Schirmer <os@emlix.com>
Signed-off-by: Johannes Weiner <jw@emlix.com>
Signed-off-by: Daniel Glöckner <dg@emlix.com>
---
drivers/media/video/Kconfig | 2 +
drivers/media/video/Makefile | 2 +
drivers/media/video/s6dp/Kconfig | 6 +
drivers/media/video/s6dp/Makefile | 1 +
drivers/media/video/s6dp/s6dp.c | 1664 +++++++++++++++++++++++++++++++++++++
drivers/media/video/s6dp/s6dp.h | 121 +++
include/media/s6dp-link.h | 63 ++
7 files changed, 1859 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/s6dp/Kconfig
create mode 100644 drivers/media/video/s6dp/Makefile
create mode 100644 drivers/media/video/s6dp/s6dp.c
create mode 100644 drivers/media/video/s6dp/s6dp.h
create mode 100644 include/media/s6dp-link.h
diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index 19cf3b8..a94c20f 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -683,6 +683,8 @@ source "drivers/media/video/ivtv/Kconfig"
source "drivers/media/video/cx18/Kconfig"
+source "drivers/media/video/s6dp/Kconfig"
+
config VIDEO_M32R_AR
tristate "AR devices"
depends on M32R && VIDEO_V4L1
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index 72f6d03..7109cfe 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -134,6 +134,8 @@ obj-$(CONFIG_VIDEO_CX18) += cx18/
obj-$(CONFIG_VIDEO_VIVI) += vivi.o
obj-$(CONFIG_VIDEO_CX23885) += cx23885/
+obj-$(CONFIG_VIDEO_S6000) += s6dp/
+
obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o
obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o
obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o
diff --git a/drivers/media/video/s6dp/Kconfig b/drivers/media/video/s6dp/Kconfig
new file mode 100644
index 0000000..11cc91d
--- /dev/null
+++ b/drivers/media/video/s6dp/Kconfig
@@ -0,0 +1,6 @@
+config VIDEO_S6000
+ tristate "S6000 video"
+ depends on VIDEO_V4L2
+ default n
+ help
+ Enables the s6000 video driver.
diff --git a/drivers/media/video/s6dp/Makefile b/drivers/media/video/s6dp/Makefile
new file mode 100644
index 0000000..c503d5b
--- /dev/null
+++ b/drivers/media/video/s6dp/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_S6000) += s6dp.o
diff --git a/drivers/media/video/s6dp/s6dp.c b/drivers/media/video/s6dp/s6dp.c
new file mode 100644
index 0000000..434cec5
--- /dev/null
+++ b/drivers/media/video/s6dp/s6dp.c
@@ -0,0 +1,1664 @@
+/*
+ * Video driver for S6105 on chip data port device
+ * (c)2007 Stretch, Inc.
+ * (c)2009 emlix GmbH
+ * Authors: Fabian Godehardt <fg@emlix.com>
+ * Oskar Schirmer <os@emlix.com>
+ * Johannes Weiner <jw@emlix.com>
+ * Daniel Gloeckner <dg@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/dma-mapping.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/s6dp-link.h>
+#include <linux/io.h>
+#include <variant/dmac.h>
+#include <variant/hardware.h>
+#include "s6dp.h"
+
+#define DRV_NAME "s6dp"
+#define DRIVER_VERSION_NUM KERNEL_VERSION(0, 0, 1)
+#define DRV_ERR KERN_ERR DRV_NAME ": "
+#define DRV_INFO KERN_INFO DRV_NAME ": "
+
+#define DP_NB_PORTS (S6_DPDMA_NB / S6_DP_CHAN_PER_PORT)
+
+/* device not opened */
+#define DP_STATE_UNUSED 0
+/* after open */
+#define DP_STATE_IDLE 1
+/* after reqbufs */
+#define DP_STATE_READY 2
+/* after streamon */
+#define DP_STATE_ACTIVE 3
+
+#define DP_CB_OFFSET 0
+#define DP_Y_OFFSET 1
+#define DP_CR_OFFSET 2
+#define DP_K_OFFSET 3
+
+#define CURRENT_BUF_TYPE(pd) ((pd)->ext.egress ? V4L2_BUF_TYPE_VIDEO_OUTPUT \
+ : V4L2_BUF_TYPE_VIDEO_CAPTURE)
+
+struct s6dp_frame {
+ void *data;
+ dma_addr_t dma_handle;
+ struct timeval timestamp;
+ struct list_head list;
+ u32 sequence;
+ u32 flags;
+};
+
+struct s6dp {
+ u8 port;
+ u16 irq;
+ void __iomem *dp;
+ u32 dataram;
+ u32 dmac;
+ struct s6dp_link *link;
+ struct list_head idleq;
+ struct list_head busyq;
+ struct list_head fullq;
+ spinlock_t lock;
+ wait_queue_head_t wait;
+ u32 outstanding;
+ struct {
+ u8 state;
+ u8 aligned:1;
+ u8 framerepeat:1;
+ u8 progressive:1;
+
+ u16 width;
+ u16 height;
+ u8 portsperstream;
+ u8 greyperchroma;
+ u16 pixel_total;
+ u8 pixel_offset;
+ u8 pixel_padding;
+ u16 line_total;
+ u16 line_odd_total;
+ u16 line_odd_offset;
+ u16 line_even_offset;
+ u16 odd_vsync_len;
+ u16 odd_vsync_offset;
+ u16 even_vsync_len;
+ u16 even_vsync_offset;
+ u8 odd_hsync_len;
+ u8 odd_hsync_offset;
+ u8 even_hsync_len;
+ u8 even_hsync_offset;
+
+ u32 fourcc;
+ u32 bufsize;
+ u32 chanoff[S6_DP_CHAN_PER_PORT];
+ u32 chansiz[S6_DP_CHAN_PER_PORT];
+ u32 sequence;
+ enum v4l2_field vfield;
+ enum v4l2_colorspace colorspace;
+ v4l2_std_id std_id;
+ } cur;
+ struct s6dp_frame *frames;
+ unsigned nrframes;
+ unsigned nrmapped;
+ struct {
+ u8 is_10bit:1;
+ u8 micron:1;
+ u8 egress:1;
+ u8 use_1120_line_and_crc:1;
+ u8 ext_framing:1;
+ u8 vsync_pol:1;
+ u8 hsync_pol:1;
+ u8 blank_pol:1;
+ u8 field_ctrl:1;
+ u8 blank_ctrl:1;
+ u8 relaxed_framing_mode:1;
+ u32 desc_size;
+ } ext;
+ unsigned int num_io;
+};
+
+static int s6v4l_enumstd(struct s6dp *, struct v4l2_standard *);
+static int s6v4l_s_output(struct file *, void *, unsigned int);
+static int s6v4l_s_input(struct file *, void *, unsigned int);
+static int s6v4l_streamoff(struct file *, void *, enum v4l2_buf_type);
+
+#define DP_REG_R(pd, n) readl((pd)->dp + (n))
+#define DP_REG_W(pd, n, v) writel((v), (pd)->dp + (n))
+#define DP_PREG_R(pd, n) readl((pd)->dp + S6_DP_CFG_BASE((pd)->port) + (n))
+#define DP_PREG_W(pd, n, v) writel((v), \
+ (pd)->dp + S6_DP_CFG_BASE((pd)->port) + (n))
+
+static void s6dp_try_fill_dma(struct s6dp *pd)
+{
+ struct list_head *inq;
+ if (pd->cur.state != DP_STATE_ACTIVE)
+ return;
+ inq = &pd->idleq;
+ while (!list_empty(inq)) {
+ unsigned chan = pd->port * S6_DP_CHAN_PER_PORT;
+ int i;
+ struct s6dp_frame *f;
+ for (i = 0; i < S6_DP_CHAN_PER_PORT; i++)
+ if (pd->cur.chansiz[i]
+ && s6dmac_fifo_full(pd->dmac, chan + i))
+ return;
+ f = list_first_entry(inq, struct s6dp_frame, list);
+ list_del(&f->list);
+ list_add_tail(&f->list, &pd->busyq);
+ do if (pd->cur.chansiz[--i]) {
+ u32 h, b, s, d;
+ b = (u32)f->dma_handle;
+ b += pd->cur.chanoff[i];
+ h = pd->dataram + S6_DP_CHAN_OFFSET(i);
+ if (pd->ext.egress) {
+ s = b;
+ d = h;
+ } else {
+ s = h;
+ d = b;
+ }
+ s6dmac_put_fifo(pd->dmac, chan + i, s, d,
+ pd->cur.chansiz[i]);
+ } while (i > 0); /* chan #0 must be last to push */
+ pd->outstanding++;
+ }
+}
+
+static void s6dp_err_interrupt(struct s6dp *pd)
+{
+ u32 m, r = DP_REG_R(pd, S6_DP_INT_UNMAP_RAW1);
+ m = 1 << S6_DP_INT_UNDEROVERRUN(pd->port);
+ if (r & m) {
+ printk(DRV_ERR "got overrun/underrun on lane %d\n", pd->port);
+ /* mask this interrupt source */
+ DP_REG_W(pd, S6_DP_INT_ENABLE, DP_REG_R(pd, S6_DP_INT_ENABLE)
+ & ~m);
+ }
+ m = 1;
+ switch (pd->port) {
+ case 0:
+ m <<= S6_DP_INT_UNMAP_RAW1_DP0_BT1120ERR
+ - S6_DP_INT_UNMAP_RAW1_DP2_BT1120ERR;
+ case 2:
+ m <<= S6_DP_INT_UNMAP_RAW1_DP2_BT1120ERR;
+ if (r & m)
+ printk(DRV_ERR "BT-1120 error bad CRC or line number"
+ " on lane %d\n", pd->port);
+ }
+ m = 1 << S6_DP_INT_WRONGPIXEL(pd->port);
+ if (r & m) {
+ printk(DRV_ERR "bad pixels in lines\n");
+ DP_REG_W(pd, S6_DP_INT_CLEAR, m);
+ }
+ m = 1 << S6_DP_INT_WRONGLINES(pd->port);
+ if (r & m) {
+ printk(DRV_ERR "bad lines in frame\n");
+ DP_REG_W(pd, S6_DP_INT_CLEAR, m);
+ }
+ DP_REG_W(pd, S6_DP_INT_CLEAR, 1 << S6_DP_INT_ERR_INT);
+}
+
+static void s6dp_tc_interrupt(struct s6dp *pd)
+{
+ unsigned c = S6_DP_CHAN_PER_PORT * (pd->port + 1);
+ unsigned i = S6_DP_CHAN_PER_PORT;
+ u8 event[S6_DP_CHAN_PER_PORT];
+ do {
+ c -= 1;
+ s6dmac_lowwmark_irq(pd->dmac, c);
+ s6dmac_pendcnt_irq(pd->dmac, c);
+ event[--i] = s6dmac_termcnt_irq(pd->dmac, c);
+ } while (i > 0);
+ while (!pd->cur.chansiz[i]) {
+ if (++i >= S6_DP_CHAN_PER_PORT)
+ return;
+ c += 1;
+ }
+ if (event[i]) {
+ struct timeval now;
+ u32 newfc, pending, global;
+ struct list_head *outq = &pd->fullq;
+
+ do_gettimeofday(&now);
+ global = readl(S6_REG_GREG1 + S6_GREG1_GLOBAL_TIMER);
+ DP_REG_W(pd, S6_DP_INT_CLEAR, 1 << S6_DP_INT_TERMCNT(pd->port));
+ newfc = DP_PREG_R(pd, S6_DP_FRAME_COUNT);
+ pending = s6dmac_pending_count(pd->dmac, c);
+ if (unlikely(!pending) && pd->cur.framerepeat)
+ pending = 1;
+ while (pd->outstanding > pending) {
+ struct s6dp_frame *f;
+ u32 delta;
+ delta = global - s6dmac_timestamp(pd->dmac, c);
+ if (unlikely(list_empty(&pd->busyq))) {
+ /* shouldn't happen */
+ printk(DRV_ERR "no buffers in interrupt\n");
+ break;
+ }
+ f = list_first_entry(&pd->busyq, struct s6dp_frame,
+ list);
+ f->sequence = pd->cur.sequence++;
+ f->flags &= ~V4L2_BUF_FLAG_QUEUED;
+ f->flags |= V4L2_BUF_FLAG_DONE;
+ f->timestamp = now;
+ f->timestamp.tv_usec -= delta * 10;
+ while (f->timestamp.tv_usec < 0) {
+ f->timestamp.tv_sec -= 1;
+ f->timestamp.tv_usec += 1000000;
+ }
+
+ list_del(&f->list);
+ list_add_tail(&f->list, outq);
+ pd->outstanding--;
+ }
+ pd->cur.sequence = newfc;
+ if (unlikely(list_empty(&pd->busyq)) && pending)
+ printk(DRV_ERR "no repeating frame?\n");
+ s6dp_try_fill_dma(pd);
+ if (!list_empty(&pd->fullq))
+ wake_up_interruptible(&pd->wait);
+ }
+}
+
+static irqreturn_t s6dp_interrupt(int irq, void *dev_id)
+{
+ struct video_device **devs = dev_id;
+ irqreturn_t ret = IRQ_NONE;
+ int i;
+ for (i = 0; i < DP_NB_PORTS; i++) {
+ struct video_device *dev = devs[i];
+ if (dev) {
+ struct s6dp *pd = video_get_drvdata(dev);
+ u32 s;
+ spin_lock(&pd->lock);
+ s = DP_REG_R(pd, S6_DP_INT_STATUS);
+ if (unlikely(s & (1 << S6_DP_INT_ERR_INT))) {
+ s6dp_err_interrupt(pd);
+ ret = IRQ_HANDLED;
+ }
+ if (s & (1 << S6_DP_INT_TERMCNT(pd->port))) {
+ s6dp_tc_interrupt(pd);
+ ret = IRQ_HANDLED;
+ }
+ spin_unlock(&pd->lock);
+ }
+ }
+ return ret;
+}
+
+static int s6dp_dma_init(struct video_device *dev)
+{
+ struct s6dp *pd = video_get_drvdata(dev);
+ unsigned i, n, burstsize;
+
+ n = DP_PREG_R(pd, S6_DP_CBCR_DMA_CONVERT) |
+ DP_PREG_R(pd, S6_DP_Y_DMA_CONVERT) |
+ DP_PREG_R(pd, S6_DP_ANC_DMA_CONVERT);
+ burstsize = 7;
+ for (i = (1 << (burstsize - 4)) - 1; n & i; i >>= 1)
+ burstsize--;
+
+ n = 3;
+ i = 0;
+ do {
+ int ret;
+ ret = s6dmac_request_chan(pd->dmac,
+ pd->port * S6_DP_CHAN_PER_PORT + i, /* channel */
+ 0, /* prio */
+ 0, /* pxfer */
+ pd->ext.egress, /* srcinc */
+ !pd->ext.egress, /* dstinc */
+ 0, /* sc./ga. */
+ 0, /* srcskip */
+ 0, /* dstskip */
+ burstsize, /* burstsize */
+ -1, /* bwconsv */
+ pd->ext.egress ? 4 : 2, /* lowwmark */
+ 1, /* timestamp */
+ 0); /* enable */
+ if (ret < 0) {
+ printk(DRV_ERR
+ "error - can not request DMA channel %d\n",
+ pd->port * S6_DP_CHAN_PER_PORT + i);
+ goto errdma;
+ }
+ } while (++i < n);
+
+ pd->cur.framerepeat = 1;
+ s6dmac_dp_setup_group(pd->dmac, pd->port, n, 1);
+
+ DP_REG_W(pd, S6_DP_VIDEO_DMA_CFG, (DP_REG_R(pd, S6_DP_VIDEO_DMA_CFG)
+ & ~(7 << S6_DP_VIDEO_DMA_CFG_BURST_BITS(pd->port)))
+ | (burstsize << S6_DP_VIDEO_DMA_CFG_BURST_BITS(pd->port)));
+
+ return 0;
+errdma:
+ while (i > 0) {
+ i -= 1;
+ s6dmac_release_chan(pd->dmac,
+ pd->port * S6_DP_CHAN_PER_PORT + i);
+ }
+ return -EIO;
+}
+
+static void s6dp_dma_free(struct video_device *dev)
+{
+ struct s6dp *pd = video_get_drvdata(dev);
+ unsigned i, n;
+
+ if (pd->cur.state < DP_STATE_ACTIVE)
+ return;
+
+ n = 3;
+ i = 0;
+ do {
+ s6dmac_release_chan(pd->dmac,
+ pd->port * S6_DP_CHAN_PER_PORT + i);
+ } while (++i < n);
+}
+
+static int s6dp_setup_stream(struct video_device *dev)
+{
+ struct s6dp *pd = video_get_drvdata(dev);
+ unsigned i, n, y;
+ unsigned long flags;
+
+ i = pd->cur.portsperstream;
+ if (i != 1) {
+ printk(DRV_ERR "multi port mode not implemented\n");
+ /* needs cross device checking for free channels */
+ return -EINVAL;
+ }
+ pd->cur.portsperstream = i; /* FIXME -> set_current */
+ /* write port configuration (24 regs. minus ANC stuff, see below) */
+ DP_PREG_W(pd, S6_DP_PIXEL_TOTAL, pd->cur.pixel_total);
+ DP_PREG_W(pd, S6_DP_PIXEL_ACTIVE,
+ pd->cur.width / pd->cur.greyperchroma);
+ DP_PREG_W(pd, S6_DP_PIXEL_OFFSET, pd->cur.pixel_offset);
+ DP_PREG_W(pd, S6_DP_PIXEL_PADDING, pd->cur.pixel_padding);
+ DP_PREG_W(pd, S6_DP_LINE_TOTAL, pd->cur.line_total);
+ DP_PREG_W(pd, S6_DP_LINE_ODD_TOTAL, pd->cur.line_odd_total);
+ i = pd->cur.progressive ? 0 : pd->cur.height/2;
+ DP_PREG_W(pd, S6_DP_LINE_ODD_ACTIVE, pd->cur.height - i);
+ DP_PREG_W(pd, S6_DP_LINE_ODD_OFFSET, pd->cur.line_odd_offset);
+ DP_PREG_W(pd, S6_DP_LINE_EVEN_ACTIVE, i);
+ DP_PREG_W(pd, S6_DP_LINE_EVEN_OFFSET, pd->cur.line_even_offset);
+ DP_PREG_W(pd, S6_DP_ODD_VSYNC_LENGTH, pd->cur.odd_vsync_len);
+ DP_PREG_W(pd, S6_DP_ODD_VSYNC_OFFSET, pd->cur.odd_vsync_offset);
+ DP_PREG_W(pd, S6_DP_EVEN_VSYNC_LENGTH, pd->cur.even_vsync_len);
+ DP_PREG_W(pd, S6_DP_EVEN_VSYNC_OFFSET, pd->cur.even_vsync_offset);
+ DP_PREG_W(pd, S6_DP_ODD_HSYNC_LENGTH, pd->cur.odd_hsync_len);
+ DP_PREG_W(pd, S6_DP_ODD_HSYNC_OFFSET, pd->cur.odd_hsync_offset);
+ DP_PREG_W(pd, S6_DP_EVEN_HSYNC_LENGTH, pd->cur.even_hsync_len);
+ DP_PREG_W(pd, S6_DP_EVEN_HSYNC_OFFSET, pd->cur.even_hsync_offset);
+
+ DP_PREG_W(pd, S6_DP_ANC_PIXEL_ACTIVE, 0);
+ DP_PREG_W(pd, S6_DP_ANC_PIXEL_OFFSET, 0);
+ DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_ACTIVE, 0);
+ DP_PREG_W(pd, S6_DP_LINE_ODD_ANC_OFFSET, 0);
+ DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_ACTIVE, 0);
+ DP_PREG_W(pd, S6_DP_LINE_EVEN_ANC_OFFSET, 0);
+
+ /*
+ * Program the _dma_convert registers. These values calculate for the
+ * hardware the number of bursts a frame will require (in video mode).
+ * In streaming mode, cbcr_dma_convert indicates the number of 16b
+ * lines to do before issuing the last transfer.
+ */
+ n = pd->cur.width / pd->cur.greyperchroma;
+ y = pd->cur.height;
+ i = pd->ext.is_10bit ? 12 : 16;
+ DP_PREG_W(pd, S6_DP_CBCR_DMA_CONVERT, ((n + i - 1) / i) * y);
+ i /= pd->cur.greyperchroma;
+ DP_PREG_W(pd, S6_DP_Y_DMA_CONVERT, ((n + i - 1) / i) * y);
+ DP_PREG_W(pd, S6_DP_ANC_DMA_CONVERT, 0);
+
+ /* Program dp_config. Function of mode and optional configs */
+ /* Video configuration */
+ i = (pd->cur.greyperchroma == 1 ? S6_DP_VIDEO_CFG_MODE_444_SERIAL
+ : S6_DP_VIDEO_CFG_MODE_422_SERIAL)
+ << S6_DP_VIDEO_CFG_MODE;
+ i |= pd->ext.use_1120_line_and_crc << S6_DP_VIDEO_CFG_1120_VIDEO_MODE;
+ /* Progressive / interlaced */
+ /* Micron mode: Must be progressive (regardless of what mode says) */
+ i |= pd->ext.micron << S6_DP_VIDEO_CFG_MICRON_MODE;
+ i |= (pd->ext.micron | pd->cur.progressive)
+ << S6_DP_VIDEO_CFG_INTERL_OR_PROGR;
+ /* External framing */
+ if (pd->ext.ext_framing) {
+ i |= 1 << S6_DP_VIDEO_CFG_FRAMING;
+ i |= pd->ext.vsync_pol << S6_DP_VIDEO_CFG_VSYNC_POL;
+ i |= pd->ext.hsync_pol << S6_DP_VIDEO_CFG_HSYNC_POL;
+ i |= pd->ext.blank_pol << S6_DP_VIDEO_CFG_BLANK_POL;
+ i |= pd->ext.field_ctrl << S6_DP_VIDEO_CFG_FIELD_CTRL;
+ i |= pd->ext.blank_ctrl << S6_DP_VIDEO_CFG_BLANK_CTRL;
+ } else {
+ /* Embedded framing */
+ i |= pd->ext.relaxed_framing_mode << S6_DP_VIDEO_CFG_RELAX_MODE;
+ }
+ i |= pd->ext.is_10bit << S6_DP_VIDEO_CFG_8_OR_10;
+ i |= pd->ext.egress << S6_DP_VIDEO_CFG_IN_OR_OUT;
+ DP_REG_W(pd, S6_DP_VIDEO_CFG(pd->port), i);
+
+ spin_lock_irqsave(&pd->lock, flags);
+ /*
+ * Program the clk_mux in DP_CLK_SETTING.
+ * NOTE: all ports share this register, so this would need to be
+ * atomic if this function were re-entrant (which it is not).
+ */
+ i = DP_REG_R(pd, S6_DP_DP_CLK_SETTING)
+ & ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK <<
+ S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port));
+ i |= (pd->ext.egress ? 0 : 1) <<
+ S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port);
+ DP_REG_W(pd, S6_DP_DP_CLK_SETTING, i);
+
+ /* Initialize DP DMA registers for this stream */
+ i = s6dp_dma_init(dev);
+ spin_unlock_irqrestore(&pd->lock, flags);
+ return i;
+}
+
+static void _s6dp_reset_port(struct s6dp *pd)
+{
+ unsigned i, m;
+ unsigned long flags;
+ struct list_head *queue[3] = {
+ &pd->idleq, &pd->busyq, &pd->fullq
+ };
+
+ spin_lock_irqsave(&pd->lock, flags);
+ DP_REG_W(pd, S6_DP_INT_ENABLE, DP_REG_R(pd, S6_DP_INT_ENABLE)
+ & ~((1 << S6_DP_INT_UNDEROVERRUN(pd->port))
+ | (1 << S6_DP_INT_WRONGPIXEL(pd->port))
+ | (1 << S6_DP_INT_WRONGLINES(pd->port))));
+
+ /* Clear the enable bit for the entire DMA group */
+ s6dmac_dp_switch_group(pd->dmac, pd->port, 0);
+ pd->outstanding = 0;
+ spin_unlock_irqrestore(&pd->lock, flags);
+ /* wait for first channel's DMA to become disabled */
+ i = 0;
+ do if (pd->cur.chansiz[i]) {
+ while (s6dmac_channel_enabled(pd->dmac,
+ i + pd->port * S6_DP_CHAN_PER_PORT))
+ ;
+ break;
+ } while (++i < S6_DP_CHAN_PER_PORT);
+
+ spin_lock_irqsave(&pd->lock, flags);
+ DP_REG_W(pd, S6_DP_VIDEO_ENABLE, DP_REG_R(pd, S6_DP_VIDEO_ENABLE)
+ & ~(1 << S6_DP_VIDEO_ENABLE_ENABLE(pd->port)));
+
+ /* FIXME, sort out true channel list: */
+ s6dmac_disable_error_irqs(pd->dmac,
+ 15 << (pd->port * S6_DP_CHAN_PER_PORT));
+
+ DP_REG_W(pd, S6_DP_INT_CLEAR,
+ (1 << S6_DP_INT_LOWWMARK(pd->port)) |
+ (1 << S6_DP_INT_PENDGCNT(pd->port)) |
+ (1 << S6_DP_INT_TERMCNT(pd->port)));
+ for (i = 0; i < 3; i++) {
+ struct s6dp_frame *f, *next;
+ list_for_each_entry_safe(f, next, queue[i], list) {
+ list_del_init(&f->list);
+ f->flags &= ~(V4L2_BUF_FLAG_QUEUED |
+ V4L2_BUF_FLAG_DONE);
+ }
+ }
+ m = 1 << S6_DP_INT_DMAERR;
+ for (i = 0; i < S6_DP_CHAN_PER_PORT; i++)
+ if (pd->cur.chansiz[i])
+ m |= (1 << (i + S6_DP_CHAN_PER_PORT * pd->port));
+ DP_REG_W(pd, S6_DP_INT_ENABLE, DP_REG_R(pd, S6_DP_INT_ENABLE) & ~m);
+ spin_unlock_irqrestore(&pd->lock, flags);
+}
+
+static void s6dp_reset_port(struct video_device *dev)
+{
+ struct s6dp *pd = video_get_drvdata(dev);
+ unsigned i;
+ unsigned long flags;
+
+ _s6dp_reset_port(pd);
+ spin_lock_irqsave(&pd->lock, flags);
+ s6dp_dma_free(dev);
+ DP_REG_W(pd, S6_DP_DP_CLK_SETTING, DP_REG_R(pd, S6_DP_DP_CLK_SETTING)
+ & ~(S6_DP_DP_CLK_SETTING_CLK_MUX_MASK
+ << S6_DP_DP_CLK_SETTING_CLK_MUX(pd->port)));
+ spin_unlock_irqrestore(&pd->lock, flags);
+ DP_REG_W(pd, S6_DP_VIDEO_CFG(pd->port), 0);
+ for (i = 0; i < 24; i++)
+ DP_PREG_W(pd, i*4, 0);
+}
+
+static int s6dp_video_open(struct file *file)
+{
+ struct video_device *dev = video_devdata(file);
+ struct s6dp *pd = video_get_drvdata(dev);
+ unsigned long flags;
+
+ file->private_data = dev;
+ spin_lock_irqsave(&pd->lock, flags);
+ if (pd->cur.state != DP_STATE_UNUSED) {
+ spin_unlock_irqrestore(&pd->lock, flags);
+ return -EBUSY; /* TODO: v4l2 allows multiple opens */
+ }
+ pd->cur.state = DP_STATE_IDLE;
+ spin_unlock_irqrestore(&pd->lock, flags);
+
+ /* deferred initialization to avoid problems with the probing order */
+ if (!pd->cur.height) {
+ struct v4l2_cropcap cap;
+ struct v4l2_format vf;
+ v4l2_std_id first = 0;
+ int i;
+
+ if (pd->ext.egress)
+ s6v4l_s_output(file, file->private_data, 0);
+ else
+ s6v4l_s_input(file, file->private_data, 0);
+
+ for (i = 0; ; i++) {
+ struct v4l2_standard std;
+ std.index = i;
+ if (s6v4l_enumstd(pd, &std) < 0)
+ break;
+ if (!first)
+ first = std.id;
+ dev->tvnorms |= std.id;
+ }
+ if (dev->ioctl_ops->vidioc_s_std)
+ if (!dev->ioctl_ops->vidioc_s_std(file,
+ file->private_data,
+ &first))
+ dev->current_norm = first;
+ cap.type = CURRENT_BUF_TYPE(pd);
+ if (!dev->ioctl_ops->vidioc_cropcap(file, file->private_data,
+ &cap)) {
+ struct v4l2_crop vc;
+ vc.type = CURRENT_BUF_TYPE(pd);
+ vc.c = cap.defrect;
+ dev->ioctl_ops->vidioc_s_crop(file, file->private_data,
+ &vc);
+ }
+ vf.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P;
+ vf.fmt.pix.field = V4L2_FIELD_ANY;
+ vf.fmt.pix.width = 704;
+ vf.fmt.pix.height = 480;
+ vf.fmt.pix.bytesperline = 0;
+ vf.fmt.pix.priv = 0;
+ if (pd->ext.egress)
+ dev->ioctl_ops->vidioc_s_fmt_vid_out(file,
+ file->private_data,
+ &vf);
+ else
+ dev->ioctl_ops->vidioc_s_fmt_vid_cap(file,
+ file->private_data,
+ &vf);
+ }
+ return 0;
+}
+
+static void s6dp_relbufs(struct video_device *dev)
+{
+ struct s6dp *pd = video_get_drvdata(dev);
+ int i;
+ unsigned long flags;
+
+ if (!pd->nrframes)
+ return;
+ spin_lock_irqsave(&pd->lock, flags);
+ INIT_LIST_HEAD(&pd->idleq);
+ INIT_LIST_HEAD(&pd->busyq);
+ INIT_LIST_HEAD(&pd->fullq);
+ spin_unlock_irqrestore(&pd->lock, flags);
+ for (i = 0; i < pd->nrframes; i++)
+ dma_free_coherent(dev->parent, pd->cur.bufsize,
+ pd->frames[i].data, pd->frames[i].dma_handle);
+ kfree(pd->frames);
+ pd->nrframes = 0;
+}
+
+static int s6dp_video_close(struct file *file)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ /* reset port and free dma channels */
+ s6dp_reset_port(dev);
+
+ /* free buffer(s) */
+ s6dp_relbufs(dev);
+ pd->cur.state = DP_STATE_UNUSED;
+ return 0;
+}
+
+static void s6dp_video_vm_close(struct vm_area_struct *area)
+{
+ struct video_device *dev = area->vm_file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ struct s6dp_frame *f = area->vm_private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pd->lock, flags);
+ f->flags &= ~V4L2_BUF_FLAG_MAPPED;
+ pd->nrmapped--;
+ spin_unlock_irqrestore(&pd->lock, flags);
+}
+
+static struct vm_operations_struct s6dp_vm_ops = {
+ .close = s6dp_video_vm_close,
+};
+
+static int s6dp_video_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ unsigned long flags;
+ u32 buf;
+ int index;
+
+ /* we use the vma_pgoff to distinguish between the buffers */
+#define MAX_FRAMES 256
+ index = vma->vm_pgoff & 0xFF;
+ if (pd->cur.state < DP_STATE_READY || index >= pd->nrframes)
+ return -ENOMEM;
+ buf = (u32)pd->frames[index].data;
+ BUG_ON(buf & ~PAGE_MASK);
+
+ vma->vm_pgoff = buf >> PAGE_SHIFT;
+ if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ PAGE_ALIGN(pd->cur.bufsize), vma->vm_page_prot)) {
+ printk(DRV_ERR "error - mapping frame #%d\n", index);
+ return -EAGAIN;
+ }
+ vma->vm_flags &= ~VM_IO; /* not I/O memory */
+ vma->vm_flags |= VM_MAYSHARE | VM_RESERVED; /* avoid to swap out */
+ vma->vm_ops = &s6dp_vm_ops;
+ vma->vm_private_data = pd->frames + index;
+
+ spin_lock_irqsave(&pd->lock, flags);
+ pd->nrmapped++;
+ pd->frames[index].flags |= V4L2_BUF_FLAG_MAPPED;
+ spin_unlock_irqrestore(&pd->lock, flags);
+ return 0;
+}
+
+static unsigned long s6dp_video_get_unmapped_area(struct file *file,
+ unsigned long addr, unsigned long len,
+ unsigned long pgoff, unsigned long flags)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ int index;
+
+ index = pgoff & 0xFF;
+ if (pd->cur.state < DP_STATE_READY || index >= pd->nrframes)
+ return -ENOMEM;
+ return (unsigned long)pd->frames[index].data;
+}
+
+static unsigned int s6dp_video_poll(struct file *file, poll_table *wait)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ poll_wait(file, &pd->wait, wait);
+ if (pd->cur.state < DP_STATE_ACTIVE)
+ return POLLERR;
+ if (list_empty(&pd->fullq))
+ return 0;
+ return pd->ext.egress ? (POLLOUT | POLLWRNORM) : (POLLIN | POLLRDNORM);
+}
+
+static long s6dp_video_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ if (cmd == VIDIOC_ENUMSTD) {
+ struct v4l2_standard std;
+ int ret;
+ if (copy_from_user(&std, (void __user *)arg, sizeof(std)))
+ return -EFAULT;
+ ret = s6v4l_enumstd(pd, &std);
+ if (copy_to_user((void __user *)arg, &std, sizeof(std)))
+ return -EFAULT;
+ return ret;
+ }
+ return video_ioctl2(file, cmd, arg);
+}
+
+static inline u32 s6dp_byteperline(struct s6dp *pd, int divide)
+{
+ u32 n = pd->cur.width;
+ if (divide)
+ n /= pd->cur.greyperchroma;
+ if (pd->ext.is_10bit) /* pack 3 samples into 4 bytes: */
+ n = ((n * 4) + 2) / 3;
+ return n;
+}
+
+static inline u32 s6dp_bytealigned(u32 unaligned)
+{
+ return (unaligned + 15) & ~15;
+}
+
+static inline u32 s6dp_byteperframe(struct s6dp *pd, int divide, u32 perline)
+{
+ u32 n = pd->cur.height;
+ if (divide)
+ n /= 2;
+ return n * perline;
+}
+
+static inline unsigned s6dp_set_hw2buf(struct s6dp *pd, int chan,
+ unsigned offset, unsigned asize)
+{
+ pd->cur.chanoff[chan] = offset;
+ pd->cur.chansiz[chan] = asize;
+ return 1 << chan;
+}
+
+static int s6dp_set_current(struct video_device *dev, u32 fourcc, int aligned)
+{
+ struct s6dp *pd = video_get_drvdata(dev);
+ u32 uyl, ayl, uyf, ayf, ucl, acl, acf;
+ pd->cur.fourcc = fourcc;
+ pd->cur.aligned = aligned;
+ pd->cur.chansiz[DP_K_OFFSET] = 0;
+ uyl = s6dp_byteperline(pd, 0);
+ ayl = s6dp_bytealigned(uyl);
+ ucl = s6dp_byteperline(pd, 1);
+ acl = s6dp_bytealigned(ucl);
+ uyf = s6dp_byteperframe(pd, 0, uyl);
+ ayf = s6dp_byteperframe(pd, 0, ayl);
+ if (!aligned && ayl != pd->cur.greyperchroma * acl)
+ return -EINVAL;
+ acf = s6dp_byteperframe(pd, 0, acl);
+ switch (fourcc) {
+ case V4L2_PIX_FMT_YUV444P:
+ if (aligned || uyl == ayl) {
+ s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+ s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
+ s6dp_set_hw2buf(pd, DP_CR_OFFSET, ayf + acf, acf);
+ pd->cur.bufsize = ayf + 2 * acf;
+ }
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ if (aligned || ucl == acl) {
+ s6dp_set_hw2buf(pd, DP_Y_OFFSET, 0, ayf);
+ s6dp_set_hw2buf(pd, DP_CB_OFFSET, ayf, acf);
+ s6dp_set_hw2buf(pd, DP_CR_OFFSET, ayf + acf, acf);
+ pd->cur.bufsize = ayf + 2 * acf;
+ }
+ break;
+ default:
+ BUG();
+ }
+ BUG_ON(pd->cur.bufsize >= (1 << 24));
+ return 0;
+}
+
+static int s6v4l_update(struct s6dp *pd, int r)
+{
+ struct s6dp_mode mode;
+ int divi, sub;
+
+ if (r < 0)
+ return r;
+ if (!pd->link || !pd->link->g_mode)
+ return -EINVAL; /* no driver, no V4L */
+ pd->link->g_mode(pd->link->context, &mode);
+
+ pd->cur.width = mode.pixel_active;
+ pd->cur.height = mode.odd_active + mode.even_active;
+ pd->cur.progressive = mode.progressive;
+ switch (mode.type) {
+ case S6_DP_VIDEO_CFG_MODE_422_SERIAL:
+ pd->cur.portsperstream = 1;
+ divi = 2;
+ sub = 2;
+ break;
+ case S6_DP_VIDEO_CFG_MODE_444_SERIAL:
+ pd->cur.portsperstream = 1;
+ divi = 1;
+ sub = 3;
+ break;
+ case S6_DP_VIDEO_CFG_MODE_422_PARALLEL:
+ pd->cur.portsperstream = 2;
+ divi = 2;
+ sub = 4;
+ break;
+ case S6_DP_VIDEO_CFG_MODE_444_PARALLEL:
+ pd->cur.portsperstream = 3;
+ divi = 1;
+ sub = 8;
+ break;
+ default:
+ divi = 1;
+ sub = 0;
+ }
+ pd->cur.greyperchroma = divi;
+ pd->cur.pixel_total = mode.pixel_total / divi - sub;
+ pd->cur.pixel_offset = mode.pixel_offset / divi;
+ pd->cur.pixel_padding = mode.pixel_padding / divi;
+ pd->cur.line_total = mode.framelines;
+ pd->cur.line_odd_total = mode.odd_total;
+ pd->cur.line_odd_offset = mode.odd_first;
+ pd->cur.line_even_offset = mode.even_first;
+ pd->cur.odd_vsync_len = mode.odd_vsync_len;
+ pd->cur.odd_vsync_offset = mode.odd_vsync_offset;
+ pd->cur.even_vsync_len = mode.even_vsync_len;
+ pd->cur.even_vsync_offset = mode.even_vsync_offset;
+ pd->cur.odd_hsync_len = mode.hsync_len / divi;
+ pd->cur.odd_hsync_offset = mode.hsync_offset / divi;
+ pd->cur.even_hsync_len = mode.hsync_len / divi;
+ pd->cur.even_hsync_offset = mode.hsync_offset / divi;
+ pd->ext.ext_framing = !mode.embedded_sync;
+ pd->ext.micron = mode.micron_mode;
+ pd->ext.vsync_pol = mode.vsync_pol;
+ pd->ext.hsync_pol = mode.hsync_pol;
+ pd->ext.blank_pol = mode.blank_pol;
+ pd->ext.field_ctrl = mode.field_ctrl;
+ pd->ext.blank_ctrl = mode.blank_ctrl;
+ pd->ext.relaxed_framing_mode = mode.relaxed_framing;
+ pd->ext.is_10bit = mode.ten_bit;
+ pd->ext.use_1120_line_and_crc = mode.line_and_crc;
+ return 0;
+}
+
+
+static int s6v4l_enumstd(struct s6dp *pd, struct v4l2_standard *std)
+{
+ int ret = -EINVAL;
+ if (pd->link && pd->link->e_std)
+ ret = pd->link->e_std(pd->link->context, std);
+ return ret;
+}
+
+static int s6v4l_s_std(struct file *file, void *priv, v4l2_std_id *std)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ int ret = -EINVAL;
+ if (pd->link && pd->link->s_std)
+ ret = pd->link->s_std(pd->link->context, std,
+ pd->cur.state >= DP_STATE_READY);
+ return s6v4l_update(pd, ret);
+}
+
+static int s6v4l_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ u32 i;
+ struct s6dp_frame *f;
+ unsigned long flags;
+
+ if (pd->cur.state < DP_STATE_READY)
+ return -EINVAL;
+ if (p->memory != V4L2_MEMORY_MMAP)
+ return -EINVAL;
+ if (p->type != CURRENT_BUF_TYPE(pd))
+ return -EINVAL;
+ i = p->index;
+ if (i >= pd->nrframes) {
+ printk(DRV_ERR "buffer index range error (%u/%u)\n",
+ i, pd->nrframes);
+ return -EINVAL;
+ }
+ f = &pd->frames[i];
+ if (!list_empty(&f->list)) {
+ printk(DRV_ERR "error - frame %d already queued\n", i);
+ return -EINVAL;
+ }
+ f->timestamp.tv_sec = 0;
+ f->timestamp.tv_usec = 0;
+ f->flags |= V4L2_BUF_FLAG_QUEUED;
+ p->flags = f->flags;
+ spin_lock_irqsave(&pd->lock, flags);
+ list_add_tail(&f->list, &pd->idleq);
+ s6dp_try_fill_dma(pd);
+ spin_unlock_irqrestore(&pd->lock, flags);
+ return 0;
+}
+
+static int s6v4l_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ struct s6dp_frame *f;
+ unsigned long flags;
+
+ if (pd->cur.state < DP_STATE_READY)
+ return -EINVAL;
+retry:
+ if (!(file->f_flags & O_NONBLOCK) &&
+ wait_event_interruptible(pd->wait, !list_empty(&pd->fullq)))
+ return -ERESTARTSYS;
+ spin_lock_irqsave(&pd->lock, flags);
+ if (list_empty(&pd->fullq)) {
+ spin_unlock_irqrestore(&pd->lock, flags);
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ goto retry;
+ }
+ f = list_first_entry(&pd->fullq, struct s6dp_frame, list);
+ list_del_init(&f->list);
+ f->flags &= ~V4L2_BUF_FLAG_DONE;
+ spin_unlock_irqrestore(&pd->lock, flags);
+
+ p->index = f - &pd->frames[0];
+ p->timestamp = f->timestamp;
+ p->sequence = f->sequence;
+ p->memory = V4L2_MEMORY_MMAP;
+ p->flags = f->flags;
+ p->field = pd->cur.vfield;
+ p->length = pd->cur.bufsize;
+ if (!pd->ext.egress)
+ p->bytesused = pd->cur.bufsize;
+ return 0;
+}
+
+static int s6v4l_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *req)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ int i;
+
+ if (req->memory != V4L2_MEMORY_MMAP)
+ return -EINVAL;
+ if (req->type != CURRENT_BUF_TYPE(pd))
+ return -EINVAL;
+ if (pd->nrmapped)
+ return -EBUSY;
+ if (pd->cur.state > DP_STATE_READY) {
+ if (req->count)
+ return -EBUSY;
+ i = s6v4l_streamoff(file, priv, req->type);
+ if (i < 0)
+ return i;
+ }
+ if (req->count > MAX_FRAMES)
+ req->count = MAX_FRAMES;
+
+ s6dp_relbufs(dev);
+ if (req->count == 0) {
+ pd->cur.state = DP_STATE_IDLE;
+ return 0;
+ }
+
+ pd->frames =
+ kmalloc(req->count * sizeof(struct s6dp_frame), GFP_KERNEL);
+ if (!pd->frames)
+ return -ENOMEM;
+ for (i = 0; i < req->count; i++) {
+ struct s6dp_frame *f;
+ f = &pd->frames[i];
+ f->data = dma_alloc_coherent(dev->parent, pd->cur.bufsize,
+ &f->dma_handle, GFP_KERNEL);
+ if (!f->data) {
+ req->count = i;
+ break;
+ }
+ INIT_LIST_HEAD(&f->list);
+ f->flags = 0;
+ }
+ if (!i) {
+ kfree(pd->frames);
+ return -ENOMEM;
+ }
+ pd->nrframes = i;
+ pd->cur.state = DP_STATE_READY;
+ return 0;
+}
+
+static int s6v4l_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ if (p->type != CURRENT_BUF_TYPE(pd))
+ return -EINVAL;
+ if (pd->cur.state < DP_STATE_READY)
+ return -EINVAL;
+ if (p->index >= pd->nrframes)
+ return -EINVAL;
+
+ p->memory = V4L2_MEMORY_MMAP;
+ p->m.offset = p->index << PAGE_SHIFT; /*
+ * a "magic cookie" that the
+ * appl. can pass to mmap to
+ * specifiy which buffer is
+ * being mapped
+ */
+
+ p->length = pd->cur.bufsize;
+ p->flags = pd->frames[p->index].flags;
+ p->field = pd->cur.vfield;
+ return 0;
+}
+
+static int s6v4l_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type vtype)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ unsigned i, m;
+ unsigned long flags;
+
+ if (pd->cur.state != DP_STATE_READY) {
+ printk(DRV_ERR "device not ready\n");
+ return -EINVAL;
+ }
+
+ if (list_empty(&pd->idleq)) {
+ printk(DRV_ERR "no buffers queued\n");
+ return -EINVAL;
+ }
+
+ i = s6dp_setup_stream(dev);
+ if (i) {
+ printk(DRV_ERR "error - video setup failed\n");
+ return i;
+ }
+ pd->cur.sequence = 0;
+ pd->cur.state = DP_STATE_ACTIVE;
+
+ /* Set the enable bit for the entire DMA group */
+ s6dmac_dp_switch_group(pd->dmac, pd->port, 1);
+
+ m = (1 << S6_DP_INT_DMAERR)
+ | (1 << S6_DP_INT_UNDEROVERRUN(pd->port))
+ | (1 << S6_DP_INT_WRONGPIXEL(pd->port))
+ | (1 << S6_DP_INT_WRONGLINES(pd->port));
+ for (i = 0; i < S6_DP_CHAN_PER_PORT; i++)
+ if (pd->cur.chansiz[i])
+ m |= (1 << (i + S6_DP_CHAN_PER_PORT * pd->port));
+ spin_lock_irqsave(&pd->lock, flags);
+ DP_REG_W(pd, S6_DP_INT_ENABLE, DP_REG_R(pd, S6_DP_INT_ENABLE) | m);
+ DP_REG_W(pd, S6_DP_VIDEO_ENABLE, DP_REG_R(pd, S6_DP_VIDEO_ENABLE)
+ | (1 << S6_DP_VIDEO_ENABLE_ENABLE(pd->port)));
+ s6dp_try_fill_dma(pd);
+ spin_unlock_irqrestore(&pd->lock, flags);
+ return 0;
+}
+
+
+static int s6v4l_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ if (pd->cur.state != DP_STATE_ACTIVE)
+ return -EINVAL;
+ s6dp_reset_port(dev);
+ pd->cur.state = DP_STATE_READY;
+ return 0;
+}
+
+const static struct {
+ u32 pixelformat;
+ u8 *description;
+} s6dp_enum_fmt[] = {
+ { V4L2_PIX_FMT_YUV444P,
+ "YUV 4:4:4 planar",
+ },
+ { V4L2_PIX_FMT_YUV422P,
+ "YUV 4:2:2 planar",
+ },
+};
+
+static int s6v4l_enum_fmt_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ u32 i = f->index;
+ if (i >= ARRAY_SIZE(s6dp_enum_fmt))
+ return -EINVAL;
+ f->pixelformat = s6dp_enum_fmt[i].pixelformat;
+ strlcpy(f->description, s6dp_enum_fmt[i].description,
+ sizeof(f->description));
+ f->flags = 0;
+ return 0;
+}
+
+static int s6v4l_enum_fmt_out(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ u32 i = f->index;
+ if (i > 0)
+ return -EINVAL;
+ /* Only 422 for now */
+ f->pixelformat = s6dp_enum_fmt[1].pixelformat;
+ strlcpy(f->description, s6dp_enum_fmt[1].description,
+ sizeof(f->description));
+ f->flags = 0;
+ return 0;
+}
+
+static int s6v4l_cropcap(struct file *file, void *priv, struct v4l2_cropcap *c)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ if (c->type != CURRENT_BUF_TYPE(pd))
+ return -EINVAL;
+
+ if (!pd->link || !pd->link->cropcap)
+ return -EINVAL;
+
+ return pd->link->cropcap(pd->link->context, c);
+}
+
+static int s6v4l_s_crop(struct file *file, void *priv, struct v4l2_crop *c)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ int ret;
+
+ if (c->type != CURRENT_BUF_TYPE(pd))
+ return -EINVAL;
+
+ if (!pd->link || !pd->link->s_crop)
+ return -EINVAL;
+
+ ret = pd->link->s_crop(pd->link->context, c,
+ pd->cur.state >= DP_STATE_READY);
+
+ return s6v4l_update(pd, ret);
+}
+
+static int s6v4l_g_crop(struct file *file, void *priv, struct v4l2_crop *c)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ if (c->type != CURRENT_BUF_TYPE(pd))
+ return -EINVAL;
+
+ if (!pd->link || !pd->link->g_crop)
+ return -EINVAL;
+
+ return pd->link->g_crop(pd->link->context, c);
+}
+
+static int s6v4l_try_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ int cwidth, cheight, cbytesperline, aligned = 1;
+ if (!pd->link || !pd->link->s_fmt || !pd->link->g_mode)
+ return 0;
+
+ pd->link->s_fmt(pd->link->context, 1, &f->fmt.pix, 1);
+
+ switch (f->fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P;
+ f->fmt.pix.width &= ~1;
+ break;
+ default:
+ f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV444P;
+ }
+ if (f->fmt.pix.field == V4L2_FIELD_ALTERNATE)
+ f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
+ cheight = f->fmt.pix.height;
+ switch (f->fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_YUV444P:
+ cwidth = f->fmt.pix.width;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ cheight = f->fmt.pix.height / 2;
+ case V4L2_PIX_FMT_YUV422P:
+ cwidth = f->fmt.pix.width / 2;
+ break;
+ default:
+ cwidth = 0;
+ }
+ if (aligned) {
+ f->fmt.pix.bytesperline = s6dp_bytealigned(f->fmt.pix.width);
+ cbytesperline = s6dp_bytealigned(cwidth);
+ } else {
+ f->fmt.pix.bytesperline = f->fmt.pix.width;
+ cbytesperline = cwidth;
+ }
+ f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height
+ + cbytesperline * cheight * 2;
+ return 0;
+}
+
+static int s6v4l_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ unsigned i;
+
+ memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format));
+ if (pd->link && pd->link->g_fmt) {
+ i = pd->link->g_fmt(pd->link->context, &f->fmt.pix);
+ if (i < 0)
+ return i;
+ } else {
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+ }
+ f->fmt.pix.field = pd->cur.vfield;
+ if (f->fmt.pix.field == V4L2_FIELD_ALTERNATE)
+ f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
+ f->fmt.pix.width = pd->cur.width;
+ f->fmt.pix.height = pd->cur.height;
+ f->fmt.pix.pixelformat = pd->cur.fourcc;
+ f->fmt.pix.bytesperline = s6dp_bytealigned(f->fmt.pix.width);
+ f->fmt.pix.priv = pd->cur.aligned;
+ f->fmt.pix.sizeimage = pd->cur.bufsize;
+ return 0;
+}
+
+static int s6v4l_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct video_device *dev = file->private_data;
+ struct s6dp *pd = video_get_drvdata(dev);
+ struct v4l2_pix_format pfmt;
+ int r, align;
+ if (pd->cur.state != DP_STATE_IDLE)
+ return -EBUSY;
+ r = s6v4l_try_fmt(file, dev, f);
+ if (r < 0)
+ return r;
+ if (pd->link && pd->link->s_fmt) {
+ pfmt = f->fmt.pix;
+ r = pd->link->s_fmt(pd->link->context, 0, &pfmt, 0);
+ }
+ r = s6v4l_update(pd, r);
+ if (r < 0)
+ return r;
+
+ align = f->fmt.pix.priv & 1;
+ pd->cur.vfield = f->fmt.pix.field;
+ pd->cur.colorspace = f->fmt.pix.colorspace;
+ r = s6dp_set_current(dev, f->fmt.pix.pixelformat, align);
+ return r;
+}
+
+static int s6v4l_enum_input(struct file *file, void *fh, struct v4l2_input *inp)
+{
+ struct video_device *dev = video_devdata(file);
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ if (!pd->link || !pd->link->dir.ingress.e_inp)
+ return -EINVAL;
+
+ return pd->link->dir.ingress.e_inp(pd->link->context, inp);
+}
+
+static int s6v4l_enum_output(struct file *file, void *fh,
+ struct v4l2_output *outp)
+{
+ struct video_device *dev = video_devdata(file);
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ if (!pd->link || !pd->link->dir.egress.e_outp)
+ return -EINVAL;
+
+ return pd->link->dir.egress.e_outp(pd->link->context, outp);
+}
+
+static int s6v4l_g_input(struct file *file, void *fh, unsigned int *i)
+{
+ struct video_device *dev = video_devdata(file);
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ if (!pd->link || !pd->link->dir.ingress.s_inp)
+ return -EINVAL;
+
+ *i = pd->num_io;
+ return 0;
+}
+
+static int s6v4l_g_output(struct file *file, void *fh, unsigned int *i)
+{
+ struct video_device *dev = video_devdata(file);
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ if (!pd->link || !pd->link->dir.egress.s_outp)
+ return -EINVAL;
+
+ *i = pd->num_io;
+ return 0;
+}
+
+static int s6v4l_s_input(struct file *file, void *fh, unsigned int i)
+{
+ struct video_device *dev = video_devdata(file);
+ struct s6dp *pd = video_get_drvdata(dev);
+ int ret = -EINVAL;
+
+ if (pd->link && pd->link->dir.ingress.s_inp) {
+ ret = pd->link->dir.ingress.s_inp(pd->link->context, i,
+ pd->cur.state
+ >= DP_STATE_READY);
+ if (ret >= 0)
+ pd->num_io = i;
+ }
+
+ return s6v4l_update(pd, ret);
+}
+
+static int s6v4l_s_output(struct file *file, void *fh, unsigned int i)
+{
+ struct video_device *dev = video_devdata(file);
+ struct s6dp *pd = video_get_drvdata(dev);
+ int ret = -EINVAL;
+
+ if (pd->link && pd->link->dir.egress.s_outp) {
+ ret = pd->link->dir.egress.s_outp(pd->link->context, i,
+ pd->cur.state >= DP_STATE_READY);
+ if (ret >= 0)
+ pd->num_io = i;
+ }
+
+ return s6v4l_update(pd, ret);
+}
+
+static int s6v4l_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct video_device *dev = video_devdata(file);
+ struct s6dp *pd = video_get_drvdata(dev);
+
+ strcpy(cap->driver, "s6dp");
+ strcpy(cap->card, "Stretch data port");
+ sprintf(cap->bus_info, "Data port %i", pd->port);
+ cap->version = DRIVER_VERSION_NUM;
+ if (pd->ext.egress)
+ cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT;
+ else
+ cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ return 0;
+}
+
+static const struct v4l2_file_operations s6v4l_video_fops = {
+ .owner = THIS_MODULE,
+ .open = s6dp_video_open,
+ .release = s6dp_video_close,
+ .get_unmapped_area = s6dp_video_get_unmapped_area,
+ .mmap = s6dp_video_mmap,
+ .poll = s6dp_video_poll,
+ .ioctl = s6dp_video_ioctl,
+};
+
+static const struct v4l2_ioctl_ops capture_v4l_ioctl_ops = {
+ .vidioc_querycap = s6v4l_querycap,
+ .vidioc_enum_fmt_vid_cap = s6v4l_enum_fmt_cap,
+ .vidioc_g_fmt_vid_cap = s6v4l_g_fmt,
+ .vidioc_s_fmt_vid_cap = s6v4l_s_fmt,
+ .vidioc_try_fmt_vid_cap = s6v4l_try_fmt,
+ .vidioc_reqbufs = s6v4l_reqbufs,
+ .vidioc_querybuf = s6v4l_querybuf,
+ .vidioc_qbuf = s6v4l_qbuf,
+ .vidioc_dqbuf = s6v4l_dqbuf,
+ .vidioc_streamon = s6v4l_streamon,
+ .vidioc_streamoff = s6v4l_streamoff,
+ .vidioc_s_std = s6v4l_s_std,
+ .vidioc_enum_input = s6v4l_enum_input,
+ .vidioc_g_input = s6v4l_g_input,
+ .vidioc_s_input = s6v4l_s_input,
+ .vidioc_cropcap = s6v4l_cropcap,
+ .vidioc_g_crop = s6v4l_g_crop,
+ .vidioc_s_crop = s6v4l_s_crop,
+};
+
+static const struct v4l2_ioctl_ops output_v4l_ioctl_ops = {
+ .vidioc_querycap = s6v4l_querycap,
+ .vidioc_enum_fmt_vid_out = s6v4l_enum_fmt_out,
+ .vidioc_g_fmt_vid_out = s6v4l_g_fmt,
+ .vidioc_s_fmt_vid_out = s6v4l_s_fmt,
+ .vidioc_try_fmt_vid_out = s6v4l_try_fmt,
+ .vidioc_reqbufs = s6v4l_reqbufs,
+ .vidioc_querybuf = s6v4l_querybuf,
+ .vidioc_qbuf = s6v4l_qbuf,
+ .vidioc_dqbuf = s6v4l_dqbuf,
+ .vidioc_streamon = s6v4l_streamon,
+ .vidioc_streamoff = s6v4l_streamoff,
+ .vidioc_s_std = s6v4l_s_std,
+ .vidioc_enum_output = s6v4l_enum_output,
+ .vidioc_g_output = s6v4l_g_output,
+ .vidioc_s_output = s6v4l_s_output,
+ .vidioc_cropcap = s6v4l_cropcap,
+ .vidioc_g_crop = s6v4l_g_crop,
+ .vidioc_s_crop = s6v4l_s_crop,
+};
+
+
+static int probe_one(struct platform_device *pdev, int irq,
+ struct video_device **devs, struct s6dp_link *link,
+ void __iomem *dpbase, void __iomem *dmac, u32 physbase)
+{
+ struct video_device *dev;
+ struct s6dp *pd;
+ int index, res = -ENOMEM;
+
+ dev = video_device_alloc();
+ if (!dev) {
+ printk(DRV_ERR "video device alloc failed.\n");
+ goto err_allocd;
+ }
+ pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+ if (!pd) {
+ printk(DRV_ERR "video device alloc failed.\n");
+ goto err_allocp;
+ }
+ pd->ext.egress = link->is_egress;
+ strlcpy(dev->name, pdev->name, sizeof(dev->name));
+ dev->fops = &s6v4l_video_fops;
+ dev->release = video_device_release;
+ dev->tvnorms = 0;
+ dev->parent = &pdev->dev;
+ if (pd->ext.egress)
+ dev->ioctl_ops = &output_v4l_ioctl_ops;
+ else
+ dev->ioctl_ops = &capture_v4l_ioctl_ops;
+ video_set_drvdata(dev, pd);
+ pd->irq = irq;
+ pd->dp = dpbase;
+ pd->dmac = (u32)dmac;
+ for (index = 0; !(link->port_mask & (1 << index)); index++)
+ ;
+ if (link->port_mask != (1 << index)) {
+ printk(DRV_ERR "multi port mode not implemented\n");
+ goto err_videor;
+ }
+ pd->port = index;
+ pd->dataram = physbase + S6_DP_DATARAM(index);
+ pd->cur.state = DP_STATE_UNUSED;
+ pd->frames = NULL;
+ pd->nrframes = 0;
+ pd->link = link;
+ INIT_LIST_HEAD(&pd->idleq);
+ INIT_LIST_HEAD(&pd->busyq);
+ INIT_LIST_HEAD(&pd->fullq);
+ init_waitqueue_head(&pd->wait);
+ spin_lock_init(&pd->lock);
+ if (video_register_device_index(dev, VFL_TYPE_GRABBER, link->minor,
+ index)) {
+ printk(DRV_ERR "video_register_device failed!\n");
+ res = -ENODEV;
+ goto err_videor;
+ }
+ s6dp_reset_port(dev);
+ *devs = dev;
+ return 0;
+
+err_videor:
+ kfree(pd);
+err_allocp:
+ video_device_release(dev);
+err_allocd:
+ return res;
+}
+
+static int __devinit s6dp_probe(struct platform_device *pdev)
+{
+ int i, ret, irq;
+ unsigned in_use;
+ struct video_device **devs;
+ struct s6dp_link *links;
+ void __iomem *dpbase, *dmacbase;
+ struct resource *res, *regs, *dmac;
+ if (!pdev->dev.platform_data) {
+ printk(DRV_ERR "no platform data given\n");
+ return -EINVAL;
+ }
+ devs = kzalloc(DP_NB_PORTS * sizeof(*devs), GFP_KERNEL);
+ if (!devs) {
+ printk(DRV_ERR "video device alloc failed.\n");
+ return -ENOMEM;
+ }
+ irq = platform_get_irq(pdev, 0);
+ ret = request_irq(irq, &s6dp_interrupt, 0, DRV_NAME, devs);
+ if (ret) {
+ printk(DRV_ERR "irq request failed: %d\n", irq);
+ goto err_free_mem;
+ }
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -EINVAL;
+ goto err_free_irq;
+ }
+ regs = request_mem_region(res->start, res->end - res->start + 1,
+ pdev->name);
+ if (!res) {
+ ret = -EBUSY;
+ goto err_free_irq;
+ }
+ dpbase = ioremap_nocache(regs->start, regs->end - regs->start + 1);
+ if (!dpbase) {
+ ret = -ENOMEM;
+ goto err_free_regs;
+ }
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!res) {
+ ret = -EINVAL;
+ goto err_unmap_regs;
+ }
+ dmac = request_mem_region(res->start, res->end - res->start + 1,
+ pdev->name);
+ if (!dmac) {
+ ret = -EBUSY;
+ goto err_unmap_regs;
+ }
+ dmacbase = ioremap_nocache(dmac->start, dmac->end - dmac->start + 1);
+ if (!dmacbase) {
+ ret = -ENOMEM;
+ goto err_free_dmac;
+ }
+ i = 0;
+ in_use = 0;
+ for (links = pdev->dev.platform_data; links->port_mask; links++) {
+ if (in_use & links->port_mask) {
+ printk(DRV_ERR "port already in use - skipping\n");
+ continue;
+ }
+ ret = probe_one(pdev, irq, &devs[i], links, dpbase, dmacbase,
+ regs->start);
+ if (ret)
+ goto err_free_devs;
+ in_use |= links->port_mask;
+ i++;
+ }
+ platform_set_drvdata(pdev, devs);
+ return 0;
+
+err_free_devs:
+ while (i--) {
+ if (devs[i]) {
+ struct s6dp *pd = video_get_drvdata(devs[i]);
+ video_unregister_device(devs[i]);
+ kfree(pd);
+ video_device_release(devs[i]);
+ }
+ }
+ iounmap(dmacbase);
+err_free_dmac:
+ release_mem_region(dmac->start, dmac->end - dmac->start + 1);
+err_unmap_regs:
+ iounmap(dpbase);
+err_free_regs:
+ release_mem_region(regs->start, regs->end - regs->start + 1);
+err_free_irq:
+ free_irq(irq, devs);
+err_free_mem:
+ kfree(devs);
+ return ret;
+}
+
+static int __devexit s6dp_remove(struct platform_device *pdev)
+{
+ struct video_device **devs = platform_get_drvdata(pdev);
+ int i;
+ platform_set_drvdata(pdev, NULL);
+ for (i = 0; i < DP_NB_PORTS; i++) {
+ struct video_device *dev = devs[i];
+ if (dev) {
+ struct s6dp *pd = video_get_drvdata(dev);
+ video_unregister_device(dev);
+ kfree(pd);
+ video_device_release(dev);
+ }
+ }
+ i = platform_get_irq(pdev, 0);
+ free_irq(i, devs);
+ kfree(devs);
+ return 0;
+}
+
+static struct platform_driver s6dp_driver = {
+ .probe = s6dp_probe,
+ .remove = __devexit_p(s6dp_remove),
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s6dp_init(void)
+{
+ printk(DRV_INFO "S6 video driver <info@emlix.com>\n");
+ return platform_driver_register(&s6dp_driver);
+}
+
+static void __exit s6dp_exit(void)
+{
+ platform_driver_unregister(&s6dp_driver);
+}
+
+module_init(s6dp_init);
+module_exit(s6dp_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("S6105 on chip video driver");
+MODULE_AUTHOR("Fabian Godehardt, Hannes Weiner, "
+ "Oskar Schirmer, Daniel Gloeckner");
diff --git a/drivers/media/video/s6dp/s6dp.h b/drivers/media/video/s6dp/s6dp.h
new file mode 100644
index 0000000..4f299b7
--- /dev/null
+++ b/drivers/media/video/s6dp/s6dp.h
@@ -0,0 +1,121 @@
+/*
+ * drivers/media/video/s6dp/s6dp.h
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2008 emlix GmbH <info@emlix.com>
+ * Authors: Fabian Godehardt <fg@emlix.com>
+ * Oskar Schirmer <os@emlix.com>
+ */
+
+#ifndef __ASM_XTENSA_S6105_DP_H
+#define __ASM_XTENSA_S6105_DP_H
+
+#define S6_DP_CHAN_PER_PORT 4
+
+/* global data port setup */
+#define S6_DP_INT_STATUS 0x00
+#define S6_DP_INT_LOWWMARK(p) (p)
+#define S6_DP_INT_PENDGCNT(p) ((p) + 4)
+#define S6_DP_INT_TERMCNT(p) ((p) + 8)
+#define S6_DP_INT_ERR_INT 12
+#define S6_DP_INT_ENABLE 0x04
+#define S6_DP_INT_DMAERR 16
+#define S6_DP_INT_UNDEROVERRUN(p) ((p) + 20)
+#define S6_DP_INT_WRONGPIXEL(p) ((p) * 2 + 24)
+#define S6_DP_INT_WRONGLINES(p) ((p) * 2 + 25)
+#define S6_DP_INT_RAW 0x08
+#define S6_DP_INT_CLEAR 0x0c
+#define S6_DP_INT_SET 0x10
+#define S6_DP_INT_UNMAP_RAW0 0x14
+#define S6_DP_INT_UNMAP_RAW1 0x18
+#define S6_DP_INT_UNMAP_RAW1_DP2_BT1120ERR 18
+#define S6_DP_INT_UNMAP_RAW1_DP0_BT1120ERR 19
+#define S6_DP_DP_CLK_SETTING 0x40
+#define S6_DP_DP_CLK_SETTING_CLK_MUX(p) ((p) * 4)
+#define S6_DP_DP_CLK_SETTING_CLK_MUX_MASK 3
+#define S6_DP_VIDEO_OUT_DLL_SEL 0x50
+#define S6_DP_VIDEO_REF_DLL_SEL 0x54
+#define S6_DP_VIDEO_FBK_DLL_SEL 0x58
+#define S6_DP_VIDEO_ENABLE 0x80
+#define S6_DP_VIDEO_ENABLE_ENABLE(p) ((p) * 8)
+#define S6_DP_VIDEO_DMA_CFG 0x84
+#define S6_DP_VIDEO_DMA_CFG_BURST_BITS(p) ((p) * 8)
+#define S6_DP_VIDEO_CFG(p) ((p) * 0x4 + 0x90)
+#define S6_DP_VIDEO_CFG_8_OR_10 0
+#define S6_DP_VIDEO_CFG_IN_OR_OUT 1
+#define S6_DP_VIDEO_CFG_FRAMING 2
+#define S6_DP_VIDEO_CFG_MODE 3
+#define S6_DP_VIDEO_CFG_MODE_422_SERIAL 0
+#define S6_DP_VIDEO_CFG_MODE_444_SERIAL 1
+#define S6_DP_VIDEO_CFG_MODE_422_PARALLEL 2
+#define S6_DP_VIDEO_CFG_MODE_444_PARALLEL 3
+#define S6_DP_VIDEO_CFG_MODE_422_SERIAL_CASCADE 4
+#define S6_DP_VIDEO_CFG_MODE_444_SERIAL_CASCADE 5
+#define S6_DP_VIDEO_CFG_MODE_422_PARALLEL_CASCADE 6
+#define S6_DP_VIDEO_CFG_MODE_RAW 7
+#define S6_DP_VIDEO_CFG_MODE_FIFO8 8
+#define S6_DP_VIDEO_CFG_MODE_FIFO16 9
+#define S6_DP_VIDEO_CFG_MODE_FIFO32 10
+#define S6_DP_VIDEO_CFG_MODE_STREAM8 11
+#define S6_DP_VIDEO_CFG_MODE_STREAM16 12
+#define S6_DP_VIDEO_CFG_MODE_STREAM32 13
+#define S6_DP_VIDEO_CFG_MODE_STREAM8_CASCADE 14
+#define S6_DP_VIDEO_CFG_MODE_STREAM16_CASCADE 15
+#define S6_DP_VIDEO_CFG_INTERL_OR_PROGR 8
+#define S6_DP_VIDEO_CFG_1120_VIDEO_MODE 9
+#define S6_DP_VIDEO_CFG_ANCILLARY_DATA 10
+#define S6_DP_VIDEO_CFG_VSYNC_POL 12
+#define S6_DP_VIDEO_CFG_HSYNC_POL 13
+#define S6_DP_VIDEO_CFG_BLANK_POL 14
+#define S6_DP_VIDEO_CFG_FIELD_CTRL 15
+#define S6_DP_VIDEO_CFG_BLANK_CTRL 16
+#define S6_DP_VIDEO_CFG_RELAX_MODE 21
+#define S6_DP_VIDEO_CFG_MICRON_MODE 22
+#define S6_DP_VIDEO_BLANK(p) ((p) * 0x4 + 0xa0)
+#define S6_DP_VIDEO_BAD_FRAME_NUM(p) ((p) * 0x4 + 0xc0)
+#define S6_DP_VIDEO_BAD_PIXEL_CNT(p) ((p) * 0x8 + 0xd0)
+#define S6_DP_VIDEO_BAD_LINE_CNT(p) ((p) * 0x8 + 0xd4)
+
+/* per port configuration registers */
+#define S6_DP_PIXEL_TOTAL 0x00
+#define S6_DP_PIXEL_ACTIVE 0x04
+#define S6_DP_PIXEL_OFFSET 0x08
+#define S6_DP_PIXEL_PADDING 0x0c
+#define S6_DP_ANC_PIXEL_ACTIVE 0x10
+#define S6_DP_ANC_PIXEL_OFFSET 0x14
+#define S6_DP_LINE_TOTAL 0x18
+#define S6_DP_LINE_ODD_TOTAL 0x1c
+#define S6_DP_LINE_ODD_ACTIVE 0x20
+#define S6_DP_LINE_ODD_OFFSET 0x24
+#define S6_DP_LINE_EVEN_ACTIVE 0x28
+#define S6_DP_LINE_EVEN_OFFSET 0x2c
+#define S6_DP_LINE_ODD_ANC_ACTIVE 0x30
+#define S6_DP_LINE_ODD_ANC_OFFSET 0x34
+#define S6_DP_LINE_EVEN_ANC_ACTIVE 0x38
+#define S6_DP_LINE_EVEN_ANC_OFFSET 0x3c
+#define S6_DP_ODD_VSYNC_LENGTH 0x40
+#define S6_DP_ODD_VSYNC_OFFSET 0x44
+#define S6_DP_EVEN_VSYNC_LENGTH 0x48
+#define S6_DP_EVEN_VSYNC_OFFSET 0x4c
+#define S6_DP_ODD_HSYNC_LENGTH 0x50
+#define S6_DP_ODD_HSYNC_OFFSET 0x54
+#define S6_DP_EVEN_HSYNC_LENGTH 0x58
+#define S6_DP_EVEN_HSYNC_OFFSET 0x5c
+
+#define S6_DP_FRAME_COUNT 0x60
+#define S6_DP_TSI_TIMESTAMP_UPDATE 0x64
+#define S6_DP_TSI_TIMESTAMP_HI 0x68
+#define S6_DP_TSI_TIMESTAMP_LO 0x6c
+#define S6_DP_CBCR_DMA_CONVERT 0x70
+#define S6_DP_Y_DMA_CONVERT 0x74
+#define S6_DP_ANC_DMA_CONVERT 0x78
+
+#define S6_DP_CFG_BASE(n) ((n) * 0x80 + 0x100)
+#define S6_DP_CHAN_OFFSET(n) ((n) * 0x100)
+#define S6_DP_DATARAM(port) ((port) * S6_DP_CHAN_PER_PORT * 0x100 \
+ + 0x1000)
+
+#endif /* __ASM_XTENSA_S6105_DP_H */
diff --git a/include/media/s6dp-link.h b/include/media/s6dp-link.h
new file mode 100644
index 0000000..d1197da
--- /dev/null
+++ b/include/media/s6dp-link.h
@@ -0,0 +1,63 @@
+#ifndef __S6DP_LINK_H__
+#define __S6DP_LINK_H__
+
+#include <linux/videodev2.h>
+
+struct s6dp_mode {
+ unsigned int type:4;
+ unsigned int progressive:1;
+ unsigned int embedded_sync:1;
+ unsigned int micron_mode:1;
+ unsigned int vsync_pol:1;
+ unsigned int hsync_pol:1;
+ unsigned int blank_pol:1;
+ unsigned int field_ctrl:1;
+ unsigned int blank_ctrl:1;
+ unsigned int relaxed_framing:1;
+ unsigned int ten_bit:1;
+ unsigned int line_and_crc:1;
+ u16 pixel_total;
+ u16 pixel_offset;
+ u16 pixel_active;
+ u16 pixel_padding;
+ u16 hsync_offset;
+ u16 hsync_len;
+ u16 framelines;
+ u16 odd_vsync_offset;
+ u16 odd_vsync_len;
+ u16 odd_first;
+ u16 odd_active;
+ u16 odd_total;
+ u16 even_vsync_offset;
+ u16 even_vsync_len;
+ u16 even_first;
+ u16 even_active;
+};
+
+struct s6dp_link {
+ void *context;
+ unsigned port_mask:4;
+ unsigned is_egress:1;
+ int minor;
+ void (*g_mode)(void *ctx, struct s6dp_mode *mode);
+ int (*cropcap)(void *ctx, struct v4l2_cropcap *cap);
+ int (*s_crop)(void *ctx, struct v4l2_crop *crop, int busy);
+ int (*g_crop)(void *ctx, struct v4l2_crop *crop);
+ int (*e_std)(void *ctx, struct v4l2_standard *std);
+ int (*s_std)(void *ctx, v4l2_std_id *mask, int busy);
+ int (*s_fmt)(void *ctx, int try_fmt, struct v4l2_pix_format *fmt,
+ int busy);
+ int (*g_fmt)(void *ctx, struct v4l2_pix_format *fmt);
+ union {
+ struct {
+ int (*e_inp)(void *ctx, struct v4l2_input *inp);
+ int (*s_inp)(void *ctx, unsigned int nr, int busy);
+ } ingress;
+ struct {
+ int (*e_outp)(void *ctx, struct v4l2_output *outp);
+ int (*s_outp)(void *ctx, unsigned int nr, int busy);
+ } egress;
+ } dir;
+};
+
+#endif
--
1.6.2.107.ge47ee
^ permalink raw reply related [flat|nested] 8+ messages in thread* [patch 5/5] saa7121 driver for s6000 data port
2009-03-26 14:36 [patch 1/5] s6000 data port driver Daniel Glöckner
@ 2009-03-26 14:36 ` Daniel Glöckner
0 siblings, 0 replies; 8+ messages in thread
From: Daniel Glöckner @ 2009-03-26 14:36 UTC (permalink / raw)
To: Mauro Carvalho Chehab; +Cc: Chris Zankel, linux-media, Daniel Glöckner
This patch adds a driver to support the saa7121 PAL/NTSC video encoder
in combination with the s6000 data port driver.
The chip is configured for embedded BT.656 syncs as this mode should
be supported on all devices.
The driver presents two outputs to applications and while it is true
that the device has these two outputs, both of them are always active.
The only difference on the "Y/C" output is that it disables the luma
notch filter.
Signed-off-by: Daniel Glöckner <dg@emlix.com>
---
drivers/media/video/s6dp/Kconfig | 7 +
drivers/media/video/s6dp/Makefile | 1 +
drivers/media/video/s6dp/s6dp-saa7121.c | 478 +++++++++++++++++++++++++++++++
3 files changed, 486 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/s6dp/s6dp-saa7121.c
diff --git a/drivers/media/video/s6dp/Kconfig b/drivers/media/video/s6dp/Kconfig
index 853e6b1..c95904c 100644
--- a/drivers/media/video/s6dp/Kconfig
+++ b/drivers/media/video/s6dp/Kconfig
@@ -20,3 +20,10 @@ config VIDEO_S6DP_MT9D131
default n
help
Enables the MT9D131 camera driver.
+
+config VIDEO_S6DP_SAA7121
+ tristate "SAA7121 video encoder"
+ depends on VIDEO_S6000
+ default n
+ help
+ Enables the SAA7121 video encoder driver.
diff --git a/drivers/media/video/s6dp/Makefile b/drivers/media/video/s6dp/Makefile
index af0bc0f..61d86c9 100644
--- a/drivers/media/video/s6dp/Makefile
+++ b/drivers/media/video/s6dp/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_VIDEO_S6000) += s6dp.o
obj-$(CONFIG_VIDEO_S6DP_MT9D131) += s6dp-mt9d131.o
+obj-$(CONFIG_VIDEO_S6DP_SAA7121) += s6dp-saa7121.o
diff --git a/drivers/media/video/s6dp/s6dp-saa7121.c b/drivers/media/video/s6dp/s6dp-saa7121.c
new file mode 100644
index 0000000..70799cd
--- /dev/null
+++ b/drivers/media/video/s6dp/s6dp-saa7121.c
@@ -0,0 +1,478 @@
+/*
+ * drivers/media/video/s6dp/s6dp-saa7121.c
+ *
+ * Description: Driver for SAA7121 chips hooked up to a S6000 family data port
+ * (c) 2009 emlix GmbH <info@emlix.com>
+ *
+ * Author: Daniel Gloeckner <dg@emlix.com>
+ */
+
+#include <media/s6dp-link.h>
+#include "s6dp.h"
+#include <linux/i2c.h>
+
+static const u8 initial_setup[][2] = {
+ {0x3a, 0x13}
+};
+
+static const u8 pal_values[][2] = {
+ {0x28, 33}, {0x29, 29}, {0x5a, 0x00}, {0x5b, 125},
+ {0x5c, 175}, {0x5d, 35}, {0x5e, 53}, {0x5f, 0x40+53},
+ {0x61, 0x06}, {0x62, 47}, {0x63, 0xcb}, {0x64, 0x8a},
+ {0x65, 0x09}, {0x66, 0x2a}, {0x6c, 0x05}, {0x6d, 0x20},
+ {0x6e, 0xa0}
+};
+
+static const u8 pal_nc_values[][2] = {
+ {0x28, 33}, {0x29, 37}, {0x5a, 0x00}, {0x5b, 125},
+ {0x5c, 175}, {0x5d, 35}, {0x5e, 53}, {0x5f, 0xc0+53},
+ {0x61, 0x06}, {0x62, 47}, {0x63, 0x46}, {0x64, 0x94},
+ {0x65, 0xf6}, {0x66, 0x21}, {0x6c, 0x05}, {0x6d, 0x20},
+ {0x6e, 0xa0}
+};
+
+static const u8 pal_m_values[][2] = {
+ {0x28, 25}, {0x29, 29}, {0x5a, 0x00}, {0x5b, 118},
+ {0x5c, 165}, {0x5d, 45}, {0x5e, 49}, {0x5f, 0xc0+59},
+ {0x61, 0x17}, {0x62, 45}, {0x63, 0xe3}, {0x64, 0xef},
+ {0x65, 0xe6}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
+ {0x6e, 0xa0}
+};
+
+static const u8 ntsc_values[][2] = {
+ {0x28, 25}, {0x29, 29}, {0x5a, 0x88}, {0x5b, 118},
+ {0x5c, 165}, {0x5d, 42}, {0x5e, 46}, {0x5f, 0xc0+46},
+ {0x61, 0x15}, {0x62, 63}, {0x63, 0x1f}, {0x64, 0x7c},
+ {0x65, 0xf0}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
+ {0x6e, 0x80}
+};
+
+static const u8 ntsc_jp_values[][2] = {
+ {0x28, 25}, {0x29, 29}, {0x5a, 0x88}, {0x5b, 118},
+ {0x5c, 165}, {0x5d, 19}, {0x5e, 46}, {0x5f, 0xc0+46},
+ {0x61, 0x05}, {0x62, 62}, {0x63, 0x1f}, {0x64, 0x7c},
+ {0x65, 0xf0}, {0x66, 0x21}, {0x6c, 0xf9}, {0x6d, 0x00},
+ {0x6e, 0x80}
+};
+
+struct saa7121 {
+ int std;
+ int yc;
+ struct v4l2_pix_format fmt;
+ struct v4l2_rect crop;
+ u8 regs[128];
+};
+
+static int saa7121_write_regs(struct i2c_client *client)
+{
+ struct saa7121 *me = i2c_get_clientdata(client);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(me->regs); i++) {
+ int ret = i2c_smbus_write_byte_data(client, i, me->regs[i]);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static void saa7121_change_regs(struct saa7121 *me, const u8 (*regs)[2],
+ int num)
+{
+ int i;
+ for (i = 0; i < num; i++)
+ me->regs[regs[i][0]] = regs[i][1];
+}
+
+static const struct {
+ v4l2_std_id mask;
+ const char *name;
+ const u8 (*regs)[2];
+ int num;
+} standards[] = {
+ {
+ V4L2_STD_PAL | V4L2_STD_PAL_N,
+ "PAL",
+ pal_values,
+ ARRAY_SIZE(pal_values)
+ },
+ {
+ V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR,
+ "NTSC",
+ ntsc_values,
+ ARRAY_SIZE(ntsc_values)
+ },
+ {
+ V4L2_STD_NTSC_M_JP,
+ "NTSC-JP",
+ ntsc_jp_values,
+ ARRAY_SIZE(ntsc_jp_values)
+ },
+ {
+ V4L2_STD_PAL_M,
+ "PAL-M",
+ pal_m_values,
+ ARRAY_SIZE(pal_nc_values)
+ },
+ {
+ V4L2_STD_PAL_Nc,
+ "PAL-Nc",
+ pal_nc_values,
+ ARRAY_SIZE(pal_nc_values)
+ },
+};
+
+static int saa7121_e_std(void *ctx, struct v4l2_standard *std)
+{
+ if (std->index >= ARRAY_SIZE(standards))
+ return -EINVAL;
+ std->id = standards[std->index].mask;
+ strcpy(std->name, standards[std->index].name);
+ if (std->id & V4L2_STD_625_50) {
+ std->frameperiod.numerator = 1;
+ std->frameperiod.denominator = 25;
+ std->framelines = 625;
+ } else {
+ std->frameperiod.numerator = 1001;
+ std->frameperiod.denominator = 30000;
+ std->framelines = 525;
+ }
+ return 0;
+}
+
+static int saa7121_cropcap(void *ctx, struct v4l2_cropcap *cap)
+{
+ struct i2c_client *client = ctx;
+ struct saa7121 *me = i2c_get_clientdata(client);
+
+ if (standards[me->std].mask & V4L2_STD_625_50) {
+ cap->bounds.top = 23 * 2;
+ cap->bounds.left = 132;
+ cap->bounds.width = 720;
+ cap->bounds.height = 576;
+ cap->defrect.left = 140;
+ cap->defrect.top = 23 * 2;
+ cap->defrect.width = 702;
+ cap->defrect.height = 576;
+ cap->pixelaspect.numerator = 54;
+ cap->pixelaspect.denominator = 59;
+ } else {
+ cap->bounds.top = 20 * 2;
+ cap->bounds.left = 122;
+ cap->bounds.width = 720;
+ cap->bounds.height = 487;
+ cap->defrect.left = 130;
+ cap->defrect.top = 22 * 2;
+ cap->defrect.width = 704;
+ cap->defrect.height = 480;
+ cap->pixelaspect.numerator = 11;
+ cap->pixelaspect.denominator = 10;
+ }
+ return 0;
+}
+
+static int saa7121_s_crop(void *ctx, struct v4l2_crop *crop, int busy)
+{
+ struct i2c_client *client = ctx;
+ struct saa7121 *me = i2c_get_clientdata(client);
+ struct v4l2_cropcap cap;
+
+ if (busy)
+ return -EBUSY;
+
+ saa7121_cropcap(ctx, &cap);
+ me->crop = crop->c;
+
+ if (me->crop.width > cap.bounds.width)
+ me->crop.width = cap.bounds.width;
+ me->crop.left += me->crop.width & 1;
+ me->crop.width &= ~1;
+ if (me->crop.height > cap.bounds.height)
+ me->crop.height = cap.bounds.height;
+ if (me->crop.left < cap.bounds.left)
+ me->crop.left = cap.bounds.left;
+ if (me->crop.left > cap.bounds.left + cap.bounds.width - me->crop.width)
+ me->crop.left = cap.bounds.left + cap.bounds.width
+ - me->crop.width;
+ me->crop.left &= ~1;
+ if (me->crop.top < cap.bounds.top)
+ me->crop.top = cap.bounds.top;
+ if (me->crop.top > cap.bounds.top + cap.bounds.height - me->crop.height)
+ me->crop.top = cap.bounds.top + cap.bounds.height
+ - me->crop.height;
+ me->crop.top &= ~1;
+ me->fmt.width = me->crop.width;
+ me->fmt.height = me->crop.height;
+ return 0;
+}
+
+static int saa7121_g_crop(void *ctx, struct v4l2_crop *crop)
+{
+ struct i2c_client *client = ctx;
+ struct saa7121 *me = i2c_get_clientdata(client);
+
+ crop->c = me->crop;
+ return 0;
+}
+
+static int saa7121_s_fmt(void *ctx, int try_fmt, struct v4l2_pix_format *fmt,
+ int busy)
+{
+ struct i2c_client *client = ctx;
+ struct saa7121 *me = i2c_get_clientdata(client);
+ struct v4l2_cropcap cap;
+
+ if (!try_fmt && busy)
+ return -EBUSY;
+
+ saa7121_cropcap(ctx, &cap);
+ fmt->pixelformat = V4L2_PIX_FMT_UYVY;
+ fmt->field = V4L2_FIELD_ALTERNATE;
+
+ if (standards[me->std].mask & V4L2_STD_625_50)
+ fmt->colorspace = V4L2_COLORSPACE_470_SYSTEM_BG;
+ else if (standards[me->std].mask == V4L2_STD_PAL_M)
+ fmt->colorspace = V4L2_COLORSPACE_470_SYSTEM_M;
+ else
+ fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ fmt->width &= ~1;
+ if (fmt->width > cap.bounds.width)
+ fmt->width = cap.bounds.width;
+ if (fmt->height > cap.bounds.height)
+ fmt->height = cap.bounds.height;
+
+ if (!try_fmt) {
+ me->fmt = *fmt;
+ me->crop.width = fmt->width;
+ me->crop.height = fmt->height;
+ if (me->crop.left > cap.bounds.left + cap.bounds.width
+ - fmt->width) {
+ me->crop.left = cap.bounds.left + cap.bounds.width
+ - fmt->width;
+ me->crop.left &= ~1;
+ }
+ if (me->crop.top > cap.bounds.top + cap.bounds.height
+ - fmt->height) {
+ me->crop.top = cap.bounds.top + cap.bounds.height
+ - fmt->height;
+ me->crop.top &= ~1;
+ }
+ }
+ return 0;
+}
+
+static int saa7121_g_fmt(void *ctx, struct v4l2_pix_format *fmt)
+{
+ struct i2c_client *client = ctx;
+ struct saa7121 *me = i2c_get_clientdata(client);
+ *fmt = me->fmt;
+ return 0;
+}
+
+static void saa7121_g_mode(void *ctx, struct s6dp_mode *mode)
+{
+ struct i2c_client *client = ctx;
+ struct saa7121 *me = i2c_get_clientdata(client);
+ struct v4l2_cropcap cap;
+
+ saa7121_cropcap(ctx, &cap);
+ mode->type = S6_DP_VIDEO_CFG_MODE_422_SERIAL;
+ mode->progressive = 0;
+ mode->embedded_sync = 1;
+ mode->relaxed_framing = 0;
+ mode->ten_bit = 0;
+ mode->line_and_crc = 0;
+ mode->micron_mode = 0;
+ if (standards[me->std].mask & V4L2_STD_625_50) {
+ mode->pixel_total = 864;
+ mode->framelines = 625;
+ mode->odd_vsync_offset = 623;
+ mode->odd_vsync_len = 24;
+ mode->odd_first = 22;
+ mode->odd_total = 312;
+ mode->even_vsync_offset = 310;
+ mode->even_vsync_len = 25;
+ mode->even_first = 335;
+ mode->hsync_offset = 0;
+ mode->hsync_len = 4;
+ } else {
+ mode->pixel_total = 858;
+ mode->framelines = 525;
+ mode->odd_vsync_offset = 522;
+ mode->odd_vsync_len = 19;
+ mode->odd_first = 16;
+ mode->odd_total = 262;
+ mode->even_vsync_offset = 260;
+ mode->even_vsync_len = 19;
+ mode->even_first = 279;
+ mode->hsync_offset = 0;
+ mode->hsync_len = 4;
+ }
+ mode->even_active = me->crop.height / 2;
+ mode->odd_active = me->crop.height - mode->even_active;
+ mode->odd_first += (me->crop.top - cap.bounds.top) / 2;
+ mode->even_first += (me->crop.top - cap.bounds.top) / 2;
+ mode->pixel_active = me->crop.width;
+ mode->pixel_offset = me->crop.left - cap.bounds.left;
+ mode->pixel_padding = 720 - mode->pixel_active - mode->pixel_offset;
+}
+
+static void saa7121_reconfigure(struct i2c_client *client)
+{
+ struct saa7121 *me = i2c_get_clientdata(client);
+
+ saa7121_change_regs(me, standards[me->std].regs,
+ standards[me->std].num);
+ if (me->yc)
+ me->regs[0x5f] &= 0x3f;
+
+ saa7121_write_regs(client);
+}
+
+static int saa7121_s_std(void *ctx, v4l2_std_id *mask, int busy)
+{
+ struct i2c_client *client = ctx;
+ struct saa7121 *me = i2c_get_clientdata(client);
+ int i;
+
+ if (busy && !(standards[me->std].mask & V4L2_STD_625_50)
+ == !(*mask & V4L2_STD_625_50))
+ return -EBUSY;
+
+ for (i = 0; i < ARRAY_SIZE(standards); i++) {
+ if (standards[i].mask & *mask) {
+ me->std = i;
+ saa7121_reconfigure(client);
+ *mask = standards[i].mask;
+ saa7121_s_fmt(ctx, 0, &me->fmt, 0);
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int saa7121_e_outp(void *ctx, struct v4l2_output *outp)
+{
+ int i;
+ if (outp->index > 1)
+ return -EINVAL;
+
+ outp->type = V4L2_OUTPUT_TYPE_ANALOG;
+ for (i = 0; i < ARRAY_SIZE(standards); i++)
+ outp->std |= standards[i].mask;
+
+ strcpy(outp->name, outp->index ? "Y/C" : "CVBS");
+ return 0;
+}
+
+static int saa7121_s_outp(void *ctx, unsigned int nr, int busy)
+{
+ struct i2c_client *client = ctx;
+ struct saa7121 *me = i2c_get_clientdata(client);
+
+ if (nr > 1)
+ return -EINVAL;
+
+ /*
+ * both outputs are always active
+ * we just disable the cross color filter for Y/C
+ */
+ me->yc = nr;
+ saa7121_reconfigure(client);
+ return 0;
+}
+
+static int saa7121_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct saa7121 *me;
+ struct s6dp_link *link;
+ s32 val;
+
+ if (!client->dev.platform_data)
+ return -EINVAL;
+ link = client->dev.platform_data;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE
+ | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
+ return -ENODEV;
+
+ val = i2c_smbus_read_byte(client);
+ if (val < 0) {
+ printk(KERN_ERR "saa7121: can't read status byte\n");
+ return -EIO;
+ }
+ if ((val & 0xE0) != 0x20) {
+ printk(KERN_ERR "saa7121: unsupported chip version %i"
+ " (status = 0x%02x)\n", val >> 5, val);
+ return -ENODEV;
+ }
+ me = kzalloc(sizeof(*me), GFP_KERNEL);
+ if (!me)
+ return -ENOMEM;
+ me->std = V4L2_STD_PAL;
+ i2c_set_clientdata(client, me);
+
+ saa7121_change_regs(me, initial_setup, ARRAY_SIZE(initial_setup));
+ saa7121_change_regs(me, pal_values, ARRAY_SIZE(pal_values));
+ if (saa7121_write_regs(client) < 0) {
+ printk(KERN_ERR "saa7121: can't write registers\n");
+ kfree(me);
+ return -EIO;
+ }
+
+ link->g_mode = saa7121_g_mode;
+ link->e_std = saa7121_e_std;
+ link->s_std = saa7121_s_std;
+ link->s_fmt = saa7121_s_fmt;
+ link->g_fmt = saa7121_g_fmt;
+ link->cropcap = saa7121_cropcap;
+ link->s_crop = saa7121_s_crop;
+ link->g_crop = saa7121_g_crop;
+ link->dir.egress.e_outp = saa7121_e_outp;
+ link->dir.egress.s_outp = saa7121_s_outp;
+ link->context = client;
+ printk(KERN_INFO "saa7121 probed successfully\n");
+ return 0;
+}
+
+static int saa7121_remove(struct i2c_client *client)
+{
+ struct saa7121_data *data;
+ data = i2c_get_clientdata(client);
+ i2c_set_clientdata(client, NULL);
+ kfree(data);
+ return 0;
+}
+
+static const struct i2c_device_id saa7121_id[] = {
+ { "saa7121", 0 },
+ { }
+};
+
+static struct i2c_driver saa7121_driver = {
+ .driver = {
+ .name = "s6dp-saa7121",
+ },
+ .probe = saa7121_probe,
+ .remove = saa7121_remove,
+ .id_table = saa7121_id,
+};
+
+static int __init saa7121_init(void)
+{
+ return i2c_add_driver(&saa7121_driver);
+}
+
+static void __exit saa7121_exit(void)
+{
+ i2c_del_driver(&saa7121_driver);
+}
+
+MODULE_AUTHOR("Daniel Gloeckner <dg@emlix.com>");
+MODULE_DESCRIPTION("SAA7121 driver");
+MODULE_LICENSE("GPL");
+
+module_init(saa7121_init);
+module_exit(saa7121_exit);
--
1.6.2.107.ge47ee
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2009-03-30 13:42 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-03-26 15:08 [patch 5/5] saa7121 driver for s6000 data port Hans Verkuil
2009-03-30 9:56 ` Daniel Glöckner
2009-03-30 10:03 ` Hans Verkuil
2009-03-30 12:12 ` Daniel Glöckner
2009-03-30 12:50 ` Hans Verkuil
2009-03-30 13:36 ` Daniel Glöckner
2009-03-30 13:41 ` Hans Verkuil
-- strict thread matches above, loose matches on Subject: below --
2009-03-26 14:36 [patch 1/5] s6000 data port driver Daniel Glöckner
2009-03-26 14:36 ` [patch 5/5] saa7121 driver for s6000 data port Daniel Glöckner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox