Re: [PATCH V2] media: i2c: Add ADV761X support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 11/19/2013 01:50 PM, Hans Verkuil wrote:
Hi Valentine,

Hi Hans,
thanks for your review.


I don't entirely understand how you use this driver with soc-camera.
Since soc-camera doesn't support FMT_CHANGE notifies it can't really
act on it. Did you hack soc-camera to do this?

I did not. The format is queried before reading the frame by the user-space.
I'm not sure if there's some kind of generic interface to notify the camera
layer about format change events. Different subdevices use different FMT_CHANGE
defines for that. I've implemented the format change notifier based on the adv7604
in hope that it may be useful later.


The way it stands I would prefer to see a version of the driver without
soc-camera support. I wouldn't have a problem merging that as this driver
is a good base for further development.

I've tried to implement the driver base good enough to work with both SoC
and non-SoC cameras since I don't think having 2 separate drivers for
different camera models is a good idea.

The problem is that I'm using it with R-Car VIN SoC camera driver and don't
have any other h/w. Having a platform data quirk for SoC camera in
the subdevice driver seemed simple and clean enough.

Hacking SoC camera to make it support both generic and SoC cam subdevices
doesn't seem that straightforward to me.

Re-implementing R-Car VIN as a non-SoC model seems quite a big task that
involves a lot of regression testing with other R-Car boards that use different
subdevices with VIN.

What would you suggest?


You do however have to add support for the V4L2_CID_DV_RX_POWER_PRESENT
control. It's easy to implement and that way apps can be notified when
the hotplug changes value.

OK, thanks.


Regards,

	Hans

Thanks,
Val.


On 11/15/13 13:54, Valentine Barshak wrote:
This adds ADV7611/ADV7612 Xpressview  HDMI Receiver base
support. Only one HDMI port is supported on ADV7612.

The code is based on the ADV7604 driver, and ADV7612 patch by
Shinobu Uehara <shinobu.uehara.xc@xxxxxxxxxxx>

Changes in version 2:
* Used platform data for I2C addresses setup. The driver
   should work with both SoC and non-SoC camera models.
* Dropped unnecessary code and unsupported callbacks.
* Implemented IRQ handling for format change detection.

Signed-off-by: Valentine Barshak <valentine.barshak@xxxxxxxxxxxxxxxxxx>
---
  drivers/media/i2c/Kconfig   |   11 +
  drivers/media/i2c/Makefile  |    1 +
  drivers/media/i2c/adv761x.c | 1009 +++++++++++++++++++++++++++++++++++++++++++
  include/media/adv761x.h     |   38 ++
  4 files changed, 1059 insertions(+)
  create mode 100644 drivers/media/i2c/adv761x.c
  create mode 100644 include/media/adv761x.h

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 75c8a03..2388642 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -206,6 +206,17 @@ config VIDEO_ADV7604
  	  To compile this driver as a module, choose M here: the
  	  module will be called adv7604.

+config VIDEO_ADV761X
+	tristate "Analog Devices ADV761X decoder"
+	depends on VIDEO_V4L2 && I2C
+	---help---
+	  Support for the Analog Devices ADV7611/ADV7612 video decoder.
+
+	  This is an Analog Devices Xpressview HDMI Receiver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called adv761x.
+
  config VIDEO_ADV7842
  	tristate "Analog Devices ADV7842 decoder"
  	depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index e03f177..d78d627 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o
  obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o
  obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o
  obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o
+obj-$(CONFIG_VIDEO_ADV761X) += adv761x.o
  obj-$(CONFIG_VIDEO_ADV7842) += adv7842.o
  obj-$(CONFIG_VIDEO_AD9389B) += ad9389b.o
  obj-$(CONFIG_VIDEO_ADV7511) += adv7511.o
