diff options
author | Sanchayan Maity <maitysanchayan@gmail.com> | 2016-05-18 18:37:45 +0530 |
---|---|---|
committer | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2018-12-24 01:27:27 +0100 |
commit | 4aeb071684cd132e25a19457deb79bea9b5e07cd (patch) | |
tree | defd91cff0b6523bbdea1c14c9fe5d67564a8e9a /drivers | |
parent | 7aff8b4b0e36572bd62f6b230718d2482b5bfe65 (diff) |
media: Add support for ADV7280 decoder driver
Add support for ADV7280 decoder found on Toradex Analogue
Camera Adapter V2.0A
Signed-off-by: Sanchayan Maity <sanchayan.maity@toradex.com>
Acked-by: Marcel Ziswiler <marcel.ziswiler@toradex.com>
(cherry picked from commit 481bf320816c56c726197d6155341c2f94aeb6be)
(cherry picked from commit 7df8a0f4065f1085ec1765ddfc65b0106ba3faad)
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/media/platform/mxc/capture/Kconfig | 6 | ||||
-rw-r--r-- | drivers/media/platform/mxc/capture/Makefile | 3 | ||||
-rw-r--r-- | drivers/media/platform/mxc/capture/adv7280.c | 1080 |
3 files changed, 1089 insertions, 0 deletions
diff --git a/drivers/media/platform/mxc/capture/Kconfig b/drivers/media/platform/mxc/capture/Kconfig index b0ab7bc85d2f..e842bf6def19 100644 --- a/drivers/media/platform/mxc/capture/Kconfig +++ b/drivers/media/platform/mxc/capture/Kconfig @@ -82,6 +82,12 @@ config MXC_TVIN_ADV7180 ---help--- If you plan to use the adv7180 video decoder with your MXC system, say Y here. +config MXC_TVIN_ADV7280 + tristate "Analog Device adv7280 TV Decoder Input support" + depends on !VIDEO_MXC_EMMA_CAMERA && I2C + ---help--- + If you plan to use the adv7280 video decoder with your MXC system, say Y here. + config MXC_TVIN_MAX9526 tristate "Maxim Integrated MAX9526 NTSC/PAL Decoder Input support" depends on !VIDEO_MXC_EMMA_CAMERA && I2C diff --git a/drivers/media/platform/mxc/capture/Makefile b/drivers/media/platform/mxc/capture/Makefile index 106c3f41db45..10f0c0207529 100644 --- a/drivers/media/platform/mxc/capture/Makefile +++ b/drivers/media/platform/mxc/capture/Makefile @@ -38,6 +38,9 @@ obj-$(CONFIG_MXC_HDMI_CSI2_TC358743) += tc358743_h2c_bridge.o adv7180_tvin-objs := adv7180.o obj-$(CONFIG_MXC_TVIN_ADV7180) += adv7180_tvin.o +adv7280_tvin-objs := adv7280.o +obj-$(CONFIG_MXC_TVIN_ADV7280) += adv7280_tvin.o + max9526_tvin-objs := max9526.o obj-$(CONFIG_MXC_TVIN_MAX9526) += max9526_tvin.o diff --git a/drivers/media/platform/mxc/capture/adv7280.c b/drivers/media/platform/mxc/capture/adv7280.c new file mode 100644 index 000000000000..b31e09ab9414 --- /dev/null +++ b/drivers/media/platform/mxc/capture/adv7280.c @@ -0,0 +1,1080 @@ +/* + * Copyright 2016 Toradex AG. + * + * Based on adv7180/max9526 driver for iMX6 and adv7280 driver for Tegra + * by Antmicro + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pinctrl/consumer.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-chip-ident.h> +#include "v4l2-int-device.h" +#include "mxc_v4l2_capture.h" + +#define ADV7280_VOLTAGE_ANALOG 1800000 +#define ADV7280_VOLTAGE_DIGITAL_CORE 1800000 +#define ADV7280_VOLTAGE_DIGITAL_IO 3300000 +#define ADV7280_VOLTAGE_PLL 1800000 + +static struct regulator *dvddio_regulator; +static struct regulator *dvdd_regulator; +static struct regulator *avdd_regulator; +static struct regulator *pvdd_regulator; +static int pwn_gpio; + +struct sensor { + struct sensor_data sen; + v4l2_std_id std_id; + int rev_id; +} adv7280_data; + +typedef enum { + ADV7280_NTSC = 0, /*!< Locked on (M) NTSC video signal. */ + ADV7280_PAL, /*!< (B, G, H, I, N)PAL video signal. */ + ADV7280_NOT_LOCKED, /*!< Not locked on a signal. */ +} video_fmt_idx; + +struct video_fmt_t { + int v4l2_id; + char name[16]; + u16 raw_width; + u16 raw_height; + u16 active_width; + u16 active_height; + int frame_rate; +}; + +static const struct video_fmt_t video_fmts[] = { + { + .v4l2_id = V4L2_STD_NTSC, + .name = "NTSC", + .raw_width = 720, + .raw_height = 525, + .active_width = 720, + .active_height = 480, + .frame_rate = 30, + }, + { + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 576, + .frame_rate = 25, + }, + { + .v4l2_id = V4L2_STD_ALL, + .name = "Autodetect", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 576, + .frame_rate = 0, + }, +}; + +static video_fmt_idx video_idx = ADV7280_PAL; + +static DEFINE_MUTEX(mutex); + +#define IF_NAME "adv7280" + +#define ADV7280_INPUT_CONTROL 0x00 +#define ADV7280_VIDEO_SELECTION_1 0x01 +#define ADV7280_VIDEO_SELECTION_2 0x02 +#define ADV7280_OUTPUT_CONTROL 0x03 +#define ADV7280_EXTENDED_OUTPUT_CONTROL 0x04 +#define ADV7280_AUTODETECT_ENABLE 0x07 +#define ADV7280_BRIGHTNESS 0x0A +#define ADV7280_ADI_CONTROL_1 0x0E +#define ADV7280_POWER_MANAGEMENT 0x0F +#define ADV7280_STATUS_1 0x10 +#define ADV7280_IDENT 0x11 +#define ADV7280_STATUS_3 0x13 +#define ADV7280_SHAPING_FILTER_CONTROL_1 0x17 +#define ADV7280_ADI_CONTROL_2 0x1D +#define ADV7280_PIXEL_DELAY_CONTROL 0x27 +#define ADV7280_VPP_SLAVE_ADDRESS 0xFD +#define ADV7280_OUTPUT_SYNC_SELECT_2 0x6B +#define ADV7280_SD_SATURATION_CB 0xe3 +#define ADV7280_SD_SATURATION_CR 0xe4 + +#define HW_DEINT /* Enable hardware deinterlacer */ +#define VPP_SLAVE_ADDRESS 0x42 + +#define VPP_DEINT_RESET 0x41 +#define VPP_I2C_DEINT_ENABLE 0x55 +#define VPP_ADV_TIMING_MODE_EN 0x5B + +#define ADV7280_EXTENDED_OUTPUT_CONTROL_NTSCDIS 0xC5 +#define ADV7280_AUTODETECT_DEFAULT 0x7f + +#define ADV7280_INPUT_CONTROL_COMPOSITE_IN1 0x00 +#define ADV7280_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM 0x00 + +#define ADV7280_INPUT_CONTROL_NTSC_M 0x50 +#define ADV7280_INPUT_CONTROL_PAL60 0x60 +#define ADV7280_INPUT_CONTROL_NTSC_443 0x70 +#define ADV7280_INPUT_CONTROL_PAL_BG 0x80 +#define ADV7280_INPUT_CONTROL_PAL_N 0x90 +#define ADV7280_INPUT_CONTROL_PAL_M 0xa0 +#define ADV7280_INPUT_CONTROL_PAL_M_PED 0xb0 +#define ADV7280_INPUT_CONTROL_PAL_COMB_N 0xc0 +#define ADV7280_INPUT_CONTROL_PAL_COMB_N_PED 0xd0 +#define ADV7280_INPUT_CONTROL_PAL_SECAM 0xe0 + +#define ADV7280_STATUS1_IN_LOCK 0x01 +/* Autodetection results */ +#define ADV7280_STATUS1_AUTOD_MASK 0x70 +#define ADV7280_STATUS1_AUTOD_NTSM_M_J 0x00 +#define ADV7280_STATUS1_AUTOD_NTSC_4_43 0x10 +#define ADV7280_STATUS1_AUTOD_PAL_M 0x20 +#define ADV7280_STATUS1_AUTOD_PAL_60 0x30 +#define ADV7280_STATUS1_AUTOD_PAL_B_G 0x40 +#define ADV7280_STATUS1_AUTOD_SECAM 0x50 +#define ADV7280_STATUS1_AUTOD_PAL_COMB 0x60 +#define ADV7280_STATUS1_AUTOD_SECAM_525 0x70 + +static const struct v4l2_queryctrl adv7280_qctrl[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 127, + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = 127, + .flags = 0, + } +}; + +static inline void adv7280_power_down(int enable) +{ + if (gpio_is_valid(pwn_gpio)) { + gpio_set_value_cansleep(pwn_gpio, !enable); + msleep(2); + } +} + +static int adv7280_regulator_enable(struct device *dev) +{ + int ret = 0; + + dvddio_regulator = devm_regulator_get(dev, "DOVDD"); + + if (!IS_ERR(dvddio_regulator)) { + regulator_set_voltage(dvddio_regulator, + ADV7280_VOLTAGE_DIGITAL_IO, + ADV7280_VOLTAGE_DIGITAL_IO); + ret = regulator_enable(dvddio_regulator); + if (ret) { + dev_err(dev, "set io voltage failed\n"); + return ret; + } else { + dev_dbg(dev, "set io voltage ok\n"); + } + } else { + dev_warn(dev, "cannot get io voltage\n"); + dvddio_regulator = NULL; + } + + dvdd_regulator = devm_regulator_get(dev, "DVDD"); + if (!IS_ERR(dvdd_regulator)) { + regulator_set_voltage(dvdd_regulator, + ADV7280_VOLTAGE_DIGITAL_CORE, + ADV7280_VOLTAGE_DIGITAL_CORE); + ret = regulator_enable(dvdd_regulator); + if (ret) { + dev_err(dev, "set core voltage failed\n"); + return ret; + } else { + dev_dbg(dev, "set core voltage ok\n"); + } + } else { + dev_warn(dev, "cannot get core voltage\n"); + dvdd_regulator = NULL; + } + + avdd_regulator = devm_regulator_get(dev, "AVDD"); + if (!IS_ERR(avdd_regulator)) { + regulator_set_voltage(avdd_regulator, + ADV7280_VOLTAGE_ANALOG, + ADV7280_VOLTAGE_ANALOG); + ret = regulator_enable(avdd_regulator); + if (ret) { + dev_err(dev, "set analog voltage failed\n"); + return ret; + } else { + dev_dbg(dev, "set analog voltage ok\n"); + } + } else { + dev_warn(dev, "cannot get analog voltage\n"); + avdd_regulator = NULL; + } + + pvdd_regulator = devm_regulator_get(dev, "PVDD"); + if (!IS_ERR(pvdd_regulator)) { + regulator_set_voltage(pvdd_regulator, + ADV7280_VOLTAGE_PLL, + ADV7280_VOLTAGE_PLL); + ret = regulator_enable(pvdd_regulator); + if (ret) { + dev_err(dev, "set pll voltage failed\n"); + return ret; + } else { + dev_dbg(dev, "set pll voltage ok\n"); + } + } else { + dev_warn(dev, "cannot get pll voltage\n"); + pvdd_regulator = NULL; + } + + return ret; +} + +static inline int adv7280_read(u8 reg) +{ + int val; + + val = i2c_smbus_read_byte_data(adv7280_data.sen.i2c_client, reg); + if (val < 0) { + dev_dbg(&adv7280_data.sen.i2c_client->dev, + "%s:read reg error: reg=%2x\n", __func__, reg); + return val; + } + + dev_dbg(&adv7280_data.sen.i2c_client->dev, + "%s:read reg 0x%2x, val: 0x%2x\n", __func__, reg, val); + + return val; +} + +static int adv7280_write_reg(u8 reg, u8 val) +{ + s32 ret; + + dev_dbg(&adv7280_data.sen.i2c_client->dev, + "%s:write reg 0x%2x, val: 0x%2x\n", __func__, reg, val); + + ret = i2c_smbus_write_byte_data(adv7280_data.sen.i2c_client, reg, val); + if (ret < 0) { + dev_dbg(&adv7280_data.sen.i2c_client->dev, + "%s:write reg error:reg=%2x,val=%2x\n", __func__, + reg, val); + return ret; + } + + return 0; +} + +static int adv7280_write_reg_with_client(struct i2c_client *client, + u8 reg, u8 val) +{ + s32 ret; + + dev_dbg(&client->dev, + "%s:write reg 0x%2x, val: 0x%2x\n", __func__, reg, val); + + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret < 0) { + dev_dbg(&client->dev, + "%s:write reg error:reg=%2x,val=%2x\n", __func__, + reg, val); + return ret; + } + + return 0; +} + +static void adv7280_get_std(v4l2_std_id *std) +{ + int status_1, standard, idx; + bool locked = 0; + + dev_dbg(&adv7280_data.sen.i2c_client->dev, "In %s\n", __func__); + + status_1 = adv7280_read(ADV7280_STATUS_1); + locked = status_1 & ADV7280_STATUS1_IN_LOCK; + standard = status_1 & ADV7280_STATUS1_AUTOD_MASK; + + mutex_lock(&mutex); + *std = V4L2_STD_ALL; + idx = ADV7280_NOT_LOCKED; + if (locked) { + if (standard == ADV7280_STATUS1_AUTOD_PAL_B_G) { + *std = V4L2_STD_PAL; + idx = ADV7280_PAL; + } else if (standard == ADV7280_STATUS1_AUTOD_NTSM_M_J) { + *std = V4L2_STD_NTSC; + idx = ADV7280_NTSC; + } + } + mutex_unlock(&mutex); + + /* This assumes autodetect which this device uses. */ + if (*std != adv7280_data.std_id) { + video_idx = idx; + adv7280_data.std_id = *std; + adv7280_data.sen.pix.width = video_fmts[video_idx].raw_width; + adv7280_data.sen.pix.height = video_fmts[video_idx].raw_height; + } +} + +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + struct device *dev = &adv7280_data.sen.i2c_client->dev; + + dev_dbg(dev, "adv7280: %s\n", __func__); + + if (s == NULL) { + dev_err(dev, " ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->if_type = V4L2_IF_TYPE_BT656; /* This is the only possibility. */ +#if 0 + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.nobt_hs_inv = 1; + p->u.bt656.bt_sync_correct = 1; +#else + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.nobt_hs_inv = 0; + p->u.bt656.bt_sync_correct = 0; +#ifdef HW_DEINT + p->u.bt656.clock_curr = 1; /* BT656 de-interlace clock mode */ +#endif +#endif + /* ADV7280 has a dedicated clock so no clock settings needed. */ + + return 0; +} + +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + dev_dbg(&adv7280_data.sen.i2c_client->dev, "adv7280: %s\n", __func__); + + if (on && !sensor->sen.on) { + if (adv7280_write_reg(ADV7280_POWER_MANAGEMENT, 0x04) != 0) + return -EIO; + + msleep(400); + + } else if (!on && sensor->sen.on) { + if (adv7280_write_reg(ADV7280_POWER_MANAGEMENT, 0x24) != 0) + return -EIO; + } + + sensor->sen.on = on; + + return 0; +} + +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + struct device *dev = &adv7280_data.sen.i2c_client->dev; + + dev_dbg(dev, "adv7280: %s\n", __func__); + + switch (a->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + dev_dbg(dev, " type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->sen.streamcap.capability; + cparm->timeperframe = sensor->sen.streamcap.timeperframe; + cparm->capturemode = sensor->sen.streamcap.capturemode; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + break; + + default: + dev_dbg(dev, "ioctl_g_parm:type is unknown %d\n", a->type); + break; + } + + return 0; +} + +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct device *dev = &adv7280_data.sen.i2c_client->dev; + + dev_dbg(dev, "adv7280: %s\n", __func__); + + switch (a->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + break; + + default: + dev_dbg(dev, " type is unknown - %d\n", a->type); + break; + } + + return 0; +} + +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct device *dev = &adv7280_data.sen.i2c_client->dev; + struct sensor *sensor = s->priv; + v4l2_std_id std; + + dev_dbg(&adv7280_data.sen.i2c_client->dev, "adv7280: %s\n", __func__); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + dev_dbg(dev, " Returning size of %dx%d\n", + sensor->sen.pix.width, sensor->sen.pix.height); + f->fmt.pix = sensor->sen.pix; + break; + + case V4L2_BUF_TYPE_PRIVATE: + adv7280_get_std(&std); + f->fmt.pix.pixelformat = (u32)std; + break; + + default: + f->fmt.pix = sensor->sen.pix; + break; + } + + return 0; +} + +static int ioctl_queryctrl(struct v4l2_int_device *s, + struct v4l2_queryctrl *qc) +{ + int i; + + dev_dbg(&adv7280_data.sen.i2c_client->dev, "adv7280: %s\n", __func__); + + for (i = 0; i < ARRAY_SIZE(adv7280_qctrl); i++) + if (qc->id && qc->id == adv7280_qctrl[i].id) { + *qc = adv7280_qctrl[i]; + return 0; + } + + return -EINVAL; +} + +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + int sat = 0; + + dev_dbg(&adv7280_data.sen.i2c_client->dev, "adv7280: %s\n", __func__); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_BRIGHTNESS\n"); + adv7280_data.sen.brightness = adv7280_read(ADV7280_BRIGHTNESS); + vc->value = adv7280_data.sen.brightness; + break; + case V4L2_CID_CONTRAST: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_CONTRAST\n"); + vc->value = adv7280_data.sen.contrast; + break; + case V4L2_CID_SATURATION: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_SATURATION\n"); + sat = adv7280_read(ADV7280_SD_SATURATION_CB); + adv7280_data.sen.saturation = sat; + vc->value = adv7280_data.sen.saturation; + break; + case V4L2_CID_HUE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_HUE\n"); + vc->value = adv7280_data.sen.hue; + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_RED_BALANCE\n"); + vc->value = adv7280_data.sen.red; + break; + case V4L2_CID_BLUE_BALANCE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_BLUE_BALANCE\n"); + vc->value = adv7280_data.sen.blue; + break; + case V4L2_CID_GAMMA: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_EXPOSURE\n"); + vc->value = adv7280_data.sen.ae_mode; + break; + case V4L2_CID_AUTOGAIN: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_VFLIP\n"); + break; + default: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " Default case\n"); + vc->value = 0; + ret = -EINVAL; + break; + } + + return ret; +} + +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + u8 tmp; + + dev_dbg(&adv7280_data.sen.i2c_client->dev, "adv7280: %s\n", __func__); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_BRIGHTNESS\n"); + tmp = vc->value; + adv7280_write_reg(ADV7280_BRIGHTNESS, tmp); + adv7280_data.sen.brightness = vc->value; + break; + case V4L2_CID_CONTRAST: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_SATURATION\n"); + tmp = vc->value; + adv7280_write_reg(ADV7280_SD_SATURATION_CB, tmp); + adv7280_write_reg(ADV7280_SD_SATURATION_CR, tmp); + adv7280_data.sen.saturation = vc->value; + break; + case V4L2_CID_HUE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_EXPOSURE\n"); + break; + case V4L2_CID_AUTOGAIN: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " V4L2_CID_VFLIP\n"); + break; + default: + dev_dbg(&adv7280_data.sen.i2c_client->dev, + " Default case\n"); + retval = -EINVAL; + break; + } + + return retval; +} + +static int ioctl_enum_framesizes(struct v4l2_int_device *s, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index >= 1) + return -EINVAL; + + fsize->discrete.width = video_fmts[video_idx].active_width; + fsize->discrete.height = video_fmts[video_idx].active_height; + + return 0; +} + +static int ioctl_enum_frameintervals(struct v4l2_int_device *s, + struct v4l2_frmivalenum *fival) +{ + struct video_fmt_t fmt; + int i; + + if (fival->index != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(video_fmts) - 1; i++) { + fmt = video_fmts[i]; + if (fival->width == fmt.active_width && + fival->height == fmt.active_height) { + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1; + fival->discrete.denominator = fmt.frame_rate; + return 0; + } + } + + return -EINVAL; +} + +static int ioctl_g_chip_ident(struct v4l2_int_device *s, int *id) +{ + ((struct v4l2_dbg_chip_ident *)id)->match.type = + V4L2_CHIP_MATCH_I2C_DRIVER; + strcpy(((struct v4l2_dbg_chip_ident *)id)->match.name, + "adv7280_decoder"); + ((struct v4l2_dbg_chip_ident *)id)->ident = V4L2_IDENT_ADV7280; + + return 0; +} + +static int ioctl_init(struct v4l2_int_device *s) +{ + dev_dbg(&adv7280_data.sen.i2c_client->dev, "adv7280: %s\n", __func__); + + return 0; +} + +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + dev_dbg(&adv7280_data.sen.i2c_client->dev, "adv7280: %s\n", __func__); + + return 0; +} + +static struct v4l2_int_ioctl_desc adv7280_ioctl_desc[] = { + + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func*)ioctl_dev_init}, + + {vidioc_int_s_power_num, (v4l2_int_ioctl_func*)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func*)ioctl_g_ifparm}, + {vidioc_int_init_num, (v4l2_int_ioctl_func*)ioctl_init}, + + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func*)ioctl_g_fmt_cap}, + + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func*)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func*)ioctl_s_parm}, + {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func*)ioctl_queryctrl}, + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func*)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func*)ioctl_s_ctrl}, + {vidioc_int_enum_framesizes_num, + (v4l2_int_ioctl_func *)ioctl_enum_framesizes}, + {vidioc_int_enum_frameintervals_num, + (v4l2_int_ioctl_func *) + ioctl_enum_frameintervals}, + {vidioc_int_g_chip_ident_num, + (v4l2_int_ioctl_func *)ioctl_g_chip_ident}, +}; + +static struct v4l2_int_slave adv7280_slave = { + .ioctls = adv7280_ioctl_desc, + .num_ioctls = ARRAY_SIZE(adv7280_ioctl_desc), +}; + +static struct v4l2_int_device adv7280_int_device = { + .module = THIS_MODULE, + .name = "adv7280", + .type = v4l2_int_type_slave, + .u = { + .slave = &adv7280_slave, + }, +}; + +static int adv7280_hard_reset(void) +{ + int ret; + + struct i2c_client vpp_client = { + .flags = adv7280_data.sen.i2c_client->flags, + .addr = VPP_SLAVE_ADDRESS, + .name = "ADV7280_VPP", + .adapter = adv7280_data.sen.i2c_client->adapter, + .dev = adv7280_data.sen.i2c_client->dev, + }; + + dev_dbg(&adv7280_data.sen.i2c_client->dev, + "In %s\n", __func__); + + /* Reset */ + ret = adv7280_write_reg(ADV7280_POWER_MANAGEMENT, 0xA0); + if (ret < 0) + goto error; + msleep(10); + + /* Initialize adv7280 */ + /* Exit Power Down Mode */ + ret = adv7280_write_reg(ADV7280_POWER_MANAGEMENT, 0x00); + if (ret < 0) + goto error; + + /* analog devices recommends */ + ret = adv7280_write_reg(ADV7280_ADI_CONTROL_1, 0x80); + if (ret < 0) + goto error; + + /* analog devices recommends */ + ret = adv7280_write_reg(0x9C, 0x00); + if (ret < 0) + goto error; + + /* analog devices recommends */ + ret = adv7280_write_reg(0x9C, 0xFF); + if (ret < 0) + goto error; + + /* Enter User Sub Map */ + ret = adv7280_write_reg(ADV7280_ADI_CONTROL_1, 0x00); + if (ret < 0) + goto error; + + /* Enable Pixel & Sync output drivers */ + ret = adv7280_write_reg(ADV7280_OUTPUT_CONTROL, 0x0C); + if (ret < 0) + goto error; + + /* Power-up INTRQ, HS & VS pads */ + ret = adv7280_write_reg(ADV7280_EXTENDED_OUTPUT_CONTROL, 0x07); + if (ret < 0) + goto error; + + /* Enable SH1 */ + /* + ret = adv7280_write_reg(ADV7280_SHAPING_FILTER_CONTROL_1, 0x41); + if (ret < 0) + goto error; + */ + + /* Disable comb filtering */ + /* + ret = adv7280_write_reg(0x39, 0x24); + if (ret < 0) + goto error; + */ + + /* Enable LLC output driver */ + ret = adv7280_write_reg(ADV7280_ADI_CONTROL_2, 0x40); + if (ret < 0) + goto error; + + /* VSYNC on VS/FIELD/SFL pin */ + ret = adv7280_write_reg(ADV7280_OUTPUT_SYNC_SELECT_2, 0x01); + if (ret < 0) + goto error; + + /* Enable autodetection */ + ret = adv7280_write_reg(ADV7280_INPUT_CONTROL, + ADV7280_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM | + (ADV7280_INPUT_CONTROL_COMPOSITE_IN1 + + 0)); + if (ret < 0) + goto error; + + ret = adv7280_write_reg(ADV7280_AUTODETECT_ENABLE, + ADV7280_AUTODETECT_DEFAULT); + if (ret < 0) + goto error; + + /* ITU-R BT.656-4 compatible */ + /*ret = adv7280_write_reg(ADV7280_EXTENDED_OUTPUT_CONTROL, + ADV7280_EXTENDED_OUTPUT_CONTROL_NTSCDIS); + if (ret < 0) + goto error; + */ + + /* analog devices recommends */ + ret = adv7280_write_reg(0x52, 0xCD); + if (ret < 0) + goto error; + + /* analog devices recommends */ + ret = adv7280_write_reg(0x80, 0x51); + if (ret < 0) + goto error; + + /* analog devices recommends */ + ret = adv7280_write_reg(0x81, 0x51); + if (ret < 0) + goto error; + + /* analog devices recommends */ + ret = adv7280_write_reg(0x82, 0x68); + if (ret < 0) + goto error; + +#ifdef HW_DEINT + /* Set VPP Map */ + ret = adv7280_write_reg(ADV7280_VPP_SLAVE_ADDRESS, (VPP_SLAVE_ADDRESS << 1)); + if (ret < 0) + goto error; + + /* VPP - not documented */ + ret = adv7280_write_reg_with_client(&vpp_client, + 0xA3, 0x00); + if (ret < 0) + goto error; + + /* VPP - Enbable Advanced Timing Mode */ + ret = adv7280_write_reg_with_client(&vpp_client, + VPP_ADV_TIMING_MODE_EN, 0x00); + if (ret < 0) + goto error; + + /* VPP - Enable Deinterlacer */ + ret = adv7280_write_reg_with_client(&vpp_client, + VPP_I2C_DEINT_ENABLE, 0x80); + if (ret < 0) + goto error; +#endif + + return 0; + +error: + return ret; +} + +static int adv7280_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct pinctrl *pinctrl; + struct device *dev = &client->dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "Required functionality not supported by I2C adapter\n"); + return -EIO; + } + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + pinctrl = devm_pinctrl_get_select_default(dev); + if (IS_ERR(pinctrl)) { + dev_err(dev, "setup pinctrl failed\n"); + return PTR_ERR(pinctrl); + } + + pwn_gpio = of_get_named_gpio(dev->of_node, "pwn-gpios", 0); + if (!gpio_is_valid(pwn_gpio)) + dev_warn(dev, "no sensor pwdn pin available\n"); + else { + ret = devm_gpio_request_one(dev, pwn_gpio, GPIOF_OUT_INIT_HIGH, + "adv7280_pwdn"); + if (ret < 0) + dev_err(dev, "no power pin available!\n"); + } + + adv7280_regulator_enable(dev); + + adv7280_power_down(0); + + msleep(1); + + memset(&adv7280_data, 0, sizeof(adv7280_data)); + adv7280_data.sen.i2c_client = client; + adv7280_data.sen.streamcap.timeperframe.denominator = 30; + adv7280_data.sen.streamcap.timeperframe.numerator = 1; + adv7280_data.std_id = V4L2_STD_ALL; + video_idx = ADV7280_NOT_LOCKED; + adv7280_data.sen.pix.width = video_fmts[video_idx].raw_width; + adv7280_data.sen.pix.height = video_fmts[video_idx].raw_height; + adv7280_data.sen.pix.pixelformat = V4L2_PIX_FMT_UYVY; /* YUV422 */ + adv7280_data.sen.pix.priv = 1; /* 1 is used to indicate TV in */ + adv7280_data.sen.on = true; + + adv7280_data.sen.sensor_clk = devm_clk_get(dev, "csi_mclk"); + if (IS_ERR(adv7280_data.sen.sensor_clk)) { + dev_err(dev, "get mclk failed\n"); + return PTR_ERR(adv7280_data.sen.sensor_clk); + } + + ret = of_property_read_u32(dev->of_node, "mclk", + &adv7280_data.sen.mclk); + if (ret) { + dev_err(dev, "mclk frequency is invalid\n"); + return ret; + } + + ret = of_property_read_u32( + dev->of_node, "mclk_source", + (u32 *) &(adv7280_data.sen.mclk_source)); + if (ret) { + dev_err(dev, "mclk_source invalid\n"); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "csi_id", + &(adv7280_data.sen.csi)); + if (ret) { + dev_err(dev, "csi_id invalid\n"); + return ret; + } + + /* ADV7280 is always parallel IF */ + adv7280_data.sen.mipi_camera = 0; + + clk_prepare_enable(adv7280_data.sen.sensor_clk); + + adv7280_data.rev_id = adv7280_read(ADV7280_IDENT); + switch (adv7280_data.rev_id) { + case 0x42: + dev_dbg(dev, + "%s:Analog Device adv7280 ident %2X detected!\n", + __func__, adv7280_data.rev_id); + break; + default: + dev_err(dev, + "%s:Analog Device adv7280 not detected %d!\n", __func__, + adv7280_data.rev_id); + return -ENODEV; + + } + + dev_dbg(dev, " type is %d (expect %d)\n", + adv7280_int_device.type, v4l2_int_type_slave); + dev_dbg(dev, " num ioctls is %d\n", + adv7280_int_device.u.slave->num_ioctls); + + ret = adv7280_hard_reset(); + if (ret) { + dev_err(dev, "error resetting adv7280\n"); + return ret; + } + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the adv7280_data structure here.*/ + adv7280_int_device.priv = &adv7280_data; + ret = v4l2_int_device_register(&adv7280_int_device); + + clk_disable_unprepare(adv7280_data.sen.sensor_clk); + + return 0; +} + +static int adv7280_detach(struct i2c_client *client) +{ + dev_dbg(&adv7280_data.sen.i2c_client->dev, + "%s:Removing %s video decoder @ 0x%02X from adapter %s\n", + __func__, IF_NAME, client->addr << 1, client->adapter->name); + + adv7280_write_reg(ADV7280_POWER_MANAGEMENT, 0x24); + + if (dvddio_regulator) + regulator_disable(dvddio_regulator); + + if (dvdd_regulator) + regulator_disable(dvdd_regulator); + + if (avdd_regulator) + regulator_disable(avdd_regulator); + + if (pvdd_regulator) + regulator_disable(pvdd_regulator); + + v4l2_int_device_unregister(&adv7280_int_device); + + return 0; +} + +static const struct i2c_device_id adv7280_id[] = { + {"adv7280", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, adv7280_id); + +static struct i2c_driver adv7280_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "adv7280", + }, + .probe = adv7280_probe, + .remove = adv7280_detach, + .id_table = adv7280_id, +}; +module_i2c_driver(adv7280_i2c_driver); + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Analog Device ADV7280 video decoder driver"); +MODULE_LICENSE("GPL"); |