From f902c1e95d5dfe5102f8467d69dc51d505f832ee Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 7 Jun 2017 11:46:12 -0300 Subject: [media] cec: add CEC_CAP_NEEDS_HPD Add a new capability CEC_CAP_NEEDS_HPD. If this capability is set then the hardware can only use CEC if the HDMI Hotplug Detect pin is high. Such hardware cannot handle the corner case in the CEC specification where it is possible to transmit messages even if no hotplug signal is present (needed for some displays that turn off the HPD when in standby, but still have CEC enabled). Typically hardware that needs this capability have the HPD wired to the CEC block, often to a 'power' or 'active' pin. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- include/uapi/linux/cec.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/cec.h b/include/uapi/linux/cec.h index a0dfe27bc6c7..44579a24f95d 100644 --- a/include/uapi/linux/cec.h +++ b/include/uapi/linux/cec.h @@ -336,6 +336,8 @@ static inline int cec_is_unconfigured(__u16 log_addr_mask) #define CEC_CAP_RC (1 << 4) /* Hardware can monitor all messages, not just directed and broadcast. */ #define CEC_CAP_MONITOR_ALL (1 << 5) +/* Hardware can use CEC only if the HDMI HPD pin is high. */ +#define CEC_CAP_NEEDS_HPD (1 << 6) /** * struct cec_caps - CEC capabilities structure. -- cgit v1.2.3 From 8d67ae25a9ea206f1ad53561511c1810d7838666 Mon Sep 17 00:00:00 2001 From: Ramesh Shanmugasundaram Date: Mon, 12 Jun 2017 10:26:13 -0300 Subject: [media] media: v4l2-ctrls: Reserve controls for MAX217X Reserve controls for MAX217X RF to Bits tuner family. These hybrid radio receiver chips are highly programmable and hence reserving 32 controls. Signed-off-by: Ramesh Shanmugasundaram Acked-by: Laurent Pinchart Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- include/uapi/linux/v4l2-controls.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 0d2e1e01fbd5..83b28b41123f 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -180,6 +180,11 @@ enum v4l2_colorfx { * We reserve 16 controls for this driver. */ #define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080) +/* The base for the max217x driver controls. + * We reserve 32 controls for this driver + */ +#define V4L2_CID_USER_MAX217X_BASE (V4L2_CID_USER_BASE + 0x1090) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ -- cgit v1.2.3 From b47b79d8a231d137ec9f9a5bef05f9e2f19a4347 Mon Sep 17 00:00:00 2001 From: Ramesh Shanmugasundaram Date: Tue, 13 Jun 2017 09:54:47 -0300 Subject: [media] media: i2c: max2175: Add MAX2175 support This patch adds driver support for the MAX2175 chip. This is Maxim Integrated's RF to Bits tuner front end chip designed for software-defined radio solutions. This driver exposes the tuner as a sub-device instance with standard and custom controls to configure the device. Signed-off-by: Ramesh Shanmugasundaram Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/media/v4l-drivers/index.rst | 1 + Documentation/media/v4l-drivers/max2175.rst | 62 ++ drivers/media/i2c/Kconfig | 12 + drivers/media/i2c/Makefile | 2 + drivers/media/i2c/max2175.c | 1453 +++++++++++++++++++++++++++ drivers/media/i2c/max2175.h | 109 ++ include/uapi/linux/max2175.h | 28 + 7 files changed, 1667 insertions(+) create mode 100644 Documentation/media/v4l-drivers/max2175.rst create mode 100644 drivers/media/i2c/max2175.c create mode 100644 drivers/media/i2c/max2175.h create mode 100644 include/uapi/linux/max2175.h (limited to 'include/uapi/linux') diff --git a/Documentation/media/v4l-drivers/index.rst b/Documentation/media/v4l-drivers/index.rst index 90fe22a6414a..2e24d6806052 100644 --- a/Documentation/media/v4l-drivers/index.rst +++ b/Documentation/media/v4l-drivers/index.rst @@ -42,6 +42,7 @@ For more details see the file COPYING in the source distribution of Linux. davinci-vpbe fimc ivtv + max2175 meye omap3isp omap4_camera diff --git a/Documentation/media/v4l-drivers/max2175.rst b/Documentation/media/v4l-drivers/max2175.rst new file mode 100644 index 000000000000..04478c25d57a --- /dev/null +++ b/Documentation/media/v4l-drivers/max2175.rst @@ -0,0 +1,62 @@ +Maxim Integrated MAX2175 RF to bits tuner driver +================================================ + +The MAX2175 driver implements the following driver-specific controls: + +``V4L2_CID_MAX2175_I2S_ENABLE`` +------------------------------- + Enable/Disable I2S output of the tuner. This is a private control + that can be accessed only using the subdev interface. + Refer to Documentation/media/kapi/v4l2-controls for more details. + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 4 + + * - ``(0)`` + - I2S output is disabled. + * - ``(1)`` + - I2S output is enabled. + +``V4L2_CID_MAX2175_HSLS`` +------------------------- + The high-side/low-side (HSLS) control of the tuner for a given band. + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 4 + + * - ``(0)`` + - The LO frequency position is below the desired frequency. + * - ``(1)`` + - The LO frequency position is above the desired frequency. + +``V4L2_CID_MAX2175_RX_MODE (menu)`` +----------------------------------- + The Rx mode controls a number of preset parameters of the tuner like + sample clock (sck), sampling rate etc. These multiple settings are + provided under one single label called Rx mode in the datasheet. The + list below shows the supported modes with a brief description. + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 4 + + * - ``"Europe modes"`` + * - ``"FM 1.2" (0)`` + - This configures FM band with a sample rate of 0.512 million + samples/sec with a 10.24 MHz sck. + * - ``"DAB 1.2" (1)`` + - This configures VHF band with a sample rate of 2.048 million + samples/sec with a 32.768 MHz sck. + + * - ``"North America modes"`` + * - ``"FM 1.0" (0)`` + - This configures FM band with a sample rate of 0.7441875 million + samples/sec with a 14.88375 MHz sck. + * - ``"DAB 1.2" (1)`` + - This configures FM band with a sample rate of 0.372 million + samples/sec with a 7.441875 MHz sck. diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index c380e2475c82..c0e6e78883b0 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -796,6 +796,18 @@ config VIDEO_SAA6752HS To compile this driver as a module, choose M here: the module will be called saa6752hs. +comment "SDR tuner chips" + +config SDR_MAX2175 + tristate "Maxim 2175 RF to Bits tuner" + depends on VIDEO_V4L2 && MEDIA_SDR_SUPPORT && I2C + ---help--- + Support for Maxim 2175 tuner. It is an advanced analog/digital + radio receiver with RF-to-Bits front-end designed for SDR solutions. + + To compile this driver as a module, choose M here; the + module will be called max2175. + comment "Miscellaneous helper chips" config VIDEO_THS7303 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 62323ec66be8..5a4a761f7383 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -86,3 +86,5 @@ obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o + +obj-$(CONFIG_SDR_MAX2175) += max2175.o diff --git a/drivers/media/i2c/max2175.c b/drivers/media/i2c/max2175.c new file mode 100644 index 000000000000..0d28a80f8ed2 --- /dev/null +++ b/drivers/media/i2c/max2175.c @@ -0,0 +1,1453 @@ +/* + * Maxim Integrated MAX2175 RF to Bits tuner driver + * + * This driver & most of the hard coded values are based on the reference + * application delivered by Maxim for this device. + * + * Copyright (C) 2016 Maxim Integrated Products + * Copyright (C) 2017 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 "max2175.h" + +#define DRIVER_NAME "max2175" + +#define mxm_dbg(ctx, fmt, arg...) dev_dbg(&ctx->client->dev, fmt, ## arg) +#define mxm_err(ctx, fmt, arg...) dev_err(&ctx->client->dev, fmt, ## arg) + +/* Rx mode */ +struct max2175_rxmode { + enum max2175_band band; /* Associated band */ + u32 freq; /* Default freq in Hz */ + u8 i2s_word_size; /* Bit value */ +}; + +/* Register map to define preset values */ +struct max2175_reg_map { + u8 idx; /* Register index */ + u8 val; /* Register value */ +}; + +static const struct max2175_rxmode eu_rx_modes[] = { + /* EU modes */ + [MAX2175_EU_FM_1_2] = { MAX2175_BAND_FM, 98256000, 1 }, + [MAX2175_DAB_1_2] = { MAX2175_BAND_VHF, 182640000, 0 }, +}; + +static const struct max2175_rxmode na_rx_modes[] = { + /* NA modes */ + [MAX2175_NA_FM_1_0] = { MAX2175_BAND_FM, 98255520, 1 }, + [MAX2175_NA_FM_2_0] = { MAX2175_BAND_FM, 98255520, 6 }, +}; + +/* + * Preset values: + * Based on Maxim MAX2175 Register Table revision: 130p10 + */ +static const u8 full_fm_eu_1p0[] = { + 0x15, 0x04, 0xb8, 0xe3, 0x35, 0x18, 0x7c, 0x00, + 0x00, 0x7d, 0x40, 0x08, 0x70, 0x7a, 0x88, 0x91, + 0x61, 0x61, 0x61, 0x61, 0x5a, 0x0f, 0x34, 0x1c, + 0x14, 0x88, 0x33, 0x02, 0x00, 0x09, 0x00, 0x65, + 0x9f, 0x2b, 0x80, 0x00, 0x95, 0x05, 0x2c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x4a, 0x08, 0xa8, 0x0e, 0x0e, 0x2f, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x5e, 0xa9, + 0xae, 0xbb, 0x57, 0x18, 0x3b, 0x03, 0x3b, 0x64, + 0x40, 0x60, 0x00, 0x2a, 0xbf, 0x3f, 0xff, 0x9f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0xff, 0xfc, 0xef, 0x1c, 0x40, 0x00, 0x00, 0x02, + 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, + 0x00, 0x47, 0x00, 0x00, 0x11, 0x3f, 0x22, 0x00, + 0xf1, 0x00, 0x41, 0x03, 0xb0, 0x00, 0x00, 0x00, + 0x1b, +}; + +static const u8 full_fm_na_1p0[] = { + 0x13, 0x08, 0x8d, 0xc0, 0x35, 0x18, 0x7d, 0x3f, + 0x7d, 0x75, 0x40, 0x08, 0x70, 0x7a, 0x88, 0x91, + 0x61, 0x61, 0x61, 0x61, 0x5c, 0x0f, 0x34, 0x1c, + 0x14, 0x88, 0x33, 0x02, 0x00, 0x01, 0x00, 0x65, + 0x9f, 0x2b, 0x80, 0x00, 0x95, 0x05, 0x2c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x4a, 0x08, 0xa8, 0x0e, 0x0e, 0xaf, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x5e, 0xa9, + 0xae, 0xbb, 0x57, 0x18, 0x3b, 0x03, 0x3b, 0x64, + 0x40, 0x60, 0x00, 0x2a, 0xbf, 0x3f, 0xff, 0x9f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0xff, 0xfc, 0xef, 0x1c, 0x40, 0x00, 0x00, 0x02, + 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, + 0x00, 0x35, 0x00, 0x00, 0x11, 0x3f, 0x22, 0x00, + 0xf1, 0x00, 0x41, 0x03, 0xb0, 0x00, 0x00, 0x00, + 0x1b, +}; + +/* DAB1.2 settings */ +static const struct max2175_reg_map dab12_map[] = { + { 0x01, 0x13 }, { 0x02, 0x0d }, { 0x03, 0x15 }, { 0x04, 0x55 }, + { 0x05, 0x0a }, { 0x06, 0xa0 }, { 0x07, 0x40 }, { 0x08, 0x00 }, + { 0x09, 0x00 }, { 0x0a, 0x7d }, { 0x0b, 0x4a }, { 0x0c, 0x28 }, + { 0x0e, 0x43 }, { 0x0f, 0xb5 }, { 0x10, 0x31 }, { 0x11, 0x9e }, + { 0x12, 0x68 }, { 0x13, 0x9e }, { 0x14, 0x68 }, { 0x15, 0x58 }, + { 0x16, 0x2f }, { 0x17, 0x3f }, { 0x18, 0x40 }, { 0x1a, 0x88 }, + { 0x1b, 0xaa }, { 0x1c, 0x9a }, { 0x1d, 0x00 }, { 0x1e, 0x00 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x00 }, { 0x26, 0x00 }, + { 0x27, 0x00 }, { 0x32, 0x08 }, { 0x33, 0xf8 }, { 0x36, 0x2d }, + { 0x37, 0x7e }, { 0x55, 0xaf }, { 0x56, 0x3f }, { 0x57, 0xf8 }, + { 0x58, 0x99 }, { 0x76, 0x00 }, { 0x77, 0x00 }, { 0x78, 0x02 }, + { 0x79, 0x40 }, { 0x82, 0x00 }, { 0x83, 0x00 }, { 0x85, 0x00 }, + { 0x86, 0x20 }, +}; + +/* EU FM 1.2 settings */ +static const struct max2175_reg_map fmeu1p2_map[] = { + { 0x01, 0x15 }, { 0x02, 0x04 }, { 0x03, 0xb8 }, { 0x04, 0xe3 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7c }, { 0x08, 0x00 }, + { 0x09, 0x00 }, { 0x0a, 0x73 }, { 0x0b, 0x40 }, { 0x0c, 0x08 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5a }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0x2f }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xac }, { 0x77, 0x40 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x47 }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +/* FM NA 1.0 settings */ +static const struct max2175_reg_map fmna1p0_map[] = { + { 0x01, 0x13 }, { 0x02, 0x08 }, { 0x03, 0x8d }, { 0x04, 0xc0 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7d }, { 0x08, 0x3f }, + { 0x09, 0x7d }, { 0x0a, 0x75 }, { 0x0b, 0x40 }, { 0x0c, 0x08 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5c }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0xaf }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xa6 }, { 0x77, 0x40 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x35 }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +/* FM NA 2.0 settings */ +static const struct max2175_reg_map fmna2p0_map[] = { + { 0x01, 0x13 }, { 0x02, 0x08 }, { 0x03, 0x8d }, { 0x04, 0xc0 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7c }, { 0x08, 0x54 }, + { 0x09, 0xa7 }, { 0x0a, 0x55 }, { 0x0b, 0x42 }, { 0x0c, 0x48 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5c }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0xaf }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xac }, { 0x77, 0xc0 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x6b }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +static const u16 ch_coeff_dab1[] = { + 0x001c, 0x0007, 0xffcd, 0x0056, 0xffa4, 0x0033, 0x0027, 0xff61, + 0x010e, 0xfec0, 0x0106, 0xffb8, 0xff1c, 0x023c, 0xfcb2, 0x039b, + 0xfd4e, 0x0055, 0x036a, 0xf7de, 0x0d21, 0xee72, 0x1499, 0x6a51, +}; + +static const u16 ch_coeff_fmeu[] = { + 0x0000, 0xffff, 0x0001, 0x0002, 0xfffa, 0xffff, 0x0015, 0xffec, + 0xffde, 0x0054, 0xfff9, 0xff52, 0x00b8, 0x00a2, 0xfe0a, 0x00af, + 0x02e3, 0xfc14, 0xfe89, 0x089d, 0xfa2e, 0xf30f, 0x25be, 0x4eb6, +}; + +static const u16 eq_coeff_fmeu1_ra02_m6db[] = { + 0x0040, 0xffc6, 0xfffa, 0x002c, 0x000d, 0xff90, 0x0037, 0x006e, + 0xffc0, 0xff5b, 0x006a, 0x00f0, 0xff57, 0xfe94, 0x0112, 0x0252, + 0xfe0c, 0xfc6a, 0x0385, 0x0553, 0xfa49, 0xf789, 0x0b91, 0x1a10, +}; + +static const u16 ch_coeff_fmna[] = { + 0x0001, 0x0003, 0xfffe, 0xfff4, 0x0000, 0x001f, 0x000c, 0xffbc, + 0xffd3, 0x007d, 0x0075, 0xff33, 0xff01, 0x0131, 0x01ef, 0xfe60, + 0xfc7a, 0x020e, 0x0656, 0xfd94, 0xf395, 0x02ab, 0x2857, 0x3d3f, +}; + +static const u16 eq_coeff_fmna1_ra02_m6db[] = { + 0xfff1, 0xffe1, 0xffef, 0x000e, 0x0030, 0x002f, 0xfff6, 0xffa7, + 0xff9d, 0x000a, 0x00a2, 0x00b5, 0xffea, 0xfed9, 0xfec5, 0x003d, + 0x0217, 0x021b, 0xff5a, 0xfc2b, 0xfcbd, 0x02c4, 0x0ac3, 0x0e85, +}; + +static const u8 adc_presets[2][23] = { + { + 0x83, 0x00, 0xcf, 0xb4, 0x0f, 0x2c, 0x0c, 0x49, + 0x00, 0x00, 0x00, 0x8c, 0x02, 0x02, 0x00, 0x04, + 0xec, 0x82, 0x4b, 0xcc, 0x01, 0x88, 0x0c, + }, + { + 0x83, 0x00, 0xcf, 0xb4, 0x0f, 0x2c, 0x0c, 0x49, + 0x00, 0x00, 0x00, 0x8c, 0x02, 0x20, 0x33, 0x8c, + 0x57, 0xd7, 0x59, 0xb7, 0x65, 0x0e, 0x0c, + }, +}; + +/* Tuner bands */ +static const struct v4l2_frequency_band eu_bands_rf = { + .tuner = 0, + .type = V4L2_TUNER_RF, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 65000000, + .rangehigh = 240000000, +}; + +static const struct v4l2_frequency_band na_bands_rf = { + .tuner = 0, + .type = V4L2_TUNER_RF, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 65000000, + .rangehigh = 108000000, +}; + +/* Regmap settings */ +static const struct regmap_range max2175_regmap_volatile_range[] = { + regmap_reg_range(0x30, 0x35), + regmap_reg_range(0x3a, 0x45), + regmap_reg_range(0x59, 0x5e), + regmap_reg_range(0x73, 0x75), +}; + +static const struct regmap_access_table max2175_volatile_regs = { + .yes_ranges = max2175_regmap_volatile_range, + .n_yes_ranges = ARRAY_SIZE(max2175_regmap_volatile_range), +}; + +static const struct reg_default max2175_reg_defaults[] = { + { 0x00, 0x07}, +}; + +static const struct regmap_config max2175_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .reg_defaults = max2175_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max2175_reg_defaults), + .volatile_table = &max2175_volatile_regs, + .cache_type = REGCACHE_FLAT, +}; + +struct max2175 { + struct v4l2_subdev sd; /* Sub-device */ + struct i2c_client *client; /* I2C client */ + + /* Controls */ + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_ctrl *lna_gain; /* LNA gain value */ + struct v4l2_ctrl *if_gain; /* I/F gain value */ + struct v4l2_ctrl *pll_lock; /* PLL lock */ + struct v4l2_ctrl *i2s_en; /* I2S output enable */ + struct v4l2_ctrl *hsls; /* High-side/Low-side polarity */ + struct v4l2_ctrl *rx_mode; /* Receive mode */ + + /* Regmap */ + struct regmap *regmap; + + /* Cached configuration */ + u32 freq; /* Tuned freq In Hz */ + const struct max2175_rxmode *rx_modes; /* EU or NA modes */ + const struct v4l2_frequency_band *bands_rf; /* EU or NA bands */ + + /* Device settings */ + unsigned long xtal_freq; /* Ref Oscillator freq in Hz */ + u32 decim_ratio; + bool master; /* Master/Slave */ + bool am_hiz; /* AM Hi-Z filter */ + + /* ROM values */ + u8 rom_bbf_bw_am; + u8 rom_bbf_bw_fm; + u8 rom_bbf_bw_dab; + + /* Driver private variables */ + bool mode_resolved; /* Flag to sanity check settings */ +}; + +static inline struct max2175 *max2175_from_sd(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max2175, sd); +} + +static inline struct max2175 *max2175_from_ctrl_hdl(struct v4l2_ctrl_handler *h) +{ + return container_of(h, struct max2175, ctrl_hdl); +} + +/* Get bitval of a given val */ +static inline u8 max2175_get_bitval(u8 val, u8 msb, u8 lsb) +{ + return (val & GENMASK(msb, lsb)) >> lsb; +} + +/* Read/Write bit(s) on top of regmap */ +static int max2175_read(struct max2175 *ctx, u8 idx, u8 *val) +{ + u32 regval; + int ret; + + ret = regmap_read(ctx->regmap, idx, ®val); + if (ret) + mxm_err(ctx, "read ret(%d): idx 0x%02x\n", ret, idx); + else + *val = regval; + + return ret; +} + +static int max2175_write(struct max2175 *ctx, u8 idx, u8 val) +{ + int ret; + + ret = regmap_write(ctx->regmap, idx, val); + if (ret) + mxm_err(ctx, "write ret(%d): idx 0x%02x val 0x%02x\n", + ret, idx, val); + + return ret; +} + +static u8 max2175_read_bits(struct max2175 *ctx, u8 idx, u8 msb, u8 lsb) +{ + u8 val; + + if (max2175_read(ctx, idx, &val)) + return 0; + + return max2175_get_bitval(val, msb, lsb); +} + +static int max2175_write_bits(struct max2175 *ctx, u8 idx, + u8 msb, u8 lsb, u8 newval) +{ + int ret = regmap_update_bits(ctx->regmap, idx, GENMASK(msb, lsb), + newval << lsb); + + if (ret) + mxm_err(ctx, "wbits ret(%d): idx 0x%02x\n", ret, idx); + + return ret; +} + +static int max2175_write_bit(struct max2175 *ctx, u8 idx, u8 bit, u8 newval) +{ + return max2175_write_bits(ctx, idx, bit, bit, newval); +} + +/* Checks expected pattern every msec until timeout */ +static int max2175_poll_timeout(struct max2175 *ctx, u8 idx, u8 msb, u8 lsb, + u8 exp_bitval, u32 timeout_us) +{ + unsigned int val; + + return regmap_read_poll_timeout(ctx->regmap, idx, val, + (max2175_get_bitval(val, msb, lsb) == exp_bitval), + 1000, timeout_us); +} + +static int max2175_poll_csm_ready(struct max2175 *ctx) +{ + int ret; + + ret = max2175_poll_timeout(ctx, 69, 1, 1, 0, 50000); + if (ret) + mxm_err(ctx, "csm not ready\n"); + + return ret; +} + +#define MAX2175_IS_BAND_AM(ctx) \ + (max2175_read_bits(ctx, 5, 1, 0) == MAX2175_BAND_AM) + +#define MAX2175_IS_BAND_VHF(ctx) \ + (max2175_read_bits(ctx, 5, 1, 0) == MAX2175_BAND_VHF) + +#define MAX2175_IS_FM_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 0) + +#define MAX2175_IS_FMHD_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 1) + +#define MAX2175_IS_DAB_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 2) + +static int max2175_band_from_freq(u32 freq) +{ + if (freq >= 144000 && freq <= 26100000) + return MAX2175_BAND_AM; + else if (freq >= 65000000 && freq <= 108000000) + return MAX2175_BAND_FM; + + return MAX2175_BAND_VHF; +} + +static void max2175_i2s_enable(struct max2175 *ctx, bool enable) +{ + if (enable) + /* Stuff bits are zeroed */ + max2175_write_bits(ctx, 104, 3, 0, 2); + else + /* Keep SCK alive */ + max2175_write_bits(ctx, 104, 3, 0, 9); + mxm_dbg(ctx, "i2s %sabled\n", enable ? "en" : "dis"); +} + +static void max2175_set_filter_coeffs(struct max2175 *ctx, u8 m_sel, + u8 bank, const u16 *coeffs) +{ + unsigned int i; + u8 coeff_addr, upper_address = 24; + + mxm_dbg(ctx, "set_filter_coeffs: m_sel %d bank %d\n", m_sel, bank); + max2175_write_bits(ctx, 114, 5, 4, m_sel); + + if (m_sel == 2) + upper_address = 12; + + for (i = 0; i < upper_address; i++) { + coeff_addr = i + bank * 24; + max2175_write(ctx, 115, coeffs[i] >> 8); + max2175_write(ctx, 116, coeffs[i]); + max2175_write(ctx, 117, coeff_addr | 1 << 7); + } + max2175_write_bit(ctx, 117, 7, 0); +} + +static void max2175_load_fmeu_1p2(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmeu1p2_map); i++) + max2175_write(ctx, fmeu1p2_map[i].idx, fmeu1p2_map[i].val); + + ctx->decim_ratio = 36; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, ch_coeff_fmeu); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmeu1_ra02_m6db); +} + +static void max2175_load_dab_1p2(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dab12_map); i++) + max2175_write(ctx, dab12_map[i].idx, dab12_map[i].val); + + ctx->decim_ratio = 1; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 2, ch_coeff_dab1); +} + +static void max2175_load_fmna_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmna1p0_map); i++) + max2175_write(ctx, fmna1p0_map[i].idx, fmna1p0_map[i].val); +} + +static void max2175_load_fmna_2p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmna2p0_map); i++) + max2175_write(ctx, fmna2p0_map[i].idx, fmna2p0_map[i].val); +} + +static void max2175_set_bbfilter(struct max2175 *ctx) +{ + if (MAX2175_IS_BAND_AM(ctx)) { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_am); + mxm_dbg(ctx, "set_bbfilter AM: rom %d\n", ctx->rom_bbf_bw_am); + } else if (MAX2175_IS_DAB_MODE(ctx)) { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_dab); + mxm_dbg(ctx, "set_bbfilter DAB: rom %d\n", ctx->rom_bbf_bw_dab); + } else { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_fm); + mxm_dbg(ctx, "set_bbfilter FM: rom %d\n", ctx->rom_bbf_bw_fm); + } +} + +static bool max2175_set_csm_mode(struct max2175 *ctx, + enum max2175_csm_mode new_mode) +{ + int ret = max2175_poll_csm_ready(ctx); + + if (ret) + return ret; + + max2175_write_bits(ctx, 0, 2, 0, new_mode); + mxm_dbg(ctx, "set csm new mode %d\n", new_mode); + + /* Wait for a fixed settle down time depending on new mode */ + switch (new_mode) { + case MAX2175_PRESET_TUNE: + usleep_range(51100, 51500); /* 51.1ms */ + break; + /* + * Other mode switches need different sleep values depending on band & + * mode + */ + default: + break; + } + + return max2175_poll_csm_ready(ctx); +} + +static int max2175_csm_action(struct max2175 *ctx, + enum max2175_csm_mode action) +{ + int ret; + + mxm_dbg(ctx, "csm_action: %d\n", action); + + /* Other actions can be added in future when needed */ + ret = max2175_set_csm_mode(ctx, MAX2175_LOAD_TO_BUFFER); + if (ret) + return ret; + + return max2175_set_csm_mode(ctx, MAX2175_PRESET_TUNE); +} + +static int max2175_set_lo_freq(struct max2175 *ctx, u32 lo_freq) +{ + u8 lo_mult, loband_bits = 0, vcodiv_bits = 0; + u32 int_desired, frac_desired; + enum max2175_band band; + int ret; + + band = max2175_read_bits(ctx, 5, 1, 0); + switch (band) { + case MAX2175_BAND_AM: + lo_mult = 16; + break; + case MAX2175_BAND_FM: + if (lo_freq <= 74700000) { + lo_mult = 16; + } else if (lo_freq > 74700000 && lo_freq <= 110000000) { + loband_bits = 1; + lo_mult = 8; + } else { + loband_bits = 1; + vcodiv_bits = 3; + lo_mult = 8; + } + break; + case MAX2175_BAND_VHF: + if (lo_freq <= 210000000) + vcodiv_bits = 2; + else + vcodiv_bits = 1; + + loband_bits = 2; + lo_mult = 4; + break; + default: + loband_bits = 3; + vcodiv_bits = 2; + lo_mult = 2; + break; + } + + if (band == MAX2175_BAND_L) + lo_freq /= lo_mult; + else + lo_freq *= lo_mult; + + int_desired = lo_freq / ctx->xtal_freq; + frac_desired = div_u64((u64)(lo_freq % ctx->xtal_freq) << 20, + ctx->xtal_freq); + + /* Check CSM is not busy */ + ret = max2175_poll_csm_ready(ctx); + if (ret) + return ret; + + mxm_dbg(ctx, "lo_mult %u int %u frac %u\n", + lo_mult, int_desired, frac_desired); + + /* Write the calculated values to the appropriate registers */ + max2175_write(ctx, 1, int_desired); + max2175_write_bits(ctx, 2, 3, 0, (frac_desired >> 16) & 0xf); + max2175_write(ctx, 3, frac_desired >> 8); + max2175_write(ctx, 4, frac_desired); + max2175_write_bits(ctx, 5, 3, 2, loband_bits); + max2175_write_bits(ctx, 6, 7, 6, vcodiv_bits); + + return ret; +} + +/* + * Helper similar to DIV_ROUND_CLOSEST but an inline function that accepts s64 + * dividend and s32 divisor + */ +static inline s64 max2175_round_closest(s64 dividend, s32 divisor) +{ + if ((dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0)) + return div_s64(dividend + divisor / 2, divisor); + + return div_s64(dividend - divisor / 2, divisor); +} + +static int max2175_set_nco_freq(struct max2175 *ctx, s32 nco_freq) +{ + s32 clock_rate = ctx->xtal_freq / ctx->decim_ratio; + u32 nco_reg, abs_nco_freq = abs(nco_freq); + s64 nco_val_desired; + int ret; + + if (abs_nco_freq < clock_rate / 2) { + nco_val_desired = 2 * nco_freq; + } else { + nco_val_desired = 2 * (clock_rate - abs_nco_freq); + if (nco_freq < 0) + nco_val_desired = -nco_val_desired; + } + + nco_reg = max2175_round_closest(nco_val_desired << 20, clock_rate); + + if (nco_freq < 0) + nco_reg += 0x200000; + + /* Check CSM is not busy */ + ret = max2175_poll_csm_ready(ctx); + if (ret) + return ret; + + mxm_dbg(ctx, "freq %d desired %lld reg %u\n", + nco_freq, nco_val_desired, nco_reg); + + /* Write the calculated values to the appropriate registers */ + max2175_write_bits(ctx, 7, 4, 0, (nco_reg >> 16) & 0x1f); + max2175_write(ctx, 8, nco_reg >> 8); + max2175_write(ctx, 9, nco_reg); + + return ret; +} + +static int max2175_set_rf_freq_non_am_bands(struct max2175 *ctx, u64 freq, + u32 lo_pos) +{ + s64 adj_freq, low_if_freq; + int ret; + + mxm_dbg(ctx, "rf_freq: non AM bands\n"); + + if (MAX2175_IS_FM_MODE(ctx)) + low_if_freq = 128000; + else if (MAX2175_IS_FMHD_MODE(ctx)) + low_if_freq = 228000; + else + return max2175_set_lo_freq(ctx, freq); + + if (MAX2175_IS_BAND_VHF(ctx) == (lo_pos == MAX2175_LO_ABOVE_DESIRED)) + adj_freq = freq + low_if_freq; + else + adj_freq = freq - low_if_freq; + + ret = max2175_set_lo_freq(ctx, adj_freq); + if (ret) + return ret; + + return max2175_set_nco_freq(ctx, -low_if_freq); +} + +static int max2175_set_rf_freq(struct max2175 *ctx, u64 freq, u32 lo_pos) +{ + int ret; + + if (MAX2175_IS_BAND_AM(ctx)) + ret = max2175_set_nco_freq(ctx, freq); + else + ret = max2175_set_rf_freq_non_am_bands(ctx, freq, lo_pos); + + mxm_dbg(ctx, "set_rf_freq: ret %d freq %llu\n", ret, freq); + + return ret; +} + +static int max2175_tune_rf_freq(struct max2175 *ctx, u64 freq, u32 hsls) +{ + int ret; + + ret = max2175_set_rf_freq(ctx, freq, hsls); + if (ret) + return ret; + + ret = max2175_csm_action(ctx, MAX2175_BUFFER_PLUS_PRESET_TUNE); + if (ret) + return ret; + + mxm_dbg(ctx, "tune_rf_freq: old %u new %llu\n", ctx->freq, freq); + ctx->freq = freq; + + return ret; +} + +static void max2175_set_hsls(struct max2175 *ctx, u32 lo_pos) +{ + mxm_dbg(ctx, "set_hsls: lo_pos %u\n", lo_pos); + + if ((lo_pos == MAX2175_LO_BELOW_DESIRED) == MAX2175_IS_BAND_VHF(ctx)) + max2175_write_bit(ctx, 5, 4, 1); + else + max2175_write_bit(ctx, 5, 4, 0); +} + +static void max2175_set_eu_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + switch (rx_mode) { + case MAX2175_EU_FM_1_2: + max2175_load_fmeu_1p2(ctx); + break; + + case MAX2175_DAB_1_2: + max2175_load_dab_1p2(ctx); + break; + } + /* Master is the default setting */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); +} + +static void max2175_set_na_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + switch (rx_mode) { + case MAX2175_NA_FM_1_0: + max2175_load_fmna_1p0(ctx); + break; + case MAX2175_NA_FM_2_0: + max2175_load_fmna_2p0(ctx); + break; + } + /* Master is the default setting */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); + + ctx->decim_ratio = 27; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, ch_coeff_fmna); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmna1_ra02_m6db); +} + +static int max2175_set_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + mxm_dbg(ctx, "set_rx_mode: %u am_hiz %u\n", rx_mode, ctx->am_hiz); + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) + max2175_set_eu_rx_mode(ctx, rx_mode); + else + max2175_set_na_rx_mode(ctx, rx_mode); + + if (ctx->am_hiz) { + mxm_dbg(ctx, "setting AM HiZ related config\n"); + max2175_write_bit(ctx, 50, 5, 1); + max2175_write_bit(ctx, 90, 7, 1); + max2175_write_bits(ctx, 73, 1, 0, 2); + max2175_write_bits(ctx, 80, 5, 0, 33); + } + + /* Load BB filter trim values saved in ROM */ + max2175_set_bbfilter(ctx); + + /* Set HSLS */ + max2175_set_hsls(ctx, ctx->hsls->cur.val); + + /* Use i2s enable settings */ + max2175_i2s_enable(ctx, ctx->i2s_en->cur.val); + + ctx->mode_resolved = true; + + return 0; +} + +static int max2175_rx_mode_from_freq(struct max2175 *ctx, u32 freq, u32 *mode) +{ + unsigned int i; + int band = max2175_band_from_freq(freq); + + /* Pick the first match always */ + for (i = 0; i <= ctx->rx_mode->maximum; i++) { + if (ctx->rx_modes[i].band == band) { + *mode = i; + mxm_dbg(ctx, "rx_mode_from_freq: freq %u mode %d\n", + freq, *mode); + return 0; + } + } + + return -EINVAL; +} + +static bool max2175_freq_rx_mode_valid(struct max2175 *ctx, + u32 mode, u32 freq) +{ + int band = max2175_band_from_freq(freq); + + return (ctx->rx_modes[mode].band == band); +} + +static void max2175_load_adc_presets(struct max2175 *ctx) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(adc_presets); i++) + for (j = 0; j < ARRAY_SIZE(adc_presets[0]); j++) + max2175_write(ctx, 146 + j + i * 55, adc_presets[i][j]); +} + +static int max2175_init_power_manager(struct max2175 *ctx) +{ + int ret; + + /* Execute on-chip power-up/calibration */ + max2175_write_bit(ctx, 99, 2, 0); + usleep_range(1000, 1500); + max2175_write_bit(ctx, 99, 2, 1); + + /* Wait for the power manager to finish. */ + ret = max2175_poll_timeout(ctx, 69, 7, 7, 1, 50000); + if (ret) + mxm_err(ctx, "init pm failed\n"); + + return ret; +} + +static int max2175_recalibrate_adc(struct max2175 *ctx) +{ + int ret; + + /* ADC Re-calibration */ + max2175_write(ctx, 150, 0xff); + max2175_write(ctx, 205, 0xff); + max2175_write(ctx, 147, 0x20); + max2175_write(ctx, 147, 0x00); + max2175_write(ctx, 202, 0x20); + max2175_write(ctx, 202, 0x00); + + ret = max2175_poll_timeout(ctx, 69, 4, 3, 3, 50000); + if (ret) + mxm_err(ctx, "adc recalibration failed\n"); + + return ret; +} + +static u8 max2175_read_rom(struct max2175 *ctx, u8 row) +{ + u8 data = 0; + + max2175_write_bit(ctx, 56, 4, 0); + max2175_write_bits(ctx, 56, 3, 0, row); + + usleep_range(2000, 2500); + max2175_read(ctx, 58, &data); + + max2175_write_bits(ctx, 56, 3, 0, 0); + + mxm_dbg(ctx, "read_rom: row %d data 0x%02x\n", row, data); + + return data; +} + +static void max2175_load_from_rom(struct max2175 *ctx) +{ + u8 data = 0; + + data = max2175_read_rom(ctx, 0); + ctx->rom_bbf_bw_am = data & 0x0f; + max2175_write_bits(ctx, 81, 3, 0, data >> 4); + + data = max2175_read_rom(ctx, 1); + ctx->rom_bbf_bw_fm = data & 0x0f; + ctx->rom_bbf_bw_dab = data >> 4; + + data = max2175_read_rom(ctx, 2); + max2175_write_bits(ctx, 82, 4, 0, data & 0x1f); + max2175_write_bits(ctx, 82, 7, 5, data >> 5); + + data = max2175_read_rom(ctx, 3); + if (ctx->am_hiz) { + data &= 0x0f; + data |= (max2175_read_rom(ctx, 7) & 0x40) >> 2; + if (!data) + data |= 2; + } else { + data = (data & 0xf0) >> 4; + data |= (max2175_read_rom(ctx, 7) & 0x80) >> 3; + if (!data) + data |= 30; + } + max2175_write_bits(ctx, 80, 5, 0, data + 31); + + data = max2175_read_rom(ctx, 6); + max2175_write_bits(ctx, 81, 7, 6, data >> 6); +} + +static void max2175_load_full_fm_eu_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(full_fm_eu_1p0); i++) + max2175_write(ctx, i + 1, full_fm_eu_1p0[i]); + + usleep_range(5000, 5500); + ctx->decim_ratio = 36; +} + +static void max2175_load_full_fm_na_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(full_fm_na_1p0); i++) + max2175_write(ctx, i + 1, full_fm_na_1p0[i]); + + usleep_range(5000, 5500); + ctx->decim_ratio = 27; +} + +static int max2175_core_init(struct max2175 *ctx, u32 refout_bits) +{ + int ret; + + /* MAX2175 uses 36.864MHz clock for EU & 40.154MHz for NA region */ + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) + max2175_load_full_fm_eu_1p0(ctx); + else + max2175_load_full_fm_na_1p0(ctx); + + /* The default settings assume master */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); + + mxm_dbg(ctx, "refout_bits %u\n", refout_bits); + + /* Set REFOUT */ + max2175_write_bits(ctx, 56, 7, 5, refout_bits); + + /* ADC Reset */ + max2175_write_bit(ctx, 99, 1, 0); + usleep_range(1000, 1500); + max2175_write_bit(ctx, 99, 1, 1); + + /* Load ADC preset values */ + max2175_load_adc_presets(ctx); + + /* Initialize the power management state machine */ + ret = max2175_init_power_manager(ctx); + if (ret) + return ret; + + /* Recalibrate ADC */ + ret = max2175_recalibrate_adc(ctx); + if (ret) + return ret; + + /* Load ROM values to appropriate registers */ + max2175_load_from_rom(ctx); + + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) { + /* Load FIR coefficients into bank 0 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, + ch_coeff_fmeu); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmeu1_ra02_m6db); + } else { + /* Load FIR coefficients into bank 0 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, + ch_coeff_fmna); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmna1_ra02_m6db); + } + mxm_dbg(ctx, "core initialized\n"); + + return 0; +} + +static void max2175_s_ctrl_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + /* Load mode. Range check already done */ + max2175_set_rx_mode(ctx, rx_mode); + + mxm_dbg(ctx, "s_ctrl_rx_mode: %u curr freq %u\n", rx_mode, ctx->freq); + + /* Check if current freq valid for mode & update */ + if (max2175_freq_rx_mode_valid(ctx, rx_mode, ctx->freq)) + max2175_tune_rf_freq(ctx, ctx->freq, ctx->hsls->cur.val); + else + /* Use default freq of mode if current freq is not valid */ + max2175_tune_rf_freq(ctx, ctx->rx_modes[rx_mode].freq, + ctx->hsls->cur.val); +} + +static int max2175_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max2175 *ctx = max2175_from_ctrl_hdl(ctrl->handler); + + mxm_dbg(ctx, "s_ctrl: id 0x%x, val %u\n", ctrl->id, ctrl->val); + switch (ctrl->id) { + case V4L2_CID_MAX2175_I2S_ENABLE: + max2175_i2s_enable(ctx, ctrl->val); + break; + case V4L2_CID_MAX2175_HSLS: + max2175_set_hsls(ctx, ctrl->val); + break; + case V4L2_CID_MAX2175_RX_MODE: + max2175_s_ctrl_rx_mode(ctx, ctrl->val); + break; + } + + return 0; +} + +static u32 max2175_get_lna_gain(struct max2175 *ctx) +{ + enum max2175_band band = max2175_read_bits(ctx, 5, 1, 0); + + switch (band) { + case MAX2175_BAND_AM: + return max2175_read_bits(ctx, 51, 3, 0); + case MAX2175_BAND_FM: + return max2175_read_bits(ctx, 50, 3, 0); + case MAX2175_BAND_VHF: + return max2175_read_bits(ctx, 52, 5, 0); + default: + return 0; + } +} + +static int max2175_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max2175 *ctx = max2175_from_ctrl_hdl(ctrl->handler); + + switch (ctrl->id) { + case V4L2_CID_RF_TUNER_LNA_GAIN: + ctrl->val = max2175_get_lna_gain(ctx); + break; + case V4L2_CID_RF_TUNER_IF_GAIN: + ctrl->val = max2175_read_bits(ctx, 49, 4, 0); + break; + case V4L2_CID_RF_TUNER_PLL_LOCK: + ctrl->val = (max2175_read_bits(ctx, 60, 7, 6) == 3); + break; + } + + return 0; +}; + +static int max2175_set_freq_and_mode(struct max2175 *ctx, u32 freq) +{ + u32 rx_mode; + int ret; + + /* Get band from frequency */ + ret = max2175_rx_mode_from_freq(ctx, freq, &rx_mode); + if (ret) + return ret; + + mxm_dbg(ctx, "set_freq_and_mode: freq %u rx_mode %d\n", freq, rx_mode); + + /* Load mode */ + max2175_set_rx_mode(ctx, rx_mode); + ctx->rx_mode->cur.val = rx_mode; + + /* Tune to the new freq given */ + return max2175_tune_rf_freq(ctx, freq, ctx->hsls->cur.val); +} + +static int max2175_s_frequency(struct v4l2_subdev *sd, + const struct v4l2_frequency *vf) +{ + struct max2175 *ctx = max2175_from_sd(sd); + u32 freq; + int ret = 0; + + mxm_dbg(ctx, "s_freq: new %u curr %u, mode_resolved %d\n", + vf->frequency, ctx->freq, ctx->mode_resolved); + + if (vf->tuner != 0) + return -EINVAL; + + freq = clamp(vf->frequency, ctx->bands_rf->rangelow, + ctx->bands_rf->rangehigh); + + /* Check new freq valid for rx_mode if already resolved */ + if (ctx->mode_resolved && + max2175_freq_rx_mode_valid(ctx, ctx->rx_mode->cur.val, freq)) + ret = max2175_tune_rf_freq(ctx, freq, ctx->hsls->cur.val); + else + /* Find default rx_mode for freq and tune to it */ + ret = max2175_set_freq_and_mode(ctx, freq); + + mxm_dbg(ctx, "s_freq: ret %d curr %u mode_resolved %d mode %u\n", + ret, ctx->freq, ctx->mode_resolved, ctx->rx_mode->cur.val); + + return ret; +} + +static int max2175_g_frequency(struct v4l2_subdev *sd, + struct v4l2_frequency *vf) +{ + struct max2175 *ctx = max2175_from_sd(sd); + int ret = 0; + + if (vf->tuner != 0) + return -EINVAL; + + /* RF freq */ + vf->type = V4L2_TUNER_RF; + vf->frequency = ctx->freq; + + return ret; +} + +static int max2175_enum_freq_bands(struct v4l2_subdev *sd, + struct v4l2_frequency_band *band) +{ + struct max2175 *ctx = max2175_from_sd(sd); + + if (band->tuner != 0 || band->index != 0) + return -EINVAL; + + *band = *ctx->bands_rf; + + return 0; +} + +static int max2175_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct max2175 *ctx = max2175_from_sd(sd); + + if (vt->index > 0) + return -EINVAL; + + strlcpy(vt->name, "RF", sizeof(vt->name)); + vt->type = V4L2_TUNER_RF; + vt->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS; + vt->rangelow = ctx->bands_rf->rangelow; + vt->rangehigh = ctx->bands_rf->rangehigh; + + return 0; +} + +static int max2175_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + /* Check tuner index is valid */ + if (vt->index > 0) + return -EINVAL; + + return 0; +} + +static const struct v4l2_subdev_tuner_ops max2175_tuner_ops = { + .s_frequency = max2175_s_frequency, + .g_frequency = max2175_g_frequency, + .enum_freq_bands = max2175_enum_freq_bands, + .g_tuner = max2175_g_tuner, + .s_tuner = max2175_s_tuner, +}; + +static const struct v4l2_subdev_ops max2175_ops = { + .tuner = &max2175_tuner_ops, +}; + +static const struct v4l2_ctrl_ops max2175_ctrl_ops = { + .s_ctrl = max2175_s_ctrl, + .g_volatile_ctrl = max2175_g_volatile_ctrl, +}; + +/* + * I2S output enable/disable configuration. This is a private control. + * Refer to Documentation/media/v4l-drivers/max2175 for more details. + */ +static const struct v4l2_ctrl_config max2175_i2s_en = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_I2S_ENABLE, + .name = "I2S Enable", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .is_private = 1, +}; + +/* + * HSLS value control LO freq adjacent location configuration. + * Refer to Documentation/media/v4l-drivers/max2175 for more details. + */ +static const struct v4l2_ctrl_config max2175_hsls = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_HSLS, + .name = "HSLS Above/Below Desired", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +/* + * Rx modes below are a set of preset configurations that decides the tuner's + * sck and sample rate of transmission. They are separate for EU & NA regions. + * Refer to Documentation/media/v4l-drivers/max2175 for more details. + */ +static const char * const max2175_ctrl_eu_rx_modes[] = { + [MAX2175_EU_FM_1_2] = "EU FM 1.2", + [MAX2175_DAB_1_2] = "DAB 1.2", +}; + +static const char * const max2175_ctrl_na_rx_modes[] = { + [MAX2175_NA_FM_1_0] = "NA FM 1.0", + [MAX2175_NA_FM_2_0] = "NA FM 2.0", +}; + +static const struct v4l2_ctrl_config max2175_eu_rx_mode = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_RX_MODE, + .name = "RX Mode", + .type = V4L2_CTRL_TYPE_MENU, + .max = ARRAY_SIZE(max2175_ctrl_eu_rx_modes) - 1, + .def = 0, + .qmenu = max2175_ctrl_eu_rx_modes, +}; + +static const struct v4l2_ctrl_config max2175_na_rx_mode = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_RX_MODE, + .name = "RX Mode", + .type = V4L2_CTRL_TYPE_MENU, + .max = ARRAY_SIZE(max2175_ctrl_na_rx_modes) - 1, + .def = 0, + .qmenu = max2175_ctrl_na_rx_modes, +}; + +static int max2175_refout_load_to_bits(struct i2c_client *client, u32 load, + u32 *bits) +{ + if (load >= 0 && load <= 40) + *bits = load / 10; + else if (load >= 60 && load <= 70) + *bits = load / 10 - 1; + else + return -EINVAL; + + return 0; +} + +static int max2175_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + bool master = true, am_hiz = false; + u32 refout_load, refout_bits = 0; /* REFOUT disabled */ + struct v4l2_ctrl_handler *hdl; + struct fwnode_handle *fwnode; + struct device_node *np; + struct v4l2_subdev *sd; + struct regmap *regmap; + struct max2175 *ctx; + struct clk *clk; + int ret; + + /* Parse DT properties */ + np = of_parse_phandle(client->dev.of_node, "maxim,master", 0); + if (np) { + master = false; /* Slave tuner */ + of_node_put(np); + } + + fwnode = of_fwnode_handle(client->dev.of_node); + if (fwnode_property_present(fwnode, "maxim,am-hiz-filter")) + am_hiz = true; + + if (!fwnode_property_read_u32(fwnode, "maxim,refout-load", + &refout_load)) { + ret = max2175_refout_load_to_bits(client, refout_load, + &refout_bits); + if (ret) { + dev_err(&client->dev, "invalid refout_load %u\n", + refout_load); + return -EINVAL; + } + } + + clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(&client->dev, "cannot get clock %d\n", ret); + return -ENODEV; + } + + regmap = devm_regmap_init_i2c(client, &max2175_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&client->dev, "regmap init failed %d\n", ret); + return -ENODEV; + } + + /* Alloc tuner context */ + ctx = devm_kzalloc(&client->dev, sizeof(*ctx), GFP_KERNEL); + if (ctx == NULL) + return -ENOMEM; + + sd = &ctx->sd; + ctx->master = master; + ctx->am_hiz = am_hiz; + ctx->mode_resolved = false; + ctx->regmap = regmap; + ctx->xtal_freq = clk_get_rate(clk); + dev_info(&client->dev, "xtal freq %luHz\n", ctx->xtal_freq); + + v4l2_i2c_subdev_init(sd, client, &max2175_ops); + ctx->client = client; + + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + /* Controls */ + hdl = &ctx->ctrl_hdl; + ret = v4l2_ctrl_handler_init(hdl, 7); + if (ret) + return ret; + + ctx->lna_gain = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_LNA_GAIN, + 0, 63, 1, 0); + ctx->lna_gain->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->if_gain = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_IF_GAIN, + 0, 31, 1, 0); + ctx->if_gain->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->pll_lock = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_PLL_LOCK, + 0, 1, 1, 0); + ctx->pll_lock->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->i2s_en = v4l2_ctrl_new_custom(hdl, &max2175_i2s_en, NULL); + ctx->hsls = v4l2_ctrl_new_custom(hdl, &max2175_hsls, NULL); + + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) { + ctx->rx_mode = v4l2_ctrl_new_custom(hdl, + &max2175_eu_rx_mode, NULL); + ctx->rx_modes = eu_rx_modes; + ctx->bands_rf = &eu_bands_rf; + } else { + ctx->rx_mode = v4l2_ctrl_new_custom(hdl, + &max2175_na_rx_mode, NULL); + ctx->rx_modes = na_rx_modes; + ctx->bands_rf = &na_bands_rf; + } + ctx->sd.ctrl_handler = &ctx->ctrl_hdl; + + /* Set the defaults */ + ctx->freq = ctx->bands_rf->rangelow; + + /* Register subdev */ + ret = v4l2_async_register_subdev(sd); + if (ret) { + dev_err(&client->dev, "register subdev failed\n"); + goto err_reg; + } + + /* Initialize device */ + ret = max2175_core_init(ctx, refout_bits); + if (ret) + goto err_init; + + ret = v4l2_ctrl_handler_setup(hdl); + if (ret) + goto err_init; + + return 0; + +err_init: + v4l2_async_unregister_subdev(sd); +err_reg: + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + + return ret; +} + +static int max2175_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct max2175 *ctx = max2175_from_sd(sd); + + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + v4l2_async_unregister_subdev(sd); + + return 0; +} + +static const struct i2c_device_id max2175_id[] = { + { DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, max2175_id); + +static const struct of_device_id max2175_of_ids[] = { + { .compatible = "maxim,max2175", }, + { } +}; +MODULE_DEVICE_TABLE(of, max2175_of_ids); + +static struct i2c_driver max2175_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = max2175_of_ids, + }, + .probe = max2175_probe, + .remove = max2175_remove, + .id_table = max2175_id, +}; + +module_i2c_driver(max2175_driver); + +MODULE_DESCRIPTION("Maxim MAX2175 RF to Bits tuner driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ramesh Shanmugasundaram "); diff --git a/drivers/media/i2c/max2175.h b/drivers/media/i2c/max2175.h new file mode 100644 index 000000000000..eb43373ce7e2 --- /dev/null +++ b/drivers/media/i2c/max2175.h @@ -0,0 +1,109 @@ +/* + * Maxim Integrated MAX2175 RF to Bits tuner driver + * + * This driver & most of the hard coded values are based on the reference + * application delivered by Maxim for this device. + * + * Copyright (C) 2016 Maxim Integrated Products + * Copyright (C) 2017 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 __MAX2175_H__ +#define __MAX2175_H__ + +#define MAX2175_EU_XTAL_FREQ 36864000 /* In Hz */ +#define MAX2175_NA_XTAL_FREQ 40186125 /* In Hz */ + +enum max2175_region { + MAX2175_REGION_EU = 0, /* Europe */ + MAX2175_REGION_NA, /* North America */ +}; + +enum max2175_band { + MAX2175_BAND_AM = 0, + MAX2175_BAND_FM, + MAX2175_BAND_VHF, + MAX2175_BAND_L, +}; + +enum max2175_eu_mode { + /* EU modes */ + MAX2175_EU_FM_1_2 = 0, + MAX2175_DAB_1_2, + + /* + * Other possible modes to add in future + * MAX2175_DAB_1_0, + * MAX2175_DAB_1_3, + * MAX2175_EU_FM_2_2, + * MAX2175_EU_FMHD_4_0, + * MAX2175_EU_AM_1_0, + * MAX2175_EU_AM_2_2, + */ +}; + +enum max2175_na_mode { + /* NA modes */ + MAX2175_NA_FM_1_0 = 0, + MAX2175_NA_FM_2_0, + + /* + * Other possible modes to add in future + * MAX2175_NA_FMHD_1_0, + * MAX2175_NA_FMHD_1_2, + * MAX2175_NA_AM_1_0, + * MAX2175_NA_AM_1_2, + */ +}; + +/* Supported I2S modes */ +enum { + MAX2175_I2S_MODE0 = 0, + MAX2175_I2S_MODE1, + MAX2175_I2S_MODE2, + MAX2175_I2S_MODE3, + MAX2175_I2S_MODE4, +}; + +/* Coefficient table groups */ +enum { + MAX2175_CH_MSEL = 0, + MAX2175_EQ_MSEL, + MAX2175_AA_MSEL, +}; + +/* HSLS LO injection polarity */ +enum { + MAX2175_LO_BELOW_DESIRED = 0, + MAX2175_LO_ABOVE_DESIRED, +}; + +/* Channel FSM modes */ +enum max2175_csm_mode { + MAX2175_LOAD_TO_BUFFER = 0, + MAX2175_PRESET_TUNE, + MAX2175_SEARCH, + MAX2175_AF_UPDATE, + MAX2175_JUMP_FAST_TUNE, + MAX2175_CHECK, + MAX2175_LOAD_AND_SWAP, + MAX2175_END, + MAX2175_BUFFER_PLUS_PRESET_TUNE, + MAX2175_BUFFER_PLUS_SEARCH, + MAX2175_BUFFER_PLUS_AF_UPDATE, + MAX2175_BUFFER_PLUS_JUMP_FAST_TUNE, + MAX2175_BUFFER_PLUS_CHECK, + MAX2175_BUFFER_PLUS_LOAD_AND_SWAP, + MAX2175_NO_ACTION +}; + +#endif /* __MAX2175_H__ */ diff --git a/include/uapi/linux/max2175.h b/include/uapi/linux/max2175.h new file mode 100644 index 000000000000..3ef5d264440f --- /dev/null +++ b/include/uapi/linux/max2175.h @@ -0,0 +1,28 @@ +/* + * max2175.h + * + * Maxim Integrated MAX2175 RF to Bits tuner driver - user space header file. + * + * Copyright (C) 2016 Maxim Integrated Products + * Copyright (C) 2017 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 __UAPI_MAX2175_H_ +#define __UAPI_MAX2175_H_ + +#include + +#define V4L2_CID_MAX2175_I2S_ENABLE (V4L2_CID_USER_MAX217X_BASE + 0x01) +#define V4L2_CID_MAX2175_HSLS (V4L2_CID_USER_MAX217X_BASE + 0x02) +#define V4L2_CID_MAX2175_RX_MODE (V4L2_CID_USER_MAX217X_BASE + 0x03) + +#endif /* __UAPI_MAX2175_H_ */ -- cgit v1.2.3 From c28f2118a2129f8e2c1cdf2454ffe4833885edff Mon Sep 17 00:00:00 2001 From: Ramesh Shanmugasundaram Date: Mon, 12 Jun 2017 10:26:16 -0300 Subject: [media] media: Add new SDR formats PC16, PC18 & PC20 This patch adds support for the three new SDR formats. These formats were prefixed with "planar" indicating I & Q data are not interleaved as in other formats. Here, I & Q data constitutes the top half and bottom half of the received buffer respectively. V4L2_SDR_FMT_PCU16BE - 14-bit complex (I & Q) unsigned big-endian sample inside 16-bit. V4L2 FourCC: PC16 V4L2_SDR_FMT_PCU18BE - 16-bit complex (I & Q) unsigned big-endian sample inside 18-bit. V4L2 FourCC: PC18 V4L2_SDR_FMT_PCU20BE - 18-bit complex (I & Q) unsigned big-endian sample inside 20-bit. V4L2 FourCC: PC20 Signed-off-by: Ramesh Shanmugasundaram Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/v4l2-core/v4l2-ioctl.c | 3 +++ include/uapi/linux/videodev2.h | 3 +++ 2 files changed, 6 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 4f27cfa134a1..ce40183d9daa 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1229,6 +1229,9 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_SDR_FMT_CS8: descr = "Complex S8"; break; case V4L2_SDR_FMT_CS14LE: descr = "Complex S14LE"; break; case V4L2_SDR_FMT_RU12LE: descr = "Real U12LE"; break; + case V4L2_SDR_FMT_PCU16BE: descr = "Planar Complex U16BE"; break; + case V4L2_SDR_FMT_PCU18BE: descr = "Planar Complex U18BE"; break; + case V4L2_SDR_FMT_PCU20BE: descr = "Planar Complex U20BE"; break; case V4L2_TCH_FMT_DELTA_TD16: descr = "16-bit signed deltas"; break; case V4L2_TCH_FMT_DELTA_TD08: descr = "8-bit signed deltas"; break; case V4L2_TCH_FMT_TU16: descr = "16-bit unsigned touch data"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 2b8feb86d09e..45cf7359822c 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -669,6 +669,9 @@ struct v4l2_pix_format { #define V4L2_SDR_FMT_CS8 v4l2_fourcc('C', 'S', '0', '8') /* complex s8 */ #define V4L2_SDR_FMT_CS14LE v4l2_fourcc('C', 'S', '1', '4') /* complex s14le */ #define V4L2_SDR_FMT_RU12LE v4l2_fourcc('R', 'U', '1', '2') /* real u12le */ +#define V4L2_SDR_FMT_PCU16BE v4l2_fourcc('P', 'C', '1', '6') /* planar complex u16be */ +#define V4L2_SDR_FMT_PCU18BE v4l2_fourcc('P', 'C', '1', '8') /* planar complex u18be */ +#define V4L2_SDR_FMT_PCU20BE v4l2_fourcc('P', 'C', '2', '0') /* planar complex u20be */ /* Touch formats - used for Touch devices */ #define V4L2_TCH_FMT_DELTA_TD16 v4l2_fourcc('T', 'D', '1', '6') /* 16-bit signed deltas */ -- cgit v1.2.3 From b45cd756368823ce9e19bcb8c69d575595df5c5a Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 7 Jun 2017 15:33:54 -0300 Subject: [media] add mux and video interface bridge entity functions Add two new media entity function definitions for video multiplexers and video interface bridges. - renamed MEDIA_ENT_F_MUX to MEDIA_ENT_F_VID_MUX Signed-off-by: Philipp Zabel Signed-off-by: Steve Longerbeam Acked-by: Sakari Ailus Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/media/uapi/mediactl/media-types.rst | 21 +++++++++++++++++++++ include/uapi/linux/media.h | 6 ++++++ 2 files changed, 27 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/media/uapi/mediactl/media-types.rst b/Documentation/media/uapi/mediactl/media-types.rst index 2a5164aea2b4..71078565d644 100644 --- a/Documentation/media/uapi/mediactl/media-types.rst +++ b/Documentation/media/uapi/mediactl/media-types.rst @@ -299,6 +299,27 @@ Types and flags used to represent the media graph elements received on its sink pad and outputs the statistics data on its source pad. + - .. row 29 + + .. _MEDIA-ENT-F-VID-MUX: + + - ``MEDIA_ENT_F_VID_MUX`` + + - Video multiplexer. An entity capable of multiplexing must have at + least two sink pads and one source pad, and must pass the video + frame(s) received from the active sink pad to the source pad. + + - .. row 30 + + .. _MEDIA-ENT-F-VID-IF-BRIDGE: + + - ``MEDIA_ENT_F_VID_IF_BRIDGE`` + + - Video interface bridge. A video interface bridge entity must have at + least one sink pad and at least one source pad. It receives video + frames on its sink pad from an input video bus of one type (HDMI, eDP, + MIPI CSI-2, ...), and outputs them on its source pad to an output + video bus of another type (eDP, MIPI CSI-2, parallel, ...). .. tabularcolumns:: |p{5.5cm}|p{12.0cm}| diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index 4890787731b8..fac96c64fe51 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -104,6 +104,12 @@ struct media_device_info { #define MEDIA_ENT_F_PROC_VIDEO_SCALER (MEDIA_ENT_F_BASE + 0x4005) #define MEDIA_ENT_F_PROC_VIDEO_STATISTICS (MEDIA_ENT_F_BASE + 0x4006) +/* + * Switch and bridge entitites + */ +#define MEDIA_ENT_F_VID_MUX (MEDIA_ENT_F_BASE + 0x5001) +#define MEDIA_ENT_F_VID_IF_BRIDGE (MEDIA_ENT_F_BASE + 0x5002) + /* * Connectors */ -- cgit v1.2.3 From e130291212df5ce8160cd2e35387c96439863ad3 Mon Sep 17 00:00:00 2001 From: Steve Longerbeam Date: Sat, 10 Jun 2017 16:00:29 -0300 Subject: [media] media: Add i.MX media core driver Add the core media driver for i.MX SOC. Switch from the v4l2_of_ APIs to the v4l2_fwnode_ APIs. Add the bayer formats to imx-media's list of supported pixel and bus formats. Signed-off-by: Steve Longerbeam Signed-off-by: Philipp Zabel Signed-off-by: Russell King Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/media/v4l-drivers/imx.rst | 614 ++++++++++++++++ drivers/staging/media/Kconfig | 2 + drivers/staging/media/Makefile | 1 + drivers/staging/media/imx/Kconfig | 7 + drivers/staging/media/imx/Makefile | 5 + drivers/staging/media/imx/imx-media-dev.c | 667 +++++++++++++++++ drivers/staging/media/imx/imx-media-fim.c | 494 +++++++++++++ drivers/staging/media/imx/imx-media-internal-sd.c | 349 +++++++++ drivers/staging/media/imx/imx-media-of.c | 270 +++++++ drivers/staging/media/imx/imx-media-utils.c | 834 ++++++++++++++++++++++ drivers/staging/media/imx/imx-media.h | 323 +++++++++ include/media/imx.h | 15 + include/uapi/linux/v4l2-controls.h | 4 + 13 files changed, 3585 insertions(+) create mode 100644 Documentation/media/v4l-drivers/imx.rst create mode 100644 drivers/staging/media/imx/Kconfig create mode 100644 drivers/staging/media/imx/Makefile create mode 100644 drivers/staging/media/imx/imx-media-dev.c create mode 100644 drivers/staging/media/imx/imx-media-fim.c create mode 100644 drivers/staging/media/imx/imx-media-internal-sd.c create mode 100644 drivers/staging/media/imx/imx-media-of.c create mode 100644 drivers/staging/media/imx/imx-media-utils.c create mode 100644 drivers/staging/media/imx/imx-media.h create mode 100644 include/media/imx.h (limited to 'include/uapi/linux') diff --git a/Documentation/media/v4l-drivers/imx.rst b/Documentation/media/v4l-drivers/imx.rst new file mode 100644 index 000000000000..e0ee0f1aeb05 --- /dev/null +++ b/Documentation/media/v4l-drivers/imx.rst @@ -0,0 +1,614 @@ +i.MX Video Capture Driver +========================= + +Introduction +------------ + +The Freescale i.MX5/6 contains an Image Processing Unit (IPU), which +handles the flow of image frames to and from capture devices and +display devices. + +For image capture, the IPU contains the following internal subunits: + +- Image DMA Controller (IDMAC) +- Camera Serial Interface (CSI) +- Image Converter (IC) +- Sensor Multi-FIFO Controller (SMFC) +- Image Rotator (IRT) +- Video De-Interlacing or Combining Block (VDIC) + +The IDMAC is the DMA controller for transfer of image frames to and from +memory. Various dedicated DMA channels exist for both video capture and +display paths. During transfer, the IDMAC is also capable of vertical +image flip, 8x8 block transfer (see IRT description), pixel component +re-ordering (for example UYVY to YUYV) within the same colorspace, and +even packed <--> planar conversion. It can also perform a simple +de-interlacing by interleaving even and odd lines during transfer +(without motion compensation which requires the VDIC). + +The CSI is the backend capture unit that interfaces directly with +camera sensors over Parallel, BT.656/1120, and MIPI CSI-2 busses. + +The IC handles color-space conversion, resizing (downscaling and +upscaling), horizontal flip, and 90/270 degree rotation operations. + +There are three independent "tasks" within the IC that can carry out +conversions concurrently: pre-process encoding, pre-process viewfinder, +and post-processing. Within each task, conversions are split into three +sections: downsizing section, main section (upsizing, flip, colorspace +conversion, and graphics plane combining), and rotation section. + +The IPU time-shares the IC task operations. The time-slice granularity +is one burst of eight pixels in the downsizing section, one image line +in the main processing section, one image frame in the rotation section. + +The SMFC is composed of four independent FIFOs that each can transfer +captured frames from sensors directly to memory concurrently via four +IDMAC channels. + +The IRT carries out 90 and 270 degree image rotation operations. The +rotation operation is carried out on 8x8 pixel blocks at a time. This +operation is supported by the IDMAC which handles the 8x8 block transfer +along with block reordering, in coordination with vertical flip. + +The VDIC handles the conversion of interlaced video to progressive, with +support for different motion compensation modes (low, medium, and high +motion). The deinterlaced output frames from the VDIC can be sent to the +IC pre-process viewfinder task for further conversions. The VDIC also +contains a Combiner that combines two image planes, with alpha blending +and color keying. + +In addition to the IPU internal subunits, there are also two units +outside the IPU that are also involved in video capture on i.MX: + +- MIPI CSI-2 Receiver for camera sensors with the MIPI CSI-2 bus + interface. This is a Synopsys DesignWare core. +- Two video multiplexers for selecting among multiple sensor inputs + to send to a CSI. + +For more info, refer to the latest versions of the i.MX5/6 reference +manuals [#f1]_ and [#f2]_. + + +Features +-------- + +Some of the features of this driver include: + +- Many different pipelines can be configured via media controller API, + that correspond to the hardware video capture pipelines supported in + the i.MX. + +- Supports parallel, BT.565, and MIPI CSI-2 interfaces. + +- Concurrent independent streams, by configuring pipelines to multiple + video capture interfaces using independent entities. + +- Scaling, color-space conversion, horizontal and vertical flip, and + image rotation via IC task subdevs. + +- Many pixel formats supported (RGB, packed and planar YUV, partial + planar YUV). + +- The VDIC subdev supports motion compensated de-interlacing, with three + motion compensation modes: low, medium, and high motion. Pipelines are + defined that allow sending frames to the VDIC subdev directly from the + CSI. There is also support in the future for sending frames to the + VDIC from memory buffers via a output/mem2mem devices. + +- Includes a Frame Interval Monitor (FIM) that can correct vertical sync + problems with the ADV718x video decoders. + + +Entities +-------- + +imx6-mipi-csi2 +-------------- + +This is the MIPI CSI-2 receiver entity. It has one sink pad to receive +the MIPI CSI-2 stream (usually from a MIPI CSI-2 camera sensor). It has +four source pads, corresponding to the four MIPI CSI-2 demuxed virtual +channel outputs. Multpiple source pads can be enabled to independently +stream from multiple virtual channels. + +This entity actually consists of two sub-blocks. One is the MIPI CSI-2 +core. This is a Synopsys Designware MIPI CSI-2 core. The other sub-block +is a "CSI-2 to IPU gasket". The gasket acts as a demultiplexer of the +four virtual channels streams, providing four separate parallel buses +containing each virtual channel that are routed to CSIs or video +multiplexers as described below. + +On i.MX6 solo/dual-lite, all four virtual channel buses are routed to +two video multiplexers. Both CSI0 and CSI1 can receive any virtual +channel, as selected by the video multiplexers. + +On i.MX6 Quad, virtual channel 0 is routed to IPU1-CSI0 (after selected +by a video mux), virtual channels 1 and 2 are hard-wired to IPU1-CSI1 +and IPU2-CSI0, respectively, and virtual channel 3 is routed to +IPU2-CSI1 (again selected by a video mux). + +ipuX_csiY_mux +------------- + +These are the video multiplexers. They have two or more sink pads to +select from either camera sensors with a parallel interface, or from +MIPI CSI-2 virtual channels from imx6-mipi-csi2 entity. They have a +single source pad that routes to a CSI (ipuX_csiY entities). + +On i.MX6 solo/dual-lite, there are two video mux entities. One sits +in front of IPU1-CSI0 to select between a parallel sensor and any of +the four MIPI CSI-2 virtual channels (a total of five sink pads). The +other mux sits in front of IPU1-CSI1, and again has five sink pads to +select between a parallel sensor and any of the four MIPI CSI-2 virtual +channels. + +On i.MX6 Quad, there are two video mux entities. One sits in front of +IPU1-CSI0 to select between a parallel sensor and MIPI CSI-2 virtual +channel 0 (two sink pads). The other mux sits in front of IPU2-CSI1 to +select between a parallel sensor and MIPI CSI-2 virtual channel 3 (two +sink pads). + +ipuX_csiY +--------- + +These are the CSI entities. They have a single sink pad receiving from +either a video mux or from a MIPI CSI-2 virtual channel as described +above. + +This entity has two source pads. The first source pad can link directly +to the ipuX_vdic entity or the ipuX_ic_prp entity, using hardware links +that require no IDMAC memory buffer transfer. + +When the direct source pad is routed to the ipuX_ic_prp entity, frames +from the CSI can be processed by one or both of the IC pre-processing +tasks. + +When the direct source pad is routed to the ipuX_vdic entity, the VDIC +will carry out motion-compensated de-interlace using "high motion" mode +(see description of ipuX_vdic entity). + +The second source pad sends video frames directly to memory buffers +via the SMFC and an IDMAC channel, bypassing IC pre-processing. This +source pad is routed to a capture device node, with a node name of the +format "ipuX_csiY capture". + +Note that since the IDMAC source pad makes use of an IDMAC channel, it +can do pixel reordering within the same colorspace. For example, the +sink pad can take UYVY2X8, but the IDMAC source pad can output YUYV2X8. +If the sink pad is receiving YUV, the output at the capture device can +also be converted to a planar YUV format such as YUV420. + +It will also perform simple de-interlace without motion compensation, +which is activated if the sink pad's field type is an interlaced type, +and the IDMAC source pad field type is set to none. + +This subdev can generate the following event when enabling the second +IDMAC source pad: + +- V4L2_EVENT_IMX_FRAME_INTERVAL_ERROR + +The user application can subscribe to this event from the ipuX_csiY +subdev node. This event is generated by the Frame Interval Monitor +(see below for more on the FIM). + +Cropping in ipuX_csiY +--------------------- + +The CSI supports cropping the incoming raw sensor frames. This is +implemented in the ipuX_csiY entities at the sink pad, using the +crop selection subdev API. + +The CSI also supports fixed divide-by-two downscaling indepently in +width and height. This is implemented in the ipuX_csiY entities at +the sink pad, using the compose selection subdev API. + +The output rectangle at the ipuX_csiY source pad is the same as +the compose rectangle at the sink pad. So the source pad rectangle +cannot be negotiated, it must be set using the compose selection +API at sink pad (if /2 downscale is desired, otherwise source pad +rectangle is equal to incoming rectangle). + +To give an example of crop and /2 downscale, this will crop a +1280x960 input frame to 640x480, and then /2 downscale in both +dimensions to 320x240 (assumes ipu1_csi0 is linked to ipu1_csi0_mux): + +media-ctl -V "'ipu1_csi0_mux':2[fmt:UYVY2X8/1280x960]" +media-ctl -V "'ipu1_csi0':0[crop:(0,0)/640x480]" +media-ctl -V "'ipu1_csi0':0[compose:(0,0)/320x240]" + +Frame Skipping in ipuX_csiY +--------------------------- + +The CSI supports frame rate decimation, via frame skipping. Frame +rate decimation is specified by setting the frame intervals at +sink and source pads. The ipuX_csiY entity then applies the best +frame skip setting to the CSI to achieve the desired frame rate +at the source pad. + +The following example reduces an assumed incoming 60 Hz frame +rate by half at the IDMAC output source pad: + +media-ctl -V "'ipu1_csi0':0[fmt:UYVY2X8/640x480@1/60]" +media-ctl -V "'ipu1_csi0':2[fmt:UYVY2X8/640x480@1/30]" + +Frame Interval Monitor in ipuX_csiY +----------------------------------- + +The adv718x decoders can occasionally send corrupt fields during +NTSC/PAL signal re-sync (too little or too many video lines). When +this happens, the IPU triggers a mechanism to re-establish vertical +sync by adding 1 dummy line every frame, which causes a rolling effect +from image to image, and can last a long time before a stable image is +recovered. Or sometimes the mechanism doesn't work at all, causing a +permanent split image (one frame contains lines from two consecutive +captured images). + +From experiment it was found that during image rolling, the frame +intervals (elapsed time between two EOF's) drop below the nominal +value for the current standard, by about one frame time (60 usec), +and remain at that value until rolling stops. + +While the reason for this observation isn't known (the IPU dummy +line mechanism should show an increase in the intervals by 1 line +time every frame, not a fixed value), we can use it to detect the +corrupt fields using a frame interval monitor. If the FIM detects a +bad frame interval, the ipuX_csiY subdev will send the event +V4L2_EVENT_IMX_FRAME_INTERVAL_ERROR. Userland can register with +the FIM event notification on the ipuX_csiY subdev device node. +Userland can issue a streaming restart when this event is received +to correct the rolling/split image. + +The ipuX_csiY subdev includes custom controls to tweak some dials for +FIM. If one of these controls is changed during streaming, the FIM will +be reset and will continue at the new settings. + +- V4L2_CID_IMX_FIM_ENABLE + +Enable/disable the FIM. + +- V4L2_CID_IMX_FIM_NUM + +How many frame interval measurements to average before comparing against +the nominal frame interval reported by the sensor. This can reduce noise +caused by interrupt latency. + +- V4L2_CID_IMX_FIM_TOLERANCE_MIN + +If the averaged intervals fall outside nominal by this amount, in +microseconds, the V4L2_EVENT_IMX_FRAME_INTERVAL_ERROR event is sent. + +- V4L2_CID_IMX_FIM_TOLERANCE_MAX + +If any intervals are higher than this value, those samples are +discarded and do not enter into the average. This can be used to +discard really high interval errors that might be due to interrupt +latency from high system load. + +- V4L2_CID_IMX_FIM_NUM_SKIP + +How many frames to skip after a FIM reset or stream restart before +FIM begins to average intervals. + +- V4L2_CID_IMX_FIM_ICAP_CHANNEL +- V4L2_CID_IMX_FIM_ICAP_EDGE + +These controls will configure an input capture channel as the method +for measuring frame intervals. This is superior to the default method +of measuring frame intervals via EOF interrupt, since it is not subject +to uncertainty errors introduced by interrupt latency. + +Input capture requires hardware support. A VSYNC signal must be routed +to one of the i.MX6 input capture channel pads. + +V4L2_CID_IMX_FIM_ICAP_CHANNEL configures which i.MX6 input capture +channel to use. This must be 0 or 1. + +V4L2_CID_IMX_FIM_ICAP_EDGE configures which signal edge will trigger +input capture events. By default the input capture method is disabled +with a value of IRQ_TYPE_NONE. Set this control to IRQ_TYPE_EDGE_RISING, +IRQ_TYPE_EDGE_FALLING, or IRQ_TYPE_EDGE_BOTH to enable input capture, +triggered on the given signal edge(s). + +When input capture is disabled, frame intervals will be measured via +EOF interrupt. + + +ipuX_vdic +--------- + +The VDIC carries out motion compensated de-interlacing, with three +motion compensation modes: low, medium, and high motion. The mode is +specified with the menu control V4L2_CID_DEINTERLACING_MODE. It has +two sink pads and a single source pad. + +The direct sink pad receives from an ipuX_csiY direct pad. With this +link the VDIC can only operate in high motion mode. + +When the IDMAC sink pad is activated, it receives from an output +or mem2mem device node. With this pipeline, it can also operate +in low and medium modes, because these modes require receiving +frames from memory buffers. Note that an output or mem2mem device +is not implemented yet, so this sink pad currently has no links. + +The source pad routes to the IC pre-processing entity ipuX_ic_prp. + +ipuX_ic_prp +----------- + +This is the IC pre-processing entity. It acts as a router, routing +data from its sink pad to one or both of its source pads. + +It has a single sink pad. The sink pad can receive from the ipuX_csiY +direct pad, or from ipuX_vdic. + +This entity has two source pads. One source pad routes to the +pre-process encode task entity (ipuX_ic_prpenc), the other to the +pre-process viewfinder task entity (ipuX_ic_prpvf). Both source pads +can be activated at the same time if the sink pad is receiving from +ipuX_csiY. Only the source pad to the pre-process viewfinder task entity +can be activated if the sink pad is receiving from ipuX_vdic (frames +from the VDIC can only be processed by the pre-process viewfinder task). + +ipuX_ic_prpenc +-------------- + +This is the IC pre-processing encode entity. It has a single sink +pad from ipuX_ic_prp, and a single source pad. The source pad is +routed to a capture device node, with a node name of the format +"ipuX_ic_prpenc capture". + +This entity performs the IC pre-process encode task operations: +color-space conversion, resizing (downscaling and upscaling), +horizontal and vertical flip, and 90/270 degree rotation. Flip +and rotation are provided via standard V4L2 controls. + +Like the ipuX_csiY IDMAC source, it can also perform simple de-interlace +without motion compensation, and pixel reordering. + +ipuX_ic_prpvf +------------- + +This is the IC pre-processing viewfinder entity. It has a single sink +pad from ipuX_ic_prp, and a single source pad. The source pad is routed +to a capture device node, with a node name of the format +"ipuX_ic_prpvf capture". + +It is identical in operation to ipuX_ic_prpenc, with the same resizing +and CSC operations and flip/rotation controls. It will receive and +process de-interlaced frames from the ipuX_vdic if ipuX_ic_prp is +receiving from ipuX_vdic. + +Like the ipuX_csiY IDMAC source, it can perform simple de-interlace +without motion compensation. However, note that if the ipuX_vdic is +included in the pipeline (ipuX_ic_prp is receiving from ipuX_vdic), +it's not possible to use simple de-interlace in ipuX_ic_prpvf, since +the ipuX_vdic has already carried out de-interlacing (with motion +compensation) and therefore the field type output from ipuX_ic_prp can +only be none. + +Capture Pipelines +----------------- + +The following describe the various use-cases supported by the pipelines. + +The links shown do not include the backend sensor, video mux, or mipi +csi-2 receiver links. This depends on the type of sensor interface +(parallel or mipi csi-2). So these pipelines begin with: + +sensor -> ipuX_csiY_mux -> ... + +for parallel sensors, or: + +sensor -> imx6-mipi-csi2 -> (ipuX_csiY_mux) -> ... + +for mipi csi-2 sensors. The imx6-mipi-csi2 receiver may need to route +to the video mux (ipuX_csiY_mux) before sending to the CSI, depending +on the mipi csi-2 virtual channel, hence ipuX_csiY_mux is shown in +parenthesis. + +Unprocessed Video Capture: +-------------------------- + +Send frames directly from sensor to camera device interface node, with +no conversions, via ipuX_csiY IDMAC source pad: + +-> ipuX_csiY:2 -> ipuX_csiY capture + +IC Direct Conversions: +---------------------- + +This pipeline uses the preprocess encode entity to route frames directly +from the CSI to the IC, to carry out scaling up to 1024x1024 resolution, +CSC, flipping, and image rotation: + +-> ipuX_csiY:1 -> 0:ipuX_ic_prp:1 -> 0:ipuX_ic_prpenc:1 -> + ipuX_ic_prpenc capture + +Motion Compensated De-interlace: +-------------------------------- + +This pipeline routes frames from the CSI direct pad to the VDIC entity to +support motion-compensated de-interlacing (high motion mode only), +scaling up to 1024x1024, CSC, flip, and rotation: + +-> ipuX_csiY:1 -> 0:ipuX_vdic:2 -> 0:ipuX_ic_prp:2 -> + 0:ipuX_ic_prpvf:1 -> ipuX_ic_prpvf capture + + +Usage Notes +----------- + +To aid in configuration and for backward compatibility with V4L2 +applications that access controls only from video device nodes, the +capture device interfaces inherit controls from the active entities +in the current pipeline, so controls can be accessed either directly +from the subdev or from the active capture device interface. For +example, the FIM controls are available either from the ipuX_csiY +subdevs or from the active capture device. + +The following are specific usage notes for the Sabre* reference +boards: + + +SabreLite with OV5642 and OV5640 +-------------------------------- + +This platform requires the OmniVision OV5642 module with a parallel +camera interface, and the OV5640 module with a MIPI CSI-2 +interface. Both modules are available from Boundary Devices: + +https://boundarydevices.com/product/nit6x_5mp +https://boundarydevices.com/product/nit6x_5mp_mipi + +Note that if only one camera module is available, the other sensor +node can be disabled in the device tree. + +The OV5642 module is connected to the parallel bus input on the i.MX +internal video mux to IPU1 CSI0. It's i2c bus connects to i2c bus 2. + +The MIPI CSI-2 OV5640 module is connected to the i.MX internal MIPI CSI-2 +receiver, and the four virtual channel outputs from the receiver are +routed as follows: vc0 to the IPU1 CSI0 mux, vc1 directly to IPU1 CSI1, +vc2 directly to IPU2 CSI0, and vc3 to the IPU2 CSI1 mux. The OV5640 is +also connected to i2c bus 2 on the SabreLite, therefore the OV5642 and +OV5640 must not share the same i2c slave address. + +The following basic example configures unprocessed video capture +pipelines for both sensors. The OV5642 is routed to ipu1_csi0, and +the OV5640, transmitting on MIPI CSI-2 virtual channel 1 (which is +imx6-mipi-csi2 pad 2), is routed to ipu1_csi1. Both sensors are +configured to output 640x480, and the OV5642 outputs YUYV2X8, the +OV5640 UYVY2X8: + +.. code-block:: none + + # Setup links for OV5642 + media-ctl -l "'ov5642 1-0042':0 -> 'ipu1_csi0_mux':1[1]" + media-ctl -l "'ipu1_csi0_mux':2 -> 'ipu1_csi0':0[1]" + media-ctl -l "'ipu1_csi0':2 -> 'ipu1_csi0 capture':0[1]" + # Setup links for OV5640 + media-ctl -l "'ov5640 1-0040':0 -> 'imx6-mipi-csi2':0[1]" + media-ctl -l "'imx6-mipi-csi2':2 -> 'ipu1_csi1':0[1]" + media-ctl -l "'ipu1_csi1':2 -> 'ipu1_csi1 capture':0[1]" + # Configure pads for OV5642 pipeline + media-ctl -V "'ov5642 1-0042':0 [fmt:YUYV2X8/640x480 field:none]" + media-ctl -V "'ipu1_csi0_mux':2 [fmt:YUYV2X8/640x480 field:none]" + media-ctl -V "'ipu1_csi0':2 [fmt:AYUV32/640x480 field:none]" + # Configure pads for OV5640 pipeline + media-ctl -V "'ov5640 1-0040':0 [fmt:UYVY2X8/640x480 field:none]" + media-ctl -V "'imx6-mipi-csi2':2 [fmt:UYVY2X8/640x480 field:none]" + media-ctl -V "'ipu1_csi1':2 [fmt:AYUV32/640x480 field:none]" + +Streaming can then begin independently on the capture device nodes +"ipu1_csi0 capture" and "ipu1_csi1 capture". The v4l2-ctl tool can +be used to select any supported YUV pixelformat on the capture device +nodes, including planar. + +SabreAuto with ADV7180 decoder +------------------------------ + +On the SabreAuto, an on-board ADV7180 SD decoder is connected to the +parallel bus input on the internal video mux to IPU1 CSI0. + +The following example configures a pipeline to capture from the ADV7180 +video decoder, assuming NTSC 720x480 input signals, with Motion +Compensated de-interlacing. Pad field types assume the adv7180 outputs +"interlaced". $outputfmt can be any format supported by the ipu1_ic_prpvf +entity at its output pad: + +.. code-block:: none + + # Setup links + media-ctl -l "'adv7180 3-0021':0 -> 'ipu1_csi0_mux':1[1]" + media-ctl -l "'ipu1_csi0_mux':2 -> 'ipu1_csi0':0[1]" + media-ctl -l "'ipu1_csi0':1 -> 'ipu1_vdic':0[1]" + media-ctl -l "'ipu1_vdic':2 -> 'ipu1_ic_prp':0[1]" + media-ctl -l "'ipu1_ic_prp':2 -> 'ipu1_ic_prpvf':0[1]" + media-ctl -l "'ipu1_ic_prpvf':1 -> 'ipu1_ic_prpvf capture':0[1]" + # Configure pads + media-ctl -V "'adv7180 3-0021':0 [fmt:UYVY2X8/720x480]" + media-ctl -V "'ipu1_csi0_mux':2 [fmt:UYVY2X8/720x480 field:interlaced]" + media-ctl -V "'ipu1_csi0':1 [fmt:AYUV32/720x480 field:interlaced]" + media-ctl -V "'ipu1_vdic':2 [fmt:AYUV32/720x480 field:none]" + media-ctl -V "'ipu1_ic_prp':2 [fmt:AYUV32/720x480 field:none]" + media-ctl -V "'ipu1_ic_prpvf':1 [fmt:$outputfmt field:none]" + +Streaming can then begin on the capture device node at +"ipu1_ic_prpvf capture". The v4l2-ctl tool can be used to select any +supported YUV or RGB pixelformat on the capture device node. + +This platform accepts Composite Video analog inputs to the ADV7180 on +Ain1 (connector J42). + +SabreSD with MIPI CSI-2 OV5640 +------------------------------ + +Similarly to SabreLite, the SabreSD supports a parallel interface +OV5642 module on IPU1 CSI0, and a MIPI CSI-2 OV5640 module. The OV5642 +connects to i2c bus 1 and the OV5640 to i2c bus 2. + +The device tree for SabreSD includes OF graphs for both the parallel +OV5642 and the MIPI CSI-2 OV5640, but as of this writing only the MIPI +CSI-2 OV5640 has been tested, so the OV5642 node is currently disabled. +The OV5640 module connects to MIPI connector J5 (sorry I don't have the +compatible module part number or URL). + +The following example configures a direct conversion pipeline to capture +from the OV5640, transmitting on MIPI CSI-2 virtual channel 1. $sensorfmt +can be any format supported by the OV5640. $sensordim is the frame +dimension part of $sensorfmt (minus the mbus pixel code). $outputfmt can +be any format supported by the ipu1_ic_prpenc entity at its output pad: + +.. code-block:: none + + # Setup links + media-ctl -l "'ov5640 1-003c':0 -> 'imx6-mipi-csi2':0[1]" + media-ctl -l "'imx6-mipi-csi2':2 -> 'ipu1_csi1':0[1]" + media-ctl -l "'ipu1_csi1':1 -> 'ipu1_ic_prp':0[1]" + media-ctl -l "'ipu1_ic_prp':1 -> 'ipu1_ic_prpenc':0[1]" + media-ctl -l "'ipu1_ic_prpenc':1 -> 'ipu1_ic_prpenc capture':0[1]" + # Configure pads + media-ctl -V "'ov5640 1-003c':0 [fmt:$sensorfmt field:none]" + media-ctl -V "'imx6-mipi-csi2':2 [fmt:$sensorfmt field:none]" + media-ctl -V "'ipu1_csi1':1 [fmt:AYUV32/$sensordim field:none]" + media-ctl -V "'ipu1_ic_prp':1 [fmt:AYUV32/$sensordim field:none]" + media-ctl -V "'ipu1_ic_prpenc':1 [fmt:$outputfmt field:none]" + +Streaming can then begin on "ipu1_ic_prpenc capture" node. The v4l2-ctl +tool can be used to select any supported YUV or RGB pixelformat on the +capture device node. + + +Known Issues +------------ + +1. When using 90 or 270 degree rotation control at capture resolutions + near the IC resizer limit of 1024x1024, and combined with planar + pixel formats (YUV420, YUV422p), frame capture will often fail with + no end-of-frame interrupts from the IDMAC channel. To work around + this, use lower resolution and/or packed formats (YUYV, RGB3, etc.) + when 90 or 270 rotations are needed. + + +File list +--------- + +drivers/staging/media/imx/ +include/media/imx.h +include/linux/imx-media.h + +References +---------- + +.. [#f1] http://www.nxp.com/assets/documents/data/en/reference-manuals/IMX6DQRM.pdf +.. [#f2] http://www.nxp.com/assets/documents/data/en/reference-manuals/IMX6SDLRM.pdf + + +Authors +------- +Steve Longerbeam +Philipp Zabel +Russell King + +Copyright (C) 2012-2017 Mentor Graphics Inc. diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index dbda4d9a08e7..f8c25ee082ef 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -27,6 +27,8 @@ source "drivers/staging/media/cxd2099/Kconfig" source "drivers/staging/media/davinci_vpfe/Kconfig" +source "drivers/staging/media/imx/Kconfig" + source "drivers/staging/media/omap4iss/Kconfig" # Keep LIRC at the end, as it has sub-menus diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index c04600c81264..ac090c5fce30 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_I2C_BCM2048) += bcm2048/ obj-$(CONFIG_DVB_CXD2099) += cxd2099/ +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/ obj-$(CONFIG_LIRC_STAGING) += lirc/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig new file mode 100644 index 000000000000..5e79a36ce225 --- /dev/null +++ b/drivers/staging/media/imx/Kconfig @@ -0,0 +1,7 @@ +config VIDEO_IMX_MEDIA + tristate "i.MX5/6 V4L2 media core driver" + depends on MEDIA_CONTROLLER && VIDEO_V4L2 && ARCH_MXC && IMX_IPUV3_CORE + select V4L2_FWNODE + ---help--- + Say yes here to enable support for video4linux media controller + driver for the i.MX5/6 SOC. diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile new file mode 100644 index 000000000000..ddd7d94dbac9 --- /dev/null +++ b/drivers/staging/media/imx/Makefile @@ -0,0 +1,5 @@ +imx-media-objs := imx-media-dev.o imx-media-internal-sd.o imx-media-of.o +imx-media-common-objs := imx-media-utils.o imx-media-fim.o + +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media.o +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media-common.o diff --git a/drivers/staging/media/imx/imx-media-dev.c b/drivers/staging/media/imx/imx-media-dev.c new file mode 100644 index 000000000000..48cbc7716758 --- /dev/null +++ b/drivers/staging/media/imx/imx-media-dev.c @@ -0,0 +1,667 @@ +/* + * V4L2 Media Controller Driver for Freescale i.MX5/6 SOC + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include