From patchwork Fri Nov 15 12:54:31 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Valentine Barshak X-Patchwork-Id: 3188131 Return-Path: X-Original-To: patchwork-linux-media@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 0A54B9F39E for ; Fri, 15 Nov 2013 12:54:49 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id AD086207CB for ; Fri, 15 Nov 2013 12:54:46 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id A159F207C9 for ; Fri, 15 Nov 2013 12:54:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751601Ab3KOMyi (ORCPT ); Fri, 15 Nov 2013 07:54:38 -0500 Received: from mail-lb0-f179.google.com ([209.85.217.179]:32935 "EHLO mail-lb0-f179.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751595Ab3KOMyg (ORCPT ); Fri, 15 Nov 2013 07:54:36 -0500 Received: by mail-lb0-f179.google.com with SMTP id l4so237342lbv.10 for ; Fri, 15 Nov 2013 04:54:35 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=BSMmybcgC3rQgQfVFCaaJpAcovmf/77Vp/47KfyoDnA=; b=WnTYHrAc84E5u+85jSeCFo3GqOxy43Cy0+FPUmwgEJl1bvo/l92mZClIWVlyd+yIkg mWLFASFl5OTmNFiSDQ4wYxTkIuNi/TgBv/FZ6qPplZA01XJ83K3MoVy3i313Zvib3LTB IaWLx96kMSD+X4XiqxzSAaC2mKa11OfVpgKVk28ORCSBRR6cEiH34/JBL7RtjyEdbGfu y5+ZmZZSjpem9OqZQmf0WXmdyT9/EQYWXT2qEghCMX2Le9NkzYL6HLIG57/bjoiQcDD5 IjU2tvSn+pRaHiQlJ779ufDmbJlA1eV9CRqmFUzBO7YFpXy7UEnJ0CbZx5Bz0Zm2RnZb 8HRg== X-Gm-Message-State: ALoCoQlUVS7xJGX/RNHRO7P/f5t9BepLLNxaSQfHgQqIjTgjBGjvNVCL/vAqRQkz5QFHwBPsbvIc X-Received: by 10.152.1.234 with SMTP id 10mr3760154lap.19.1384520074878; Fri, 15 Nov 2013 04:54:34 -0800 (PST) Received: from black.localnet ([93.100.122.208]) by mx.google.com with ESMTPSA id ew3sm2969758lac.1.2013.11.15.04.54.33 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 15 Nov 2013 04:54:33 -0800 (PST) From: Valentine Barshak To: linux-media@vger.kernel.org Cc: Mauro Carvalho Chehab , Hans Verkuil , Laurent Pinchart , Guennadi Liakhovetski , Simon Horman Subject: [PATCH V2] media: i2c: Add ADV761X support Date: Fri, 15 Nov 2013 16:54:31 +0400 Message-Id: <1384520071-16463-1-git-send-email-valentine.barshak@cogentembedded.com> X-Mailer: git-send-email 1.8.3.1 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); 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_ */