diff --git a/drivers/media/i2c/adv761x.c b/drivers/media/i2c/adv761x.c
new file mode 100644
index 0000000..95939f5
--- /dev/null
+++ b/drivers/media/i2c/adv761x.c
@@ -0,0 +1,1009 @@
+/*
+ * adv761x Analog Devices ADV761X HDMI receiver driver
+ *
+ * Copyright (C) 2013 Cogent Embedded, Inc.
+ * Copyright (C) 2013 Renesas Electronics Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <media/adv761x.h>
+#include <media/soc_camera.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+
+#define ADV761X_DRIVER_NAME "adv761x"
+
+/* VERT_FILTER_LOCKED and DE_REGEN_FILTER_LOCKED flags */
+#define ADV761X_HDMI_F_LOCKED(v)	(((v) & 0xa0) == 0xa0)
+
+/* Maximum supported resolution */
+#define ADV761X_MAX_WIDTH		1920
+#define ADV761X_MAX_HEIGHT		1080
+
+/* Use SoC camera subdev desc private data for platform_data */
+#define ADV761X_SOC_CAM_QUIRK		0x1
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level (0-2)");
+
+struct adv761x_color_format {
+	enum v4l2_mbus_pixelcode code;
+	enum v4l2_colorspace colorspace;
+};
+
+/* Supported color format list */
+static const struct adv761x_color_format adv761x_cfmts[] = {
+	{
+		.code		= V4L2_MBUS_FMT_RGB888_1X24,
+		.colorspace	= V4L2_COLORSPACE_SRGB,
+	},
+};
+
+/* ADV761X descriptor structure */
+struct adv761x_state {
+	struct v4l2_subdev			sd;
+	struct media_pad			pad;
+	struct v4l2_ctrl_handler		ctrl_hdl;
+
+	u8					edid[256];
+	unsigned				edid_blocks;
+
+	struct rw_semaphore			rwsem;
+	const struct adv761x_color_format	*cfmt;
+	u32					width;
+	u32					height;
+	enum v4l2_field				scanmode;
+	u32					status;
+
+	int					gpio;
+	int					irq;
+
+	struct workqueue_struct			*work_queue;
+	struct delayed_work			enable_hotplug;
+	struct work_struct			interrupt_service;
+
+	struct i2c_client			*i2c_cec;
+	struct i2c_client			*i2c_inf;
+	struct i2c_client			*i2c_dpll;
+	struct i2c_client			*i2c_rep;
+	struct i2c_client			*i2c_edid;
+	struct i2c_client			*i2c_hdmi;
+	struct i2c_client			*i2c_cp;
+};
+
+static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
+{
+	return &container_of(ctrl->handler, struct adv761x_state, ctrl_hdl)->sd;
+}
+
+static inline struct adv761x_state *to_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct adv761x_state, sd);
+}
+
+/* I2C I/O operations */
+static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command)
+{
+	s32 ret, i;
+
+	for (i = 0; i < 3; i++) {
+		ret = i2c_smbus_read_byte_data(client, command);
+		if (ret >= 0)
+			return ret;
+	}
+
+	v4l_err(client, "Reading addr:%02x reg:%02x\n failed",
+		client->addr, command);
+	return ret;
+}
+
+static s32 adv_smbus_write_byte_data(struct i2c_client *client, u8 command,
+				     u8 value)
+{
+	s32 ret, i;
+
+	for (i = 0; i < 3; i++) {
+		ret = i2c_smbus_write_byte_data(client, command, value);
+		if (!ret)
+			return 0;
+	}
+
+	v4l_err(client, "Writing addr:%02x reg:%02x val:%02x failed\n",
+		client->addr, command, value);
+	return ret;
+}
+
+static s32 adv_smbus_write_i2c_block_data(struct i2c_client *client, u8 command,
+					  u8 length, const u8 *values)
+{
+	s32 ret, i;
+
+	ret = i2c_smbus_write_i2c_block_data(client, command, length, values);
+	if (!ret)
+		return 0;
+
+	for (i = 0; i < length; i++) {
+		ret = adv_smbus_write_byte_data(client, command + i, values[i]);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+
+static inline int io_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	return adv_smbus_read_byte_data(client, reg);
+}
+
+static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	return adv_smbus_write_byte_data(client, reg, val);
+}
+
+static inline int cec_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_cec, reg);
+}
+
+static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_cec, reg, val);
+}
+
+static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_inf, reg);
+}
+
+static inline int infoframe_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_inf, reg, val);
+}
+
+static inline int dpll_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_dpll, reg);
+}
+
+static inline int dpll_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_dpll, reg, val);
+}
+
+static inline int rep_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_rep, reg);
+}
+
+static inline int rep_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_rep, reg, val);
+}
+
+static inline int edid_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_edid, reg);
+}
+
+static inline int edid_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_edid, reg, val);
+}
+
+static inline int edid_write_block(struct v4l2_subdev *sd,
+				   unsigned len, const u8 *val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct adv761x_state *state = to_state(sd);
+	int ret = 0;
+	int i;
+
+	v4l2_dbg(2, debug, sd, "Writing EDID block (%d bytes)\n", len);
+
+	v4l2_subdev_notify(sd, ADV761X_HOTPLUG, (void *)0);
+
+	/* Disable I2C access to internal EDID ram from DDC port */
+	rep_write(sd, 0x74, 0x0);
+
+	for (i = 0; !ret && i < len; i += I2C_SMBUS_BLOCK_MAX)
+		ret = adv_smbus_write_i2c_block_data(state->i2c_edid, i,
+				I2C_SMBUS_BLOCK_MAX, val + i);
+	if (ret)
+		return ret;
+
+	/*
+	 * ADV761x calculates the checksums and enables I2C access
+	 * to internal EDID ram from DDC port.
+	 */
+	rep_write(sd, 0x74, 0x01);
+
+	for (i = 0; i < 1000; i++) {
+		if (rep_read(sd, 0x76) & 0x1) {
+			/* Enable hotplug after 100 ms */
+			queue_delayed_work(state->work_queue,
+					   &state->enable_hotplug, HZ / 10);
+			return 0;
+		}
+		schedule();
+	}
+
+	v4l_err(client, "Enabling EDID failed\n");
+	return -EIO;
+}
+
+static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_hdmi, reg);
+}
+
+static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_hdmi, reg, val);
+}
+
+static inline int cp_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_cp, reg);
+}
+
+static inline int cp_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_cp, reg, val);
+}
+
+static inline int adv761x_power_off(struct v4l2_subdev *sd)
+{
+	return io_write(sd, 0x0c, 0x62);
+}
+
+static int adv761x_core_init(struct v4l2_subdev *sd)
+{
+	io_write(sd, 0x01, 0x06);	/* V-FREQ = 60Hz */
+					/* Prim_Mode = HDMI-GR */
+	io_write(sd, 0x02, 0xf2);	/* Auto CSC, RGB out */
+					/* Disable op_656 bit */
+	io_write(sd, 0x03, 0x42);	/* 36 bit SDR 444 Mode 0 */
+	io_write(sd, 0x05, 0x28);	/* AV Codes Off */
+	io_write(sd, 0x0b, 0x44);	/* Power up part */
+	io_write(sd, 0x0c, 0x42);	/* Power up part */
+	io_write(sd, 0x14, 0x7f);	/* Max Drive Strength */
+	io_write(sd, 0x15, 0x80);	/* Disable Tristate of Pins */
+					/* (Audio output pins active) */
+	io_write(sd, 0x19, 0x83);	/* LLC DLL phase */
+	io_write(sd, 0x33, 0x40);	/* LLC DLL enable */
+
+	cp_write(sd, 0xba, 0x01);	/* Set HDMI FreeRun */
+	cp_write(sd, 0x3e, 0x80);	/* Enable color adjustments */
+
+	hdmi_write(sd, 0x9b, 0x03);	/* ADI recommended setting */
+	hdmi_write(sd, 0x00, 0x08);	/* Set HDMI Input Port A */
+					/* (BG_MEAS_PORT_SEL = 001b) */
+	hdmi_write(sd, 0x02, 0x03);	/* Enable Ports A & B in */
+					/* background mode */
+	hdmi_write(sd, 0x6d, 0x80);	/* Enable TDM mode */
+	hdmi_write(sd, 0x03, 0x18);	/* I2C mode 24 bits */
+	hdmi_write(sd, 0x83, 0xfc);	/* Enable clock terminators */
+					/* for port A & B */
+	hdmi_write(sd, 0x6f, 0x0c);	/* ADI recommended setting */
+	hdmi_write(sd, 0x85, 0x1f);	/* ADI recommended setting */
+	hdmi_write(sd, 0x87, 0x70);	/* ADI recommended setting */
+	hdmi_write(sd, 0x8d, 0x04);	/* LFG Port A */
+	hdmi_write(sd, 0x8e, 0x1e);	/* HFG Port A */
+	hdmi_write(sd, 0x1a, 0x8a);	/* unmute audio */
+	hdmi_write(sd, 0x57, 0xda);	/* ADI recommended setting */
+	hdmi_write(sd, 0x58, 0x01);	/* ADI recommended setting */
+	hdmi_write(sd, 0x75, 0x10);	/* DDC drive strength */
+	hdmi_write(sd, 0x90, 0x04);	/* LFG Port B */
+	hdmi_write(sd, 0x91, 0x1e);	/* HFG Port B */
+	hdmi_write(sd, 0x04, 0x03);
+	hdmi_write(sd, 0x14, 0x00);
+	hdmi_write(sd, 0x15, 0x00);
+	hdmi_write(sd, 0x16, 0x00);
+
+	rep_write(sd, 0x40, 0x81);	/* Disable HDCP 1.1 features */
+	rep_write(sd, 0x74, 0x00);	/* Disable the Internal EDID */
+					/* for all ports */
+
+	/* Setup interrupts */
+	io_write(sd, 0x40, 0xc2);	/* Active high until cleared */
+	io_write(sd, 0x6e, 0x03);	/* INT1 HDMI DE_REGEN and V_LOCK */
+
+	return v4l2_ctrl_handler_setup(sd->ctrl_handler);
+}
+
+static int adv761x_hdmi_info(struct v4l2_subdev *sd, enum v4l2_field *scanmode,
+			     u32 *width, u32 *height)
+{
+	int msb, val;
+
+	/* Line width */
+	msb = hdmi_read(sd, 0x07);
+	if (msb < 0)
+		return msb;
+
+	if (!ADV761X_HDMI_F_LOCKED(msb))
+		return -EAGAIN;
+
+	/* Interlaced or progressive */
+	val = hdmi_read(sd, 0x0b);
+	if (val < 0)
+		return val;
+
+	*scanmode = (val & 0x20) ? V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE;
+	val = hdmi_read(sd, 0x08);
+	if (val < 0)
+		return val;
+
+	val |= (msb & 0x1f) << 8;
+	*width = val;
+
+	/* Lines per frame */
+	msb = hdmi_read(sd, 0x09);
+	if (msb < 0)
+		return msb;
+
+	val = hdmi_read(sd, 0x0a);
+	if (val < 0)
+		return val;
+
+	val |= (msb & 0x1f) << 8;
+	if (*scanmode == V4L2_FIELD_INTERLACED)
+		val <<= 1;
+	*height = val;
+
+	if (*width == 0 || *height == 0)
+		return -EIO;
+
+	return 0;
+}
+
+/* Hotplug work */
+static void adv761x_enable_hotplug(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct adv761x_state *state = container_of(dwork, struct adv761x_state,
+						   enable_hotplug);
+	struct v4l2_subdev *sd = &state->sd;
+
+	v4l2_dbg(2, debug, sd, "Enable hotplug\n");
+	v4l2_subdev_notify(sd, ADV761X_HOTPLUG, (void *)1);
+}
+
+/* IRQ work */
+static void adv761x_interrupt_service(struct work_struct *work)
+{
+	struct adv761x_state *state = container_of(work, struct adv761x_state,
+						   interrupt_service);
+	struct v4l2_subdev *sd = &state->sd;
+	enum v4l2_field scanmode;
+	u32 width, height;
+	u32 status = 0;
+	int ret;
+
+	/* Clear HDMI interrupts */
+	io_write(sd, 0x6c, 0xff);
+
+	ret = adv761x_hdmi_info(sd, &scanmode, &width, &height);
+	if (ret) {
+		if (state->status == V4L2_IN_ST_NO_SIGNAL)
+			return;
+
+		width = ADV761X_MAX_WIDTH;
+		height = ADV761X_MAX_HEIGHT;
+		scanmode = V4L2_FIELD_NONE;
+		status = V4L2_IN_ST_NO_SIGNAL;
+	}
+
+	if (status)
+		v4l2_dbg(2, debug, sd, "No HDMI video input detected\n");
+	else
+		v4l2_dbg(2, debug, sd, "HDMI video input detected (%ux%u%c)\n",
+			 width, height,
+			 scanmode == V4L2_FIELD_NONE ? 'p' : 'i');
+
+	down_write(&state->rwsem);
+	state->width = width;
+	state->height = height;
+	state->scanmode = scanmode;
+	state->status = status;
+	up_write(&state->rwsem);
+
+	v4l2_subdev_notify(sd, ADV761X_FMT_CHANGE, NULL);
+}
+
+/* IRQ handler */
+static irqreturn_t adv761x_irq_handler(int irq, void *devid)
+{
+	struct adv761x_state *state = devid;
+
+	queue_work(state->work_queue, &state->interrupt_service);
+	return IRQ_HANDLED;
+}
+
+/* v4l2_subdev_core_ops */
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static void adv761x_inv_register(struct v4l2_subdev *sd)
+{
+	v4l2_info(sd, "0x000-0x0ff: IO Map\n");
+	v4l2_info(sd, "0x100-0x1ff: CEC Map\n");
+	v4l2_info(sd, "0x200-0x2ff: InfoFrame Map\n");
+	v4l2_info(sd, "0x300-0x3ff: DPLL Map\n");
+	v4l2_info(sd, "0x400-0x4ff: Repeater Map\n");
+	v4l2_info(sd, "0x500-0x5ff: EDID Map\n");
+	v4l2_info(sd, "0x600-0x6ff: HDMI Map\n");
+	v4l2_info(sd, "0x700-0x7ff: CP Map\n");
+}
+
+static int adv761x_g_register(struct v4l2_subdev *sd,
+			      struct v4l2_dbg_register *reg)
+{
+	reg->size = 1;
+	switch (reg->reg >> 8) {
+	case 0:
+		reg->val = io_read(sd, reg->reg & 0xff);
+		break;
+	case 1:
+		reg->val = cec_read(sd, reg->reg & 0xff);
+		break;
+	case 2:
+		reg->val = infoframe_read(sd, reg->reg & 0xff);
+		break;
+	case 3:
+		reg->val = dpll_read(sd, reg->reg & 0xff);
+		break;
+	case 4:
+		reg->val = rep_read(sd, reg->reg & 0xff);
+		break;
+	case 5:
+		reg->val = edid_read(sd, reg->reg & 0xff);
+		break;
+	case 6:
+		reg->val = hdmi_read(sd, reg->reg & 0xff);
+		break;
+	case 7:
+		reg->val = cp_read(sd, reg->reg & 0xff);
+		break;
+	default:
+		v4l2_info(sd, "Register %03llx not supported\n", reg->reg);
+		adv761x_inv_register(sd);
+		break;
+	}
+	return 0;
+}
+
+static int adv761x_s_register(struct v4l2_subdev *sd,
+			      const struct v4l2_dbg_register *reg)
+{
+	switch (reg->reg >> 8) {
+	case 0:
+		io_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 1:
+		cec_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 2:
+		infoframe_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 3:
+		dpll_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 4:
+		rep_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 5:
+		edid_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 6:
+		hdmi_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 7:
+		cp_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	default:
+		v4l2_info(sd, "Register %03llx not supported\n", reg->reg);
+		adv761x_inv_register(sd);
+		break;
+	}
+	return 0;
+}
+#endif	/* CONFIG_VIDEO_ADV_DEBUG */
+
+/* v4l2_subdev_video_ops */
+static int adv761x_g_input_status(struct v4l2_subdev *sd, u32 *status)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	down_read(&state->rwsem);
+	*status = state->status;
+	up_read(&state->rwsem);
+	return 0;
+}
+
+static int adv761x_g_mbus_fmt(struct v4l2_subdev *sd,
+			      struct v4l2_mbus_framefmt *mf)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	down_read(&state->rwsem);
+	mf->width = state->width;
+	mf->height = state->height;
+	mf->field = state->scanmode;
+	mf->code = state->cfmt->code;
+	mf->colorspace = state->cfmt->colorspace;
+	up_read(&state->rwsem);
+	return 0;
+}
+
+static int adv761x_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index,
+				 enum v4l2_mbus_pixelcode *code)
+{
+	/* Check requested format index is within range */
+	if (index >= ARRAY_SIZE(adv761x_cfmts))
+		return -EINVAL;
+
+	*code = adv761x_cfmts[index].code;
+
+	return 0;
+}
+
+static int adv761x_g_mbus_config(struct v4l2_subdev *sd,
+				 struct v4l2_mbus_config *cfg)
+{
+	cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER |
+		V4L2_MBUS_VSYNC_ACTIVE_LOW | V4L2_MBUS_HSYNC_ACTIVE_LOW |
+		V4L2_MBUS_DATA_ACTIVE_HIGH;
+	cfg->type = V4L2_MBUS_PARALLEL;
+
+	return 0;
+}
+
+/* v4l2_subdev_pad_ops */
+static int adv761x_get_edid(struct v4l2_subdev *sd,
+			    struct v4l2_subdev_edid *edid)
+{
+	struct adv761x_state *state = to_state(sd);
+
+	if (edid->pad != 0)
+		return -EINVAL;
+
+	if (edid->blocks == 0)
+		return -EINVAL;
+
+	if (edid->start_block >= state->edid_blocks)
+		return -EINVAL;
+
+	if (edid->start_block + edid->blocks > state->edid_blocks)
+		edid->blocks = state->edid_blocks - edid->start_block;
+	if (!edid->edid)
+		return -EINVAL;
+
+	memcpy(edid->edid + edid->start_block * 128,
+	       state->edid + edid->start_block * 128,
+	       edid->blocks * 128);
+	return 0;
+}
+
+static int adv761x_set_edid(struct v4l2_subdev *sd,
+			    struct v4l2_subdev_edid *edid)
+{
+	struct adv761x_state *state = to_state(sd);
+	int ret;
+
+	if (edid->pad != 0)
+		return -EINVAL;
+
+	if (edid->start_block != 0)
+		return -EINVAL;
+
+	if (edid->blocks == 0) {
+		/* Pull down the hotplug pin */
+		v4l2_subdev_notify(sd, ADV761X_HOTPLUG, (void *)0);
+		/* Disable I2C access to internal EDID RAM from DDC port */
+		rep_write(sd, 0x74, 0x0);
+		state->edid_blocks = 0;
+		return 0;
+	}
+
+	if (edid->blocks > 2)
+		return -E2BIG;
+
+	if (!edid->edid)
+		return -EINVAL;
+
+	memcpy(state->edid, edid->edid, 128 * edid->blocks);
+	state->edid_blocks = edid->blocks;
+
+	ret = edid_write_block(sd, 128 * edid->blocks, state->edid);
+	if (ret < 0)
+		v4l2_err(sd, "Writing EDID failed\n");
+
+	return ret;
+}
+
+/* v4l2_ctrl_ops */
+static int adv761x_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct v4l2_subdev *sd = to_sd(ctrl);
+	u8 val = ctrl->val;
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		ret = cp_write(sd, 0x3c, val);
+		break;
+	case V4L2_CID_CONTRAST:
+		ret = cp_write(sd, 0x3a, val);
+		break;
+	case V4L2_CID_SATURATION:
+		ret = cp_write(sd, 0x3b, val);
+		break;
+	case V4L2_CID_HUE:
+		ret = cp_write(sd, 0x3d, val);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+/* V4L structures */
+static const struct v4l2_subdev_core_ops adv761x_core_ops = {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register	= adv761x_g_register,
+	.s_register	= adv761x_s_register,
+#endif
+};
+
+static const struct v4l2_subdev_video_ops adv761x_video_ops = {
+	.g_input_status = adv761x_g_input_status,
+	.g_mbus_fmt	= adv761x_g_mbus_fmt,
+	.try_mbus_fmt	= adv761x_g_mbus_fmt,
+	.s_mbus_fmt	= adv761x_g_mbus_fmt,
+	.enum_mbus_fmt	= adv761x_enum_mbus_fmt,
+	.g_mbus_config	= adv761x_g_mbus_config,
+};
+
+static const struct v4l2_subdev_pad_ops adv761x_pad_ops = {
+	.get_edid = adv761x_get_edid,
+	.set_edid = adv761x_set_edid,
+};
+
+static const struct v4l2_subdev_ops adv761x_ops = {
+	.core = &adv761x_core_ops,
+	.video = &adv761x_video_ops,
+	.pad = &adv761x_pad_ops,
+};
+
+static const struct v4l2_ctrl_ops adv761x_ctrl_ops = {
+	.s_ctrl = adv761x_s_ctrl,
+};
+
+/* Device initialization and clean-up */
+static void adv761x_unregister_clients(struct adv761x_state *state)
+{
+	if (state->i2c_cec)
+		i2c_unregister_device(state->i2c_cec);
+	if (state->i2c_inf)
+		i2c_unregister_device(state->i2c_inf);
+	if (state->i2c_dpll)
+		i2c_unregister_device(state->i2c_dpll);
+	if (state->i2c_rep)
+		i2c_unregister_device(state->i2c_rep);
+	if (state->i2c_edid)
+		i2c_unregister_device(state->i2c_edid);
+	if (state->i2c_hdmi)
+		i2c_unregister_device(state->i2c_hdmi);
+	if (state->i2c_cp)
+		i2c_unregister_device(state->i2c_cp);
+}
+
+static struct i2c_client *adv761x_dummy_client(struct v4l2_subdev *sd,
+					       u8 addr, u8 def_addr, u8 io_reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (!addr)
+		addr = def_addr;
+
+	io_write(sd, io_reg, addr << 1);
+	return i2c_new_dummy(client->adapter, addr);
+}
+
+static inline int adv761x_check_rev(struct i2c_client *client)
+{
+	int msb, rev;
+
+	msb = adv_smbus_read_byte_data(client, 0xea);
+	if (msb < 0)
+		return msb;
+
+	rev = adv_smbus_read_byte_data(client, 0xeb);
+	if (rev < 0)
+		return rev;
+
+	rev |= msb << 8;
+
+	switch (rev) {
+	case 0x2051:
+		return 7611;
+	case 0x2041:
+		return 7612;
+	default:
+		break;
+	}
+
+	return -ENODEV;
+}
+
+static int adv761x_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct adv761x_platform_data *pdata;
+	struct adv761x_state *state;
+	struct v4l2_ctrl_handler *ctrl_hdl;
+	struct v4l2_subdev *sd;
+	int irq, ret;
+
+	/* Check if the adapter supports the needed features */
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EIO;
+
+	/* Check chip revision */
+	ret = adv761x_check_rev(client);
+	if (ret < 0)
+		return ret;
+
+	v4l_info(client, "Chip found @ 0x%02x (adv%d)\n", client->addr, ret);
+
+	/* Get platform data */
+	if (id->driver_data == ADV761X_SOC_CAM_QUIRK) {
+		struct soc_camera_subdev_desc *ssdd;
+
+		v4l_info(client, "Using SoC camera glue\n");
+		ssdd = soc_camera_i2c_to_desc(client);
+		pdata = ssdd ? ssdd->drv_priv : NULL;
+	} else {
+		pdata = client->dev.platform_data;
+	}
+
+	if (!pdata) {
+		v4l_err(client, "No platform data found\n");
+		return -ENODEV;
+	}
+
+	state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
+	if (state == NULL) {
+		v4l_err(client, "Memory allocation failed\n");
+		return -ENOMEM;
+	}
+
+	init_rwsem(&state->rwsem);
+
+	/* Setup default values */
+	state->cfmt = &adv761x_cfmts[0];
+	state->width = ADV761X_MAX_WIDTH;
+	state->height = ADV761X_MAX_HEIGHT;
+	state->scanmode = V4L2_FIELD_NONE;
+	state->status = V4L2_IN_ST_NO_SIGNAL;
+	state->gpio = -1;
+
+	/* Setup subdev */
+	sd = &state->sd;
+	v4l2_i2c_subdev_init(sd, client, &adv761x_ops);
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	/* Setup I2C clients */
+	state->i2c_cec = adv761x_dummy_client(sd, pdata->i2c_cec, 0x40, 0xf4);
+	state->i2c_inf = adv761x_dummy_client(sd, pdata->i2c_inf, 0x3e, 0xf5);
+	state->i2c_dpll = adv761x_dummy_client(sd, pdata->i2c_dpll, 0x26, 0xf8);
+	state->i2c_rep = adv761x_dummy_client(sd, pdata->i2c_rep, 0x32, 0xf9);
+	state->i2c_edid = adv761x_dummy_client(sd, pdata->i2c_edid, 0x36, 0xfa);
+	state->i2c_hdmi = adv761x_dummy_client(sd, pdata->i2c_hdmi, 0x34, 0xfb);
+	state->i2c_cp = adv761x_dummy_client(sd, pdata->i2c_cp, 0x22, 0xfd);
+	if (!state->i2c_cec || !state->i2c_inf || !state->i2c_dpll ||
+	    !state->i2c_rep || !state->i2c_edid ||
+	    !state->i2c_hdmi || !state->i2c_cp) {
+		ret = -ENODEV;
+		v4l2_err(sd, "I2C clients setup failed\n");
+		goto err_i2c;
+	}
+
+	/* Setup control handlers */
+	ctrl_hdl = &state->ctrl_hdl;
+	v4l2_ctrl_handler_init(ctrl_hdl, 4);
+	v4l2_ctrl_new_std(ctrl_hdl, &adv761x_ctrl_ops,
+			  V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
+	v4l2_ctrl_new_std(ctrl_hdl, &adv761x_ctrl_ops,
+			  V4L2_CID_CONTRAST, 0, 255, 1, 128);
+	v4l2_ctrl_new_std(ctrl_hdl, &adv761x_ctrl_ops,
+			  V4L2_CID_SATURATION, 0, 255, 1, 128);
+	v4l2_ctrl_new_std(ctrl_hdl, &adv761x_ctrl_ops,
+			  V4L2_CID_HUE, 0, 255, 1, 0);
+	sd->ctrl_handler = ctrl_hdl;
+	if (ctrl_hdl->error) {
+		ret = ctrl_hdl->error;
+		v4l2_err(sd, "Control handlers setup failed\n");
+		goto err_hdl;
+	}
+
+	/* Setup media entity */
+	state->pad.flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_init(&sd->entity, 1, &state->pad, 0);
+	if (ret) {
+		v4l2_err(sd, "Media entity setup failed\n");
+		goto err_hdl;
+	}
+
+	/* Setup work queue */
+	state->work_queue = create_singlethread_workqueue(client->name);
+	if (!state->work_queue) {
+		ret = -ENOMEM;
+		v4l2_err(sd, "Work queue setup failed\n");
+		goto err_entity;
+	}
+
+	INIT_DELAYED_WORK(&state->enable_hotplug, adv761x_enable_hotplug);
+	INIT_WORK(&state->interrupt_service, adv761x_interrupt_service);
+
+	/* Setup IRQ */
+	irq = client->irq;
+	if (irq <= 0) {
+		v4l_info(client, "Using GPIO IRQ\n");
+		ret = gpio_request_one(pdata->gpio, GPIOF_IN,
+				       ADV761X_DRIVER_NAME);
+		if (ret) {
+			v4l_err(client, "GPIO setup failed\n");
+			goto err_work;
+		}
+
+		state->gpio = pdata->gpio;
+		irq = gpio_to_irq(pdata->gpio);
+	}
+
+	if (irq <= 0) {
+		ret = -ENODEV;
+		v4l_err(client, "IRQ not found\n");
+		goto err_gpio;
+	}
+
+	ret = request_irq(irq, adv761x_irq_handler, IRQF_TRIGGER_RISING,
+			  ADV761X_DRIVER_NAME, state);
+	if (ret) {
+		v4l_err(client, "IRQ setup failed\n");
+		goto err_gpio;
+	}
+
+	state->irq = irq;
+
+	/* Setup core registers */
+	ret = adv761x_core_init(sd);
+	if (ret < 0) {
+		v4l_err(client, "Core setup failed\n");
+		goto err_core;
+	}
+
+	return 0;
+
+err_core:
+	adv761x_power_off(sd);
+	free_irq(state->irq, state);
+err_gpio:
+	if (gpio_is_valid(state->gpio))
+		gpio_free(state->gpio);
+err_work:
+	cancel_work_sync(&state->interrupt_service);
+	cancel_delayed_work_sync(&state->enable_hotplug);
+	destroy_workqueue(state->work_queue);
+err_entity:
+	media_entity_cleanup(&sd->entity);
+err_hdl:
+	v4l2_ctrl_handler_free(ctrl_hdl);
+err_i2c:
+	adv761x_unregister_clients(state);
+	return ret;
+}
+
+static int adv761x_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct adv761x_state *state = to_state(sd);
+
+	/* Release IRQ/GPIO */
+	free_irq(state->irq, state);
+	if (gpio_is_valid(state->gpio))
+		gpio_free(state->gpio);
+
+	/* Destroy workqueue */
+	cancel_work_sync(&state->interrupt_service);
+	cancel_delayed_work_sync(&state->enable_hotplug);
+	destroy_workqueue(state->work_queue);
+
+	/* Power off */
+	adv761x_power_off(sd);
+
+	/* Clean up*/
+	v4l2_device_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(sd->ctrl_handler);
+	adv761x_unregister_clients(state);
+	return 0;
+}
+
+static const struct i2c_device_id adv761x_id[] = {
+	{ "adv761x", 0 },
+	{ "adv761x-soc_cam", ADV761X_SOC_CAM_QUIRK },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(i2c, adv761x_id);
+
+static struct i2c_driver adv761x_driver = {
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name	= ADV761X_DRIVER_NAME,
+	},
+	.probe		= adv761x_probe,
+	.remove		= adv761x_remove,
+	.id_table	= adv761x_id,
+};
+
+module_i2c_driver(adv761x_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ADV761X HDMI receiver video decoder driver");
+MODULE_AUTHOR("Valentine Barshak <valentine.barshak@xxxxxxxxxxxxxxxxxx>");
diff --git a/include/media/adv761x.h b/include/media/adv761x.h
new file mode 100644
index 0000000..ec54361
--- /dev/null
+++ b/include/media/adv761x.h
@@ -0,0 +1,38 @@
+/*
+ * adv761x Analog Devices ADV761X HDMI receiver driver
+ *
+ * Copyright (C) 2013 Cogent Embedded, Inc.
+ * Copyright (C) 2013 Renesas Electronics Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#ifndef _ADV761X_H_
+#define _ADV761X_H_
+
+struct adv761x_platform_data {
+	/* INT1 GPIO IRQ */
+	int gpio;
+
+	/* I2C addresses: 0 == use default */
+	u8 i2c_cec;
+	u8 i2c_inf;
+	u8 i2c_dpll;
+	u8 i2c_rep;
+	u8 i2c_edid;
+	u8 i2c_hdmi;
+	u8 i2c_cp;
+};
+
+/* Notify events */
+#define ADV761X_HOTPLUG		1
+#define ADV761X_FMT_CHANGE	2
+
+#endif	/* _ADV761X_H_ */



--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux