From: Luca Ceresoli <luca@lucaceresoli.net>
To: linux-media@vger.kernel.org
Cc: Luca Ceresoli <luca@lucaceresoli.net>,
Kieran Bingham <kieran.bingham@ideasonboard.com>,
Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
jacopo mondi <jacopo@jmondi.org>,
Vladimir Zapolskiy <vz@mleia.com>,
Wolfram Sang <wsa@the-dreams.de>, Peter Rosin <peda@axentia.se>,
Mauro Carvalho Chehab <mchehab@kernel.org>,
Rob Herring <robh+dt@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-i2c@vger.kernel.org
Subject: [RFC 4/4] media: ds90ub954: new driver for TI DS90UB954-Q1 video deserializer
Date: Tue, 8 Jan 2019 23:39:53 +0100 [thread overview]
Message-ID: <20190108223953.9969-5-luca@lucaceresoli.net> (raw)
In-Reply-To: <20190108223953.9969-1-luca@lucaceresoli.net>
LIMITATIONS / TODO:
* Only I2C forwarding is quite complete. Most other features are
only stubbed or not implemented at all. Remote GPIOs have a very
naive implementation, where devicetree contains the values to
write into registers. V4L2 callbacks are almost empty.
* Testing is equally incomplete.
* Only single camera and single flow is currently (partially)
implemented
* Interrupt handler not implemented: interrupts are polled in a
kthread at the moment
* The 'status' sysfs file is not sysfs compliant (contains multiple
values). It was initially used to test the code and it should be
rewritten differently.
* In ds90_rxport_remove_serializer remove only 1 adapter, not all!
(Needs work in i2c-mux)
* The port registers are paged, we need to select the port each time
and to use a lock to keep it selected. Make the driver simpler and
more efficient by using a separate I2C address for each port (see
datasheet, 7.5.1.2 Device Address).
* Some registers are paged, thus regmap usage is probably wrong.
3 solutions:
- use a different I2C address for each rxport (see above)
- use a regmap per port and one for the common registers
- don't use regmap
* Instantiate the remote serializer under the mux adapter? See
comment in ds90_rxport_add_serializer()
Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>
---
drivers/media/Kconfig | 1 +
drivers/media/Makefile | 2 +-
drivers/media/serdes/Kconfig | 13 +
drivers/media/serdes/Makefile | 1 +
drivers/media/serdes/ds90ub954.c | 1335 ++++++++++++++++++++++++++++++
5 files changed, 1351 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/serdes/Kconfig
create mode 100644 drivers/media/serdes/Makefile
create mode 100644 drivers/media/serdes/ds90ub954.c
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
index 102eb35fcf3f..9ddb638973d6 100644
--- a/drivers/media/Kconfig
+++ b/drivers/media/Kconfig
@@ -251,5 +251,6 @@ source "drivers/media/i2c/Kconfig"
source "drivers/media/spi/Kconfig"
source "drivers/media/tuners/Kconfig"
source "drivers/media/dvb-frontends/Kconfig"
+source "drivers/media/serdes/Kconfig"
endif # MEDIA_SUPPORT
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index 985d35ec6b29..09d5c6b5f0a6 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -10,7 +10,7 @@ media-objs := media-device.o media-devnode.o media-entity.o \
# I2C drivers should come before other drivers, otherwise they'll fail
# when compiled as builtin drivers
#
-obj-y += i2c/ tuners/
+obj-y += i2c/ tuners/ serdes/
obj-$(CONFIG_DVB_CORE) += dvb-frontends/
#
diff --git a/drivers/media/serdes/Kconfig b/drivers/media/serdes/Kconfig
new file mode 100644
index 000000000000..6f8d02c187e6
--- /dev/null
+++ b/drivers/media/serdes/Kconfig
@@ -0,0 +1,13 @@
+menu "Video serializers and deserializers"
+
+config SERDES_DS90UB954
+ tristate "TI DS90UB954-Q1 deserializer"
+ help
+ Device driver for the Texas Instruments "DS90UB954-Q1 Dual
+ 4.16 Gbps FPD-Link III Deserializer Hub With MIPI CSI-2
+ Outputs for 2MP/60fps Cameras and RADAR".
+
+ To compile this driver as a module, choose M here: the
+ module will be called ds90ub954.
+
+endmenu
diff --git a/drivers/media/serdes/Makefile b/drivers/media/serdes/Makefile
new file mode 100644
index 000000000000..19589a721cb4
--- /dev/null
+++ b/drivers/media/serdes/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SERDES_DS90UB954) += ds90ub954.o
diff --git a/drivers/media/serdes/ds90ub954.c b/drivers/media/serdes/ds90ub954.c
new file mode 100644
index 000000000000..53fea6aa8e9a
--- /dev/null
+++ b/drivers/media/serdes/ds90ub954.c
@@ -0,0 +1,1335 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2019 Luca Ceresoli <luca@lucaceresoli.net>
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/i2c-mux.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <media/v4l2-subdev.h>
+
+#define DS90_NUM_RXPORTS 2 /* Physical RX ports */
+
+#define DS90_NUM_GPIOS 7 /* Physical GPIO pins */
+#define DS90_NUM_BC_GPIOS 4 /* Max GPIOs on Back Channel */
+#define DS90_GPIO_NSOURCES 8 /* Possible GPIO out sources */
+#define DS90_GPIO_NFUNCS 8 /* Possible GPIO out functions */
+
+#define DS90_NUM_SLAVE_ALIASES 8
+#define DS90_MAX_POOL_ALIASES (DS90_NUM_RXPORTS * DS90_NUM_SLAVE_ALIASES)
+
+#define DS90_V4L2_NUM_PADS 2 /* TODO +1 for DS90_NUM_CSITXPORTS */
+
+#define DS90_REG_I2C_DEV_ID 0x00
+#define DS90_REG_RESET 0x01
+#define DS90_REG_GEN_CONFIG 0x02
+#define DS90_REG_REV_MASK 0x03
+#define DS90_REG_DEVICE_STS 0x04
+#define DS90_REG_PAR_ERR_THOLD_HI 0x05
+#define DS90_REG_PAR_ERR_THOLD_LO 0x06
+#define DS90_REG_BCC_WDOG_CTL 0x07
+#define DS90_REG_I2C_CTL1 0x08
+#define DS90_REG_I2C_CTL2 0x09
+#define DS90_REG_SCL_HIGH_TIME 0x0A
+#define DS90_REG_SCL_LOW_TIME 0x0B
+#define DS90_REG_RX_PORT_CTL 0x0C
+#define DS90_REG_IO_CTL 0x0D
+#define DS90_REG_GPIO_PIN_STS 0x0E
+#define DS90_REG_GPIO_INPUT_CTL 0x0F
+#define DS90_REG_GPIO_PIN_CTL(n) (0x10 + (n)) /* n < DS90_NUM_GPIOS */
+#define DS90_REG_FS_CTL 0x18
+#define DS90_REG_FS_HIGH_TIME_1 0x19
+#define DS90_REG_FS_HIGH_TIME_0 0x1A
+#define DS90_REG_FS_LOW_TIME_1 0x1B
+#define DS90_REG_FS_LOW_TIME_0 0x1C
+#define DS90_REG_MAX_FRM_HI 0x1D
+#define DS90_REG_MAX_FRM_LO 0x1E
+#define DS90_REG_CSI_PLL_CTL 0x1F
+#define DS90_REG_FWD_CTL1 0x20
+#define DS90_REG_FWD_CTL2 0x21
+#define DS90_REG_FWD_STS 0x22
+
+#define DS90_REG_INTERRUPT_CTL 0x23
+#define DS90_REG_INTERRUPT_CTL_INT_EN BIT(7)
+#define DS90_REG_INTERRUPT_CTL_IE_CSI_TX0 BIT(4)
+#define DS90_REG_INTERRUPT_CTL_IE_RX(n) BIT((n)) /* rxport[n] IRQ */
+
+#define DS90_REG_INTERRUPT_STS 0x24
+#define DS90_REG_INTERRUPT_STS_INT BIT(7)
+#define DS90_REG_INTERRUPT_STS_IS_CSI_TX0 BIT(4)
+#define DS90_REG_INTERRUPT_STS_IS_RX(n) BIT((n)) /* rxport[n] IRQ */
+
+#define DS90_REG_TS_CONFIG 0x25
+#define DS90_REG_TS_CONTROL 0x26
+#define DS90_REG_TS_LINE_HI 0x27
+#define DS90_REG_TS_LINE_LO 0x28
+#define DS90_REG_TS_STATUS 0x29
+#define DS90_REG_TIMESTAMP_P0_HI 0x2A
+#define DS90_REG_TIMESTAMP_P0_LO 0x2B
+#define DS90_REG_TIMESTAMP_P1_HI 0x2C
+#define DS90_REG_TIMESTAMP_P1_LO 0x2D
+#define DS90_REG_CSI_CTL 0x33
+#define DS90_REG_CSI_CTL2 0x34
+#define DS90_REG_CSI_STS 0x35
+#define DS90_REG_CSI_TX_ICR 0x36
+#define DS90_REG_CSI_TX_ISR 0x37
+#define DS90_REG_CSI_TX_ISR_IS_CSI_SYNC_ERROR BIT(3)
+#define DS90_REG_CSI_TX_ISR_IS_CSI_PASS_ERROR BIT(1)
+
+#define DS90_REG_CSI_TEST_CTL 0x38
+#define DS90_REG_CSI_TEST_PATT_HI 0x39
+#define DS90_REG_CSI_TEST_PATT_LO 0x3A
+#define DS90_REG_AEQ_CTL1 0x42
+#define DS90_REG_AEQ_ERR_THOLD 0x43
+#define DS90_REG_FPD3_CAP 0x4A
+#define DS90_REG_RAW_EMBED_DTYPE 0x4B
+#define DS90_REG_FPD3_PORT_SEL 0x4C
+
+#define DS90_REG_RX_PORT_STS1 0x4D
+#define DS90_REG_RX_PORT_STS1_BCC_CRC_ERROR BIT(5)
+#define DS90_REG_RX_PORT_STS1_LOCK_STS_CHG BIT(4)
+#define DS90_REG_RX_PORT_STS1_BCC_SEQ_ERROR BIT(3)
+#define DS90_REG_RX_PORT_STS1_PARITY_ERROR BIT(2)
+#define DS90_REG_RX_PORT_STS1_PORT_PASS BIT(1)
+#define DS90_REG_RX_PORT_STS1_LOCK_STS BIT(0)
+
+#define DS90_REG_RX_PORT_STS2 0x4E
+#define DS90_REG_RX_PORT_STS2_LINE_LEN_UNSTABLE BIT(7)
+#define DS90_REG_RX_PORT_STS2_LINE_LEN_CHG BIT(6)
+#define DS90_REG_RX_PORT_STS2_FPD3_ENCODE_ERROR BIT(5)
+#define DS90_REG_RX_PORT_STS2_BUFFER_ERROR BIT(4)
+#define DS90_REG_RX_PORT_STS2_CSI_ERROR BIT(3)
+#define DS90_REG_RX_PORT_STS2_FREQ_STABLE BIT(2)
+#define DS90_REG_RX_PORT_STS2_CABLE_FAULT BIT(1)
+#define DS90_REG_RX_PORT_STS2_LINE_CNT_CHG BIT(0)
+
+#define DS90_REG_RX_FREQ_HIGH 0x4F
+#define DS90_REG_RX_FREQ_LOW 0x50
+#define DS90_REG_SENSOR_STS_0 0x51
+#define DS90_REG_SENSOR_STS_1 0x52
+#define DS90_REG_SENSOR_STS_2 0x53
+#define DS90_REG_SENSOR_STS_3 0x54
+#define DS90_REG_RX_PAR_ERR_HI 0x55
+#define DS90_REG_RX_PAR_ERR_LO 0x56
+#define DS90_REG_BIST_ERR_COUNT 0x57
+
+#define DS90_REG_BCC_CONFIG 0x58
+#define DS90_REG_BCC_CONFIG_I2C_PASS_THROUGH BIT(6)
+
+#define DS90_REG_DATAPATH_CTL1 0x59
+#define DS90_REG_DATAPATH_CTL2 0x5A
+#define DS90_REG_SER_ID 0x5B
+#define DS90_REG_SER_ALIAS_ID 0x5C
+
+#define DS90_REG_SLAVE_ID(n) (0x5D + (n)) /* n < DS90_NUM_SLAVE_ALIASES */
+#define DS90_REG_SLAVE_ALIAS(n) (0x65 + (n)) /* n < DS90_NUM_SLAVE_ALIASES */
+
+#define DS90_REG_PORT_CONFIG 0x6D
+#define DS90_REG_BC_GPIO_CTL(n) (0x6E + (n)) /* n < 2 */
+#define DS90_REG_RAW10_ID 0x70
+#define DS90_REG_RAW12_ID 0x71
+#define DS90_REG_CSI_VC_MAP 0x72
+#define DS90_REG_LINE_COUNT_HI 0x73
+#define DS90_REG_LINE_COUNT_LO 0x74
+#define DS90_REG_LINE_LEN_1 0x75
+#define DS90_REG_LINE_LEN_0 0x76
+#define DS90_REG_FREQ_DET_CTL 0x77
+#define DS90_REG_MAILBOX_1 0x78
+#define DS90_REG_MAILBOX_2 0x79
+
+#define DS90_REG_CSI_RX_STS 0x7A
+#define DS90_REG_CSI_RX_STS_LENGTH_ERR BIT(3)
+#define DS90_REG_CSI_RX_STS_CKSUM_ERR BIT(2)
+#define DS90_REG_CSI_RX_STS_ECC2_ERR BIT(1)
+#define DS90_REG_CSI_RX_STS_ECC1_ERR BIT(0)
+
+#define DS90_REG_CSI_ERR_COUNTER 0x7B
+#define DS90_REG_PORT_CONFIG2 0x7C
+#define DS90_REG_PORT_PASS_CTL 0x7D
+#define DS90_REG_SEN_INT_RISE_CTL 0x7E
+#define DS90_REG_SEN_INT_FALL_CTL 0x7F
+#define DS90_REG_REFCLK_FREQ 0xA5
+#define DS90_REG_IND_ACC_CTL 0xB0
+#define DS90_REG_IND_ACC_ADDR 0xB1
+#define DS90_REG_IND_ACC_DATA 0xB2
+#define DS90_REG_BIST_CONTROL 0xB3
+#define DS90_REG_MODE_IDX_STS 0xB8
+#define DS90_REG_LINK_ERROR_COUNT 0xB9
+#define DS90_REG_FPD3_ENC_CTL 0xBA
+#define DS90_REG_FV_MIN_TIME 0xBC
+#define DS90_REG_GPIO_PD_CTL 0xBE
+#define DS90_REG_PORT_DEBUG 0xD0
+#define DS90_REG_AEQ_CTL2 0xD2
+#define DS90_REG_AEQ_STATUS 0xD3
+#define DS90_REG_AEQ_BYPASS 0xD4
+#define DS90_REG_AEQ_MIN_MAX 0xD5
+#define DS90_REG_PORT_ICR_HI 0xD8
+#define DS90_REG_PORT_ICR_LO 0xD9
+#define DS90_REG_PORT_ISR_HI 0xDA
+#define DS90_REG_PORT_ISR_LO 0xDB
+#define DS90_REG_FC_GPIO_STS 0xDC
+#define DS90_REG_FC_GPIO_ICR 0xDD
+#define DS90_REG_SEN_INT_RISE_STS 0xDE
+#define DS90_REG_SEN_INT_FALL_STS 0xDF
+#define DS90_REG_FPD3_RX_ID0 0xF0
+#define DS90_REG_FPD3_RX_ID1 0xF1
+#define DS90_REG_FPD3_RX_ID2 0xF2
+#define DS90_REG_FPD3_RX_ID3 0xF3
+#define DS90_REG_FPD3_RX_ID4 0xF4
+#define DS90_REG_FPD3_RX_ID5 0xF5
+#define DS90_REG_I2C_RX0_ID 0xF8
+#define DS90_REG_I2C_RX1_ID 0xF9
+
+struct ds90_rxport {
+ /* Errors and anomalies counters */
+ u64 bcc_crc_error_count;
+ u64 bcc_seq_error_count;
+ u64 line_len_unstable_count;
+ u64 line_len_chg_count;
+ u64 fpd3_encode_error_count;
+ u64 buffer_error_count;
+ u64 line_cnt_chg_count;
+ u64 csi_rx_sts_length_err_count;
+ u64 csi_rx_sts_cksum_err_count;
+ u64 csi_rx_sts_ecc2_err_count;
+ u64 csi_rx_sts_ecc1_err_count;
+
+ struct i2c_client *ser_client;
+ unsigned short ser_alias; /* ser i2c alias (lower 7 bits) */
+ bool locked;
+
+ struct ds90_data *ds90; /* Owner */
+ unsigned short nport; /* Port number, and index in ds90->rxport[] */
+};
+
+struct ds90_gpio {
+ bool output : 1; /* Direction */
+ unsigned int source : 3;
+ unsigned int func : 3;
+};
+
+struct ds90_data {
+ struct i2c_client *client;
+ struct i2c_mux_core *muxc; /* i2c-mux with ATR */
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ struct task_struct *kthread;
+ struct ds90_rxport *rxport[DS90_NUM_RXPORTS];
+ struct ds90_gpio *gpio [DS90_NUM_GPIOS];
+
+ struct v4l2_subdev sd;
+ struct media_pad pads[DS90_V4L2_NUM_PADS];
+ struct v4l2_mbus_framefmt fmt[DS90_V4L2_NUM_PADS];
+
+ struct mutex lock; /* Lock ATR table AND RX port selection */
+
+ /* Address Translator alias-to-slave map table */
+ size_t atr_alias_num; /* Number of aliases configured */
+ u16 atr_alias_id[DS90_MAX_POOL_ALIASES]; /* 0 = no alias */
+ u16 atr_slave_id[DS90_MAX_POOL_ALIASES]; /* 0 = not in use */
+};
+
+#define sd_to_ds90(_sd) container_of(_sd, struct ds90_data, sd)
+
+/* -----------------------------------------------------------------------------
+ * Basic device access
+ */
+
+static bool ds90_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return (reg == DS90_REG_INTERRUPT_STS ||
+ reg == DS90_REG_RX_PORT_STS1 ||
+ reg == DS90_REG_RX_PORT_STS2 ||
+ reg == DS90_REG_CSI_RX_STS ||
+ reg == DS90_REG_CSI_TX_ISR);
+}
+
+const struct regmap_config ds90_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = ds90_is_volatile_reg,
+};
+
+static int ds90_read(const struct ds90_data *ds90,
+ unsigned int reg,
+ unsigned int *val)
+{
+ int err;
+
+ err = regmap_read(ds90->regmap, reg, val);
+ if (err)
+ dev_err(&ds90->client->dev,
+ "Cannot read register 0x%02x (%d)!\n", reg, err);
+
+ return err;
+}
+
+static int ds90_write(const struct ds90_data *ds90,
+ unsigned int reg,
+ unsigned int val)
+{
+ int err;
+
+ err = regmap_write(ds90->regmap, reg, val);
+ if (err)
+ dev_err(&ds90->client->dev,
+ "Cannot write register 0x%02x (%d)!\n", reg, err);
+
+ return err;
+}
+
+static void ds90_reset(const struct ds90_data *ds90, bool keep_reset)
+{
+ gpiod_set_value_cansleep(ds90->reset_gpio, 0);
+ usleep_range(3000, 6000); /* min 2 ms */
+
+ if (!keep_reset) {
+ gpiod_set_value_cansleep(ds90->reset_gpio, 1);
+ usleep_range(2000, 4000); /* min 1 ms */
+ }
+}
+
+/* Select a port for register reading and writing */
+static int ds90_rxport_select(struct ds90_data *ds90, unsigned nport)
+{
+ int err = ds90_write(ds90, DS90_REG_FPD3_PORT_SEL,
+ (nport << 4) | BIT(nport));
+
+ return err;
+}
+
+/* -----------------------------------------------------------------------------
+ * CSI port
+ */
+
+static void ds90_csi_handle_events(struct ds90_data *ds90)
+{
+ struct device *dev = &ds90->client->dev;
+ unsigned int csi_tx_isr;
+ int err;
+
+ err = ds90_read(ds90, DS90_REG_CSI_TX_ISR, &csi_tx_isr);
+
+ if (!err) {
+ if (csi_tx_isr & DS90_REG_CSI_TX_ISR_IS_CSI_SYNC_ERROR)
+ dev_warn(dev, "CSI_SYNC_ERROR\n");
+
+ if (csi_tx_isr & DS90_REG_CSI_TX_ISR_IS_CSI_PASS_ERROR)
+ dev_warn(dev, "CSI_PASS_ERROR\n");
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * I2C-MUX with ATR (address translator)
+ */
+
+static int ds90_mux_attach_client(struct i2c_mux_core *muxc,
+ u32 chan_id,
+ const struct i2c_board_info *info,
+ struct i2c_client *client,
+ u16 *alias_id)
+{
+ struct ds90_data **ds90p = i2c_mux_priv(muxc);
+ struct ds90_data *ds90 = *ds90p;
+ struct ds90_rxport *rxport = ds90->rxport[chan_id];
+ struct device *dev = &ds90->client->dev;
+ u16 alias = 0;
+ int reg_idx;
+ int pool_idx;
+ int err = 0;
+
+ mutex_lock(&ds90->lock);
+
+ /* Find unused alias in table */
+
+ for (pool_idx = 0; pool_idx < ds90->atr_alias_num; pool_idx++)
+ if (ds90->atr_slave_id[pool_idx] == 0)
+ break;
+
+ if (pool_idx == ds90->atr_alias_num) {
+ dev_warn(dev, "rx%d: alias pool exhausted\n", rxport->nport);
+ err = -EADDRNOTAVAIL;
+ goto out;
+ }
+
+ alias = ds90->atr_alias_id[pool_idx];
+
+ /* Find first unused alias register */
+
+ ds90_rxport_select(ds90, rxport->nport);
+
+ for (reg_idx = 0; reg_idx < DS90_NUM_SLAVE_ALIASES; reg_idx++) {
+ unsigned int regval;
+
+ err = ds90_read(ds90, DS90_REG_SLAVE_ALIAS(reg_idx), ®val);
+ if (!err && regval == 0)
+ break;
+ }
+
+ if (reg_idx == DS90_NUM_SLAVE_ALIASES) {
+ dev_warn(dev, "rx%d: all aliases in use\n", rxport->nport);
+ err = -EADDRNOTAVAIL;
+ goto out;
+ }
+
+ /* Map alias to slave */
+
+ ds90_write(ds90, DS90_REG_SLAVE_ID(reg_idx), client->addr << 1);
+ ds90_write(ds90, DS90_REG_SLAVE_ALIAS(reg_idx), alias << 1);
+
+ ds90->atr_slave_id[pool_idx] = client->addr;
+
+ *alias_id = alias; /* tell the mux which alias we chose */
+
+ dev_info(dev, "rx%d: map alias 0x%02x to client 0x%02x\n",
+ rxport->nport, alias, client->addr);
+
+out:
+ mutex_unlock(&ds90->lock);
+ return err;
+}
+
+static void ds90_mux_detach_client(struct i2c_mux_core *muxc,
+ u32 chan_id,
+ struct i2c_client *client)
+{
+ struct ds90_data **ds90p = i2c_mux_priv(muxc);
+ struct ds90_data *ds90 = *ds90p;
+ struct ds90_rxport *rxport = ds90->rxport[chan_id];
+ struct device *dev = &ds90->client->dev;
+ u16 alias = 0;
+ int reg_idx;
+ int pool_idx;
+
+ mutex_lock(&ds90->lock);
+
+ /* Find alias mapped to this client */
+
+ for (pool_idx = 0; pool_idx < ds90->atr_alias_num; pool_idx++)
+ if (ds90->atr_slave_id[pool_idx] == client->addr)
+ break;
+
+ if (pool_idx == ds90->atr_alias_num) {
+ dev_err(dev, "rx%d: client 0x%02x is not mapped!\n",
+ rxport->nport, client->addr);
+ goto out;
+ }
+
+ alias = ds90->atr_alias_id[pool_idx];
+
+ /* Find alias register used for this client */
+
+ ds90_rxport_select(ds90, rxport->nport);
+
+ for (reg_idx = 0; reg_idx < DS90_NUM_SLAVE_ALIASES; reg_idx++) {
+ unsigned int regval;
+ int err;
+
+ err = ds90_read(ds90, DS90_REG_SLAVE_ALIAS(reg_idx), ®val);
+ if (!err && regval == (alias << 1))
+ break;
+ }
+
+ if (reg_idx == DS90_NUM_SLAVE_ALIASES) {
+ dev_err(dev, "rx%d: cannot find alias 0x%02x reg (client 0x%02x)!\n",
+ rxport->nport, alias, client->addr);
+ goto out;
+ }
+
+ /* Unmap */
+
+ ds90_write(ds90, DS90_REG_SLAVE_ALIAS(reg_idx), 0);
+ ds90->atr_slave_id[pool_idx] = 0;
+
+ dev_info(dev, "rx%d: unmapped alias 0x%02x from client 0x%02x\n",
+ rxport->nport, alias, client->addr);
+
+out:
+ mutex_unlock(&ds90->lock);
+}
+
+static const struct i2c_mux_attach_operations ds90_mux_attach_ops = {
+ .i2c_mux_attach_client = ds90_mux_attach_client,
+ .i2c_mux_detach_client = ds90_mux_detach_client,
+};
+
+static int ds90_mux_select(struct i2c_mux_core *muxc, u32 chan_id)
+{
+ struct ds90_data **ds90p = i2c_mux_priv(muxc);
+ struct ds90_data *ds90 = *ds90p;
+
+ return ds90_rxport_select(ds90, chan_id);
+}
+
+/* -----------------------------------------------------------------------------
+ * RX ports
+ */
+
+static ssize_t locked_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+static ssize_t status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+
+static struct device_attribute dev_attr_locked[] = {
+ __ATTR_RO(locked),
+ __ATTR_RO(locked),
+};
+
+static struct device_attribute dev_attr_status[] = {
+ __ATTR_RO(status),
+ __ATTR_RO(status),
+};
+
+static struct attribute *ds90_rxport0_attrs[] = {
+ &dev_attr_locked[0].attr,
+ &dev_attr_status[0].attr,
+ NULL
+};
+
+static struct attribute *ds90_rxport1_attrs[] = {
+ &dev_attr_locked[1].attr,
+ &dev_attr_status[1].attr,
+ NULL
+};
+
+static ssize_t locked_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int nport = (attr - dev_attr_locked);
+ const struct ds90_data *ds90 = dev_get_drvdata(dev);
+ const struct ds90_rxport *rxport = ds90->rxport[nport];
+
+ return scnprintf(buf, PAGE_SIZE, "%d", rxport->locked);
+}
+
+static ssize_t status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int nport = (attr - dev_attr_status);
+ const struct ds90_data *ds90 = dev_get_drvdata(dev);
+ const struct ds90_rxport *rxport = ds90->rxport[nport];
+
+ return scnprintf(buf, PAGE_SIZE,
+ "bcc_crc_error_count = %llu\n"
+ "bcc_seq_error_count = %llu\n"
+ "line_len_unstable_count = %llu\n"
+ "line_len_chg_count = %llu\n"
+ "fpd3_encode_error_count = %llu\n"
+ "buffer_error_count = %llu\n"
+ "line_cnt_chg_count = %llu\n"
+ "csi_rx_sts_length_err_count = %llu\n"
+ "csi_rx_sts_cksum_err_count = %llu\n"
+ "csi_rx_sts_ecc2_err_count = %llu\n"
+ "csi_rx_sts_ecc1_err_count = %llu\n",
+ rxport->bcc_crc_error_count,
+ rxport->bcc_seq_error_count,
+ rxport->line_len_unstable_count,
+ rxport->line_len_chg_count,
+ rxport->fpd3_encode_error_count,
+ rxport->buffer_error_count,
+ rxport->line_cnt_chg_count,
+ rxport->csi_rx_sts_length_err_count,
+ rxport->csi_rx_sts_cksum_err_count,
+ rxport->csi_rx_sts_ecc2_err_count,
+ rxport->csi_rx_sts_ecc1_err_count);
+}
+
+struct attribute_group ds90_rxport_attr_group[] = {
+ { .name = "rx0", .attrs = ds90_rxport0_attrs },
+ { .name = "rx1", .attrs = ds90_rxport1_attrs },
+};
+
+/*
+ * Instantiate serializer and i2c adapter for the just-locked remote
+ * end.
+ *
+ * @note Must be called with ds90->lock not held! The added i2c
+ * adapter will probe new slaves, which can request i2c transfers,
+ * ending up in calling ds90_mux_attach_client() where the lock is
+ * taken.
+ */
+static int ds90_rxport_add_serializer(struct ds90_data *ds90, int nport)
+{
+ struct ds90_rxport *rxport = ds90->rxport[nport];
+ struct device *dev = &ds90->client->dev;
+ struct i2c_board_info ser_info = { .type = "ds90ub953-q1" };
+ struct i2c_client *client;
+ int err;
+
+ /*
+ * Adding the serializer under the rxport-specific adapter
+ * would be cleaner, but it would need tweaks to bypass the
+ * alias table. Adding to the upstream adapter is way simpler.
+ */
+ ser_info.addr = rxport->ser_alias;
+ client = i2c_new_device(ds90->client->adapter, &ser_info);
+ if (!client) {
+ dev_err(dev, "rx%d: cannot add %s i2c device",
+ nport, ser_info.type);
+ return -EIO;
+ }
+ rxport->ser_client = client;
+
+ err = i2c_mux_add_adapter(ds90->muxc, 0, nport, 0);
+ if (err) {
+ dev_err(dev, "rx%d: cannot add adapter", nport);
+ i2c_unregister_device(rxport->ser_client);
+ rxport->ser_client = NULL;
+ }
+
+ return err;
+}
+
+static void ds90_rxport_remove_serializer(struct ds90_data *ds90, int nport)
+{
+ struct ds90_rxport *rxport = ds90->rxport[nport];
+
+ i2c_mux_del_adapters(ds90->muxc); /* FIXME remove one only */
+
+ if (rxport->ser_client) {
+ i2c_unregister_device(rxport->ser_client);
+ rxport->ser_client = NULL;
+ }
+}
+
+/*
+ * Map a local GPIO output to a back-channel GPIO number.
+ *
+ * Example: GPIO<N> from SoC -> GPIO<X> input at deser -> GPIO<Y> on FPD-3 link.
+ */
+static int ds90_rxport_map_bc_gpios(struct ds90_data *ds90,
+ int nport,
+ const struct device_node *np)
+{
+ struct device *dev = &ds90->client->dev;
+ const __be32 *bc_gpio_map;
+ int bc_gpio_map_len;
+ struct device_node *local_gpio_np;
+ const __be32 *local_gpio_regp;
+ u32 bc_gpio_idx;
+ u32 local_gpio_ph;
+ u32 local_gpio_reg;
+ unsigned int reg;
+ unsigned reg_shift;
+ int err;
+ int i;
+
+ bc_gpio_map = of_get_property(np, "bc-gpio-map", &bc_gpio_map_len);
+ if (!bc_gpio_map) {
+ dev_dbg(dev, "rx%d: bc-gpio-map NOT FOUND \n", nport);
+ return -ENOENT;
+ }
+
+ bc_gpio_map_len /= sizeof(*bc_gpio_map);
+
+ for (i = 0; i + 2 <= bc_gpio_map_len; i += 2) {
+ bc_gpio_idx = be32_to_cpu(bc_gpio_map[i]);
+ local_gpio_ph = be32_to_cpu(bc_gpio_map[i + 1]);
+
+ if (bc_gpio_idx >= DS90_NUM_BC_GPIOS)
+ continue;
+
+ local_gpio_np = of_find_node_by_phandle(local_gpio_ph);
+ if (!local_gpio_np)
+ continue;
+
+ local_gpio_regp = of_get_property(local_gpio_np, "reg", NULL);
+ if (!local_gpio_regp)
+ goto put_and_continue;
+
+ local_gpio_reg = be32_to_cpup(local_gpio_regp);
+ if (local_gpio_reg >= DS90_NUM_GPIOS)
+ goto put_and_continue;
+
+ reg = DS90_REG_BC_GPIO_CTL(bc_gpio_idx / 2);
+ reg_shift = (bc_gpio_idx % 2) * 4;
+
+ dev_dbg(dev, "rx%d: BC GPIO %d from local GPIO %d\n",
+ nport, bc_gpio_idx, local_gpio_reg);
+
+ err = regmap_update_bits(ds90->regmap, reg, 0xf << reg_shift,
+ local_gpio_reg << reg_shift);
+ if (err)
+ dev_err(dev, "rx%d: Cannot update reg 0x%02x (%d)\n",
+ nport, reg, err);
+
+ put_and_continue:
+ of_node_put(local_gpio_np);
+ }
+
+ return 0;
+}
+
+static int ds90_rxport_probe_one(struct ds90_data *ds90,
+ const struct device_node *np)
+{
+ struct device *dev = &ds90->client->dev;
+ struct ds90_rxport *rxport;
+ u32 ser_alias;
+ u32 nport;
+ int err;
+
+ if (of_property_read_u32(np, "reg", &nport) != 0 ||
+ nport >= DS90_NUM_RXPORTS)
+ return -EINVAL;
+
+ if (ds90->rxport[nport]) {
+ dev_err(dev, "OF: %s: reg value %d is duplicated\n",
+ of_node_full_name(np), nport);
+ return -EADDRINUSE;
+ }
+
+ if (of_property_read_u32(np, "ser-i2c-alias", &ser_alias) != 0 ||
+ ser_alias == 0) {
+ dev_err(dev, "OF: %s: invalid ser-i2c-alias\n",
+ of_node_full_name(np));
+ return -EINVAL;
+ }
+
+ rxport = devm_kzalloc(dev, sizeof(*rxport), GFP_KERNEL);
+ if (!rxport)
+ return -ENOMEM;
+
+ rxport->nport = nport;
+ rxport->ser_alias = ser_alias;
+ rxport->ds90 = ds90;
+
+ dev_dbg(dev, "OF: rxport[%d], alias=0x%02x\n", nport, ser_alias);
+
+ ds90->rxport[nport] = rxport; /* Needed by successive calls */
+
+ ds90_rxport_select(ds90, nport);
+
+ ds90_rxport_map_bc_gpios(ds90, nport, np);
+
+ regmap_update_bits(ds90->regmap, DS90_REG_INTERRUPT_CTL,
+ DS90_REG_INTERRUPT_CTL_IE_RX(nport), ~0);
+
+ /* Enable all interrupt sources from this port */
+ ds90_write(ds90, DS90_REG_PORT_ICR_HI, 0x07);
+ ds90_write(ds90, DS90_REG_PORT_ICR_LO, 0x7f);
+
+ regmap_update_bits(ds90->regmap,
+ DS90_REG_BCC_CONFIG,
+ DS90_REG_BCC_CONFIG_I2C_PASS_THROUGH, ~0);
+
+ /* Enable I2C communication to the serializer via the alias addr */
+ ds90_write(ds90, DS90_REG_SER_ALIAS_ID, rxport->ser_alias << 1);
+
+ err = sysfs_create_group(&dev->kobj, &ds90_rxport_attr_group[nport]);
+ if (err) {
+ dev_err(dev, "rx%d: failed creating sysfs group", nport);
+ goto err_sysfs;
+ }
+
+ return 0;
+
+err_sysfs:
+ ds90->rxport[nport] = NULL;
+ return err;
+}
+
+static void ds90_rxport_remove_one(struct ds90_data *ds90, int nport)
+{
+ struct device *dev = &ds90->client->dev;
+
+ ds90_rxport_remove_serializer(ds90, nport);
+ sysfs_remove_group(&dev->kobj, &ds90_rxport_attr_group[nport]);
+}
+
+static int ds90_rxport_probe(struct ds90_data *ds90)
+{
+ struct device_node *ds90_node = ds90->client->dev.of_node;
+ struct i2c_adapter *parent_adap = ds90->client->adapter;
+ struct device *dev = &ds90->client->dev;
+ const struct device_node *rxports_node;
+ struct device_node *rxport_node;
+ int err = 0;
+ int i;
+
+ /*
+ * TODO Maybe could be simplified by allocating the muxc in
+ * ds90_probe() with the ds90 as its "priv" data?
+ * Code: i2c_mux_alloc(parent_adap, DS90_NUM_RXPORTS, sizeof(ds90_data), ...)
+ * ds90_data *ds90 = i2c_mux_priv(muxc)
+ */
+ struct ds90_data **mux_data;
+ ds90->muxc = i2c_mux_alloc(parent_adap, dev, DS90_NUM_RXPORTS,
+ sizeof(*mux_data),
+ I2C_MUX_LOCKED | I2C_MUX_ATR,
+ ds90_mux_select, NULL,
+ &ds90_mux_attach_ops);
+ if (!ds90->muxc)
+ return -ENOMEM;
+ mux_data = i2c_mux_priv(ds90->muxc);
+ *mux_data = ds90;
+
+ rxports_node = of_get_child_by_name(ds90_node, "rxports");
+ if (!rxports_node) {
+ dev_warn(dev, "OF: no rxports defined!\n");
+ } else {
+ for_each_child_of_node(rxports_node, rxport_node) {
+ err = ds90_rxport_probe_one(ds90, rxport_node);
+ if (err)
+ break;
+ }
+ of_node_put(rxport_node);
+ }
+
+ if (err)
+ for (i = 0; i < DS90_NUM_RXPORTS; i++)
+ if (ds90->rxport[i])
+ ds90_rxport_remove_one(ds90, i);
+
+ return err;
+}
+
+static void ds90_rxport_remove(struct ds90_data *ds90)
+{
+ int i;
+
+ i2c_mux_del_adapters(ds90->muxc);
+
+ for (i = 0; i < DS90_NUM_RXPORTS; i++)
+ if (ds90->rxport[i])
+ ds90_rxport_remove_one(ds90, i);
+}
+
+static void ds90_rxport_handle_events(struct ds90_data *ds90, int nport)
+{
+ struct ds90_rxport *rxport = ds90->rxport[nport];
+ struct device *dev = &ds90->client->dev;
+ unsigned int rx_port_sts1;
+ unsigned int rx_port_sts2;
+ unsigned int csi_rx_sts;
+ bool locked;
+ int err;
+
+ mutex_lock(&ds90->lock);
+
+ /* Select port for register reading and writing */
+ err = ds90_rxport_select(ds90, nport);
+
+ /* Read interrupts (also clears most of them) */
+ if (!err)
+ err = ds90_read(ds90, DS90_REG_RX_PORT_STS1, &rx_port_sts1);
+ if (!err)
+ err = ds90_read(ds90, DS90_REG_RX_PORT_STS2, &rx_port_sts2);
+ if (!err)
+ err = ds90_read(ds90, DS90_REG_CSI_RX_STS, &csi_rx_sts);
+
+ mutex_unlock(&ds90->lock);
+
+ if (err)
+ return;
+
+ if (rx_port_sts1 & DS90_REG_RX_PORT_STS1_BCC_CRC_ERROR)
+ rxport->bcc_crc_error_count++;
+
+ if (rx_port_sts1 & DS90_REG_RX_PORT_STS1_BCC_SEQ_ERROR)
+ rxport->bcc_seq_error_count++;
+
+ if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_LINE_LEN_UNSTABLE)
+ rxport->line_len_unstable_count++;
+
+ if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_LINE_LEN_CHG)
+ rxport->line_len_chg_count++;
+
+ if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_FPD3_ENCODE_ERROR)
+ rxport->fpd3_encode_error_count++;
+
+ if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_BUFFER_ERROR)
+ rxport->buffer_error_count++;
+
+ if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_LINE_CNT_CHG)
+ rxport->line_cnt_chg_count++;
+
+ if (csi_rx_sts & DS90_REG_CSI_RX_STS_LENGTH_ERR)
+ rxport->csi_rx_sts_length_err_count++;
+
+ if (csi_rx_sts & DS90_REG_CSI_RX_STS_CKSUM_ERR)
+ rxport->csi_rx_sts_cksum_err_count++;
+
+ if (csi_rx_sts & DS90_REG_CSI_RX_STS_ECC2_ERR)
+ rxport->csi_rx_sts_ecc2_err_count++;
+
+ if (csi_rx_sts & DS90_REG_CSI_RX_STS_ECC1_ERR)
+ rxport->csi_rx_sts_ecc1_err_count++;
+
+ /* Update locked status */
+ locked = rx_port_sts1 & DS90_REG_RX_PORT_STS1_LOCK_STS;
+ if (locked && !rxport->locked) {
+ dev_info(dev, "rx%d LOCKED\n", nport);
+ /* See note about locking in ds90_rxport_add_serializer()! */
+ ds90_rxport_add_serializer(ds90, nport);
+ }
+ else if (!locked && rxport->locked) {
+ dev_info(dev, "rx%d NOT LOCKED\n", nport);
+ ds90_rxport_remove_serializer(ds90, nport);
+ }
+ rxport->locked = locked;
+}
+
+/* -----------------------------------------------------------------------------
+ * GPIOs
+ */
+
+/* TODO struct ds90_gpio is not used outside of this function. Remove it??? */
+static int ds90_gpio_probe_one(struct ds90_data *ds90, struct device_node *np)
+{
+ struct device *dev = &ds90->client->dev;
+ struct ds90_gpio *gpio;
+ u32 reg;
+ u32 source;
+ u32 func;
+
+ if (of_property_read_u32(np, "reg", ®) != 0 ||
+ reg >= DS90_NUM_GPIOS) {
+ dev_err(dev, "OF: %s: invalid reg\n", of_node_full_name(np));
+ return -EINVAL;
+ }
+
+ if (ds90->gpio[reg]) {
+ dev_err(dev, "OF: %s: reg value %d is duplicated\n",
+ of_node_full_name(np), reg);
+ return -EADDRINUSE;
+ }
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->output = false; /* A safe default */
+
+ if (of_property_read_bool(np, "output")) {
+ if (of_property_read_u32(np, "source", &source) == 0 &&
+ of_property_read_u32(np, "function", &func) == 0 &&
+ source < DS90_GPIO_NSOURCES &&
+ func < DS90_GPIO_NFUNCS) {
+ gpio->output = true;
+ gpio->source = source;
+ gpio->func = func;
+ } else {
+ dev_err(dev,
+ "OF: %s: incorrect output parameters, fallback to input!\n",
+ of_node_full_name(np));
+ }
+ } else if (!of_property_read_bool(np, "input")) {
+ dev_err(dev,
+ "OF: %s: no direction specified, fallback to input!\n",
+ of_node_full_name(np));
+ }
+
+ if (gpio->output)
+ dev_dbg(dev, "OF: gpio[%d], output, source %d, function %d\n",
+ reg, gpio->source, gpio->func);
+ else
+ dev_dbg(dev, "OF: gpio[%d], input\n", reg);
+
+ ds90->gpio[reg] = gpio;
+
+ if (gpio->output) {
+ u8 pin_ctl = gpio->source << 5 | gpio->func << 2 | 1;
+
+ regmap_update_bits(ds90->regmap, DS90_REG_GPIO_INPUT_CTL,
+ BIT(reg), 0);
+ ds90_write(ds90, DS90_REG_GPIO_PIN_CTL(reg), pin_ctl);
+ } else {
+ regmap_update_bits(ds90->regmap, DS90_REG_GPIO_INPUT_CTL,
+ BIT(reg), ~0);
+ }
+
+ return 0;
+}
+
+static int ds90_gpio_probe(struct ds90_data *ds90)
+{
+ struct device_node *ds90_node = ds90->client->dev.of_node;
+ struct device *dev = &ds90->client->dev;
+ const struct device_node *gpios_node;
+ struct device_node *gpio_node;
+ int err = 0;
+
+ gpios_node = of_get_child_by_name(ds90_node, "gpios");
+ if (!gpios_node) {
+ dev_warn(dev, "OF: no gpios defined!\n");
+ } else {
+ for_each_child_of_node(gpios_node, gpio_node) {
+ err = ds90_gpio_probe_one(ds90, gpio_node);
+ if (err)
+ break;
+ }
+ of_node_put(gpio_node);
+ }
+
+ return err;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2
+ */
+
+static int ds90_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct ds90_data *ds90 = sd_to_ds90(sd);
+ struct device *dev = &ds90->client->dev;
+
+ dev_info(dev, "%s: TODO\n", __func__);
+
+ return 0;
+}
+
+static int ds90_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct ds90_data *ds90 = sd_to_ds90(sd);
+ struct device *dev = &ds90->client->dev;
+
+ dev_info(dev, "%s: TODO\n", __func__);
+
+ return 0;
+}
+
+static struct v4l2_mbus_framefmt *
+ds90_get_pad_format(struct ds90_data *ds90,
+ struct v4l2_subdev_pad_config *cfg,
+ unsigned int pad, u32 which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_format(&ds90->sd, cfg, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &ds90->fmt[pad];
+ default:
+ return NULL;
+ }
+}
+
+static int ds90_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct ds90_data *ds90 = sd_to_ds90(sd);
+ struct device *dev = &ds90->client->dev;
+
+ dev_info(dev, "%s: TODO\n", __func__);
+
+ return 0;
+}
+
+static int ds90_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct ds90_data *ds90 = sd_to_ds90(sd);
+ struct v4l2_mbus_framefmt *cfg_fmt;
+ struct device *dev = &ds90->client->dev;
+
+ dev_dbg(dev, "pad %d which %d", format->pad, format->which);
+
+ if (format->pad >= DS90_V4L2_NUM_PADS)
+ return -EINVAL;
+
+ cfg_fmt = ds90_get_pad_format(ds90, cfg, format->pad, format->which);
+ if (!cfg_fmt)
+ return -EINVAL;
+
+ format->format = *cfg_fmt;
+
+ return 0;
+}
+
+static int ds90_get_frame_desc(struct v4l2_subdev *sd,
+ unsigned int pad,
+ struct v4l2_mbus_frame_desc *fd)
+{
+ struct ds90_data *ds90 = sd_to_ds90(sd);
+ struct device *dev = &ds90->client->dev;
+
+ dev_info(dev, "%s: TODO\n", __func__);
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops ds90_video_ops = {
+ .s_stream = ds90_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ds90_pad_ops = {
+ .enum_mbus_code = ds90_enum_mbus_code,
+ .get_fmt = ds90_get_fmt,
+ .set_fmt = ds90_set_fmt,
+ .get_frame_desc = ds90_get_frame_desc,
+};
+
+static const struct v4l2_subdev_ops ds90_subdev_ops = {
+ .video = &ds90_video_ops,
+ .pad = &ds90_pad_ops,
+};
+
+static void ds90_init_format(struct v4l2_mbus_framefmt *fmt)
+{
+ fmt->width = 1920;
+ fmt->height = 1080;
+ fmt->code = MEDIA_BUS_FMT_SRGGB8_1X8;
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
+ fmt->field = V4L2_FIELD_NONE;
+}
+
+static int ds90_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_mbus_framefmt *format;
+ unsigned int i;
+
+ for (i = 0; i < DS90_V4L2_NUM_PADS; i++) {
+ format = v4l2_subdev_get_try_format(subdev, fh->pad, i);
+ ds90_init_format(format);
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_internal_ops ds90_subdev_internal_ops = {
+ .open = ds90_open,
+};
+
+/* -----------------------------------------------------------------------------
+ * Core
+ */
+
+static int ds90_run(void *arg)
+{
+ struct ds90_data *ds90 = arg;
+ unsigned int int_sts;
+ int err;
+ int i;
+
+ while (1) {
+ if (kthread_should_stop())
+ break;
+
+ err = ds90_read(ds90, DS90_REG_INTERRUPT_STS, &int_sts);
+
+ if (!err && int_sts) {
+ if (int_sts & DS90_REG_INTERRUPT_STS_IS_CSI_TX0)
+ ds90_csi_handle_events(ds90);
+
+ for (i = 0; i < DS90_NUM_RXPORTS; i++)
+ if (int_sts & DS90_REG_INTERRUPT_STS_IS_RX(i) &&
+ ds90->rxport[i])
+ ds90_rxport_handle_events(ds90, i);
+ }
+
+ msleep(1000);
+ }
+
+ return 0;
+}
+
+static int ds90_parse_dt(struct ds90_data *ds90)
+{
+ struct device_node *np = ds90->client->dev.of_node;
+ struct device *dev = &ds90->client->dev;
+ int n;
+
+ if (!np) {
+ dev_err(dev, "OF: no device tree node!\n");
+ return -ENOENT;
+ }
+
+ n = of_property_read_variable_u16_array(np, "i2c-alias-pool",
+ ds90->atr_alias_id,
+ 2, DS90_MAX_POOL_ALIASES);
+ if (n < 0)
+ dev_warn(dev,
+ "OF: no i2c-alias-pool, can't access remote I2C slaves");
+
+ ds90->atr_alias_num = n;
+
+ dev_dbg(dev, "i2c-alias-pool has %zu aliases", ds90->atr_alias_num);
+
+ return 0;
+}
+
+static int ds90_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ds90_data *ds90;
+ unsigned int rev_mask;
+ int err;
+ int i;
+
+ ds90 = devm_kzalloc(&client->dev, sizeof(*ds90), GFP_KERNEL);
+ if (!ds90)
+ return -ENOMEM;
+
+ ds90->client = client;
+
+ err = ds90_parse_dt(ds90);
+ if (err)
+ goto err_parse_dt;
+
+ /* get reset pin from DT */
+ ds90->reset_gpio = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ds90->reset_gpio)) {
+ if (PTR_ERR(ds90->reset_gpio) != -EPROBE_DEFER)
+ dev_err(&client->dev, "Cannot get reset GPIO");
+ return PTR_ERR(ds90->reset_gpio);
+ }
+
+ mutex_init(&ds90->lock);
+
+ i2c_set_clientdata(client, ds90);
+
+ /* initialize regmap */
+ ds90->regmap = devm_regmap_init_i2c(client, &ds90_regmap_config);
+ if (IS_ERR(ds90->regmap)) {
+ err = PTR_ERR(ds90->regmap);
+ dev_err(&client->dev, "regmap init failed (%d)\n", err);
+ goto err_regmap;
+ }
+
+ /* Runtime check register accessibility */
+ ds90_reset(ds90, false);
+
+ err = ds90_read(ds90, DS90_REG_REV_MASK, &rev_mask);
+ if (err) {
+ dev_err(&client->dev,
+ "Cannot read first register (%d), abort\n", err);
+ goto err_reg_read;
+ }
+
+ /* Init rxports, remote GPIOs and I2C */
+
+ err = ds90_gpio_probe(ds90);
+ if (err) {
+ dev_err(&client->dev, "Error probing gpios (%d)\n", err);
+ goto err_configure_deser_gpios;
+ }
+
+ err = ds90_rxport_probe(ds90);
+ if (err)
+ goto err_rxport_probe;
+
+ /* V4L2 */
+
+ for (i = 0; i < DS90_V4L2_NUM_PADS; i++)
+ ds90_init_format(&ds90->fmt[i]);
+
+ v4l2_i2c_subdev_init(&ds90->sd, client, &ds90_subdev_ops);
+
+ /* Let both the I2C client and the subdev point to us */
+ i2c_set_clientdata(client, ds90); /* v4l2_i2c_subdev_init writes it */
+ v4l2_set_subdevdata(&ds90->sd, ds90);
+
+ ds90->sd.internal_ops = &ds90_subdev_internal_ops;
+ ds90->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ /* TODO MEDIA_ENT_F_VID_IF_BRIDGE (since kernel 4.13) is better? */
+ ds90->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+
+ ds90->pads[0].flags = MEDIA_PAD_FL_SINK;
+ ds90->pads[1].flags = MEDIA_PAD_FL_SOURCE;
+ err = media_entity_pads_init(&ds90->sd.entity,
+ DS90_V4L2_NUM_PADS, ds90->pads);
+ if (err)
+ goto err_pads_init;
+
+ err = v4l2_async_register_subdev(&ds90->sd);
+ if (err) {
+ dev_err(&client->dev, "v4l2_async_register_subdev error %d\n", err);
+ goto err_register_subdev;
+ }
+
+ /* Kick off */
+
+ regmap_update_bits(ds90->regmap,
+ DS90_REG_INTERRUPT_CTL,
+ DS90_REG_INTERRUPT_CTL_INT_EN, ~0);
+
+ ds90->kthread = kthread_run(ds90_run, ds90, dev_name(&client->dev));
+ if (IS_ERR(ds90->kthread)) {
+ err = PTR_ERR(ds90->kthread);
+ dev_err(&client->dev, "Cannot create kthread (%d)\n", err);
+ goto err_kthread;
+ }
+
+ dev_info(&client->dev, "Successfully probed (rev/mask %02x)\n", rev_mask);
+
+ return 0;
+
+err_kthread:
+ v4l2_async_unregister_subdev(&ds90->sd);
+err_register_subdev:
+ media_entity_cleanup(&ds90->sd.entity);
+err_pads_init:
+ ds90_rxport_remove(ds90);
+err_rxport_probe:
+err_configure_deser_gpios:
+err_reg_read:
+ ds90_reset(ds90, true);
+err_regmap:
+ mutex_destroy(&ds90->lock);
+err_parse_dt:
+ return err;
+}
+
+static int ds90_remove(struct i2c_client *client)
+{
+ struct ds90_data *ds90 = i2c_get_clientdata(client);
+
+ dev_info(&client->dev, "Removing\n");
+
+ kthread_stop(ds90->kthread);
+ v4l2_async_unregister_subdev(&ds90->sd);
+ media_entity_cleanup(&ds90->sd.entity);
+ ds90_rxport_remove(ds90);
+ ds90_reset(ds90, true);
+ mutex_destroy(&ds90->lock);
+
+ return 0;
+}
+
+static const struct i2c_device_id ds90_id[] = {
+ { "ds90ub954-q1", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ds90_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id ds90_dt_ids[] = {
+ { .compatible = "ti,ds90ub954-q1", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ds90_dt_ids);
+#endif
+
+static struct i2c_driver ds90ub954_driver = {
+ .probe = ds90_probe,
+ .remove = ds90_remove,
+ .id_table = ds90_id,
+ .driver = {
+ .name = "ds90ub954",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(ds90_dt_ids),
+ },
+};
+
+module_i2c_driver(ds90ub954_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Texas Instruments DS90UB954-Q1 CSI-2 dual deserializer driver");
+MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
--
2.17.1
next prev parent reply other threads:[~2019-01-08 23:08 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-01-08 22:39 [RFC 0/4] TI camera serdes - I2C address translation draft Luca Ceresoli
2019-01-08 22:39 ` [RFC 1/4] i2c: core: let adapters be notified of client attach/detach Luca Ceresoli
2019-01-08 22:39 ` [RFC 2/4] i2c: mux: notify client attach/detach, add ATR Luca Ceresoli
2019-01-08 22:39 ` [RFC 3/4] media: dt-bindings: add DS90UB954-Q1 video deserializer Luca Ceresoli
2019-01-08 22:39 ` Luca Ceresoli [this message]
2019-05-21 17:40 ` [RFC 0/4] TI camera serdes - I2C address translation draft Mauro Carvalho Chehab
2019-05-22 7:38 ` Luca Ceresoli
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190108223953.9969-5-luca@lucaceresoli.net \
--to=luca@lucaceresoli.net \
--cc=devicetree@vger.kernel.org \
--cc=jacopo@jmondi.org \
--cc=kieran.bingham@ideasonboard.com \
--cc=laurent.pinchart@ideasonboard.com \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=mchehab@kernel.org \
--cc=peda@axentia.se \
--cc=robh+dt@kernel.org \
--cc=vz@mleia.com \
--cc=wsa@the-dreams.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.