blob: da5685578e870d06a403778921ca15e4816e002d [file] [log] [blame]
/* tw9990.c
*
* Driver for techwell tw9990
* 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/types.h>
#include <linux/interrupt.h>
#include <linux/videodev2.h>
#include <linux/delay.h>
#include <media/tw9990.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 tw9990");
MODULE_LICENSE("GPL");
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "debug level (0-2)");
#define TW9990_NUM_INPUTS 2
#define TW9990_CVBS_WIDTH 720
#define TW9990_CVBS_PAL_HEIGHT 576
#define TW9990_CVBS_NTSC_HEIGHT 480
#define TW_BIT_SHIFT_L(val, shift) (val<<shift)
#define TW_MASK(bits) (TW_BIT_SHIFT_L(1, bits)-1)
/* registers */
#define TW9990_CHIP_STATUS 0x01
#define TW9990_IN_FORM 0x02
#define TW9990_OP_FORM 0x03
#define TW9990_CMN_MODE_CLAMP 0x0D
#define TW9990_AN_CTRL_2 0x1A
#define TW9990_STD_SEL 0x1C
#define TW9990_STD_RECOG 0x1D
#define TW9990_SYNCT 0x25
#define TW9990_MISC1_CTRL_1 0x2D
#define TW9990_SHORT_DIAG 0xAF
#define TW9990_INT2_ENABLE 0xB1
#define TW9990_INT2_RST 0xB4
#define TW9990_INT2_RAW_STATUS 0xB7
#define TW9990_INT2_STATUS 0xBA
enum {
TW9990_IN_FORM_ID = 0,
TW9990_OP_FORM_ID,
TW9990_CMN_MODE_CLAMP_ID,
TW9990_AN_CTRL_2_ID,
TW9990_SYNCT_ID,
TW9990_MISC1_CTRL_1_ID,
TW9990_MAX_IDS
};
/* Register maps */
struct reg_cfg {
unsigned char reg;
unsigned char val;
};
/* tw9990 data structure */
struct tw9990 {
struct v4l2_subdev sd;
struct i2c_client *i2c_client;
struct media_pad pad;
struct v4l2_mbus_framefmt format;
struct v4l2_subdev_frame_interval frame_interval;
int is_streaming;
int (*set_power)(int on);
int irq;
int differential_input;
struct reg_cfg config[TW9990_MAX_IDS];
};
static inline struct tw9990 *to_tw9990(struct v4l2_subdev *sd)
{
return container_of(sd, struct tw9990, sd);
}
static struct v4l2_dv_timings tw9990_ntsc_timings = V4L2_DV_BT_CEA_720X480I59_94;
static struct v4l2_dv_timings tw9990_pal_timings = V4L2_DV_BT_CEA_720X576I50;
/* i2c transfer functions */
static s32 tw9990_i2c_read_byte(struct tw9990 *tw9990,
u8 command)
{
struct i2c_client *client = tw9990->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 tw9990_i2c_write_byte(struct tw9990 *tw9990,
u8 command,
u8 value)
{
struct i2c_client *client = tw9990->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)
v4l2_err(client, "error writing %02x, %02x, %02x\n",
client->addr, command, value);
return err;
}
static int tw9990_write_reg_array(struct tw9990 *tw9990,
struct reg_cfg *array, int nb)
{
s32 ret;
int i;
struct v4l2_subdev *sd = &tw9990->sd;
for(i=0;i<nb;i++) {
ret = tw9990_i2c_write_byte(tw9990,
array[i].reg,
array[i].val);
if(ret < 0) {
v4l2_err(sd, "failed to write 0x%X to reg 0x%X\n",
array[i].reg, array[i].val);
return ret;
}
}
return 0;
}
/* video operations */
static struct v4l2_mbus_framefmt *
__tw9990_get_fmt(struct tw9990 *tw9990,
struct v4l2_subdev_fh *fh,
unsigned int pad,
enum v4l2_subdev_format_whence which)
{
if(pad != 0)
return NULL;
if (which == V4L2_SUBDEV_FORMAT_TRY)
return v4l2_subdev_get_try_format(fh, pad);
else
return &tw9990->format;
}
static int tw9990_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct tw9990 *tw9990 = to_tw9990(sd);
struct v4l2_mbus_framefmt *format;
format = __tw9990_get_fmt(tw9990, fh, fmt->pad, fmt->which);
if (format == NULL)
return -EINVAL;
fmt->format = *format;
return 0;
}
static int tw9990_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
struct v4l2_subdev_format *fmt)
{
struct tw9990 *tw9990 = to_tw9990(sd);
int ret = 0;
struct v4l2_mbus_framefmt *format;
format = __tw9990_get_fmt(tw9990, fh, fmt->pad, fmt->which);
if (format == NULL)
return -EINVAL;
if(fmt->format.width != 720 ||
(fmt->format.height != 480 && fmt->format.height != 576))
return -EINVAL;
fmt->format.colorspace = V4L2_COLORSPACE_SMPTE170M;
*format = fmt->format;
return ret;
}
static int tw9990_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_fh *fh,
struct v4l2_subdev_mbus_code_enum *code)
{
struct tw9990 *tw9990 = to_tw9990(sd);
if (code->index)
return -EINVAL;
code->code = tw9990->format.code;
return 0;
}
static void tw9990_setup(struct tw9990 *tw9990,
struct tw9990_platform_data *pdata)
{
/* Input format configuration: by default on MUX0 */
tw9990->config[TW9990_IN_FORM_ID].reg = TW9990_IN_FORM;
tw9990->config[TW9990_IN_FORM_ID].val = (0x40 |
((pdata->differential_input == TW9990_DIFFERENTIAL_ENABLED) ?
0x80 : 0));
/* Output format: configure ITU-656 data sequence format */
tw9990->config[TW9990_OP_FORM_ID].reg = TW9990_OP_FORM;
tw9990->config[TW9990_OP_FORM_ID].val = 0xA0;
/* Common mode clamp: based on platform data only */
tw9990->config[TW9990_CMN_MODE_CLAMP_ID].reg = TW9990_CMN_MODE_CLAMP;
tw9990->config[TW9990_CMN_MODE_CLAMP_ID].val =
pdata->common_mode_clamp;
/* Analog control II: configure anti-alias and power saving
* for Y/C channels, based on platform data.
*/
tw9990->config[TW9990_AN_CTRL_2_ID].reg = TW9990_AN_CTRL_2;
tw9990->config[TW9990_AN_CTRL_2_ID].val =
(pdata->anti_aliasing == TW9990_ANTI_ALIAS_ENABLED) ?
0x0A : 0;
tw9990->config[TW9990_AN_CTRL_2_ID].val |=
(pdata->power_saving == TW9990_POWER_SAVE_ENABLED) ?
0x05 : 0;
/* Sync amplitude (SYNCT) */
tw9990->config[TW9990_SYNCT_ID].reg = TW9990_SYNCT;
if (pdata->synct_mode == TW9990_SYNCT_FORCED)
tw9990->config[TW9990_SYNCT_ID].val =
pdata->sync_pulse_amplitude & 0x7F;
else
tw9990->config[TW9990_SYNCT_ID].val = 0xB8; /* default value */
/* Miscellaneous control I: configure range limited for Luma and
* Chroma, output data range are limited to 16~235 for Y and
* 16~240 for Cb/Cr.
*/
tw9990->config[TW9990_MISC1_CTRL_1_ID].reg = TW9990_MISC1_CTRL_1;
tw9990->config[TW9990_MISC1_CTRL_1_ID].val = 0x1C;
}
static int tw9990_enable(struct tw9990 *tw9990)
{
return tw9990_write_reg_array(tw9990,
tw9990->config,
ARRAY_SIZE(tw9990->config));
}
static int tw9990_s_stream(struct v4l2_subdev *sd, int enable)
{
struct tw9990 *tw9990 = to_tw9990(sd);
int ret = 0;
if (enable)
ret = tw9990_enable(tw9990);
tw9990->is_streaming = !!enable;
return ret;
}
static int tw9990_g_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_frame_interval *fi)
{
struct tw9990 *tw9990 = to_tw9990(sd);
if (!fi)
return -EINVAL;
fi->interval.numerator = tw9990->frame_interval.interval.numerator;
fi->interval.denominator = tw9990->frame_interval.interval.denominator;
return 0;
}
static int tw9990_query_dv_timings(struct v4l2_subdev *sd,
struct v4l2_dv_timings *timings)
{
struct tw9990 *tw9990 = to_tw9990(sd);
unsigned char std_sel, std, status;
std_sel = tw9990_i2c_read_byte(tw9990,
TW9990_STD_SEL);
while((std_sel & 0x80) != 0) {
v4l2_info(sd, "waiting for std detection\n");
msleep(500);
std_sel = tw9990_i2c_read_byte(tw9990,
TW9990_STD_SEL);
}
status = tw9990_i2c_read_byte(tw9990,
TW9990_CHIP_STATUS);
if(status & 0x80) {
v4l2_info(sd, "Video loss\n");
return -ENOLINK;
}
std = (std_sel & 0x70) >> 4;
switch(std) {
case 0:
case 3:
case 4:
*timings = tw9990_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;
break;
case 1:
case 2:
case 5:
case 6:
*timings = tw9990_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;
break;
case 7:
v4l2_info(sd, "no standard detected\n");
memset(timings, 0, sizeof(*timings));
break;
default:
v4l2_err(sd, "invalid value for register 0x%X:0x%X\n",
TW9990_STD_SEL, std_sel);
return -EPIPE;
}
return 0;
}
int tw9990_s_routing(struct v4l2_subdev *sd, u32 in, u32 out, u32 config) {
struct tw9990 *tw9990 = to_tw9990(sd);
if(!tw9990 || in >= TW9990_NUM_INPUTS)
return -EINVAL;
/* Clean-up input selection (b11110011) */
tw9990->config[TW9990_IN_FORM_ID].val &= ~(TW_MASK(2) << 2);
/* Setup new input selection: based on tw9990 data-sheet
* In differential mode YSEL take only 0 or 2 respectively
* MUX0+/- or MUX2+/-
* In single ended mode YSEL take only 0 or 1 respectively
* MUX0 or MUX1... for MUX3 or MUX4, who knows ?
*/
if (tw9990->differential_input)
tw9990->config[TW9990_IN_FORM_ID].val |= (in << 3);
else
tw9990->config[TW9990_IN_FORM_ID].val |= (in << 2);
return 0;
}
int tw9990_subscribe_event(struct v4l2_subdev *subdev,
struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
return v4l2_src_change_event_subscribe(fh, sub);
}
int tw9990_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 tw9990_core_ops = {
.subscribe_event = tw9990_subscribe_event,
.unsubscribe_event = tw9990_unsubscribe_event,
};
static const struct v4l2_subdev_video_ops tw9990_video_ops = {
.s_stream = tw9990_s_stream,
.g_frame_interval = tw9990_g_frame_interval,
.query_dv_timings = tw9990_query_dv_timings,
.s_routing = tw9990_s_routing,
};
static const struct v4l2_subdev_pad_ops tw9990_pad_ops = {
.get_fmt = tw9990_get_fmt,
.set_fmt = tw9990_set_fmt,
.enum_mbus_code = tw9990_enum_mbus_code,
};
static const struct v4l2_subdev_ops tw9990_ops = {
.core = &tw9990_core_ops,
.video = &tw9990_video_ops,
.pad = &tw9990_pad_ops,
};
static irqreturn_t tw9990_isr(int irq, void *priv)
{
struct tw9990 *tw9990 = priv;
unsigned char int2status;
struct video_device *vdev = tw9990->sd.devnode;
static const struct v4l2_event event = {
.type = V4L2_EVENT_SOURCE_CHANGE,
.u.src_change.changes =
V4L2_EVENT_SRC_CH_RESOLUTION,
};
int2status = tw9990_i2c_read_byte(tw9990,
TW9990_INT2_STATUS);
v4l2_info(&tw9990->sd,
"read int2status = 0x%X\n", int2status);
if(vdev)
v4l2_event_queue(vdev, &event);
tw9990_i2c_write_byte(tw9990, TW9990_STD_RECOG, 0xFF);
tw9990_i2c_write_byte(tw9990, TW9990_INT2_RST, 0xFF);
return IRQ_HANDLED;
}
static int tw9990_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tw9990 *tw9990;
struct tw9990_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;
}
v4l2_dbg(1, debug, client, "detecting tw9990 client on address 0x%x\n",
client->addr << 1);
tw9990 = kzalloc(sizeof(*tw9990), GFP_KERNEL);
if(!tw9990) {
ret = -ENOMEM;
v4l2_err(client, "alloc failed for data structure\n");
goto enomem;
}
tw9990->set_power = pdata->set_power;
tw9990->differential_input = pdata->differential_input;
tw9990->i2c_client = client;
tw9990_setup(tw9990, pdata);
format = &tw9990->format;
format->width = TW9990_CVBS_WIDTH;
format->height = TW9990_CVBS_PAL_HEIGHT;
format->field = V4L2_FIELD_INTERLACED;
format->code = V4L2_MBUS_FMT_UYVY8_2X8;
format->colorspace = V4L2_COLORSPACE_SMPTE170M;
tw9990->frame_interval.interval.numerator = 1;
tw9990->frame_interval.interval.denominator = 30;
sd = &tw9990->sd;
v4l2_i2c_subdev_init(sd, client, &tw9990_ops);
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
tw9990->pad.flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_init(&sd->entity, 1, &tw9990->pad, 0);
if(ret < 0) {
v4l2_err(client, "failed to init media entity\n");
goto emedia;
}
/*see if chip is present */
if(pdata->set_power) {
pdata->set_power(TW9990_POWER_ON);
msleep(500);
}
reg.reg = 0x00;
ret = tw9990_i2c_read_byte(tw9990, reg.reg);
/* not chip */
if (ret < 0)
goto echipident;
reg.val = ret;
if(reg.val != 0) {
v4l2_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,
tw9990_isr,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"tw9990", tw9990);
if(ret < 0) {
v4l2_err(client, "failed to register irq\n");
client->irq = -1;
}
}
tw9990_i2c_write_byte(tw9990, TW9990_INT2_RST, 0xFF);
tw9990_i2c_write_byte(tw9990, TW9990_INT2_ENABLE, 0x30);
tw9990_i2c_write_byte(tw9990, TW9990_SHORT_DIAG, 0x0);
tw9990_i2c_write_byte(tw9990, TW9990_STD_RECOG, 0xFF);
tw9990_enable(tw9990);
return 0;
echipident:
media_entity_cleanup(&sd->entity);
emedia:
kfree(tw9990);
enomem:
ei2c:
return ret;
}
static int tw9990_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct tw9990 *tw9990 = to_tw9990(sd);
struct tw9990_platform_data *pdata = NULL;
pdata = client->dev.platform_data;
if(tw9990->set_power)
tw9990->set_power(TW9990_POWER_OFF);
if(client->irq > 0)
free_irq(client->irq, tw9990);
v4l2_device_unregister_subdev(sd);
media_entity_cleanup(&sd->entity);
kfree(tw9990);
return 0;
}
#ifdef CONFIG_PM
int tw9990_suspend(struct i2c_client *client, pm_message_t mesg)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct tw9990 *tw9990 = to_tw9990(sd);
// Saving the state to restore at resume
int is_streaming = tw9990->is_streaming;
// Stop if it was streaming
if (is_streaming)
tw9990_s_stream(sd, 0);
tw9990->is_streaming = is_streaming;
if(tw9990->set_power)
tw9990->set_power(TW9990_POWER_OFF);
return 0;
}
int tw9990_resume(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct tw9990 *tw9990 = to_tw9990(sd);
if(tw9990->set_power)
tw9990->set_power(TW9990_POWER_ON);
if (tw9990->is_streaming)
tw9990_s_stream(sd, 1);
return 0;
}
#endif
/* ----------------------------------------------------------------------- */
static const struct i2c_device_id tw9990_id[] = {
{ "tw9990", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tw9990_id);
static struct i2c_driver tw9990_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "tw9990",
},
.probe = tw9990_probe,
.remove = tw9990_remove,
#ifdef CONFIG_PM
.suspend = tw9990_suspend,
.resume = tw9990_resume,
#endif
.id_table = tw9990_id,
};
module_i2c_driver(tw9990_driver);