From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Meng Wei 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 Signed-off-by: Meng Wei --- 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 "); +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 +#include +#include +#include +#include + +#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