567 lines
14 KiB
C
567 lines
14 KiB
C
/*
|
|
* Copyright (C) 2010 Texas Instruments Inc
|
|
*
|
|
* 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 version 2.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/videodev2.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <mach/hardware.h>
|
|
#include <mach/mux.h>
|
|
#include <mach/io.h>
|
|
#include <mach/i2c.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <media/davinci/vpbe_types.h>
|
|
#include <media/davinci/vpbe_venc.h>
|
|
#include <media/davinci/vpss.h>
|
|
#include <media/v4l2-device.h>
|
|
|
|
#include "vpbe_venc_regs.h"
|
|
|
|
#define MODULE_NAME VPBE_VENC_SUBDEV_NAME
|
|
|
|
static int debug = 2;
|
|
module_param(debug, int, 0644);
|
|
MODULE_PARM_DESC(debug, "Debug level 0-2");
|
|
|
|
struct venc_state {
|
|
struct v4l2_subdev sd;
|
|
struct venc_callback *callback;
|
|
struct venc_platform_data *pdata;
|
|
struct device *pdev;
|
|
u32 output;
|
|
v4l2_std_id std;
|
|
spinlock_t lock;
|
|
void __iomem *venc_base;
|
|
void __iomem *vdaccfg_reg;
|
|
};
|
|
|
|
static inline struct venc_state *to_state(struct v4l2_subdev *sd)
|
|
{
|
|
return container_of(sd, struct venc_state, sd);
|
|
}
|
|
|
|
static inline u32 venc_read(struct v4l2_subdev *sd, u32 offset)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
|
|
return readl(venc->venc_base + offset);
|
|
}
|
|
|
|
static inline u32 venc_write(struct v4l2_subdev *sd, u32 offset, u32 val)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
|
|
writel(val, (venc->venc_base + offset));
|
|
|
|
return val;
|
|
}
|
|
|
|
static inline u32 venc_modify(struct v4l2_subdev *sd, u32 offset,
|
|
u32 val, u32 mask)
|
|
{
|
|
u32 new_val = (venc_read(sd, offset) & ~mask) | (val & mask);
|
|
|
|
venc_write(sd, offset, new_val);
|
|
|
|
return new_val;
|
|
}
|
|
|
|
static inline u32 vdaccfg_write(struct v4l2_subdev *sd, u32 val)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
|
|
writel(val, venc->vdaccfg_reg);
|
|
|
|
val = readl(venc->vdaccfg_reg);
|
|
|
|
return val;
|
|
}
|
|
|
|
/* This function sets the dac of the VPBE for various outputs
|
|
*/
|
|
static int venc_set_dac(struct v4l2_subdev *sd, u32 out_index)
|
|
{
|
|
switch (out_index) {
|
|
case 0:
|
|
v4l2_dbg(debug, 1, sd, "Setting output to Composite\n");
|
|
venc_write(sd, VENC_DACSEL, 0);
|
|
break;
|
|
case 1:
|
|
v4l2_dbg(debug, 1, sd, "Setting output to S-Video\n");
|
|
venc_write(sd, VENC_DACSEL, 0x210);
|
|
break;
|
|
case 2:
|
|
venc_write(sd, VENC_DACSEL, 0x543);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void venc_enabledigitaloutput(struct v4l2_subdev *sd, int benable)
|
|
{
|
|
v4l2_dbg(debug, 2, sd, "venc_enabledigitaloutput\n");
|
|
|
|
if (benable) {
|
|
venc_write(sd, VENC_VMOD, 0);
|
|
venc_write(sd, VENC_CVBS, 0);
|
|
venc_write(sd, VENC_LCDOUT, 0);
|
|
venc_write(sd, VENC_HSPLS, 0);
|
|
venc_write(sd, VENC_HSTART, 0);
|
|
venc_write(sd, VENC_HVALID, 0);
|
|
venc_write(sd, VENC_HINT, 0);
|
|
venc_write(sd, VENC_VSPLS, 0);
|
|
venc_write(sd, VENC_VSTART, 0);
|
|
venc_write(sd, VENC_VVALID, 0);
|
|
venc_write(sd, VENC_VINT, 0);
|
|
venc_write(sd, VENC_YCCCTL, 0);
|
|
venc_write(sd, VENC_DACSEL, 0);
|
|
|
|
} else {
|
|
venc_write(sd, VENC_VMOD, 0);
|
|
/* disable VCLK output pin enable */
|
|
venc_write(sd, VENC_VIDCTL, 0x141);
|
|
|
|
/* Disable output sync pins */
|
|
venc_write(sd, VENC_SYNCCTL, 0);
|
|
|
|
/* Disable DCLOCK */
|
|
venc_write(sd, VENC_DCLKCTL, 0);
|
|
venc_write(sd, VENC_DRGBX1, 0x0000057C);
|
|
|
|
/* Disable LCD output control (accepting default polarity) */
|
|
venc_write(sd, VENC_LCDOUT, 0);
|
|
venc_write(sd, VENC_CMPNT, 0x100);
|
|
venc_write(sd, VENC_HSPLS, 0);
|
|
venc_write(sd, VENC_HINT, 0);
|
|
venc_write(sd, VENC_HSTART, 0);
|
|
venc_write(sd, VENC_HVALID, 0);
|
|
|
|
venc_write(sd, VENC_VSPLS, 0);
|
|
venc_write(sd, VENC_VINT, 0);
|
|
venc_write(sd, VENC_VSTART, 0);
|
|
venc_write(sd, VENC_VVALID, 0);
|
|
|
|
venc_write(sd, VENC_HSDLY, 0);
|
|
venc_write(sd, VENC_VSDLY, 0);
|
|
|
|
venc_write(sd, VENC_YCCCTL, 0);
|
|
venc_write(sd, VENC_VSTARTA, 0);
|
|
|
|
/* Set OSD clock and OSD Sync Adavance registers */
|
|
venc_write(sd, VENC_OSDCLK0, 1);
|
|
venc_write(sd, VENC_OSDCLK1, 2);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* setting NTSC mode
|
|
*/
|
|
static int venc_set_ntsc(struct v4l2_subdev *sd)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
struct venc_platform_data *pdata = venc->pdata;
|
|
|
|
v4l2_dbg(debug, 2, sd, "venc_set_ntsc\n");
|
|
|
|
/* Setup clock at VPSS & VENC for SD */
|
|
vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1);
|
|
if (pdata->setup_clock(VPBE_ENC_STD, V4L2_STD_525_60) < 0)
|
|
return -EINVAL;
|
|
|
|
venc_enabledigitaloutput(sd, 0);
|
|
|
|
/* to set VENC CLK DIV to 1 - final clock is 54 MHz */
|
|
venc_modify(sd, VENC_VIDCTL, 0, 1 << 1);
|
|
/* Set REC656 Mode */
|
|
venc_write(sd, VENC_YCCCTL, 0x1);
|
|
venc_modify(sd, VENC_VDPRO, 0, VENC_VDPRO_DAFRQ);
|
|
venc_modify(sd, VENC_VDPRO, 0, VENC_VDPRO_DAUPS);
|
|
|
|
venc_write(sd, VENC_VMOD, 0);
|
|
venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT),
|
|
VENC_VMOD_VIE);
|
|
venc_modify(sd, VENC_VMOD, (0 << VENC_VMOD_VMD), VENC_VMOD_VMD);
|
|
venc_modify(sd, VENC_VMOD, (0 << VENC_VMOD_TVTYP_SHIFT),
|
|
VENC_VMOD_TVTYP);
|
|
venc_write(sd, VENC_DACTST, 0x0);
|
|
venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* setting PAL mode
|
|
*/
|
|
static int venc_set_pal(struct v4l2_subdev *sd)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
|
|
v4l2_dbg(debug, 2, sd, "venc_set_pal\n");
|
|
|
|
/* Setup clock at VPSS & VENC for SD */
|
|
vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1);
|
|
if (venc->pdata->setup_clock(VPBE_ENC_STD, V4L2_STD_625_50) < 0)
|
|
return -EINVAL;
|
|
|
|
venc_enabledigitaloutput(sd, 0);
|
|
|
|
/* to set VENC CLK DIV to 1 - final clock is 54 MHz */
|
|
venc_modify(sd, VENC_VIDCTL, 0, 1 << 1);
|
|
/* Set REC656 Mode */
|
|
venc_write(sd, VENC_YCCCTL, 0x1);
|
|
|
|
venc_modify(sd, VENC_SYNCCTL, 1 << VENC_SYNCCTL_OVD_SHIFT,
|
|
VENC_SYNCCTL_OVD);
|
|
venc_write(sd, VENC_VMOD, 0);
|
|
venc_modify(sd, VENC_VMOD,
|
|
(1 << VENC_VMOD_VIE_SHIFT),
|
|
VENC_VMOD_VIE);
|
|
venc_modify(sd, VENC_VMOD,
|
|
(0 << VENC_VMOD_VMD), VENC_VMOD_VMD);
|
|
venc_modify(sd, VENC_VMOD,
|
|
(1 << VENC_VMOD_TVTYP_SHIFT),
|
|
VENC_VMOD_TVTYP);
|
|
venc_write(sd, VENC_DACTST, 0x0);
|
|
venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* venc_set_480p59_94
|
|
*
|
|
* This function configures the video encoder to EDTV(525p) component setting.
|
|
*/
|
|
static int venc_set_480p59_94(struct v4l2_subdev *sd)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
struct venc_platform_data *pdata = venc->pdata;
|
|
|
|
v4l2_dbg(debug, 2, sd, "venc_set_480p59_94\n");
|
|
|
|
/* Setup clock at VPSS & VENC for SD */
|
|
if (pdata->setup_clock(VPBE_ENC_DV_PRESET, V4L2_DV_480P59_94) < 0)
|
|
return -EINVAL;
|
|
|
|
venc_enabledigitaloutput(sd, 0);
|
|
|
|
venc_write(sd, VENC_OSDCLK0, 0);
|
|
venc_write(sd, VENC_OSDCLK1, 1);
|
|
venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAFRQ,
|
|
VENC_VDPRO_DAFRQ);
|
|
venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAUPS,
|
|
VENC_VDPRO_DAUPS);
|
|
venc_write(sd, VENC_VMOD, 0);
|
|
venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT),
|
|
VENC_VMOD_VIE);
|
|
venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD);
|
|
venc_modify(sd, VENC_VMOD, (HDTV_525P << VENC_VMOD_TVTYP_SHIFT),
|
|
VENC_VMOD_TVTYP);
|
|
venc_modify(sd, VENC_VMOD, VENC_VMOD_VDMD_YCBCR8 <<
|
|
VENC_VMOD_VDMD_SHIFT, VENC_VMOD_VDMD);
|
|
|
|
venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* venc_set_625p
|
|
*
|
|
* This function configures the video encoder to HDTV(625p) component setting
|
|
*/
|
|
static int venc_set_576p50(struct v4l2_subdev *sd)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
struct venc_platform_data *pdata = venc->pdata;
|
|
|
|
v4l2_dbg(debug, 2, sd, "venc_set_576p50\n");
|
|
|
|
/* Setup clock at VPSS & VENC for SD */
|
|
if (pdata->setup_clock(VPBE_ENC_DV_PRESET, V4L2_DV_576P50) < 0)
|
|
return -EINVAL;
|
|
|
|
venc_enabledigitaloutput(sd, 0);
|
|
|
|
venc_write(sd, VENC_OSDCLK0, 0);
|
|
venc_write(sd, VENC_OSDCLK1, 1);
|
|
|
|
venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAFRQ,
|
|
VENC_VDPRO_DAFRQ);
|
|
venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAUPS,
|
|
VENC_VDPRO_DAUPS);
|
|
|
|
venc_write(sd, VENC_VMOD, 0);
|
|
venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT),
|
|
VENC_VMOD_VIE);
|
|
venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD);
|
|
venc_modify(sd, VENC_VMOD, (HDTV_625P << VENC_VMOD_TVTYP_SHIFT),
|
|
VENC_VMOD_TVTYP);
|
|
|
|
venc_modify(sd, VENC_VMOD, VENC_VMOD_VDMD_YCBCR8 <<
|
|
VENC_VMOD_VDMD_SHIFT, VENC_VMOD_VDMD);
|
|
venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int venc_s_std_output(struct v4l2_subdev *sd, v4l2_std_id norm)
|
|
{
|
|
v4l2_dbg(debug, 1, sd, "venc_s_std_output\n");
|
|
|
|
if (norm & V4L2_STD_525_60)
|
|
return venc_set_ntsc(sd);
|
|
else if (norm & V4L2_STD_625_50)
|
|
return venc_set_pal(sd);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int venc_s_dv_preset(struct v4l2_subdev *sd,
|
|
struct v4l2_dv_preset *dv_preset)
|
|
{
|
|
v4l2_dbg(debug, 1, sd, "venc_s_dv_preset\n");
|
|
|
|
if (dv_preset->preset == V4L2_DV_576P50)
|
|
return venc_set_576p50(sd);
|
|
else if (dv_preset->preset == V4L2_DV_480P59_94)
|
|
return venc_set_480p59_94(sd);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int venc_s_routing(struct v4l2_subdev *sd, u32 input, u32 output,
|
|
u32 config)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
int ret;
|
|
|
|
v4l2_dbg(debug, 1, sd, "venc_s_routing\n");
|
|
|
|
ret = venc_set_dac(sd, output);
|
|
if (!ret)
|
|
venc->output = output;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static long venc_ioctl(struct v4l2_subdev *sd,
|
|
unsigned int cmd,
|
|
void *arg)
|
|
{
|
|
u32 val;
|
|
|
|
switch (cmd) {
|
|
case VENC_GET_FLD:
|
|
val = venc_read(sd, VENC_VSTAT);
|
|
*((int *)arg) = ((val & VENC_VSTAT_FIDST) ==
|
|
VENC_VSTAT_FIDST);
|
|
break;
|
|
default:
|
|
v4l2_err(sd, "Wrong IOCTL cmd\n");
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_core_ops venc_core_ops = {
|
|
.ioctl = venc_ioctl,
|
|
};
|
|
|
|
static const struct v4l2_subdev_video_ops venc_video_ops = {
|
|
.s_routing = venc_s_routing,
|
|
.s_std_output = venc_s_std_output,
|
|
.s_dv_preset = venc_s_dv_preset,
|
|
};
|
|
|
|
static const struct v4l2_subdev_ops venc_ops = {
|
|
.core = &venc_core_ops,
|
|
.video = &venc_video_ops,
|
|
};
|
|
|
|
static int venc_initialize(struct v4l2_subdev *sd)
|
|
{
|
|
struct venc_state *venc = to_state(sd);
|
|
int ret;
|
|
|
|
/* Set default to output to composite and std to NTSC */
|
|
venc->output = 0;
|
|
venc->std = V4L2_STD_525_60;
|
|
|
|
ret = venc_s_routing(sd, 0, venc->output, 0);
|
|
if (ret < 0) {
|
|
v4l2_err(sd, "Error setting output during init\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = venc_s_std_output(sd, venc->std);
|
|
if (ret < 0) {
|
|
v4l2_err(sd, "Error setting std during init\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int venc_device_get(struct device *dev, void *data)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct venc_state **venc = data;
|
|
|
|
if (strcmp(MODULE_NAME, pdev->name) == 0)
|
|
*venc = platform_get_drvdata(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct v4l2_subdev *venc_sub_dev_init(struct v4l2_device *v4l2_dev,
|
|
const char *venc_name)
|
|
{
|
|
struct venc_state *venc;
|
|
int err;
|
|
|
|
err = bus_for_each_dev(&platform_bus_type, NULL, &venc,
|
|
venc_device_get);
|
|
if (venc == NULL)
|
|
return NULL;
|
|
|
|
v4l2_subdev_init(&venc->sd, &venc_ops);
|
|
|
|
strcpy(venc->sd.name, venc_name);
|
|
if (v4l2_device_register_subdev(v4l2_dev, &venc->sd) < 0) {
|
|
v4l2_err(v4l2_dev,
|
|
"vpbe unable to register venc sub device\n");
|
|
return NULL;
|
|
}
|
|
if (venc_initialize(&venc->sd)) {
|
|
v4l2_err(v4l2_dev,
|
|
"vpbe venc initialization failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
return &venc->sd;
|
|
}
|
|
EXPORT_SYMBOL(venc_sub_dev_init);
|
|
|
|
static int venc_probe(struct platform_device *pdev)
|
|
{
|
|
struct venc_state *venc;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
venc = kzalloc(sizeof(struct venc_state), GFP_KERNEL);
|
|
if (venc == NULL)
|
|
return -ENOMEM;
|
|
|
|
venc->pdev = &pdev->dev;
|
|
venc->pdata = pdev->dev.platform_data;
|
|
if (NULL == venc->pdata) {
|
|
dev_err(venc->pdev, "Unable to get platform data for"
|
|
" VENC sub device");
|
|
ret = -ENOENT;
|
|
goto free_mem;
|
|
}
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(venc->pdev,
|
|
"Unable to get VENC register address map\n");
|
|
ret = -ENODEV;
|
|
goto free_mem;
|
|
}
|
|
|
|
if (!request_mem_region(res->start, resource_size(res), "venc")) {
|
|
dev_err(venc->pdev, "Unable to reserve VENC MMIO region\n");
|
|
ret = -ENODEV;
|
|
goto free_mem;
|
|
}
|
|
|
|
venc->venc_base = ioremap_nocache(res->start, resource_size(res));
|
|
if (!venc->venc_base) {
|
|
dev_err(venc->pdev, "Unable to map VENC IO space\n");
|
|
ret = -ENODEV;
|
|
goto release_venc_mem_region;
|
|
}
|
|
|
|
spin_lock_init(&venc->lock);
|
|
platform_set_drvdata(pdev, venc);
|
|
dev_notice(venc->pdev, "VENC sub device probe success\n");
|
|
return 0;
|
|
|
|
release_venc_mem_region:
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
release_mem_region(res->start, resource_size(res));
|
|
free_mem:
|
|
kfree(venc);
|
|
return ret;
|
|
}
|
|
|
|
static int venc_remove(struct platform_device *pdev)
|
|
{
|
|
struct venc_state *venc = platform_get_drvdata(pdev);
|
|
struct resource *res;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
iounmap((void *)venc->venc_base);
|
|
release_mem_region(res->start, resource_size(res));
|
|
kfree(venc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver venc_driver = {
|
|
.probe = venc_probe,
|
|
.remove = venc_remove,
|
|
.driver = {
|
|
.name = MODULE_NAME,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int venc_init(void)
|
|
{
|
|
if (platform_driver_register(&venc_driver)) {
|
|
printk(KERN_ERR "Unable to register venc driver\n");
|
|
return -ENODEV;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void venc_exit(void)
|
|
{
|
|
platform_driver_unregister(&venc_driver);
|
|
return;
|
|
}
|
|
|
|
module_init(venc_init);
|
|
module_exit(venc_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("VPBE VENC Driver");
|
|
MODULE_AUTHOR("Texas Instruments");
|