blob: 981cce6a58150435495ee9a4fca4d52b6493077e [file] [log] [blame]
/* tw8834.c
*
* Driver for techwell tw8834
* It handles 2 inputs, composite and bt656 input
* It handles one output, which is bt656
*
* Author : Julien BERAUD <julien.beraud@parrot.com>
*
* Date : 23/04/2014
*
*/
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <linux/delay.h>
#include <media/tw8834.h>
#include <media/v4l2-chip-ident.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <linux/v4l2-dv-timings.h>
#include <media/v4l2-event.h>
MODULE_AUTHOR("Julien BERAUD <julien.beraud@parrot.com>");
MODULE_DESCRIPTION("kernel driver for tw8834");
MODULE_LICENSE("GPL");
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "debug level (0-2)");
enum {
TW8834_IN_PAD = 0,
TW8834_OUT_PAD,
TW8834_NUM_PADS,
};
enum {
TW8834_INPUT_CVBS = 0,
TW8834_INPUT_DTV,
TW8834_NUM_INPUTS,
};
static struct v4l2_dv_timings tw8834_ntsc_timings = V4L2_DV_BT_CEA_720X480I59_94;
static struct v4l2_dv_timings tw8834_pal_timings = V4L2_DV_BT_CEA_720X576I50;
#define TW8834_CVBS_WIDTH 720
#define TW8834_CVBS_PAL_HEIGHT 576
#define TW8834_CVBS_NTSC_HEIGHT 480
#define TW8834_IRQ 0x002
#define TW8834_IMASK 0x003
#define TW8834_STATUS 0x004
#define TW8834_STD 0x11C
#define TW8834_STDR 0x11D
#define TW8834_BT656_DEC_CTRL2 0x48
#define TW8834_CSTATUS 0x101
/* tw8834 data structure */
struct tw8834 {
struct v4l2_subdev sd;
struct i2c_client *i2c_client;
struct media_pad pad[TW8834_NUM_PADS];
struct v4l2_mbus_framefmt format[TW8834_NUM_PADS];
int input;
int is_streaming;
int (*set_power)(int on);
};
static inline struct tw8834 *to_tw8834(struct v4l2_subdev *sd)
{
return container_of(sd, struct tw8834, sd);
}
/* Register maps */
struct reg_cfg {
unsigned char reg;
unsigned char val;
};
static struct reg_cfg tw8834_common_cfg[] = {
{ 0xFF, 0x00 },
{ 0x08, 0xC6 },
{ 0x07, 0x08 },//Reserved bit in tw8834 doc but enable bt656 in tw8836 doc
{ 0x09, 0x05 },//VD input clock polarity control:negative + reserved
{ 0x41, 0xC0 },//Diff-dtv thresholds for DTV no use for cvbs
{ 0x51, 0x00 },//
{ 0x52, 0x02 },//
{ 0x53, 0x01 },//Diff->unnecessary diff, apply dtv config to both.
{ 0xDC, 0x00 },//pwm1 freq control
{ 0xDD, 0x80 },//pwm1 duty ctrl
{ 0xDE, 0x00 },//pw2 freq ctrl
{ 0xDF, 0x80 },//pw2 duty ctrl
{ 0xF9, 0x50 },//sspll freq
{ 0xFC, 0x30 },//sspll
{ 0xFF, 0x00 },//
{ 0xFF, 0x01 },//
{ 0x02, 0x48 },//Diff-> unapplyable to dtv cvbs input select
{ 0x05, 0x09 },//anti-aliasing enable
{ 0x08, 0x16 },//
{ 0x0A, 0x10 },//
{ 0x11, 0x64 },//
{ 0x17, 0x80 },//
{ 0x21, 0x22 },//
{ 0x2F, 0xE4 },//LCS enabled
{ 0x30, 0x00 },//undocumented
{ 0x3D, 0x40 },
{ 0xFF, 0x01 },
{ 0xFF, 0x02 },
{ 0x40, 0x11 },
{ 0x41, 0x0A },
{ 0x42, 0x05 },
{ 0x43, 0x01 },
{ 0x44, 0x64 },
{ 0x45, 0xF4 },
{ 0x47, 0x0A },
{ 0x4C, 0x00 },
{ 0x4D, 0x44 },
{ 0x80, 0x00 },
{ 0x8B, 0x44 },
{ 0xB0, 0x30 },
{ 0xFF, 0x02 },
};
static struct reg_cfg tw8834_cvbs_in_cfg[] = {
{ 0xff, 0x00 },
{ 0x06, 0x06 },//Diff
{ 0x67, 0x00 },//Diff source of bt656 output
{ 0x69, 0x02 },//Diff crop dtv
{ 0x6A, 0x20 },//Diff crop dtv
{ 0x6B, 0xF0 },//Diff crop dtv
{ 0x6C, 0x20 },//Diff crop dtv
{ 0x6D, 0xD0 },//Diff crop dtv
{ 0xFF, 0x01 },//
{ 0x0C, 0xDC },//black level is 7.5 IRE above the blank level
{ 0x10, 0xD0 },//Diff-brightness
{ 0x20, 0x30 },//Diff-clamping
{ 0x24, 0x60 },//Diff-clamping
{ 0x37, 0xBA },//Diff
{ 0x3A, 0x00 },//Diff
{ 0x3B, 0x0F },//Diff
{ 0xFF, 0x01 },//
{ 0xFF, 0x02 },//
{ 0x48, 0x36 },//Added (not present for dtv)
{ 0xFF, 0x02 },
};
static struct reg_cfg tw8834_dtv_in_cfg[] = {
{ 0xFF, 0x00 },//
{ 0x06, 0x26 },//Diff
{ 0x67, 0x02 },//Diff source of bt656 output
{ 0x69, 0x12 },//Diff crop dtv
{ 0x6A, 0x4F },//Diff crop dtv
{ 0x6B, 0xE0 },//Diff crop dtv
{ 0x6C, 0xB4 },//Diff crop dtv
{ 0x6D, 0x80 },//Diff crop dtv
{ 0xFF, 0x01 },//
{ 0x10, 0x00 },//Diff-brightness
{ 0x20, 0x50 },//Diff-clamping
{ 0x24, 0x3C },//Diff-clamping
{ 0x37, 0xB9 },//Diff
{ 0x3A, 0x88 },//Diff
{ 0x3B, 0xCF },//Diff
{ 0xFF, 0x01 },//
};
/* i2c transfer functions */
static s32 tw8834_i2c_read_byte(struct tw8834 *tw8834,
u8 command)
{
struct i2c_client *client = tw8834->i2c_client;
union i2c_smbus_data data;
if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_READ, command,
I2C_SMBUS_BYTE_DATA, &data))
return data.byte;
else
return -EIO;
}
static s32 tw8834_i2c_write_byte(struct tw8834 *tw8834,
u8 command,
u8 value)
{
struct i2c_client *client = tw8834->i2c_client;
union i2c_smbus_data data;
int err;
int i;
data.byte = value;
for (i = 0; i < 3; i++) {
err = i2c_smbus_xfer(client->adapter, client->addr,
client->flags,
I2C_SMBUS_WRITE, command,
I2C_SMBUS_BYTE_DATA, &data);
if (!err)
break;
}
if (err < 0)
v4l_err(client, "error writing %02x, %02x, %02x\n",
client->addr, command, value);
return err;
}
static int tw8834_write_reg_array(struct tw8834 *tw8834,
struct reg_cfg *array, int nb)
{
s32 ret;
int i;
struct v4l2_subdev *sd = &tw8834->sd;
for(i=0;i<nb;i++) {
ret = tw8834_i2c_write_byte(tw8834,
array[i].reg,
array[i].val);
if(ret < 0) {
v4l2_err(sd, "failed to write 0x%X to reg 0x%X\n",
array[i].val, array[i].reg);
return ret;
}
}
return 0;
}
static int tw8834_read_register(struct tw8834 *tw8834,
u16 reg)
{
int ret;
u8 page = (reg >> 8);
ret = tw8834_i2c_write_byte(tw8834, 0xFF, page);
if(ret < 0)
return ret;
return (tw8834_i2c_read_byte(tw8834, (u8)(reg & 0xFF)));
}
static int tw8834_write_register(struct tw8834 *tw8834,
u16 reg,
u8 value)
{
int ret;
u8 page = (reg >> 8);
ret = tw8834_i2c_write_byte(tw8834, 0xFF, page);
if(ret < 0)
return ret;
ret = tw8834_i2c_write_byte(tw8834, (u8)(reg & 0xFF), value);
return ret;
}
/* Helpers */
static struct v4l2_subdev *
tw8834_remote_subdev(struct tw8834 *tw8834, u32 *pad)
{
struct media_pad *remote;
remote = media_entity_remote_source(&tw8834->pad[TW8834_IN_PAD]);
if (remote == NULL ||
media_entity_type(remote->entity) != MEDIA_ENT_T_V4L2_SUBDEV)
return NULL;
if (pad)
*pad = remote->index;
return media_entity_to_v4l2_subdev(remote->entity);
}
/* video operations */
static int tw8834_s_routing(struct v4l2_subdev *sd, u32 in, u32 out, u32 config)
{
struct tw8834 *tw8834 = to_tw8834(sd);
if(!tw8834 || in >= TW8834_NUM_INPUTS)
return -EINVAL;
tw8834->input = in;
return 0;
}
static struct v4l2_mbus_framefmt *
__tw8834_get_fmt(struct tw8834 *tw8834,
struct v4l2_subdev_fh *fh,
unsigned int pad,
enum v4l2_subdev_format_whence which)
{
if(pad > TW8834_NUM_PADS)
return NULL;
if (which == V4L2_SUBDEV_FORMAT_TRY)
return v4l2_subdev_get_try_format(fh, pad);
else
return &tw8834->format[pad];
}
static int tw8834_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct tw8834 *tw8834 = to_tw8834(sd);
struct v4l2_mbus_framefmt *format;
format = __tw8834_get_fmt(tw8834, fh, fmt->pad, fmt->which);
if (format == NULL)
return -EINVAL;
fmt->format = *format;
return 0;
}
static int tw8834_set_in_fmt(struct tw8834 *tw8834,struct v4l2_subdev_fh *fh,
struct v4l2_mbus_framefmt *fmt)
{
struct v4l2_mbus_framefmt *in_format;
struct v4l2_mbus_framefmt *out_format;
out_format = __tw8834_get_fmt(tw8834, fh, TW8834_OUT_PAD, V4L2_SUBDEV_FORMAT_ACTIVE);
if(out_format == NULL)
return -EINVAL;
if(tw8834->input == TW8834_INPUT_CVBS) {
v4l2_err(&tw8834->sd, "Cannot set input pad format for cvbs\n");
return -EINVAL;
}
else if(tw8834->input == TW8834_INPUT_DTV) {
in_format = __tw8834_get_fmt(tw8834, fh, TW8834_IN_PAD, V4L2_SUBDEV_FORMAT_ACTIVE);
if (in_format == NULL)
return -EINVAL;
if(fmt->width == 640 && fmt->height == 480) {
/*Up to now, I have only been able to make it work in bt656
because bt601 is not functionnal with the register config given
by Intersil and I only had time to make VGA mode work, which is
the mode we are willing to use the input sensor in (ref:mt9v117) */
fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
}
else
return -EINVAL;
*in_format = *fmt;
*out_format = *fmt;
/*I have currently only been able to output 694 lines that have
to be cropped with top=1, height=494*/
out_format->height = 494;
}
return 0;
}
static int tw8834_set_out_fmt(struct tw8834 *tw8834,struct v4l2_subdev_fh *fh,
struct v4l2_mbus_framefmt *fmt)
{
struct v4l2_mbus_framefmt *out_format;
out_format = __tw8834_get_fmt(tw8834, fh, TW8834_OUT_PAD, V4L2_SUBDEV_FORMAT_ACTIVE);
if(out_format == NULL)
return -EINVAL;
if(tw8834->input == TW8834_INPUT_CVBS) {
if(fmt->width == TW8834_CVBS_WIDTH &&
fmt->height == TW8834_CVBS_PAL_HEIGHT) {
fmt->field = V4L2_FIELD_INTERLACED;
fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
}
else if(fmt->width == TW8834_CVBS_WIDTH &&
fmt->height == TW8834_CVBS_NTSC_HEIGHT) {
fmt->field = V4L2_FIELD_INTERLACED;
fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
}
else
return -EINVAL;
/*propagate to out pad */
*out_format = *fmt;
}
else if(tw8834->input == TW8834_INPUT_DTV) {
v4l2_err(&tw8834->sd, "cannot set out fmt for dtv\n");
return -EINVAL;
}
return 0;
}
static int tw8834_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct tw8834 *tw8834 = to_tw8834(sd);
int ret = 0;
if(fmt->pad == TW8834_IN_PAD)
ret = tw8834_set_in_fmt(tw8834, fh, &fmt->format);
else
ret = tw8834_set_out_fmt(tw8834, fh, &fmt->format);
return ret;
}
static int tw8834_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_mbus_code_enum *code)
{
struct tw8834 *tw8834 = to_tw8834(sd);
if (code->index)
return -EINVAL;
code->code = tw8834->format[code->pad].code;
return 0;
}
static int tw8834_enable_cvbs(struct tw8834 *tw8834)
{
tw8834_write_reg_array(tw8834,
tw8834_cvbs_in_cfg,
ARRAY_SIZE(tw8834_cvbs_in_cfg));
return 0;
}
static int tw8834_enable_dtv(struct v4l2_subdev *sd, struct tw8834 *tw8834)
{
struct v4l2_subdev *dtv_sd;
int ret = 0;
dtv_sd = tw8834_remote_subdev(tw8834, NULL);
if(!sd) {
v4l2_err(sd, "no remote subdev found\n");
return -ENODEV;
}
ret = v4l2_subdev_call(dtv_sd, video, s_stream, 1);
if(ret < 0) {
v4l2_err(sd, "cannot start streaming on dtv subdev\n");
return ret;
}
else {
tw8834_write_reg_array(tw8834,
tw8834_dtv_in_cfg,
ARRAY_SIZE(tw8834_dtv_in_cfg));
}
return ret;
}
static int tw8834_disable_dtv(struct v4l2_subdev *sd, struct tw8834 *tw8834)
{
struct v4l2_subdev *dtv_sd;
int ret = 0;
dtv_sd = tw8834_remote_subdev(tw8834, NULL);
if(!sd) {
v4l2_err(sd, "no remote subdev found\n");
return -ENODEV;
}
ret = v4l2_subdev_call(dtv_sd, video, s_stream, 0);
if(ret < 0) {
v4l2_err(sd, "cannot stop streaming on dtv subdev\n");
return ret;
}
return ret;
}
static int tw8834_s_stream(struct v4l2_subdev *sd, int enable)
{
struct tw8834 *tw8834 = to_tw8834(sd);
int ret = 0;
if(enable) {
if(tw8834->set_power)
tw8834->set_power(TW8834_POWER_INPUT_ENABLE);
if(tw8834->input == TW8834_INPUT_CVBS)
ret = tw8834_enable_cvbs(tw8834);
else
ret = tw8834_enable_dtv(sd, tw8834);
}
else
{
if(tw8834->set_power)
tw8834->set_power(TW8834_POWER_INPUT_DISABLE);
if(tw8834->input == TW8834_INPUT_DTV)
ret = tw8834_disable_dtv(sd, tw8834);
}
tw8834->is_streaming = !!enable;
return ret;
}
static int tw8834_query_dv_timings(struct v4l2_subdev *sd,
struct v4l2_dv_timings *timings)
{
struct tw8834 *tw8834 = to_tw8834(sd);
unsigned char status;
unsigned char freq_50_hz = 0;
int lock_timeout = 20;
while (--lock_timeout) {
status = tw8834_read_register(tw8834,
TW8834_CSTATUS);
if (status & 0x80) {
v4l2_info(sd, "Video loss\n");
return -ENOLINK;
}
else if (((status & 0x68) == 0x68)) {
v4l2_info(sd, "signal locked\n");
break;
}
else {
v4l2_info(sd, "wait for lock, status = 0x%x\n", status);
msleep(50);
}
}
if (!lock_timeout) {
v4l2_info(sd, "timeout waiting for lock\n");
return -ENOLINK;
}
freq_50_hz = (status & 0x1);
if (freq_50_hz) {
*timings = tw8834_pal_timings;
/*pclk doubled for 8 bits transmission of YUYV
so horizontal timngs are doubled*/
timings->bt.pixelclock = 2 * timings->bt.pixelclock;
timings->bt.hfrontporch = 2 * timings->bt.hfrontporch;
timings->bt.hsync = 2 * timings->bt.hsync;
timings->bt.hbackporch = 2 * timings->bt.hbackporch;
}
else {
*timings = tw8834_ntsc_timings;
/*pclk doubled for 8 bits transmission of YUYV
so horizontal timings are doubled*/
timings->bt.pixelclock = 2 * timings->bt.pixelclock;
timings->bt.hfrontporch = 2 * timings->bt.hfrontporch;
timings->bt.hsync = 2 * timings->bt.hsync;
timings->bt.hbackporch = 2 * timings->bt.hbackporch;
}
return 0;
}
int tw8834_subscribe_event(struct v4l2_subdev *subdev,
struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
return v4l2_src_change_event_subscribe(fh, sub);
}
int tw8834_unsubscribe_event(struct v4l2_subdev *subdev,
struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
return v4l2_event_unsubscribe(fh, sub);
}
static const struct v4l2_subdev_core_ops tw8834_core_ops = {
.subscribe_event = tw8834_subscribe_event,
.unsubscribe_event = tw8834_unsubscribe_event,
};
static const struct v4l2_subdev_video_ops tw8834_video_ops = {
.s_stream = tw8834_s_stream,
.query_dv_timings = tw8834_query_dv_timings,
.s_routing = tw8834_s_routing,
};
static const struct v4l2_subdev_pad_ops tw8834_pad_ops = {
.get_fmt = tw8834_get_fmt,
.set_fmt = tw8834_set_fmt,
.enum_mbus_code = tw8834_enum_mbus_code,
};
static const struct v4l2_subdev_ops tw8834_ops = {
.core = &tw8834_core_ops,
.video = &tw8834_video_ops,
.pad = &tw8834_pad_ops,
};
static irqreturn_t tw8834_isr(int irq, void *priv)
{
struct tw8834 *tw8834 = priv;
unsigned char status, tw8834_irq;
struct video_device *vdev = tw8834->sd.devnode;
static const struct v4l2_event event = {
.type = V4L2_EVENT_SOURCE_CHANGE,
.u.src_change.changes =
V4L2_EVENT_SRC_CH_RESOLUTION,
};
tw8834_irq = tw8834_read_register(tw8834, TW8834_IRQ);
status = tw8834_read_register(tw8834, TW8834_CSTATUS);
tw8834_write_register(tw8834, TW8834_IRQ, tw8834_irq);
v4l2_info(&tw8834->sd,
"read status = 0x%X irq = 0x%X\n", status, tw8834_irq);
if(vdev)
v4l2_event_queue(vdev, &event);
return IRQ_HANDLED;
}
static int tw8834_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tw8834 *tw8834;
struct tw8834_platform_data *pdata = NULL;
struct v4l2_subdev *sd;
int ret = -ENODEV;
struct v4l2_mbus_framefmt *format;
struct reg_cfg reg;
pdata = client->dev.platform_data;
if(!pdata)
return -EINVAL;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
ret = -EIO;
goto ei2c;
}
v4l_dbg(1, debug, client, "detecting tw8834 client on address 0x%x\n",
client->addr << 1);
tw8834 = kzalloc(sizeof(*tw8834), GFP_KERNEL);
if(!tw8834) {
v4l_err(client, "alloc failed for data structure\n");
ret = -ENOMEM;
goto enomem;
}
tw8834->set_power = pdata->set_power;
tw8834->i2c_client = client;
tw8834->input = TW8834_INPUT_CVBS;
format = &tw8834->format[TW8834_IN_PAD];
format->width = 640;
format->height = 480;
format->field = V4L2_FIELD_NONE;
format->code = V4L2_MBUS_FMT_UYVY8_2X8;
format->colorspace = V4L2_COLORSPACE_SMPTE170M;
format = &tw8834->format[TW8834_OUT_PAD];
format->width = TW8834_CVBS_WIDTH;
format->height = TW8834_CVBS_PAL_HEIGHT;
format->field = V4L2_FIELD_INTERLACED;
format->code = V4L2_MBUS_FMT_UYVY8_2X8;
format->colorspace = V4L2_COLORSPACE_SMPTE170M;
sd = &tw8834->sd;
v4l2_i2c_subdev_init(sd, client, &tw8834_ops);
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
tw8834->pad[TW8834_IN_PAD].flags = MEDIA_PAD_FL_SINK;
tw8834->pad[TW8834_OUT_PAD].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_init(&sd->entity, 2, tw8834->pad, 0);
if(ret < 0) {
v4l_err(client, "failed to init media entity\n");
goto emedia;
}
/*see if chip is present */
if(pdata->set_power) {
pdata->set_power(TW8834_POWER_ON);
msleep(500);
}
reg.reg = 0xFF;
reg.val = 0x00;
ret = tw8834_i2c_write_byte(tw8834, reg.reg, reg.val);
if(ret < 0) {
v4l_err(client, "impossible to write to tw8834\n");
goto echipident;
}
reg.reg = 0x00;
ret = tw8834_i2c_read_byte(tw8834, reg.reg);
if(ret < 0)
{
v4l_err(client, "failed to read chip id\n");
goto echipident;
}
reg.val = ret;
if(reg.val != 0x34) {
v4l_err(client, "read 0x%d in chip ident reg\n", reg.val);
ret = -ENODEV;
goto echipident;
}
if(client->irq > 0) {
ret = request_threaded_irq(client->irq,NULL,
tw8834_isr,
IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"tw8834", tw8834);
if(ret < 0) {
v4l2_err(client, "failed to register irq\n");
client->irq = -1;
}
}
tw8834_write_reg_array(tw8834,
tw8834_common_cfg,
ARRAY_SIZE(tw8834_common_cfg));
tw8834_enable_cvbs(tw8834);
tw8834_write_register(tw8834, TW8834_IRQ, 0xFF);
tw8834_write_register(tw8834, TW8834_STATUS, 0xFF);
tw8834_write_register(tw8834, TW8834_IMASK, 0xEC);
tw8834_write_register(tw8834, TW8834_BT656_DEC_CTRL2, 0x10);
return 0;
echipident:
if(pdata->set_power)
pdata->set_power(TW8834_POWER_OFF);
media_entity_cleanup(&sd->entity);
emedia:
kfree(tw8834);
enomem:
ei2c:
return ret;
}
static int tw8834_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct tw8834 *tw8834 = to_tw8834(sd);
struct tw8834_platform_data *pdata = NULL;
pdata = client->dev.platform_data;
if(pdata->set_power)
pdata->set_power(TW8834_POWER_OFF);
if(client->irq > 0)
free_irq(client->irq, tw8834);
v4l2_device_unregister_subdev(sd);
media_entity_cleanup(&sd->entity);
kfree(tw8834);
return 0;
}
#ifdef CONFIG_PM
int tw8834_suspend(struct i2c_client *client, pm_message_t mesg)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct tw8834 *tw8834 = to_tw8834(sd);
int is_streaming;
if(tw8834->set_power)
tw8834->set_power(TW8834_POWER_OFF);
// Saving the state to restore at resume
is_streaming = tw8834->is_streaming;
// Stop if it was streaming
if (is_streaming)
tw8834_s_stream(sd, 0);
tw8834->is_streaming = is_streaming;
return 0;
}
int tw8834_resume(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct tw8834 *tw8834 = to_tw8834(sd);
if(tw8834->set_power){
tw8834->set_power(TW8834_POWER_ON);
msleep(100);
}
tw8834_write_reg_array(tw8834,
tw8834_common_cfg,
ARRAY_SIZE(tw8834_common_cfg));
tw8834_enable_cvbs(tw8834);
tw8834_write_register(tw8834, TW8834_IRQ, 0xFF);
tw8834_write_register(tw8834, TW8834_STATUS, 0xFF);
tw8834_write_register(tw8834, TW8834_IMASK, 0xEC);
tw8834_write_register(tw8834, TW8834_BT656_DEC_CTRL2, 0x10);
if (tw8834->is_streaming)
tw8834_s_stream(sd, 1);
return 0;
}
#endif
/* ----------------------------------------------------------------------- */
static const struct i2c_device_id tw8834_id[] = {
{ "tw8834", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tw8834_id);
static struct i2c_driver tw8834_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "tw8834",
},
.probe = tw8834_probe,
.remove = tw8834_remove,
#ifdef CONFIG_PM
.suspend = tw8834_suspend,
.resume = tw8834_resume,
#endif
.id_table = tw8834_id,
};
module_i2c_driver(tw8834_driver);