1685 lines
42 KiB
Diff
1685 lines
42 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Meng Wei <wei.meng@intel.com>
|
|
Date: Fri, 26 Oct 2018 09:52:57 +0800
|
|
Subject: [PATCH] media: i2c: add TI960/953 SER/DES driver
|
|
|
|
TI960 is a versatile sensor hub capable of connecting
|
|
serialized sensor data received from four independent
|
|
video data streams through an FPD-Link III interface.
|
|
|
|
Signed-off-by: Chang Ying <ying.chang@intel.com>
|
|
Signed-off-by: Meng Wei <wei.meng@intel.com>
|
|
---
|
|
drivers/media/i2c/Kconfig | 6 +
|
|
drivers/media/i2c/Makefile | 1 +
|
|
drivers/media/i2c/ti960-reg.h | 209 +++++
|
|
drivers/media/i2c/ti960.c | 1344 +++++++++++++++++++++++++++++++++
|
|
include/media/ti960.h | 62 ++
|
|
5 files changed, 1622 insertions(+)
|
|
create mode 100644 drivers/media/i2c/ti960-reg.h
|
|
create mode 100644 drivers/media/i2c/ti960.c
|
|
create mode 100644 include/media/ti960.h
|
|
|
|
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
|
|
index fe620388ad6a..5dda368ad88c 100644
|
|
--- a/drivers/media/i2c/Kconfig
|
|
+++ b/drivers/media/i2c/Kconfig
|
|
@@ -1081,6 +1081,12 @@ config VIDEO_MAX9286
|
|
---help---
|
|
This is a MAXIM 96705 Serializer and MAXIM 9286 CSI-2 Deserializer driver.
|
|
|
|
+config VIDEO_TI960
|
|
+ tristate "TI960 driver support"
|
|
+ depends on I2C && VIDEO_V4L2
|
|
+ ---help---
|
|
+ This is a driver for TI960 Deserializer.
|
|
+
|
|
endmenu
|
|
|
|
menu "Sensors used on soc_camera driver"
|
|
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
|
|
index 8f906f7b599b..b23bd05aab9c 100644
|
|
--- a/drivers/media/i2c/Makefile
|
|
+++ b/drivers/media/i2c/Makefile
|
|
@@ -114,3 +114,4 @@ obj-$(CONFIG_SDR_MAX2175) += max2175.o
|
|
obj-$(CONFIG_VIDEO_CRLMODULE) += crlmodule/
|
|
obj-$(CONFIG_VIDEO_TI964) += ti964.o
|
|
obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
|
|
+obj-$(CONFIG_VIDEO_TI960) += ti960.o
|
|
diff --git a/drivers/media/i2c/ti960-reg.h b/drivers/media/i2c/ti960-reg.h
|
|
new file mode 100644
|
|
index 000000000000..260c17884c7d
|
|
--- /dev/null
|
|
+++ b/drivers/media/i2c/ti960-reg.h
|
|
@@ -0,0 +1,209 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/* Copyright (C) 2018 Intel Corporation */
|
|
+
|
|
+#ifndef TI960_REG_H
|
|
+#define TI960_REG_H
|
|
+
|
|
+struct ti960_register_write {
|
|
+ u8 reg;
|
|
+ u8 val;
|
|
+};
|
|
+
|
|
+static const struct ti960_register_write ti960_frame_sync_settings[2][5] = {
|
|
+ {
|
|
+ {0x18, 0x00}, /* Disable frame sync. */
|
|
+ {0x19, 0x00},
|
|
+ {0x1a, 0x02},
|
|
+ {0x1b, 0x0a},
|
|
+ {0x1c, 0xd3},
|
|
+ },
|
|
+ {
|
|
+ {0x19, 0x01}, /* Frame sync high time.*/
|
|
+ {0x1a, 0x15},
|
|
+ {0x1b, 0x09}, /* Frame sync low time. */
|
|
+ {0x1c, 0xC3},
|
|
+ {0x18, 0x01}, /* Enable frame sync. and use high/low mode */
|
|
+ }
|
|
+};
|
|
+
|
|
+static const struct ti960_register_write ti960_gpio_settings[] = {
|
|
+ {0x10, 0x81},
|
|
+ {0x11, 0x85},
|
|
+ {0x12, 0x89},
|
|
+ {0x13, 0x8d},
|
|
+};
|
|
+
|
|
+static const struct ti960_register_write ti960_init_settings[] = {
|
|
+ {0x0c, 0x0f}, /* RX_PORT_CTL */
|
|
+ {0x1f, 0x06}, /* CSI_PLL_CTL */
|
|
+ {0x4c, 0x01}, /* FPD3_PORT_SEL */
|
|
+ {0x58, 0x5e}, /* BCC_CONFIG */
|
|
+ {0x5c, 0xb0}, /* SER_ALIAS_ID */
|
|
+ {0x5d, 0x6c}, /* SlaveID[0] */
|
|
+ {0x65, 0x60}, /* SlaveAlias[0] */
|
|
+ {0x6d, 0x7c}, /* PORT_CONFIG */
|
|
+ {0x7c, 0x01}, /* PORT_CONFIG2 */
|
|
+ {0x70, 0x2b}, /* RAW10_ID */
|
|
+ {0x71, 0x2c}, /* RAW12_ID */
|
|
+ {0x72, 0xe4}, /* CSI_VC_MAP */
|
|
+ {0x4c, 0x12}, /* FPD3_PORT_SEL */
|
|
+ {0x58, 0x5e},
|
|
+ {0x5c, 0xb2},
|
|
+ {0x5d, 0x6c},
|
|
+ {0x65, 0x62},
|
|
+ {0x6d, 0x7c},
|
|
+ {0x7c, 0x01},
|
|
+ {0x70, 0x2b},
|
|
+ {0x71, 0x2c},
|
|
+ {0x72, 0xee}, /* CSI_VC_MAP */
|
|
+ {0x4c, 0x24}, /* FPD3_PORT_SEL */
|
|
+ {0x58, 0x5e},
|
|
+ {0x5c, 0xb4},
|
|
+ {0x5d, 0x6c},
|
|
+ {0x65, 0x64},
|
|
+ {0x6d, 0x7c},
|
|
+ {0x7c, 0x01},
|
|
+ {0x70, 0x2b},
|
|
+ {0x71, 0x2c},
|
|
+ {0x72, 0xe4},
|
|
+ {0x4c, 0x38}, /* FPD3_PORT_SEL */
|
|
+ {0x58, 0x5e},
|
|
+ {0x5c, 0xb6},
|
|
+ {0x5d, 0x6c},
|
|
+ {0x65, 0x66},
|
|
+ {0x6d, 0x7c},
|
|
+ {0x7c, 0x01},
|
|
+ {0x70, 0x2b},
|
|
+ {0x71, 0x2c},
|
|
+ {0x72, 0xe4},
|
|
+};
|
|
+
|
|
+static const struct ti960_register_write ti953_init_settings[] = {
|
|
+ {0x4c, 0x01},
|
|
+ {0xb0, 0x04},
|
|
+ {0xb1, 0x03},
|
|
+ {0xb2, 0x25},
|
|
+ {0xb1, 0x13},
|
|
+ {0xb2, 0x25},
|
|
+ {0xb0, 0x04},
|
|
+ {0xb1, 0x04},
|
|
+ {0xb2, 0x30},
|
|
+ {0xb1, 0x14},
|
|
+ {0xb2, 0x30},
|
|
+ {0xb0, 0x04},
|
|
+ {0xb1, 0x06},
|
|
+ {0xb2, 0x40},
|
|
+ {0x42, 0x01}, /* SLAVE_ID_ALIAS_1 */
|
|
+ {0x41, 0x93}, /* SLAVE_ID_ALIAS_0 */
|
|
+ {0x4c, 0x12},
|
|
+ {0xb0, 0x08},
|
|
+ {0xb1, 0x03},
|
|
+ {0xb2, 0x25},
|
|
+ {0xb1, 0x13},
|
|
+ {0xb2, 0x25},
|
|
+ {0xb0, 0x08},
|
|
+ {0xb1, 0x04},
|
|
+ {0xb2, 0x30},
|
|
+ {0xb1, 0x14},
|
|
+ {0xb2, 0x30},
|
|
+ {0xb0, 0x08},
|
|
+ {0xb1, 0x06},
|
|
+ {0xb2, 0x40},
|
|
+ {0x42, 0x01},
|
|
+ {0x41, 0x93},
|
|
+ {0x4c, 0x24},
|
|
+ {0xb0, 0x0c},
|
|
+ {0xb1, 0x03},
|
|
+ {0xb2, 0x25},
|
|
+ {0xb1, 0x13},
|
|
+ {0xb2, 0x25},
|
|
+ {0xb0, 0x0c},
|
|
+ {0xb1, 0x04},
|
|
+ {0xb2, 0x30},
|
|
+ {0xb1, 0x14},
|
|
+ {0xb2, 0x30},
|
|
+ {0xb0, 0x0c},
|
|
+ {0xb1, 0x06},
|
|
+ {0xb2, 0x40},
|
|
+ {0x42, 0x01},
|
|
+ {0x41, 0x93},
|
|
+ {0x4c, 0x38},
|
|
+ {0xb0, 0x10},
|
|
+ {0xb1, 0x03},
|
|
+ {0xb2, 0x25},
|
|
+ {0xb1, 0x13},
|
|
+ {0xb2, 0x25},
|
|
+ {0xb0, 0x10},
|
|
+ {0xb1, 0x04},
|
|
+ {0xb2, 0x30},
|
|
+ {0xb1, 0x14},
|
|
+ {0xb2, 0x30},
|
|
+ {0xb0, 0x10},
|
|
+ {0xb1, 0x06},
|
|
+ {0xb2, 0x40},
|
|
+ {0x42, 0x01},
|
|
+ {0x41, 0x93},
|
|
+};
|
|
+
|
|
+static const struct ti960_register_write ti960_init_settings_2[] = {
|
|
+ {0xb0, 0x14},
|
|
+ {0xb1, 0x03},
|
|
+ {0xb2, 0x04},
|
|
+ {0xb1, 0x04},
|
|
+ {0xb2, 0x04},
|
|
+ {0x4c, 0x01},
|
|
+ {0x32, 0x01},
|
|
+ {0x33, 0x03},
|
|
+ {0x32, 0x12},
|
|
+ {0x33, 0x03},
|
|
+ {0x20, 0x00},
|
|
+ {0x21, 0x03},
|
|
+};
|
|
+
|
|
+static const struct ti960_register_write ti953_init_settings_2[] = {
|
|
+ {0x06, 0x41},
|
|
+ {0x07, 0x28},
|
|
+ {0x0e, 0xf0},
|
|
+};
|
|
+
|
|
+/* register definition */
|
|
+#define TI960_DEVID 0x0
|
|
+#define TI960_RESET 0x1
|
|
+#define TI960_CSI_PLL_CTL 0x1f
|
|
+#define TI960_FS_CTL 0x18
|
|
+#define TI960_FWD_CTL1 0x20
|
|
+#define TI960_RX_PORT_SEL 0x4c
|
|
+#define TI960_SLAVE_ID0 0x5d
|
|
+#define TI960_SLAVE_ALIAS_ID0 0x65
|
|
+#define TI960_PORT_CONFIG 0x6d
|
|
+#define TI960_BC_GPIO_CTL0 0x6e
|
|
+#define TI960_RAW10_ID 0x70
|
|
+#define TI960_RAW12_ID 0x71
|
|
+#define TI960_PORT_CONFIG2 0x7c
|
|
+#define TI960_CSI_CTL 0x33
|
|
+
|
|
+/* register value definition */
|
|
+#define TI960_POWER_ON 0x1
|
|
+#define TI960_POWER_OFF 0x20
|
|
+#define TI960_FPD3_RAW10_100MHz 0x7f
|
|
+#define TI960_FPD3_RAW12_50MHz 0x7d
|
|
+#define TI960_FPD3_RAW12_75MHz 0x7e
|
|
+#define TI960_FPD3_CSI 0x7c
|
|
+#define TI960_RAW12 0x41
|
|
+#define TI960_RAW10_NORMAL 0x1
|
|
+#define TI960_RAW10_8BIT 0x81
|
|
+#define TI960_GPIO0_HIGH 0x09
|
|
+#define TI960_GPIO0_LOW 0x08
|
|
+#define TI960_GPIO1_HIGH 0x90
|
|
+#define TI960_GPIO1_LOW 0x80
|
|
+#define TI960_GPIO0_FSIN 0x0a
|
|
+#define TI960_GPIO1_FSIN 0xa0
|
|
+#define TI960_GPIO0_MASK 0x0f
|
|
+#define TI960_GPIO1_MASK 0xf0
|
|
+#define TI960_MIPI_800MBPS 0x2
|
|
+#define TI960_MIPI_1600MBPS 0x0
|
|
+#define TI960_CSI_ENABLE 0x1
|
|
+#define TI960_CSI_CONTS_CLOCK 0x2
|
|
+#define TI960_CSI_SKEWCAL 0x40
|
|
+#define TI960_FSIN_ENABLE 0x1
|
|
+#endif
|
|
diff --git a/drivers/media/i2c/ti960.c b/drivers/media/i2c/ti960.c
|
|
new file mode 100644
|
|
index 000000000000..2761dbe17bc8
|
|
--- /dev/null
|
|
+++ b/drivers/media/i2c/ti960.c
|
|
@@ -0,0 +1,1344 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+// Copyright (C) 2018 Intel Corporation
|
|
+
|
|
+#include <linux/device.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/wait.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/version.h>
|
|
+
|
|
+#include <media/media-device.h>
|
|
+#include <media/media-entity.h>
|
|
+#include <media/ti960.h>
|
|
+#include <media/crlmodule.h>
|
|
+#include <media/v4l2-device.h>
|
|
+#include <media/videobuf2-core.h>
|
|
+
|
|
+#include "ti960-reg.h"
|
|
+
|
|
+struct ti960_subdev {
|
|
+ struct v4l2_subdev *sd;
|
|
+ unsigned short rx_port;
|
|
+ unsigned short fsin_gpio;
|
|
+ unsigned short phy_i2c_addr;
|
|
+ unsigned short alias_i2c_addr;
|
|
+ char sd_name[16];
|
|
+};
|
|
+
|
|
+struct ti960 {
|
|
+ struct v4l2_subdev sd;
|
|
+ struct media_pad pad[NR_OF_TI960_PADS];
|
|
+ struct v4l2_ctrl_handler ctrl_handler;
|
|
+ struct ti960_pdata *pdata;
|
|
+ struct ti960_subdev sub_devs[NR_OF_TI960_SINK_PADS];
|
|
+ struct crlmodule_platform_data subdev_pdata[NR_OF_TI960_SINK_PADS];
|
|
+ const char *name;
|
|
+
|
|
+ struct mutex mutex;
|
|
+
|
|
+ struct regmap *regmap8;
|
|
+ struct regmap *regmap16;
|
|
+
|
|
+ struct v4l2_mbus_framefmt *ffmts[NR_OF_TI960_PADS];
|
|
+ struct rect *crop;
|
|
+ struct rect *compose;
|
|
+
|
|
+ struct v4l2_subdev_route *ti960_route;
|
|
+
|
|
+ unsigned int nsinks;
|
|
+ unsigned int nsources;
|
|
+ unsigned int nstreams;
|
|
+ unsigned int npads;
|
|
+
|
|
+ struct gpio_chip gc;
|
|
+
|
|
+ struct v4l2_ctrl *link_freq;
|
|
+ struct v4l2_ctrl *test_pattern;
|
|
+};
|
|
+
|
|
+#define to_ti960(_sd) container_of(_sd, struct ti960, sd)
|
|
+
|
|
+static const s64 ti960_op_sys_clock[] = {400000000, 800000000};
|
|
+static const u8 ti960_op_sys_clock_reg_val[] = {
|
|
+ TI960_MIPI_800MBPS,
|
|
+ TI960_MIPI_1600MBPS
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Order matters.
|
|
+ *
|
|
+ * 1. Bits-per-pixel, descending.
|
|
+ * 2. Bits-per-pixel compressed, descending.
|
|
+ * 3. Pixel order, same as in pixel_order_str. Formats for all four pixel
|
|
+ * orders must be defined.
|
|
+ */
|
|
+static const struct ti960_csi_data_format va_csi_data_formats[] = {
|
|
+ { MEDIA_BUS_FMT_SGRBG16_1X16, 16, 16, PIXEL_ORDER_GRBG, 0x2e },
|
|
+ { MEDIA_BUS_FMT_SRGGB16_1X16, 16, 16, PIXEL_ORDER_RGGB, 0x2e },
|
|
+ { MEDIA_BUS_FMT_SBGGR16_1X16, 16, 16, PIXEL_ORDER_BGGR, 0x2e },
|
|
+ { MEDIA_BUS_FMT_SGBRG16_1X16, 16, 16, PIXEL_ORDER_GBRG, 0x2e },
|
|
+ { MEDIA_BUS_FMT_SGRBG12_1X12, 12, 12, PIXEL_ORDER_GRBG, 0x2c },
|
|
+ { MEDIA_BUS_FMT_SRGGB12_1X12, 12, 12, PIXEL_ORDER_RGGB, 0x2c },
|
|
+ { MEDIA_BUS_FMT_SBGGR12_1X12, 12, 12, PIXEL_ORDER_BGGR, 0x2c },
|
|
+ { MEDIA_BUS_FMT_SGBRG12_1X12, 12, 12, PIXEL_ORDER_GBRG, 0x2c },
|
|
+ { MEDIA_BUS_FMT_SGRBG10_1X10, 10, 10, PIXEL_ORDER_GRBG, 0x2b },
|
|
+ { MEDIA_BUS_FMT_SRGGB10_1X10, 10, 10, PIXEL_ORDER_RGGB, 0x2b },
|
|
+ { MEDIA_BUS_FMT_SBGGR10_1X10, 10, 10, PIXEL_ORDER_BGGR, 0x2b },
|
|
+ { MEDIA_BUS_FMT_SGBRG10_1X10, 10, 10, PIXEL_ORDER_GBRG, 0x2b },
|
|
+};
|
|
+
|
|
+static const uint32_t ti960_supported_codes_pad[] = {
|
|
+ MEDIA_BUS_FMT_SBGGR16_1X16,
|
|
+ MEDIA_BUS_FMT_SGBRG16_1X16,
|
|
+ MEDIA_BUS_FMT_SGRBG16_1X16,
|
|
+ MEDIA_BUS_FMT_SRGGB16_1X16,
|
|
+ MEDIA_BUS_FMT_SBGGR12_1X12,
|
|
+ MEDIA_BUS_FMT_SGBRG12_1X12,
|
|
+ MEDIA_BUS_FMT_SGRBG12_1X12,
|
|
+ MEDIA_BUS_FMT_SRGGB12_1X12,
|
|
+ MEDIA_BUS_FMT_SBGGR10_1X10,
|
|
+ MEDIA_BUS_FMT_SGBRG10_1X10,
|
|
+ MEDIA_BUS_FMT_SGRBG10_1X10,
|
|
+ MEDIA_BUS_FMT_SRGGB10_1X10,
|
|
+ 0,
|
|
+};
|
|
+
|
|
+static const uint32_t *ti960_supported_codes[] = {
|
|
+ ti960_supported_codes_pad,
|
|
+};
|
|
+
|
|
+static struct regmap_config ti960_reg_config8 = {
|
|
+ .reg_bits = 8,
|
|
+ .val_bits = 8,
|
|
+};
|
|
+
|
|
+static struct regmap_config ti960_reg_config16 = {
|
|
+ .reg_bits = 16,
|
|
+ .val_bits = 8,
|
|
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
|
|
+};
|
|
+
|
|
+static int ti953_reg_write(struct ti960 *va, unsigned short rx_port,
|
|
+ unsigned char reg, unsigned char val)
|
|
+{
|
|
+ int ret;
|
|
+ int retry, timeout = 10;
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&va->sd);
|
|
+ unsigned short ser_alias = va->pdata->subdev_info[rx_port].ser_alias;
|
|
+
|
|
+ client->addr = ser_alias;
|
|
+ for (retry = 0; retry < timeout; retry++) {
|
|
+ ret = i2c_smbus_write_byte_data(client, reg, val);
|
|
+ if (ret < 0) {
|
|
+ dev_err(va->sd.dev, "ti953 reg write ret=%x", ret);
|
|
+ usleep_range(5000, 6000);
|
|
+ } else
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ client->addr = TI960_I2C_ADDRESS;
|
|
+ if (retry >= timeout) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "%s:write reg failed: port=%2x, addr=%2x, reg=%2x\n",
|
|
+ __func__, rx_port, ser_alias, reg);
|
|
+ return -EREMOTEIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_reg_read(struct ti960 *va, unsigned char reg, unsigned int *val)
|
|
+{
|
|
+ int ret, retry, timeout = 10;
|
|
+
|
|
+ for (retry = 0; retry < timeout; retry++) {
|
|
+ ret = regmap_read(va->regmap8, reg, val);
|
|
+ if (ret < 0) {
|
|
+ dev_err(va->sd.dev, "960 reg read ret=%x", ret);
|
|
+ usleep_range(5000, 6000);
|
|
+ } else {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (retry >= timeout) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "%s:devid read failed: reg=%2x, ret=%d\n",
|
|
+ __func__, reg, ret);
|
|
+ return -EREMOTEIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_reg_set_bit(struct ti960 *va, unsigned char reg,
|
|
+ unsigned char bit, unsigned char val)
|
|
+{
|
|
+ int ret;
|
|
+ unsigned int reg_val;
|
|
+
|
|
+ ret = regmap_read(va->regmap8, reg, ®_val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (val)
|
|
+ reg_val |= 1 << bit;
|
|
+ else
|
|
+ reg_val &= ~(1 << bit);
|
|
+
|
|
+ return regmap_write(va->regmap8, reg, reg_val);
|
|
+}
|
|
+
|
|
+static int ti960_map_phy_i2c_addr(struct ti960 *va, unsigned short rx_port,
|
|
+ unsigned short addr)
|
|
+{
|
|
+ int rval;
|
|
+
|
|
+ rval = regmap_write(va->regmap8, TI960_RX_PORT_SEL,
|
|
+ (rx_port << 4) + (1 << rx_port));
|
|
+ if (rval)
|
|
+ return rval;
|
|
+
|
|
+ return regmap_write(va->regmap8, TI960_SLAVE_ID0, addr);
|
|
+}
|
|
+
|
|
+static int ti960_map_alias_i2c_addr(struct ti960 *va, unsigned short rx_port,
|
|
+ unsigned short addr)
|
|
+{
|
|
+ int rval;
|
|
+
|
|
+ rval = regmap_write(va->regmap8, TI960_RX_PORT_SEL,
|
|
+ (rx_port << 4) + (1 << rx_port));
|
|
+ if (rval)
|
|
+ return rval;
|
|
+
|
|
+ return regmap_write(va->regmap8, TI960_SLAVE_ALIAS_ID0, addr);
|
|
+}
|
|
+
|
|
+static int ti960_fsin_gpio_init(struct ti960 *va, unsigned short rx_port,
|
|
+ unsigned short fsin_gpio)
|
|
+{
|
|
+ int rval;
|
|
+ int reg_val;
|
|
+
|
|
+ dev_dbg(va->sd.dev, "%s\n", __func__);
|
|
+ rval = regmap_read(va->regmap8, TI960_FS_CTL, ®_val);
|
|
+ if (rval) {
|
|
+ dev_dbg(va->sd.dev, "Failed to read gpio status.\n");
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ if (!reg_val & TI960_FSIN_ENABLE) {
|
|
+ dev_dbg(va->sd.dev, "FSIN not enabled, skip config FSIN GPIO.\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ rval = regmap_write(va->regmap8, TI960_RX_PORT_SEL,
|
|
+ (rx_port << 4) + (1 << rx_port));
|
|
+ if (rval)
|
|
+ return rval;
|
|
+
|
|
+ rval = regmap_read(va->regmap8, TI960_BC_GPIO_CTL0, ®_val);
|
|
+ if (rval) {
|
|
+ dev_dbg(va->sd.dev, "Failed to read gpio status.\n");
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ if (fsin_gpio == 0) {
|
|
+ reg_val &= ~TI960_GPIO0_MASK;
|
|
+ reg_val |= TI960_GPIO0_FSIN;
|
|
+ } else {
|
|
+ reg_val &= ~TI960_GPIO1_MASK;
|
|
+ reg_val |= TI960_GPIO1_FSIN;
|
|
+ }
|
|
+
|
|
+ rval = regmap_write(va->regmap8, TI960_BC_GPIO_CTL0, reg_val);
|
|
+ if (rval)
|
|
+ dev_dbg(va->sd.dev, "Failed to set gpio.\n");
|
|
+
|
|
+ return rval;
|
|
+}
|
|
+
|
|
+static int ti960_get_routing(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_routing *route)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(sd);
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < min(va->nstreams, route->num_routes); ++i) {
|
|
+ route->routes[i].sink_pad = va->ti960_route[i].sink_pad;
|
|
+ route->routes[i].sink_stream = va->ti960_route[i].sink_stream;
|
|
+ route->routes[i].source_pad = va->ti960_route[i].source_pad;
|
|
+ route->routes[i].source_stream = va->ti960_route[i].source_stream;
|
|
+ route->routes[i].flags = va->ti960_route[i].flags;
|
|
+ }
|
|
+
|
|
+ route->num_routes = i;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_set_routing(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_routing *route)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(sd);
|
|
+ int i, j, ret = 0;
|
|
+
|
|
+ for (i = 0; i < min(route->num_routes, va->nstreams); ++i) {
|
|
+ struct v4l2_subdev_route *t = &route->routes[i];
|
|
+
|
|
+ if (t->sink_stream > va->nstreams - 1 ||
|
|
+ t->source_stream > va->nstreams - 1)
|
|
+ continue;
|
|
+
|
|
+ if (t->source_pad != TI960_PAD_SOURCE)
|
|
+ continue;
|
|
+
|
|
+ for (j = 0; j < va->nstreams; j++) {
|
|
+ if (t->sink_pad == va->ti960_route[j].sink_pad &&
|
|
+ t->source_pad == va->ti960_route[j].source_pad &&
|
|
+ t->sink_stream == va->ti960_route[j].sink_stream &&
|
|
+ t->source_stream == va->ti960_route[j].source_stream)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (j == va->nstreams)
|
|
+ continue;
|
|
+
|
|
+ if (t->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)
|
|
+ va->ti960_route[j].flags |=
|
|
+ V4L2_SUBDEV_ROUTE_FL_ACTIVE;
|
|
+ else if (!(t->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
|
|
+ va->ti960_route[j].flags &=
|
|
+ (~V4L2_SUBDEV_ROUTE_FL_ACTIVE);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ti960_enum_mbus_code(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(sd);
|
|
+ const uint32_t *supported_code =
|
|
+ ti960_supported_codes[code->pad];
|
|
+ bool next_stream = false;
|
|
+ int i;
|
|
+
|
|
+ if (code->stream & V4L2_SUBDEV_FLAG_NEXT_STREAM) {
|
|
+ next_stream = true;
|
|
+ code->stream &= ~V4L2_SUBDEV_FLAG_NEXT_STREAM;
|
|
+ }
|
|
+
|
|
+ if (code->stream > va->nstreams)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (next_stream) {
|
|
+ if (!(va->pad[code->pad].flags & MEDIA_PAD_FL_MULTIPLEX))
|
|
+ return -EINVAL;
|
|
+ if (code->stream < va->nstreams - 1) {
|
|
+ code->stream++;
|
|
+ return 0;
|
|
+ } else {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; supported_code[i]; i++) {
|
|
+ if (i == code->index) {
|
|
+ code->code = supported_code[i];
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static const struct ti960_csi_data_format
|
|
+ *ti960_validate_csi_data_format(u32 code)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(va_csi_data_formats); i++) {
|
|
+ if (va_csi_data_formats[i].code == code)
|
|
+ return &va_csi_data_formats[i];
|
|
+ }
|
|
+
|
|
+ return &va_csi_data_formats[0];
|
|
+}
|
|
+
|
|
+static int ti960_get_frame_desc(struct v4l2_subdev *sd,
|
|
+ unsigned int pad, struct v4l2_mbus_frame_desc *desc)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(sd);
|
|
+ struct v4l2_mbus_frame_desc_entry *entry = desc->entry;
|
|
+ u8 vc = 0;
|
|
+ int i;
|
|
+
|
|
+ desc->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
|
|
+ desc->num_entries = min_t(int, va->nstreams, V4L2_FRAME_DESC_ENTRY_MAX);
|
|
+
|
|
+ for (i = 0; i < desc->num_entries; i++) {
|
|
+ struct v4l2_mbus_framefmt *ffmt =
|
|
+ &va->ffmts[TI960_PAD_SOURCE][i];
|
|
+ const struct ti960_csi_data_format *csi_format =
|
|
+ ti960_validate_csi_data_format(ffmt->code);
|
|
+
|
|
+ entry->two_dim.width = ffmt->width;
|
|
+ entry->two_dim.height = ffmt->height;
|
|
+ entry->pixelcode = ffmt->code;
|
|
+ entry->bus.csi2.channel = vc++;
|
|
+ entry->bpp = csi_format->compressed;
|
|
+ entry++;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct v4l2_mbus_framefmt *
|
|
+__ti960_get_ffmt(struct v4l2_subdev *subdev,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ unsigned int pad, unsigned int which,
|
|
+ unsigned int stream)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+
|
|
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
|
|
+ return v4l2_subdev_get_try_format(subdev, cfg, pad);
|
|
+ else
|
|
+ return &va->ffmts[pad][stream];
|
|
+}
|
|
+
|
|
+static int ti960_get_format(struct v4l2_subdev *subdev,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+
|
|
+ if (fmt->stream > va->nstreams)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&va->mutex);
|
|
+ fmt->format = *__ti960_get_ffmt(subdev, cfg, fmt->pad,
|
|
+ fmt->which, fmt->stream);
|
|
+ mutex_unlock(&va->mutex);
|
|
+
|
|
+ dev_dbg(subdev->dev, "subdev_format: which: %s, pad: %d, stream: %d.\n",
|
|
+ fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE ?
|
|
+ "V4L2_SUBDEV_FORMAT_ACTIVE" : "V4L2_SUBDEV_FORMAT_TRY",
|
|
+ fmt->pad, fmt->stream);
|
|
+
|
|
+ dev_dbg(subdev->dev, "framefmt: width: %d, height: %d, code: 0x%x.\n",
|
|
+ fmt->format.width, fmt->format.height, fmt->format.code);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_set_format(struct v4l2_subdev *subdev,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+ const struct ti960_csi_data_format *csi_format;
|
|
+ struct v4l2_mbus_framefmt *ffmt;
|
|
+
|
|
+ if (fmt->stream > va->nstreams)
|
|
+ return -EINVAL;
|
|
+
|
|
+ csi_format = ti960_validate_csi_data_format(
|
|
+ fmt->format.code);
|
|
+
|
|
+ mutex_lock(&va->mutex);
|
|
+ ffmt = __ti960_get_ffmt(subdev, cfg, fmt->pad, fmt->which,
|
|
+ fmt->stream);
|
|
+
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
|
|
+ ffmt->width = fmt->format.width;
|
|
+ ffmt->height = fmt->format.height;
|
|
+ ffmt->code = csi_format->code;
|
|
+ }
|
|
+ fmt->format = *ffmt;
|
|
+ mutex_unlock(&va->mutex);
|
|
+
|
|
+ dev_dbg(subdev->dev, "framefmt: width: %d, height: %d, code: 0x%x.\n",
|
|
+ ffmt->width, ffmt->height, ffmt->code);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_open(struct v4l2_subdev *subdev,
|
|
+ struct v4l2_subdev_fh *fh)
|
|
+{
|
|
+ struct v4l2_mbus_framefmt *try_fmt =
|
|
+ v4l2_subdev_get_try_format(subdev, fh->pad, 0);
|
|
+
|
|
+ struct v4l2_subdev_format fmt = {
|
|
+ .which = V4L2_SUBDEV_FORMAT_TRY,
|
|
+ .pad = TI960_PAD_SOURCE,
|
|
+ .format = {
|
|
+ .width = TI960_MAX_WIDTH,
|
|
+ .height = TI960_MAX_HEIGHT,
|
|
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
|
|
+ },
|
|
+ .stream = 0,
|
|
+ };
|
|
+
|
|
+ *try_fmt = fmt.format;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_registered(struct v4l2_subdev *subdev)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(subdev);
|
|
+ int i, j, k, l, rval;
|
|
+
|
|
+ for (i = 0, k = 0; i < va->pdata->subdev_num; i++) {
|
|
+ struct ti960_subdev_info *info =
|
|
+ &va->pdata->subdev_info[i];
|
|
+ struct crlmodule_platform_data *pdata =
|
|
+ (struct crlmodule_platform_data *)
|
|
+ info->board_info.platform_data;
|
|
+
|
|
+ if (k >= va->nsinks)
|
|
+ break;
|
|
+
|
|
+ /*
|
|
+ * The sensors should not share the same pdata structure.
|
|
+ * Clone the pdata for each sensor.
|
|
+ */
|
|
+ memcpy(&va->subdev_pdata[k], pdata, sizeof(*pdata));
|
|
+ if (va->subdev_pdata[k].xshutdown != 0 &&
|
|
+ va->subdev_pdata[k].xshutdown != 1) {
|
|
+ dev_err(va->sd.dev, "xshutdown(%d) must be 0 or 1 to connect.\n",
|
|
+ va->subdev_pdata[k].xshutdown);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* If 0 is xshutdown, then 1 would be FSIN, vice versa. */
|
|
+ va->sub_devs[k].fsin_gpio = 1 - va->subdev_pdata[k].xshutdown;
|
|
+
|
|
+ /* Spin sensor subdev suffix name */
|
|
+ va->subdev_pdata[k].suffix = info->suffix;
|
|
+
|
|
+ /*
|
|
+ * Change the gpio value to have xshutdown
|
|
+ * and rx port included, so in gpio_set those
|
|
+ * can be caculated from it.
|
|
+ */
|
|
+ va->subdev_pdata[k].xshutdown += va->gc.base +
|
|
+ info->rx_port * NR_OF_GPIOS_PER_PORT;
|
|
+ info->board_info.platform_data = &va->subdev_pdata[k];
|
|
+
|
|
+ if (!info->phy_i2c_addr || !info->board_info.addr) {
|
|
+ dev_err(va->sd.dev, "can't find the physical and alias addr.\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Map PHY I2C address. */
|
|
+ rval = ti960_map_phy_i2c_addr(va, info->rx_port,
|
|
+ info->phy_i2c_addr);
|
|
+ if (rval)
|
|
+ return rval;
|
|
+
|
|
+ /* Map 7bit ALIAS I2C address. */
|
|
+ rval = ti960_map_alias_i2c_addr(va, info->rx_port,
|
|
+ info->board_info.addr << 1);
|
|
+ if (rval)
|
|
+ return rval;
|
|
+
|
|
+ va->sub_devs[k].sd = v4l2_i2c_new_subdev_board(
|
|
+ va->sd.v4l2_dev, client->adapter,
|
|
+ &info->board_info, 0);
|
|
+ if (!va->sub_devs[k].sd) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "can't create new i2c subdev %c\n",
|
|
+ info->suffix);
|
|
+ continue;
|
|
+ }
|
|
+ va->sub_devs[k].rx_port = info->rx_port;
|
|
+ va->sub_devs[k].phy_i2c_addr = info->phy_i2c_addr;
|
|
+ va->sub_devs[k].alias_i2c_addr = info->board_info.addr;
|
|
+ memcpy(va->sub_devs[k].sd_name,
|
|
+ va->subdev_pdata[k].module_name,
|
|
+ min(sizeof(va->sub_devs[k].sd_name) - 1,
|
|
+ sizeof(va->subdev_pdata[k].module_name) - 1));
|
|
+
|
|
+ for (j = 0; j < va->sub_devs[k].sd->entity.num_pads; j++) {
|
|
+ if (va->sub_devs[k].sd->entity.pads[j].flags &
|
|
+ MEDIA_PAD_FL_SOURCE)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (j == va->sub_devs[k].sd->entity.num_pads) {
|
|
+ dev_warn(va->sd.dev,
|
|
+ "no source pad in subdev %c\n",
|
|
+ info->suffix);
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ for (l = 0; l < va->nsinks; l++) {
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
|
|
+ rval = media_entity_create_link(
|
|
+#else
|
|
+ rval = media_create_pad_link(
|
|
+#endif
|
|
+ &va->sub_devs[k].sd->entity, j,
|
|
+ &va->sd.entity, l, 0);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "can't create link to %c\n",
|
|
+ info->suffix);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+ k++;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_set_power(struct v4l2_subdev *subdev, int on)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+ int ret;
|
|
+ u8 val;
|
|
+
|
|
+ ret = regmap_write(va->regmap8, TI960_RESET,
|
|
+ (on) ? TI960_POWER_ON : TI960_POWER_OFF);
|
|
+ if (ret || !on)
|
|
+ return ret;
|
|
+
|
|
+ /* Configure MIPI clock bsaed on control value. */
|
|
+ ret = regmap_write(va->regmap8, TI960_CSI_PLL_CTL,
|
|
+ ti960_op_sys_clock_reg_val[
|
|
+ v4l2_ctrl_g_ctrl(va->link_freq)]);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ val = TI960_CSI_ENABLE;
|
|
+ val |= TI960_CSI_CONTS_CLOCK;
|
|
+ /* Enable skew calculation when 1.6Gbps output is enabled. */
|
|
+ if (v4l2_ctrl_g_ctrl(va->link_freq))
|
|
+ val |= TI960_CSI_SKEWCAL;
|
|
+ return regmap_write(va->regmap8, TI960_CSI_CTL, val);
|
|
+}
|
|
+
|
|
+static bool ti960_broadcast_mode(struct v4l2_subdev *subdev)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+ struct v4l2_subdev_format fmt = { 0 };
|
|
+ struct v4l2_subdev *sd;
|
|
+ char *sd_name = NULL;
|
|
+ bool first = true;
|
|
+ unsigned int h = 0, w = 0, code = 0;
|
|
+ bool single_stream = true;
|
|
+ int i, rval;
|
|
+
|
|
+ for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) {
|
|
+ struct media_pad *remote_pad =
|
|
+ media_entity_remote_pad(&va->pad[i]);
|
|
+
|
|
+ if (!remote_pad)
|
|
+ continue;
|
|
+
|
|
+ sd = media_entity_to_v4l2_subdev(remote_pad->entity);
|
|
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
|
|
+ fmt.pad = remote_pad->index;
|
|
+ fmt.stream = 0;
|
|
+
|
|
+ rval = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
|
|
+ if (rval)
|
|
+ return false;
|
|
+
|
|
+ if (first) {
|
|
+ sd_name = va->sub_devs[i].sd_name;
|
|
+ h = fmt.format.height;
|
|
+ w = fmt.format.width;
|
|
+ code = fmt.format.code;
|
|
+ first = false;
|
|
+ } else {
|
|
+ if (strncmp(sd_name, va->sub_devs[i].sd_name, 16))
|
|
+ return false;
|
|
+
|
|
+ if (h != fmt.format.height || w != fmt.format.width
|
|
+ || code != fmt.format.code)
|
|
+ return false;
|
|
+
|
|
+ single_stream = false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (single_stream)
|
|
+ return false;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static int ti960_rx_port_config(struct ti960 *va, int sink, int rx_port)
|
|
+{
|
|
+ int rval;
|
|
+
|
|
+ /* Select RX port. */
|
|
+ rval = regmap_write(va->regmap8, TI960_RX_PORT_SEL,
|
|
+ (rx_port << 4) + (1 << rx_port));
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev, "Failed to select RX port.\n");
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ rval = regmap_write(va->regmap8, TI960_PORT_CONFIG,
|
|
+ TI960_FPD3_CSI);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev, "Failed to set port config.\n");
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * TODO: CSI VC MAPPING.
|
|
+ */
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_map_subdevs_addr(struct ti960 *va)
|
|
+{
|
|
+ unsigned short rx_port, phy_i2c_addr, alias_i2c_addr;
|
|
+ int i, rval;
|
|
+
|
|
+ for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) {
|
|
+ rx_port = va->sub_devs[i].rx_port;
|
|
+ phy_i2c_addr = va->sub_devs[i].phy_i2c_addr;
|
|
+ alias_i2c_addr = va->sub_devs[i].alias_i2c_addr;
|
|
+
|
|
+ if (!phy_i2c_addr || !alias_i2c_addr)
|
|
+ continue;
|
|
+
|
|
+ rval = ti960_map_phy_i2c_addr(va, rx_port, phy_i2c_addr);
|
|
+ if (rval)
|
|
+ return rval;
|
|
+
|
|
+ /* set 7bit alias i2c addr */
|
|
+ rval = ti960_map_alias_i2c_addr(va, rx_port,
|
|
+ alias_i2c_addr << 1);
|
|
+ if (rval)
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_find_subdev_index(struct ti960 *va, struct v4l2_subdev *sd)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) {
|
|
+ if (va->sub_devs[i].sd == sd)
|
|
+ return i;
|
|
+ }
|
|
+
|
|
+ WARN_ON(1);
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int ti960_set_frame_sync(struct ti960 *va, int enable)
|
|
+{
|
|
+ int i, rval;
|
|
+ int index = !!enable;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ti960_frame_sync_settings[index]); i++) {
|
|
+ rval = regmap_write(va->regmap8,
|
|
+ ti960_frame_sync_settings[index][i].reg,
|
|
+ ti960_frame_sync_settings[index][i].val);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev, "Failed to %s frame sync\n",
|
|
+ enable ? "enable" : "disable");
|
|
+ return rval;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_set_stream(struct v4l2_subdev *subdev, int enable)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+ struct v4l2_subdev *sd;
|
|
+ int i, j, rval;
|
|
+ bool broadcast;
|
|
+ unsigned int rx_port;
|
|
+ int sd_idx = -1;
|
|
+ DECLARE_BITMAP(rx_port_enabled, 32);
|
|
+
|
|
+ dev_dbg(va->sd.dev, "TI960 set stream, enable %d\n", enable);
|
|
+
|
|
+ broadcast = ti960_broadcast_mode(subdev);
|
|
+ if (enable)
|
|
+ dev_info(va->sd.dev, "TI960 in %s mode",
|
|
+ broadcast ? "broadcast" : "non broadcast");
|
|
+
|
|
+ bitmap_zero(rx_port_enabled, 32);
|
|
+ for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) {
|
|
+ struct media_pad *remote_pad =
|
|
+ media_entity_remote_pad(&va->pad[i]);
|
|
+
|
|
+ if (!remote_pad)
|
|
+ continue;
|
|
+
|
|
+ /* Find ti960 subdev */
|
|
+ sd = media_entity_to_v4l2_subdev(remote_pad->entity);
|
|
+ j = ti960_find_subdev_index(va, sd);
|
|
+ if (j < 0)
|
|
+ return -EINVAL;
|
|
+ rx_port = va->sub_devs[j].rx_port;
|
|
+ rval = ti960_rx_port_config(va, i, rx_port);
|
|
+ if (rval < 0)
|
|
+ return rval;
|
|
+
|
|
+ bitmap_set(rx_port_enabled, rx_port, 1);
|
|
+
|
|
+ if (broadcast && sd_idx == -1) {
|
|
+ sd_idx = j;
|
|
+ } else if (broadcast) {
|
|
+ rval = ti960_map_alias_i2c_addr(va, rx_port,
|
|
+ va->sub_devs[sd_idx].alias_i2c_addr << 1);
|
|
+ if (rval < 0)
|
|
+ return rval;
|
|
+ } else {
|
|
+ /* Stream on/off sensor */
|
|
+ dev_err(va->sd.dev,
|
|
+ "set stream for %s, enable %d\n",
|
|
+ sd->name, enable);
|
|
+ rval = v4l2_subdev_call(sd, video, s_stream, enable);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to set stream for %s, enable %d\n",
|
|
+ sd->name, enable);
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ /* RX port fordward */
|
|
+ rval = ti960_reg_set_bit(va, TI960_FWD_CTL1,
|
|
+ rx_port + 4, !enable);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to forward RX port%d. enable %d\n",
|
|
+ i, enable);
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (broadcast) {
|
|
+ if (sd_idx < 0) {
|
|
+ dev_err(va->sd.dev, "No sensor connected!\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ sd = va->sub_devs[sd_idx].sd;
|
|
+ rval = v4l2_subdev_call(sd, video, s_stream, enable);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to set stream for %s. enable %d\n",
|
|
+ sd->name, enable);
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ rval = ti960_set_frame_sync(va, enable);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to set frame sync.\n");
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) {
|
|
+ if (enable && test_bit(i, rx_port_enabled)) {
|
|
+ rval = ti960_fsin_gpio_init(va,
|
|
+ va->sub_devs[i].rx_port,
|
|
+ va->sub_devs[i].fsin_gpio);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to enable frame sync gpio init.\n");
|
|
+ return rval;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) {
|
|
+ if (!test_bit(i, rx_port_enabled))
|
|
+ continue;
|
|
+
|
|
+ /* RX port fordward */
|
|
+ rval = ti960_reg_set_bit(va, TI960_FWD_CTL1,
|
|
+ i + 4, !enable);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to forward RX port%d. enable %d\n",
|
|
+ i, enable);
|
|
+ return rval;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Restore each subdev i2c address as we may
|
|
+ * touch it later.
|
|
+ */
|
|
+ rval = ti960_map_subdevs_addr(va);
|
|
+ if (rval)
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct v4l2_subdev_internal_ops ti960_sd_internal_ops = {
|
|
+ .open = ti960_open,
|
|
+ .registered = ti960_registered,
|
|
+};
|
|
+
|
|
+static bool ti960_sd_has_route(struct media_entity *entity,
|
|
+ unsigned int pad0, unsigned int pad1, int *stream)
|
|
+{
|
|
+ struct ti960 *va = to_ti960(media_entity_to_v4l2_subdev(entity));
|
|
+ int i;
|
|
+
|
|
+ if (va == NULL || stream == NULL ||
|
|
+ *stream >= va->nstreams || *stream < 0)
|
|
+ return false;
|
|
+
|
|
+ for (i = 0; i < va->nstreams; ++i) {
|
|
+ if ((va->ti960_route[*stream].flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE) &&
|
|
+ ((va->ti960_route[*stream].source_pad == pad0 &&
|
|
+ va->ti960_route[*stream].sink_pad == pad1) ||
|
|
+ (va->ti960_route[*stream].source_pad == pad1 &&
|
|
+ va->ti960_route[*stream].sink_pad == pad0)))
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static const struct media_entity_operations ti960_sd_entity_ops = {
|
|
+ .has_route = ti960_sd_has_route,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_video_ops ti960_sd_video_ops = {
|
|
+ .s_stream = ti960_set_stream,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_core_ops ti960_core_subdev_ops = {
|
|
+ .s_power = ti960_set_power,
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
|
|
+ .g_ctrl = v4l2_subdev_g_ctrl,
|
|
+ .s_ctrl = v4l2_subdev_s_ctrl,
|
|
+ .g_ext_ctrls = v4l2_subdev_g_ext_ctrls,
|
|
+ .s_ext_ctrls = v4l2_subdev_s_ext_ctrls,
|
|
+ .try_ext_ctrls = v4l2_subdev_try_ext_ctrls,
|
|
+ .queryctrl = v4l2_subdev_queryctrl,
|
|
+#endif
|
|
+};
|
|
+
|
|
+static int ti960_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_ctrl_ops ti960_ctrl_ops = {
|
|
+ .s_ctrl = ti960_s_ctrl,
|
|
+};
|
|
+
|
|
+static const struct v4l2_ctrl_config ti960_controls[] = {
|
|
+ {
|
|
+ .ops = &ti960_ctrl_ops,
|
|
+ .id = V4L2_CID_LINK_FREQ,
|
|
+ .name = "V4L2_CID_LINK_FREQ",
|
|
+ .type = V4L2_CTRL_TYPE_INTEGER_MENU,
|
|
+ .max = ARRAY_SIZE(ti960_op_sys_clock) - 1,
|
|
+ .min = 0,
|
|
+ .step = 0,
|
|
+ .def = 0,
|
|
+ .qmenu_int = ti960_op_sys_clock,
|
|
+ },
|
|
+ {
|
|
+ .ops = &ti960_ctrl_ops,
|
|
+ .id = V4L2_CID_TEST_PATTERN,
|
|
+ .name = "V4L2_CID_TEST_PATTERN",
|
|
+ .type = V4L2_CTRL_TYPE_INTEGER,
|
|
+ .max = 1,
|
|
+ .min = 0,
|
|
+ .step = 1,
|
|
+ .def = 0,
|
|
+ },
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops ti960_sd_pad_ops = {
|
|
+ .get_fmt = ti960_get_format,
|
|
+ .set_fmt = ti960_set_format,
|
|
+ .get_frame_desc = ti960_get_frame_desc,
|
|
+ .enum_mbus_code = ti960_enum_mbus_code,
|
|
+ .set_routing = ti960_set_routing,
|
|
+ .get_routing = ti960_get_routing,
|
|
+};
|
|
+
|
|
+static struct v4l2_subdev_ops ti960_sd_ops = {
|
|
+ .core = &ti960_core_subdev_ops,
|
|
+ .video = &ti960_sd_video_ops,
|
|
+ .pad = &ti960_sd_pad_ops,
|
|
+};
|
|
+
|
|
+static int ti960_register_subdev(struct ti960 *va)
|
|
+{
|
|
+ int i, rval;
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&va->sd);
|
|
+
|
|
+ v4l2_subdev_init(&va->sd, &ti960_sd_ops);
|
|
+ snprintf(va->sd.name, sizeof(va->sd.name), "TI960 %c",
|
|
+ va->pdata->suffix);
|
|
+
|
|
+ va->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
|
|
+ V4L2_SUBDEV_FL_HAS_SUBSTREAMS;
|
|
+
|
|
+ va->sd.internal_ops = &ti960_sd_internal_ops;
|
|
+ va->sd.entity.ops = &ti960_sd_entity_ops;
|
|
+
|
|
+ v4l2_set_subdevdata(&va->sd, client);
|
|
+
|
|
+ v4l2_ctrl_handler_init(&va->ctrl_handler,
|
|
+ ARRAY_SIZE(ti960_controls));
|
|
+
|
|
+ if (va->ctrl_handler.error) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to init ti960 controls. ERR: %d!\n",
|
|
+ va->ctrl_handler.error);
|
|
+ return va->ctrl_handler.error;
|
|
+ }
|
|
+
|
|
+ va->sd.ctrl_handler = &va->ctrl_handler;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ti960_controls); i++) {
|
|
+ const struct v4l2_ctrl_config *cfg =
|
|
+ &ti960_controls[i];
|
|
+ struct v4l2_ctrl *ctrl;
|
|
+
|
|
+ ctrl = v4l2_ctrl_new_custom(&va->ctrl_handler, cfg, NULL);
|
|
+ if (!ctrl) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to create ctrl %s!\n", cfg->name);
|
|
+ rval = va->ctrl_handler.error;
|
|
+ goto failed_out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ va->link_freq = v4l2_ctrl_find(&va->ctrl_handler, V4L2_CID_LINK_FREQ);
|
|
+ va->test_pattern = v4l2_ctrl_find(&va->ctrl_handler,
|
|
+ V4L2_CID_TEST_PATTERN);
|
|
+
|
|
+ for (i = 0; i < va->nsinks; i++)
|
|
+ va->pad[i].flags = MEDIA_PAD_FL_SINK;
|
|
+ va->pad[TI960_PAD_SOURCE].flags =
|
|
+ MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MULTIPLEX;
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
|
|
+ rval = media_entity_init(&va->sd.entity, NR_OF_TI960_PADS, va->pad, 0);
|
|
+#else
|
|
+ rval = media_entity_pads_init(&va->sd.entity,
|
|
+ NR_OF_TI960_PADS, va->pad);
|
|
+#endif
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to init media entity for ti960!\n");
|
|
+ goto failed_out;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+failed_out:
|
|
+ v4l2_ctrl_handler_free(&va->ctrl_handler);
|
|
+ return rval;
|
|
+}
|
|
+
|
|
+static int ti960_init(struct ti960 *va)
|
|
+{
|
|
+ unsigned int reset_gpio = va->pdata->reset_gpio;
|
|
+ int i, rval;
|
|
+ unsigned int val;
|
|
+
|
|
+ gpio_set_value(reset_gpio, 1);
|
|
+ usleep_range(2000, 3000);
|
|
+ dev_err(va->sd.dev, "Setting reset gpio %d to 1.\n", reset_gpio);
|
|
+
|
|
+ rval = ti960_reg_read(va, TI960_DEVID, &val);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev, "Failed to read device ID of TI960!\n");
|
|
+ return rval;
|
|
+ }
|
|
+ dev_info(va->sd.dev, "TI960 device ID: 0x%X\n", val);
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ti960_gpio_settings); i++) {
|
|
+ rval = regmap_write(va->regmap8,
|
|
+ ti960_gpio_settings[i].reg,
|
|
+ ti960_gpio_settings[i].val);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to write TI960 gpio setting, reg %2x, val %2x\n",
|
|
+ ti960_gpio_settings[i].reg, ti960_gpio_settings[i].val);
|
|
+ return rval;
|
|
+ }
|
|
+ }
|
|
+ usleep_range(10000, 11000);
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ti960_init_settings); i++) {
|
|
+ rval = regmap_write(va->regmap8,
|
|
+ ti960_init_settings[i].reg,
|
|
+ ti960_init_settings[i].val);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to write TI960 init setting, reg %2x, val %2x\n",
|
|
+ ti960_init_settings[i].reg, ti960_init_settings[i].val);
|
|
+ return rval;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* wait for ti953 ready */
|
|
+ usleep_range(200000, 300000);
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ti953_init_settings); i++) {
|
|
+ rval = ti953_reg_write(va, 0,
|
|
+ ti953_init_settings[i].reg,
|
|
+ ti953_init_settings[i].val);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev, "port %d, ti953 write timeout %d\n", 0, rval);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ti960_init_settings_2); i++) {
|
|
+ rval = regmap_write(va->regmap8,
|
|
+ ti960_init_settings_2[i].reg,
|
|
+ ti960_init_settings_2[i].val);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev,
|
|
+ "Failed to write TI960 init setting 2, reg %2x, val %2x\n",
|
|
+ ti960_init_settings_2[i].reg, ti960_init_settings_2[i].val);
|
|
+ return rval;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ti953_init_settings_2); i++) {
|
|
+ rval = ti953_reg_write(va, 0,
|
|
+ ti953_init_settings_2[i].reg,
|
|
+ ti953_init_settings_2[i].val);
|
|
+ if (rval) {
|
|
+ dev_err(va->sd.dev, "port %d, ti953 write timeout %d\n", 0, rval);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* reset and power for ti953 */
|
|
+ ti953_reg_write(va, 0, 0x0d, 00);
|
|
+ usleep_range(50000, 60000);
|
|
+ ti953_reg_write(va, 0, 0x0d, 0x3);
|
|
+
|
|
+ rval = ti960_map_subdevs_addr(va);
|
|
+ if (rval)
|
|
+ return rval;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ti960_gpio_set(struct gpio_chip *chip, unsigned int gpio, int value)
|
|
+{
|
|
+}
|
|
+
|
|
+static int ti960_gpio_direction_output(struct gpio_chip *chip,
|
|
+ unsigned int gpio, int level)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_probe(struct i2c_client *client,
|
|
+ const struct i2c_device_id *devid)
|
|
+{
|
|
+ struct ti960 *va;
|
|
+ int i, rval = 0;
|
|
+
|
|
+ if (client->dev.platform_data == NULL)
|
|
+ return -ENODEV;
|
|
+
|
|
+ va = devm_kzalloc(&client->dev, sizeof(*va), GFP_KERNEL);
|
|
+ if (!va)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ va->pdata = client->dev.platform_data;
|
|
+
|
|
+ va->nsources = NR_OF_TI960_SOURCE_PADS;
|
|
+ va->nsinks = NR_OF_TI960_SINK_PADS;
|
|
+ va->npads = NR_OF_TI960_PADS;
|
|
+ va->nstreams = NR_OF_TI960_STREAMS;
|
|
+
|
|
+ va->crop = devm_kcalloc(&client->dev, va->npads,
|
|
+ sizeof(struct v4l2_rect), GFP_KERNEL);
|
|
+
|
|
+ va->compose = devm_kcalloc(&client->dev, va->npads,
|
|
+ sizeof(struct v4l2_rect), GFP_KERNEL);
|
|
+
|
|
+ if (!va->crop || !va->compose)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ for (i = 0; i < va->npads; i++) {
|
|
+ va->ffmts[i] = devm_kcalloc(&client->dev, va->nstreams,
|
|
+ sizeof(struct v4l2_mbus_framefmt),
|
|
+ GFP_KERNEL);
|
|
+ if (!va->ffmts[i])
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ va->ti960_route = devm_kcalloc(&client->dev, NR_OF_TI960_STREAMS,
|
|
+ sizeof(struct v4l2_subdev_routing), GFP_KERNEL);
|
|
+
|
|
+ if (!va->ti960_route)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ for (i = 0; i < va->nstreams; i++) {
|
|
+ va->ti960_route[i].sink_pad = i / 2;
|
|
+ va->ti960_route[i].sink_stream = i % 2;
|
|
+ va->ti960_route[i].source_pad = TI960_PAD_SOURCE;
|
|
+ va->ti960_route[i].source_stream = i % 2;
|
|
+ va->ti960_route[i].flags = MEDIA_PAD_FL_MULTIPLEX;
|
|
+ }
|
|
+
|
|
+ va->regmap8 = devm_regmap_init_i2c(client,
|
|
+ &ti960_reg_config8);
|
|
+ if (IS_ERR(va->regmap8)) {
|
|
+ dev_err(&client->dev, "Failed to init regmap8!\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ va->regmap16 = devm_regmap_init_i2c(client,
|
|
+ &ti960_reg_config16);
|
|
+ if (IS_ERR(va->regmap16)) {
|
|
+ dev_err(&client->dev, "Failed to init regmap16!\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ mutex_init(&va->mutex);
|
|
+ v4l2_i2c_subdev_init(&va->sd, client, &ti960_sd_ops);
|
|
+ rval = ti960_register_subdev(va);
|
|
+ if (rval) {
|
|
+ dev_err(&client->dev, "Failed to register va subdevice!\n");
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ if (devm_gpio_request_one(va->sd.dev, va->pdata->reset_gpio, 0,
|
|
+ "ti960 reset") != 0) {
|
|
+ dev_err(va->sd.dev, "Unable to acquire gpio %d\n",
|
|
+ va->pdata->reset_gpio);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ rval = ti960_init(va);
|
|
+ if (rval) {
|
|
+ dev_err(&client->dev, "Failed to init TI960!\n");
|
|
+ return rval;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * TI960 has several back channel GPIOs.
|
|
+ * We export GPIO0 and GPIO1 to control reset or fsin.
|
|
+ */
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
|
|
+ va->gc.dev = &client->dev;
|
|
+#else
|
|
+ va->gc.parent = &client->dev;
|
|
+#endif
|
|
+ va->gc.owner = THIS_MODULE;
|
|
+ va->gc.label = "TI960 GPIO";
|
|
+ va->gc.ngpio = NR_OF_TI960_GPIOS;
|
|
+ va->gc.base = -1;
|
|
+ va->gc.set = ti960_gpio_set;
|
|
+ va->gc.direction_output = ti960_gpio_direction_output;
|
|
+ rval = gpiochip_add(&va->gc);
|
|
+ if (rval) {
|
|
+ dev_err(&client->dev, "Failed to add gpio chip!\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+ int i;
|
|
+
|
|
+ if (!va)
|
|
+ return 0;
|
|
+
|
|
+ mutex_destroy(&va->mutex);
|
|
+ v4l2_ctrl_handler_free(&va->ctrl_handler);
|
|
+ v4l2_device_unregister_subdev(&va->sd);
|
|
+ media_entity_cleanup(&va->sd.entity);
|
|
+
|
|
+ for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) {
|
|
+ if (va->sub_devs[i].sd) {
|
|
+ struct i2c_client *sub_client =
|
|
+ v4l2_get_subdevdata(va->sub_devs[i].sd);
|
|
+
|
|
+ i2c_unregister_device(sub_client);
|
|
+ }
|
|
+ va->sub_devs[i].sd = NULL;
|
|
+ }
|
|
+
|
|
+ gpiochip_remove(&va->gc);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int ti960_suspend(struct device *dev)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ti960_resume(struct device *dev)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
|
|
+ struct ti960 *va = to_ti960(subdev);
|
|
+
|
|
+ return ti960_init(va);
|
|
+}
|
|
+#else
|
|
+#define ti960_suspend NULL
|
|
+#define ti960_resume NULL
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+static const struct i2c_device_id ti960_id_table[] = {
|
|
+ { TI960_NAME, 0 },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(i2c, ti960_id_table);
|
|
+
|
|
+static const struct dev_pm_ops ti960_pm_ops = {
|
|
+ .suspend = ti960_suspend,
|
|
+ .resume = ti960_resume,
|
|
+};
|
|
+
|
|
+static struct i2c_driver ti960_i2c_driver = {
|
|
+ .driver = {
|
|
+ .name = TI960_NAME,
|
|
+ .pm = &ti960_pm_ops,
|
|
+ },
|
|
+ .probe = ti960_probe,
|
|
+ .remove = ti960_remove,
|
|
+ .id_table = ti960_id_table,
|
|
+};
|
|
+module_i2c_driver(ti960_i2c_driver);
|
|
+
|
|
+MODULE_AUTHOR("Chen Meng J <meng.j.chen@intel.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("TI960 CSI2-Aggregator driver");
|
|
diff --git a/include/media/ti960.h b/include/media/ti960.h
|
|
new file mode 100644
|
|
index 000000000000..60e134fce6c5
|
|
--- /dev/null
|
|
+++ b/include/media/ti960.h
|
|
@@ -0,0 +1,62 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/* Copyright (C) 2018 Intel Corporation */
|
|
+
|
|
+#ifndef TI960_H
|
|
+#define TI960_H
|
|
+
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <media/v4l2-common.h>
|
|
+#include <media/v4l2-ctrls.h>
|
|
+#include <media/v4l2-device.h>
|
|
+
|
|
+#define TI960_NAME "ti960"
|
|
+
|
|
+#define TI960_I2C_ADDRESS 0x38
|
|
+
|
|
+#define PIXEL_ORDER_GRBG 0
|
|
+#define PIXEL_ORDER_RGGB 1
|
|
+#define PIXEL_ORDER_BGGR 2
|
|
+#define PIXEL_ORDER_GBRG 3
|
|
+
|
|
+#define NR_OF_TI960_STREAMS 4
|
|
+#define NR_OF_TI960_SOURCE_PADS 1
|
|
+#define NR_OF_TI960_SINK_PADS 4
|
|
+#define NR_OF_TI960_PADS \
|
|
+ (NR_OF_TI960_SOURCE_PADS + NR_OF_TI960_SINK_PADS)
|
|
+#define NR_OF_GPIOS_PER_PORT 2
|
|
+#define NR_OF_TI960_GPIOS \
|
|
+ (NR_OF_TI960_SINK_PADS * NR_OF_GPIOS_PER_PORT)
|
|
+
|
|
+#define TI960_PAD_SOURCE 4
|
|
+
|
|
+#define TI960_MIN_WIDTH 640
|
|
+#define TI960_MIN_HEIGHT 480
|
|
+#define TI960_MAX_WIDTH 1920
|
|
+#define TI960_MAX_HEIGHT 1080
|
|
+
|
|
+struct ti960_csi_data_format {
|
|
+ u32 code;
|
|
+ u8 width;
|
|
+ u8 compressed;
|
|
+ u8 pixel_order;
|
|
+ u8 mipi_dt_code;
|
|
+};
|
|
+
|
|
+struct ti960_subdev_info {
|
|
+ struct i2c_board_info board_info;
|
|
+ int i2c_adapter_id;
|
|
+ unsigned short rx_port;
|
|
+ unsigned short phy_i2c_addr;
|
|
+ unsigned short ser_alias;
|
|
+ const char suffix; /* suffix for subdevs */
|
|
+};
|
|
+
|
|
+struct ti960_pdata {
|
|
+ unsigned int subdev_num;
|
|
+ struct ti960_subdev_info *subdev_info;
|
|
+ unsigned int reset_gpio;
|
|
+ const char suffix;
|
|
+};
|
|
+
|
|
+#endif
|
|
--
|
|
https://clearlinux.org
|
|
|