This adds ADV7611/ADV7612 Dual Port Xpressview 225 MHz HDMI Receiver support. The code is based on the ADV7604 driver, and ADV7612 patch by Shinobu Uehara <shinobu.uehara.xc@xxxxxxxxxxx> Signed-off-by: Valentine Barshak <valentine.barshak@xxxxxxxxxxxxxxxxxx> --- drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/adv761x.c | 1060 +++++++++++++++++++++++++++++++++++++++++++ include/media/adv761x.h | 28 ++ 4 files changed, 1100 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 d18be19..8eede00 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 Dual Port 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 9f462df..393eb0c 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..bc3dfc3 --- /dev/null +++ b/drivers/media/i2c/adv761x.c @@ -0,0 +1,1060 @@ +/* + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <media/adv761x.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 + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +/* I2C map addresses */ +static u8 i2c_cec = 0x40; +module_param(i2c_cec, byte, 0644); +MODULE_PARM_DESC(i2c_cec, "CEC map 7-bit I2C address (default: 0x40)"); + +static u8 i2c_inf = 0x3e; +module_param(i2c_inf, byte, 0644); +MODULE_PARM_DESC(i2c_inf, "InfoFrame map 7-bit I2C address (default: 0x3e)"); + +static u8 i2c_dpll = 0x26; +module_param(i2c_dpll, byte, 0644); +MODULE_PARM_DESC(i2c_dpll, "DPLL map 7-bit I2C address (default: 0x20)"); + +static u8 i2c_rep = 0x32; +module_param(i2c_rep, byte, 0644); +MODULE_PARM_DESC(i2c_rep, "Repeater map 7-bit I2C address (default: 0x32)"); + +static u8 i2c_edid = 0x36; +module_param(i2c_edid, byte, 0644); +MODULE_PARM_DESC(i2c_edid, "EDID map 7-bit I2C address (default: 0x36)"); + +static u8 i2c_hdmi = 0x34; +module_param(i2c_hdmi, byte, 0644); +MODULE_PARM_DESC(i2c_hdmi, "HDMI map 7-bit I2C address (default: 0x34)"); + +static u8 i2c_cp = 0x22; +module_param(i2c_cp, byte, 0644); +MODULE_PARM_DESC(i2c_cp, "CP map 7-bit I2C address (default: 0x22)"); + +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; + const struct adv761x_color_format *cfmt; + u32 width; + u32 height; + enum v4l2_field scanmode; + + struct workqueue_struct *work_queue; + struct delayed_work enable_hotplug; + + /* I2C clients */ + struct i2c_client *i2c_cec; + struct i2c_client *i2c_infoframe; + struct i2c_client *i2c_dpll; + struct i2c_client *i2c_repeater; + 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, "error reading addr:%02x reg:%02x\n", + 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, "error writing addr:%02x reg:%02x val:%02x\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_infoframe, 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_infoframe, 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_repeater, 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_repeater, 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, "error enabling edid\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); +} + +/* 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, "%s: enable hotplug\n", __func__); + + v4l2_subdev_notify(sd, ADV761X_HOTPLUG, (void *)1); +} + +/* 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); + /* Disables 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, "error %d writing edid\n", ret); + + return ret; +} + +/* 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_get_vid_info(struct v4l2_subdev *sd, u8 *progressive, + 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)) { + v4l2_dbg(2, debug, sd, "No HDMI video input signal detected\n"); + return -EAGAIN; + } + + /* interlaced or progressive */ + val = hdmi_read(sd, 0x0b); + if (val < 0) + return val; + + *progressive = (val & 0x20) ? 0 : 1; + + 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; + *height = *progressive ? val : val << 1; + + if (*width == 0 || *height == 0) + return -EIO; + + v4l2_dbg(2, debug, sd, "Detected HDMI video input signal (%ux%u%c)\n", + *width, *height, *progressive ? 'p' : 'i'); + + return 0; +} + +static int adv761x_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + int ret; + + ret = hdmi_read(sd, 0x07); + if (ret < 0) + return ret; + + *status = ADV761X_HDMI_F_LOCKED(ret) ? 0 : V4L2_IN_ST_NO_SIGNAL; + return 0; +} + +static int adv761x_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + u8 progressive; + u32 width; + u32 height; + int ret; + + /* cropping limits */ + a->bounds.left = 0; + a->bounds.top = 0; + + /* get video information */ + ret = adv761x_get_vid_info(sd, &progressive, &width, &height); + if (ret < 0) { + a->bounds.width = ADV761X_MAX_WIDTH; + a->bounds.height = ADV761X_MAX_HEIGHT; + } else { + a->bounds.width = width; + a->bounds.height = height; + } + + /* default cropping rectangle */ + a->defrect = a->bounds; + + /* does not support scaling */ + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int adv761x_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + u8 progressive; + u32 width; + u32 height; + int ret; + + a->c.left = 0; + a->c.top = 0; + + /* Get video information */ + ret = adv761x_get_vid_info(sd, &progressive, &width, &height); + if (ret < 0) { + a->c.width = ADV761X_MAX_WIDTH; + a->c.height = ADV761X_MAX_HEIGHT; + } else { + a->c.width = width; + a->c.height = height; + } + + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int adv761x_g_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct adv761x_state *state = to_state(sd); + + mf->width = state->width; + mf->height = state->height; + mf->code = state->cfmt->code; + mf->field = state->scanmode; + mf->colorspace = state->cfmt->colorspace; + + return 0; +} + +static int adv761x_try_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct adv761x_state *state = to_state(sd); + int i, ret; + u8 progressive; + u32 width; + u32 height; + + for (i = 0; i < ARRAY_SIZE(adv761x_cfmts); i++) { + if (mf->code == adv761x_cfmts[i].code) + break; + } + + if (i == ARRAY_SIZE(adv761x_cfmts)) { + /* Unsupported format requested. Propose the current one */ + mf->colorspace = state->cfmt->colorspace; + mf->code = state->cfmt->code; + } else { + /* Also return the colorspace */ + mf->colorspace = adv761x_cfmts[i].colorspace; + } + + /* Get video information */ + ret = adv761x_get_vid_info(sd, &progressive, &width, &height); + if (ret < 0) { + width = ADV761X_MAX_WIDTH; + height = ADV761X_MAX_HEIGHT; + progressive = 1; + } + + mf->width = width; + mf->height = height; + mf->field = (progressive) ? V4L2_FIELD_NONE : V4L2_FIELD_INTERLACED; + + return 0; +} + +static int adv761x_s_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct adv761x_state *state = to_state(sd); + int i, ret; + u8 progressive; + u32 width; + u32 height; + + for (i = 0; i < ARRAY_SIZE(adv761x_cfmts); i++) { + if (mf->code == adv761x_cfmts[i].code) { + state->cfmt = &adv761x_cfmts[i]; + break; + } + } + if (i >= ARRAY_SIZE(adv761x_cfmts)) + return -EINVAL; + + /* Get video information */ + ret = adv761x_get_vid_info(sd, &progressive, &width, &height); + if (ret < 0) { + width = ADV761X_MAX_WIDTH; + height = ADV761X_MAX_HEIGHT; + progressive = 1; + } + + state->width = width; + state->height = height; + state->scanmode = (progressive) ? + V4L2_FIELD_NONE : V4L2_FIELD_INTERLACED; + + mf->width = state->width; + mf->height = state->height; + mf->code = state->cfmt->code; + mf->field = state->scanmode; + mf->colorspace = state->cfmt->colorspace; + + 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_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 = { + .queryctrl = v4l2_subdev_queryctrl, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .querymenu = v4l2_subdev_querymenu, +#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, + .cropcap = adv761x_cropcap, + .g_crop = adv761x_g_crop, + .enum_mbus_fmt = adv761x_enum_mbus_fmt, + .g_mbus_fmt = adv761x_g_mbus_fmt, + .try_mbus_fmt = adv761x_try_mbus_fmt, + .s_mbus_fmt = adv761x_s_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_infoframe) + i2c_unregister_device(state->i2c_infoframe); + if (state->i2c_dpll) + i2c_unregister_device(state->i2c_dpll); + if (state->i2c_repeater) + i2c_unregister_device(state->i2c_repeater); + 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 io_reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + io_write(sd, io_reg, addr << 1); + + return i2c_new_dummy(client->adapter, io_read(sd, io_reg) >> 1); +} + +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_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 */ + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +static int adv761x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv761x_state *state; + struct v4l2_ctrl_handler *ctrl_hdl; + struct v4l2_subdev *sd; + int 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); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + state->cfmt = &adv761x_cfmts[0]; + state->width = ADV761X_MAX_WIDTH; + state->height = ADV761X_MAX_HEIGHT; + state->scanmode = V4L2_FIELD_NONE; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv761x_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + /* 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; + goto err_hdl; + } + + /* I2C clients */ + state->i2c_cec = adv761x_dummy_client(sd, i2c_cec, 0xf4); + state->i2c_infoframe = adv761x_dummy_client(sd, i2c_inf, 0xf5); + state->i2c_dpll = adv761x_dummy_client(sd, i2c_dpll, 0xf7); + state->i2c_repeater = adv761x_dummy_client(sd, i2c_rep, 0xf9); + state->i2c_edid = adv761x_dummy_client(sd, i2c_edid, 0xfa); + state->i2c_hdmi = adv761x_dummy_client(sd, i2c_hdmi, 0xfb); + state->i2c_cp = adv761x_dummy_client(sd, i2c_cp, 0xfd); + if (!state->i2c_cec || !state->i2c_infoframe || + !state->i2c_dpll || !state->i2c_repeater || + !state->i2c_edid || !state->i2c_hdmi || !state->i2c_cp) { + ret = -ENODEV; + v4l2_err(sd, "Failed to create all I2C clients\n"); + goto err_i2c; + } + + /* work queue */ + state->work_queue = create_singlethread_workqueue(client->name); + if (!state->work_queue) { + v4l2_err(sd, "Could not create work queue\n"); + ret = -ENOMEM; + goto err_i2c; + } + + INIT_DELAYED_WORK(&state->enable_hotplug, adv761x_enable_hotplug); + + state->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_init(&sd->entity, 1, &state->pad, 0); + if (ret) + goto err_work_queue; + + ret = adv761x_core_init(sd); + if (ret < 0) + goto err_entity; + + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_work_queue: + cancel_delayed_work(&state->enable_hotplug); + destroy_workqueue(state->work_queue); +err_i2c: + adv761x_unregister_clients(state); +err_hdl: + v4l2_ctrl_handler_free(ctrl_hdl); + 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); + + cancel_delayed_work(&state->enable_hotplug); + destroy_workqueue(state->work_queue); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + adv761x_unregister_clients(state); + return 0; +} + +/* Power management operations */ +#ifdef CONFIG_PM_SLEEP +static int adv761x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + /* Power off */ + return io_write(sd, 0x0c, 0x62); +} + +static int adv761x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + /* Initializes ADV761X to its default values */ + return adv761x_core_init(sd); +} + +static SIMPLE_DEV_PM_OPS(adv761x_pm_ops, adv761x_suspend, adv761x_resume); +#define ADV761X_PM_OPS (&adv761x_pm_ops) +#else /* CONFIG_PM_SLEEP */ +#define ADV761X_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static const struct i2c_device_id adv761x_id[] = { + { ADV761X_DRIVER_NAME, 0 }, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, adv761x_id); + +static struct i2c_driver adv761x_driver = { + .driver = { + .owner = THIS_MODULE, + .name = ADV761X_DRIVER_NAME, + .pm = ADV761X_PM_OPS, + }, + .probe = adv761x_probe, + .remove = adv761x_remove, + .id_table = adv761x_id, +}; + +module_i2c_driver(adv761x_driver); + +MODULE_DESCRIPTION("HDMI Receiver ADV761X video decoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/media/adv761x.h b/include/media/adv761x.h new file mode 100644 index 0000000..e6e6aea --- /dev/null +++ b/include/media/adv761x.h @@ -0,0 +1,28 @@ +/* + * 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 may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#ifndef _ADV761X_H_ +#define _ADV761X_H_ + +/* notify events */ +#define ADV761X_HOTPLUG 1 + +#endif /* _ADV761X_H_ */ -- 1.8.3.1 -- 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