summaryrefslogtreecommitdiffstats
path: root/drivers/media/spi
diff options
context:
space:
mode:
authorJoel Fernandes <joelagnelf@nvidia.com>2026-07-18 13:15:36 -0400
committerPaul E. McKenney <paulmck@kernel.org>2026-07-23 10:39:57 -0700
commit34ff636f322c6d5e4f0a52338fa63a7dff09a495 (patch)
tree7629759c1ec7148b95bc8ab3080df20a5ecd2273 /drivers/media/spi
downloadlinux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.tar.gz
linux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.zip
scftorture: Count single_rpc offline failures in statistics outputgrafted
scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Diffstat (limited to 'drivers/media/spi')
-rw-r--r--drivers/media/spi/Kconfig26
-rw-r--r--drivers/media/spi/Makefile8
-rw-r--r--drivers/media/spi/cxd2880-spi.c667
-rw-r--r--drivers/media/spi/gs1662.c483
4 files changed, 1184 insertions, 0 deletions
diff --git a/drivers/media/spi/Kconfig b/drivers/media/spi/Kconfig
new file mode 100644
index 000000000..4656afae5
--- /dev/null
+++ b/drivers/media/spi/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-only
+if VIDEO_DEV && SPI
+
+comment "SPI I2C drivers auto-selected by 'Autoselect ancillary drivers'"
+ depends on MEDIA_HIDE_ANCILLARY_SUBDRV && SPI
+
+menu "Media SPI Adapters"
+
+config CXD2880_SPI_DRV
+ tristate "Sony CXD2880 SPI support"
+ depends on DVB_CORE && SPI
+ default m if !MEDIA_SUBDRV_AUTOSELECT
+ help
+ Choose if you would like to have SPI interface support for Sony CXD2880.
+
+config VIDEO_GS1662
+ tristate "Gennum Serializers video"
+ depends on SPI && VIDEO_DEV
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ help
+ Enable the GS1662 driver which serializes video streams.
+
+endmenu
+
+endif
diff --git a/drivers/media/spi/Makefile b/drivers/media/spi/Makefile
new file mode 100644
index 000000000..6ac7adc64
--- /dev/null
+++ b/drivers/media/spi/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/cxd2880
+
+# Please keep it alphabetically sorted by Kconfig name
+# (e. g. LC_ALL=C sort Makefile)
+obj-$(CONFIG_CXD2880_SPI_DRV) += cxd2880-spi.o
+obj-$(CONFIG_VIDEO_GS1662) += gs1662.o
diff --git a/drivers/media/spi/cxd2880-spi.c b/drivers/media/spi/cxd2880-spi.c
new file mode 100644
index 000000000..36207940b
--- /dev/null
+++ b/drivers/media/spi/cxd2880-spi.c
@@ -0,0 +1,667 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * cxd2880-spi.c
+ * Sony CXD2880 DVB-T2/T tuner + demodulator driver
+ * SPI adapter
+ *
+ * Copyright (C) 2016, 2017, 2018 Sony Semiconductor Solutions Corporation
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
+
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/ktime.h>
+
+#include <media/dvb_demux.h>
+#include <media/dmxdev.h>
+#include <media/dvb_frontend.h>
+#include "cxd2880.h"
+
+#define CXD2880_MAX_FILTER_SIZE 32
+#define BURST_WRITE_MAX 128
+#define MAX_TRANS_PKT 300
+
+struct cxd2880_ts_buf_info {
+ u8 read_ready:1;
+ u8 almost_full:1;
+ u8 almost_empty:1;
+ u8 overflow:1;
+ u8 underflow:1;
+ u16 pkt_num;
+};
+
+struct cxd2880_pid_config {
+ u8 is_enable;
+ u16 pid;
+};
+
+struct cxd2880_pid_filter_config {
+ u8 is_negative;
+ struct cxd2880_pid_config pid_config[CXD2880_MAX_FILTER_SIZE];
+};
+
+struct cxd2880_dvb_spi {
+ struct dvb_frontend dvb_fe;
+ struct dvb_adapter adapter;
+ struct dvb_demux demux;
+ struct dmxdev dmxdev;
+ struct dmx_frontend dmx_fe;
+ struct task_struct *cxd2880_ts_read_thread;
+ struct spi_device *spi;
+ struct mutex spi_mutex; /* For SPI access exclusive control */
+ int feed_count;
+ int all_pid_feed_count;
+ struct regulator *vcc_supply;
+ u8 *ts_buf;
+ struct cxd2880_pid_filter_config filter_config;
+};
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int cxd2880_write_spi(struct spi_device *spi, u8 *data, u32 size)
+{
+ struct spi_message msg;
+ struct spi_transfer tx = {};
+
+ if (!spi || !data) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+
+ tx.tx_buf = data;
+ tx.len = size;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&tx, &msg);
+
+ return spi_sync(spi, &msg);
+}
+
+static int cxd2880_write_reg(struct spi_device *spi,
+ u8 sub_address, const u8 *data, u32 size)
+{
+ u8 send_data[BURST_WRITE_MAX + 4];
+ const u8 *write_data_top = NULL;
+ int ret = 0;
+
+ if (!spi || !data) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+ if (size > BURST_WRITE_MAX || size > U8_MAX) {
+ pr_err("data size > WRITE_MAX\n");
+ return -EINVAL;
+ }
+
+ if (sub_address + size > 0x100) {
+ pr_err("out of range\n");
+ return -EINVAL;
+ }
+
+ send_data[0] = 0x0e;
+ write_data_top = data;
+
+ send_data[1] = sub_address;
+ send_data[2] = (u8)size;
+
+ memcpy(&send_data[3], write_data_top, send_data[2]);
+
+ ret = cxd2880_write_spi(spi, send_data, send_data[2] + 3);
+ if (ret)
+ pr_err("write spi failed %d\n", ret);
+
+ return ret;
+}
+
+static int cxd2880_spi_read_ts(struct spi_device *spi,
+ u8 *read_data,
+ u32 packet_num)
+{
+ int ret;
+ u8 data[3];
+ struct spi_message message;
+ struct spi_transfer transfer[2] = {};
+
+ if (!spi || !read_data || !packet_num) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+ if (packet_num > 0xffff) {
+ pr_err("packet num > 0xffff\n");
+ return -EINVAL;
+ }
+
+ data[0] = 0x10;
+ data[1] = packet_num >> 8;
+ data[2] = packet_num;
+
+ spi_message_init(&message);
+
+ transfer[0].len = 3;
+ transfer[0].tx_buf = data;
+ spi_message_add_tail(&transfer[0], &message);
+ transfer[1].len = packet_num * 188;
+ transfer[1].rx_buf = read_data;
+ spi_message_add_tail(&transfer[1], &message);
+
+ ret = spi_sync(spi, &message);
+ if (ret)
+ pr_err("spi_sync failed\n");
+
+ return ret;
+}
+
+static int cxd2880_spi_read_ts_buffer_info(struct spi_device *spi,
+ struct cxd2880_ts_buf_info *info)
+{
+ u8 send_data = 0x20;
+ u8 recv_data[2];
+ int ret;
+
+ if (!spi || !info) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+
+ ret = spi_write_then_read(spi, &send_data, 1,
+ recv_data, sizeof(recv_data));
+ if (ret)
+ pr_err("spi_write_then_read failed\n");
+
+ info->read_ready = (recv_data[0] & 0x80) ? 1 : 0;
+ info->almost_full = (recv_data[0] & 0x40) ? 1 : 0;
+ info->almost_empty = (recv_data[0] & 0x20) ? 1 : 0;
+ info->overflow = (recv_data[0] & 0x10) ? 1 : 0;
+ info->underflow = (recv_data[0] & 0x08) ? 1 : 0;
+ info->pkt_num = ((recv_data[0] & 0x07) << 8) | recv_data[1];
+
+ return ret;
+}
+
+static int cxd2880_spi_clear_ts_buffer(struct spi_device *spi)
+{
+ u8 data = 0x03;
+ int ret;
+
+ ret = cxd2880_write_spi(spi, &data, 1);
+
+ if (ret)
+ pr_err("write spi failed\n");
+
+ return ret;
+}
+
+static int cxd2880_set_pid_filter(struct spi_device *spi,
+ struct cxd2880_pid_filter_config *cfg)
+{
+ u8 data[65];
+ int i;
+ u16 pid = 0;
+ int ret;
+
+ if (!spi) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+
+ data[0] = 0x00;
+ ret = cxd2880_write_reg(spi, 0x00, &data[0], 1);
+ if (ret)
+ return ret;
+ if (!cfg) {
+ data[0] = 0x02;
+ ret = cxd2880_write_reg(spi, 0x50, &data[0], 1);
+ } else {
+ data[0] = cfg->is_negative ? 0x01 : 0x00;
+
+ for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) {
+ pid = cfg->pid_config[i].pid;
+ if (cfg->pid_config[i].is_enable) {
+ data[1 + (i * 2)] = (pid >> 8) | 0x20;
+ data[2 + (i * 2)] = pid & 0xff;
+ } else {
+ data[1 + (i * 2)] = 0x00;
+ data[2 + (i * 2)] = 0x00;
+ }
+ }
+ ret = cxd2880_write_reg(spi, 0x50, data, 65);
+ }
+
+ return ret;
+}
+
+static int cxd2880_update_pid_filter(struct cxd2880_dvb_spi *dvb_spi,
+ struct cxd2880_pid_filter_config *cfg,
+ bool is_all_pid_filter)
+{
+ int ret;
+
+ if (!dvb_spi || !cfg) {
+ pr_err("invalid arg.\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&dvb_spi->spi_mutex);
+ if (is_all_pid_filter) {
+ struct cxd2880_pid_filter_config tmpcfg;
+
+ memset(&tmpcfg, 0, sizeof(tmpcfg));
+ tmpcfg.is_negative = 1;
+ tmpcfg.pid_config[0].is_enable = 1;
+ tmpcfg.pid_config[0].pid = 0x1fff;
+
+ ret = cxd2880_set_pid_filter(dvb_spi->spi, &tmpcfg);
+ } else {
+ ret = cxd2880_set_pid_filter(dvb_spi->spi, cfg);
+ }
+ mutex_unlock(&dvb_spi->spi_mutex);
+
+ if (ret)
+ pr_err("set_pid_filter failed\n");
+
+ return ret;
+}
+
+static int cxd2880_ts_read(void *arg)
+{
+ struct cxd2880_dvb_spi *dvb_spi = NULL;
+ struct cxd2880_ts_buf_info info;
+ ktime_t start;
+ u32 i;
+ int ret;
+
+ dvb_spi = arg;
+ if (!dvb_spi) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+
+ ret = cxd2880_spi_clear_ts_buffer(dvb_spi->spi);
+ if (ret) {
+ pr_err("set_clear_ts_buffer failed\n");
+ return ret;
+ }
+
+ start = ktime_get();
+ while (!kthread_should_stop()) {
+ ret = cxd2880_spi_read_ts_buffer_info(dvb_spi->spi,
+ &info);
+ if (ret) {
+ pr_err("spi_read_ts_buffer_info error\n");
+ return ret;
+ }
+
+ if (info.pkt_num > MAX_TRANS_PKT) {
+ for (i = 0; i < info.pkt_num / MAX_TRANS_PKT; i++) {
+ cxd2880_spi_read_ts(dvb_spi->spi,
+ dvb_spi->ts_buf,
+ MAX_TRANS_PKT);
+ dvb_dmx_swfilter(&dvb_spi->demux,
+ dvb_spi->ts_buf,
+ MAX_TRANS_PKT * 188);
+ }
+ start = ktime_get();
+ } else if ((info.pkt_num > 0) &&
+ (ktime_to_ms(ktime_sub(ktime_get(), start)) >= 500)) {
+ cxd2880_spi_read_ts(dvb_spi->spi,
+ dvb_spi->ts_buf,
+ info.pkt_num);
+ dvb_dmx_swfilter(&dvb_spi->demux,
+ dvb_spi->ts_buf,
+ info.pkt_num * 188);
+ start = ktime_get();
+ } else {
+ usleep_range(10000, 11000);
+ }
+ }
+
+ return 0;
+}
+
+static int cxd2880_start_feed(struct dvb_demux_feed *feed)
+{
+ int ret = 0;
+ int i = 0;
+ struct dvb_demux *demux = NULL;
+ struct cxd2880_dvb_spi *dvb_spi = NULL;
+
+ if (!feed) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+
+ demux = feed->demux;
+ if (!demux) {
+ pr_err("feed->demux is NULL\n");
+ return -EINVAL;
+ }
+ dvb_spi = demux->priv;
+
+ if (dvb_spi->feed_count == CXD2880_MAX_FILTER_SIZE) {
+ pr_err("Exceeded maximum PID count (32).");
+ pr_err("Selected PID cannot be enabled.\n");
+ return -EINVAL;
+ }
+
+ if (feed->pid == 0x2000) {
+ if (dvb_spi->all_pid_feed_count == 0) {
+ ret = cxd2880_update_pid_filter(dvb_spi,
+ &dvb_spi->filter_config,
+ true);
+ if (ret) {
+ pr_err("update pid filter failed\n");
+ return ret;
+ }
+ }
+ dvb_spi->all_pid_feed_count++;
+
+ pr_debug("all PID feed (count = %d)\n",
+ dvb_spi->all_pid_feed_count);
+ } else {
+ struct cxd2880_pid_filter_config cfgtmp;
+
+ cfgtmp = dvb_spi->filter_config;
+
+ for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) {
+ if (cfgtmp.pid_config[i].is_enable == 0) {
+ cfgtmp.pid_config[i].is_enable = 1;
+ cfgtmp.pid_config[i].pid = feed->pid;
+ pr_debug("store PID %d to #%d\n",
+ feed->pid, i);
+ break;
+ }
+ }
+ if (i == CXD2880_MAX_FILTER_SIZE) {
+ pr_err("PID filter is full.\n");
+ return -EINVAL;
+ }
+ if (!dvb_spi->all_pid_feed_count)
+ ret = cxd2880_update_pid_filter(dvb_spi,
+ &cfgtmp,
+ false);
+ if (ret)
+ return ret;
+
+ dvb_spi->filter_config = cfgtmp;
+ }
+
+ if (dvb_spi->feed_count == 0) {
+ dvb_spi->ts_buf =
+ kzalloc(MAX_TRANS_PKT * 188,
+ GFP_KERNEL | GFP_DMA);
+ if (!dvb_spi->ts_buf) {
+ pr_err("ts buffer allocate failed\n");
+ memset(&dvb_spi->filter_config, 0,
+ sizeof(dvb_spi->filter_config));
+ dvb_spi->all_pid_feed_count = 0;
+ return -ENOMEM;
+ }
+ dvb_spi->cxd2880_ts_read_thread = kthread_run(cxd2880_ts_read,
+ dvb_spi,
+ "cxd2880_ts_read");
+ if (IS_ERR(dvb_spi->cxd2880_ts_read_thread)) {
+ pr_err("kthread_run failed\n");
+ kfree(dvb_spi->ts_buf);
+ dvb_spi->ts_buf = NULL;
+ memset(&dvb_spi->filter_config, 0,
+ sizeof(dvb_spi->filter_config));
+ dvb_spi->all_pid_feed_count = 0;
+ return PTR_ERR(dvb_spi->cxd2880_ts_read_thread);
+ }
+ }
+
+ dvb_spi->feed_count++;
+
+ pr_debug("start feed (count %d)\n", dvb_spi->feed_count);
+ return 0;
+}
+
+static int cxd2880_stop_feed(struct dvb_demux_feed *feed)
+{
+ int i = 0;
+ int ret;
+ struct dvb_demux *demux = NULL;
+ struct cxd2880_dvb_spi *dvb_spi = NULL;
+
+ if (!feed) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+
+ demux = feed->demux;
+ if (!demux) {
+ pr_err("feed->demux is NULL\n");
+ return -EINVAL;
+ }
+ dvb_spi = demux->priv;
+
+ if (!dvb_spi->feed_count) {
+ pr_err("no feed is started\n");
+ return -EINVAL;
+ }
+
+ if (feed->pid == 0x2000) {
+ /*
+ * Special PID case.
+ * Number of 0x2000 feed request was stored
+ * in dvb_spi->all_pid_feed_count.
+ */
+ if (dvb_spi->all_pid_feed_count <= 0) {
+ pr_err("PID %d not found\n", feed->pid);
+ return -EINVAL;
+ }
+ dvb_spi->all_pid_feed_count--;
+ } else {
+ struct cxd2880_pid_filter_config cfgtmp;
+
+ cfgtmp = dvb_spi->filter_config;
+
+ for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) {
+ if (feed->pid == cfgtmp.pid_config[i].pid &&
+ cfgtmp.pid_config[i].is_enable != 0) {
+ cfgtmp.pid_config[i].is_enable = 0;
+ cfgtmp.pid_config[i].pid = 0;
+ pr_debug("removed PID %d from #%d\n",
+ feed->pid, i);
+ break;
+ }
+ }
+ dvb_spi->filter_config = cfgtmp;
+
+ if (i == CXD2880_MAX_FILTER_SIZE) {
+ pr_err("PID %d not found\n", feed->pid);
+ return -EINVAL;
+ }
+ }
+
+ ret = cxd2880_update_pid_filter(dvb_spi,
+ &dvb_spi->filter_config,
+ dvb_spi->all_pid_feed_count > 0);
+ dvb_spi->feed_count--;
+
+ if (dvb_spi->feed_count == 0) {
+ int ret_stop = 0;
+
+ ret_stop = kthread_stop(dvb_spi->cxd2880_ts_read_thread);
+ if (ret_stop) {
+ pr_err("kthread_stop failed. (%d)\n", ret_stop);
+ ret = ret_stop;
+ }
+ kfree(dvb_spi->ts_buf);
+ dvb_spi->ts_buf = NULL;
+ }
+
+ pr_debug("stop feed ok.(count %d)\n", dvb_spi->feed_count);
+
+ return ret;
+}
+
+static const struct of_device_id cxd2880_spi_of_match[] = {
+ { .compatible = "sony,cxd2880" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, cxd2880_spi_of_match);
+
+static int
+cxd2880_spi_probe(struct spi_device *spi)
+{
+ int ret;
+ struct cxd2880_dvb_spi *dvb_spi = NULL;
+ struct cxd2880_config config;
+
+ if (!spi) {
+ pr_err("invalid arg\n");
+ return -EINVAL;
+ }
+
+ dvb_spi = kzalloc_obj(struct cxd2880_dvb_spi);
+ if (!dvb_spi)
+ return -ENOMEM;
+
+ dvb_spi->vcc_supply = devm_regulator_get_optional(&spi->dev, "vcc");
+ if (IS_ERR(dvb_spi->vcc_supply)) {
+ if (PTR_ERR(dvb_spi->vcc_supply) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto fail_regulator;
+ }
+ dvb_spi->vcc_supply = NULL;
+ } else {
+ ret = regulator_enable(dvb_spi->vcc_supply);
+ if (ret)
+ goto fail_regulator;
+ }
+
+ dvb_spi->spi = spi;
+ mutex_init(&dvb_spi->spi_mutex);
+ spi_set_drvdata(spi, dvb_spi);
+ config.spi = spi;
+ config.spi_mutex = &dvb_spi->spi_mutex;
+
+ ret = dvb_register_adapter(&dvb_spi->adapter,
+ "CXD2880",
+ THIS_MODULE,
+ &spi->dev,
+ adapter_nr);
+ if (ret < 0) {
+ pr_err("dvb_register_adapter() failed\n");
+ goto fail_adapter;
+ }
+
+ if (!dvb_attach(cxd2880_attach, &dvb_spi->dvb_fe, &config)) {
+ pr_err("cxd2880_attach failed\n");
+ ret = -ENODEV;
+ goto fail_attach;
+ }
+
+ ret = dvb_register_frontend(&dvb_spi->adapter,
+ &dvb_spi->dvb_fe);
+ if (ret < 0) {
+ pr_err("dvb_register_frontend() failed\n");
+ goto fail_frontend;
+ }
+
+ dvb_spi->demux.dmx.capabilities = DMX_TS_FILTERING;
+ dvb_spi->demux.priv = dvb_spi;
+ dvb_spi->demux.filternum = CXD2880_MAX_FILTER_SIZE;
+ dvb_spi->demux.feednum = CXD2880_MAX_FILTER_SIZE;
+ dvb_spi->demux.start_feed = cxd2880_start_feed;
+ dvb_spi->demux.stop_feed = cxd2880_stop_feed;
+
+ ret = dvb_dmx_init(&dvb_spi->demux);
+ if (ret < 0) {
+ pr_err("dvb_dmx_init() failed\n");
+ goto fail_dmx;
+ }
+
+ dvb_spi->dmxdev.filternum = CXD2880_MAX_FILTER_SIZE;
+ dvb_spi->dmxdev.demux = &dvb_spi->demux.dmx;
+ dvb_spi->dmxdev.capabilities = 0;
+ ret = dvb_dmxdev_init(&dvb_spi->dmxdev,
+ &dvb_spi->adapter);
+ if (ret < 0) {
+ pr_err("dvb_dmxdev_init() failed\n");
+ goto fail_dmxdev;
+ }
+
+ dvb_spi->dmx_fe.source = DMX_FRONTEND_0;
+ ret = dvb_spi->demux.dmx.add_frontend(&dvb_spi->demux.dmx,
+ &dvb_spi->dmx_fe);
+ if (ret < 0) {
+ pr_err("add_frontend() failed\n");
+ goto fail_dmx_fe;
+ }
+
+ ret = dvb_spi->demux.dmx.connect_frontend(&dvb_spi->demux.dmx,
+ &dvb_spi->dmx_fe);
+ if (ret < 0) {
+ pr_err("connect_frontend() failed\n");
+ goto fail_fe_conn;
+ }
+
+ pr_info("Sony CXD2880 has successfully attached.\n");
+
+ return 0;
+
+fail_fe_conn:
+ dvb_spi->demux.dmx.remove_frontend(&dvb_spi->demux.dmx,
+ &dvb_spi->dmx_fe);
+fail_dmx_fe:
+ dvb_dmxdev_release(&dvb_spi->dmxdev);
+fail_dmxdev:
+ dvb_dmx_release(&dvb_spi->demux);
+fail_dmx:
+ dvb_unregister_frontend(&dvb_spi->dvb_fe);
+fail_frontend:
+ dvb_frontend_detach(&dvb_spi->dvb_fe);
+fail_attach:
+ dvb_unregister_adapter(&dvb_spi->adapter);
+fail_adapter:
+ if (dvb_spi->vcc_supply)
+ regulator_disable(dvb_spi->vcc_supply);
+fail_regulator:
+ kfree(dvb_spi);
+ return ret;
+}
+
+static void
+cxd2880_spi_remove(struct spi_device *spi)
+{
+ struct cxd2880_dvb_spi *dvb_spi = spi_get_drvdata(spi);
+
+ dvb_spi->demux.dmx.remove_frontend(&dvb_spi->demux.dmx,
+ &dvb_spi->dmx_fe);
+ dvb_dmxdev_release(&dvb_spi->dmxdev);
+ dvb_dmx_release(&dvb_spi->demux);
+ dvb_unregister_frontend(&dvb_spi->dvb_fe);
+ dvb_frontend_detach(&dvb_spi->dvb_fe);
+ dvb_unregister_adapter(&dvb_spi->adapter);
+
+ if (dvb_spi->vcc_supply)
+ regulator_disable(dvb_spi->vcc_supply);
+
+ kfree(dvb_spi);
+ pr_info("cxd2880_spi remove ok.\n");
+}
+
+static const struct spi_device_id cxd2880_spi_id[] = {
+ { "cxd2880", 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(spi, cxd2880_spi_id);
+
+static struct spi_driver cxd2880_spi_driver = {
+ .driver = {
+ .name = "cxd2880",
+ .of_match_table = cxd2880_spi_of_match,
+ },
+ .id_table = cxd2880_spi_id,
+ .probe = cxd2880_spi_probe,
+ .remove = cxd2880_spi_remove,
+};
+module_spi_driver(cxd2880_spi_driver);
+
+MODULE_DESCRIPTION("Sony CXD2880 DVB-T2/T tuner + demod driver SPI adapter");
+MODULE_AUTHOR("Sony Semiconductor Solutions Corporation");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/spi/gs1662.c b/drivers/media/spi/gs1662.c
new file mode 100644
index 000000000..7adf55fd0
--- /dev/null
+++ b/drivers/media/spi/gs1662.c
@@ -0,0 +1,483 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GS1662 device registration.
+ *
+ * Copyright (C) 2015-2016 Nexvision
+ * Author: Charles-Antoine Couret <charles-antoine.couret@nexvision.fr>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/ctype.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-dv-timings.h>
+#include <linux/v4l2-dv-timings.h>
+
+#define REG_STATUS 0x04
+#define REG_FORCE_FMT 0x06
+#define REG_LINES_PER_FRAME 0x12
+#define REG_WORDS_PER_LINE 0x13
+#define REG_WORDS_PER_ACT_LINE 0x14
+#define REG_ACT_LINES_PER_FRAME 0x15
+
+#define MASK_H_LOCK 0x001
+#define MASK_V_LOCK 0x002
+#define MASK_STD_LOCK 0x004
+#define MASK_FORCE_STD 0x020
+#define MASK_STD_STATUS 0x3E0
+
+#define GS_WIDTH_MIN 720
+#define GS_WIDTH_MAX 2048
+#define GS_HEIGHT_MIN 487
+#define GS_HEIGHT_MAX 1080
+#define GS_PIXELCLOCK_MIN 10519200
+#define GS_PIXELCLOCK_MAX 74250000
+
+struct gs {
+ struct spi_device *pdev;
+ struct v4l2_subdev sd;
+ struct v4l2_dv_timings current_timings;
+ int enabled;
+};
+
+struct gs_reg_fmt {
+ u16 reg_value;
+ struct v4l2_dv_timings format;
+};
+
+static const struct spi_device_id gs_id[] = {
+ { "gs1662", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, gs_id);
+
+static const struct v4l2_dv_timings fmt_cap[] = {
+ V4L2_DV_BT_SDI_720X487I60,
+ V4L2_DV_BT_CEA_720X576P50,
+ V4L2_DV_BT_CEA_1280X720P24,
+ V4L2_DV_BT_CEA_1280X720P25,
+ V4L2_DV_BT_CEA_1280X720P30,
+ V4L2_DV_BT_CEA_1280X720P50,
+ V4L2_DV_BT_CEA_1280X720P60,
+ V4L2_DV_BT_CEA_1920X1080P24,
+ V4L2_DV_BT_CEA_1920X1080P25,
+ V4L2_DV_BT_CEA_1920X1080P30,
+ V4L2_DV_BT_CEA_1920X1080I50,
+ V4L2_DV_BT_CEA_1920X1080I60,
+};
+
+static const struct gs_reg_fmt reg_fmt[] = {
+ { 0x00, V4L2_DV_BT_CEA_1280X720P60 },
+ { 0x01, V4L2_DV_BT_CEA_1280X720P60 },
+ { 0x02, V4L2_DV_BT_CEA_1280X720P30 },
+ { 0x03, V4L2_DV_BT_CEA_1280X720P30 },
+ { 0x04, V4L2_DV_BT_CEA_1280X720P50 },
+ { 0x05, V4L2_DV_BT_CEA_1280X720P50 },
+ { 0x06, V4L2_DV_BT_CEA_1280X720P25 },
+ { 0x07, V4L2_DV_BT_CEA_1280X720P25 },
+ { 0x08, V4L2_DV_BT_CEA_1280X720P24 },
+ { 0x09, V4L2_DV_BT_CEA_1280X720P24 },
+ { 0x0A, V4L2_DV_BT_CEA_1920X1080I60 },
+ { 0x0B, V4L2_DV_BT_CEA_1920X1080P30 },
+
+ /* Default value: keep this field before 0xC */
+ { 0x14, V4L2_DV_BT_CEA_1920X1080I50 },
+ { 0x0C, V4L2_DV_BT_CEA_1920X1080I50 },
+ { 0x0D, V4L2_DV_BT_CEA_1920X1080P25 },
+ { 0x0E, V4L2_DV_BT_CEA_1920X1080P25 },
+ { 0x10, V4L2_DV_BT_CEA_1920X1080P24 },
+ { 0x12, V4L2_DV_BT_CEA_1920X1080P24 },
+ { 0x16, V4L2_DV_BT_SDI_720X487I60 },
+ { 0x19, V4L2_DV_BT_SDI_720X487I60 },
+ { 0x18, V4L2_DV_BT_CEA_720X576P50 },
+ { 0x1A, V4L2_DV_BT_CEA_720X576P50 },
+
+ /* Implement following timings before enable it.
+ * Because of we don't have access to these theoretical timings yet.
+ * Workaround: use functions to get and set registers for these formats.
+ */
+#if 0
+ { 0x0F, V4L2_DV_BT_XXX_1920X1080I25 }, /* SMPTE 274M */
+ { 0x11, V4L2_DV_BT_XXX_1920X1080I24 }, /* SMPTE 274M */
+ { 0x13, V4L2_DV_BT_XXX_1920X1080I25 }, /* SMPTE 274M */
+ { 0x15, V4L2_DV_BT_XXX_1920X1035I60 }, /* SMPTE 260M */
+ { 0x17, V4L2_DV_BT_SDI_720X507I60 }, /* SMPTE 125M */
+ { 0x1B, V4L2_DV_BT_SDI_720X507I60 }, /* SMPTE 125M */
+ { 0x1C, V4L2_DV_BT_XXX_2048X1080P25 }, /* SMPTE 428.1M */
+#endif
+};
+
+static const struct v4l2_dv_timings_cap gs_timings_cap = {
+ .type = V4L2_DV_BT_656_1120,
+ /* keep this initialization for compatibility with GCC < 4.4.6 */
+ .reserved = { 0 },
+ V4L2_INIT_BT_TIMINGS(GS_WIDTH_MIN, GS_WIDTH_MAX, GS_HEIGHT_MIN,
+ GS_HEIGHT_MAX, GS_PIXELCLOCK_MIN,
+ GS_PIXELCLOCK_MAX,
+ V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_SDI,
+ V4L2_DV_BT_CAP_PROGRESSIVE
+ | V4L2_DV_BT_CAP_INTERLACED)
+};
+
+static int gs_read_register(struct spi_device *spi, u16 addr, u16 *value)
+{
+ int ret;
+ u16 buf_addr = (0x8000 | (0x0FFF & addr));
+ u16 buf_value = 0;
+ struct spi_message msg;
+ struct spi_transfer tx[] = {
+ {
+ .tx_buf = &buf_addr,
+ .len = 2,
+ .delay = {
+ .value = 1,
+ .unit = SPI_DELAY_UNIT_USECS
+ },
+ }, {
+ .rx_buf = &buf_value,
+ .len = 2,
+ .delay = {
+ .value = 1,
+ .unit = SPI_DELAY_UNIT_USECS
+ },
+ },
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&tx[0], &msg);
+ spi_message_add_tail(&tx[1], &msg);
+ ret = spi_sync(spi, &msg);
+
+ *value = buf_value;
+
+ return ret;
+}
+
+static int gs_write_register(struct spi_device *spi, u16 addr, u16 value)
+{
+ int ret;
+ u16 buf_addr = addr;
+ u16 buf_value = value;
+ struct spi_message msg;
+ struct spi_transfer tx[] = {
+ {
+ .tx_buf = &buf_addr,
+ .len = 2,
+ .delay = {
+ .value = 1,
+ .unit = SPI_DELAY_UNIT_USECS
+ },
+ }, {
+ .tx_buf = &buf_value,
+ .len = 2,
+ .delay = {
+ .value = 1,
+ .unit = SPI_DELAY_UNIT_USECS
+ },
+ },
+ };
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&tx[0], &msg);
+ spi_message_add_tail(&tx[1], &msg);
+ ret = spi_sync(spi, &msg);
+
+ return ret;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int gs_g_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ struct spi_device *spi = v4l2_get_subdevdata(sd);
+ u16 val;
+ int ret;
+
+ ret = gs_read_register(spi, reg->reg & 0xFFFF, &val);
+ reg->val = val;
+ reg->size = 2;
+ return ret;
+}
+
+static int gs_s_register(struct v4l2_subdev *sd,
+ const struct v4l2_dbg_register *reg)
+{
+ struct spi_device *spi = v4l2_get_subdevdata(sd);
+
+ return gs_write_register(spi, reg->reg & 0xFFFF, reg->val & 0xFFFF);
+}
+#endif
+
+static int gs_status_format(u16 status, struct v4l2_dv_timings *timings)
+{
+ int std = (status & MASK_STD_STATUS) >> 5;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(reg_fmt); i++) {
+ if (reg_fmt[i].reg_value == std) {
+ *timings = reg_fmt[i].format;
+ return 0;
+ }
+ }
+
+ return -ERANGE;
+}
+
+static u16 get_register_timings(struct v4l2_dv_timings *timings)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(reg_fmt); i++) {
+ if (v4l2_match_dv_timings(timings, &reg_fmt[i].format, 0,
+ false))
+ return reg_fmt[i].reg_value | MASK_FORCE_STD;
+ }
+
+ return 0x0;
+}
+
+static inline struct gs *to_gs(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct gs, sd);
+}
+
+static int gs_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
+ struct v4l2_dv_timings *timings)
+{
+ struct gs *gs = to_gs(sd);
+ int reg_value;
+
+ if (pad != 0)
+ return -EINVAL;
+
+ reg_value = get_register_timings(timings);
+ if (reg_value == 0x0)
+ return -EINVAL;
+
+ gs->current_timings = *timings;
+ return 0;
+}
+
+static int gs_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
+ struct v4l2_dv_timings *timings)
+{
+ struct gs *gs = to_gs(sd);
+
+ if (pad != 0)
+ return -EINVAL;
+
+ *timings = gs->current_timings;
+ return 0;
+}
+
+static int gs_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad,
+ struct v4l2_dv_timings *timings)
+{
+ struct gs *gs = to_gs(sd);
+ struct v4l2_dv_timings fmt;
+ u16 reg_value, i;
+ int ret;
+
+ if (pad != 0)
+ return -EINVAL;
+
+ if (gs->enabled)
+ return -EBUSY;
+
+ /*
+ * Check if the component detect a line, a frame or something else
+ * which looks like a video signal activity.
+ */
+ for (i = 0; i < 4; i++) {
+ gs_read_register(gs->pdev, REG_LINES_PER_FRAME + i, &reg_value);
+ if (reg_value)
+ break;
+ }
+
+ /* If no register reports a video signal */
+ if (i >= 4)
+ return -ENOLINK;
+
+ gs_read_register(gs->pdev, REG_STATUS, &reg_value);
+ if (!(reg_value & MASK_H_LOCK) || !(reg_value & MASK_V_LOCK))
+ return -ENOLCK;
+ if (!(reg_value & MASK_STD_LOCK))
+ return -ERANGE;
+
+ ret = gs_status_format(reg_value, &fmt);
+
+ if (ret < 0)
+ return ret;
+
+ *timings = fmt;
+ return 0;
+}
+
+static int gs_enum_dv_timings(struct v4l2_subdev *sd,
+ struct v4l2_enum_dv_timings *timings)
+{
+ if (timings->index >= ARRAY_SIZE(fmt_cap))
+ return -EINVAL;
+
+ if (timings->pad != 0)
+ return -EINVAL;
+
+ timings->timings = fmt_cap[timings->index];
+ return 0;
+}
+
+static int gs_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct gs *gs = to_gs(sd);
+ int reg_value;
+
+ if (gs->enabled == enable)
+ return 0;
+
+ gs->enabled = enable;
+
+ if (enable) {
+ /* To force the specific format */
+ reg_value = get_register_timings(&gs->current_timings);
+ return gs_write_register(gs->pdev, REG_FORCE_FMT, reg_value);
+ }
+
+ /* To renable auto-detection mode */
+ return gs_write_register(gs->pdev, REG_FORCE_FMT, 0x0);
+}
+
+static int gs_g_input_status(struct v4l2_subdev *sd, u32 *status)
+{
+ struct gs *gs = to_gs(sd);
+ u16 reg_value, i;
+ int ret;
+
+ /*
+ * Check if the component detect a line, a frame or something else
+ * which looks like a video signal activity.
+ */
+ for (i = 0; i < 4; i++) {
+ ret = gs_read_register(gs->pdev,
+ REG_LINES_PER_FRAME + i, &reg_value);
+ if (reg_value)
+ break;
+ if (ret) {
+ *status = V4L2_IN_ST_NO_POWER;
+ return ret;
+ }
+ }
+
+ /* If no register reports a video signal */
+ if (i >= 4)
+ *status |= V4L2_IN_ST_NO_SIGNAL;
+
+ ret = gs_read_register(gs->pdev, REG_STATUS, &reg_value);
+ if (!(reg_value & MASK_H_LOCK))
+ *status |= V4L2_IN_ST_NO_H_LOCK;
+ if (!(reg_value & MASK_V_LOCK))
+ *status |= V4L2_IN_ST_NO_V_LOCK;
+ if (!(reg_value & MASK_STD_LOCK))
+ *status |= V4L2_IN_ST_NO_STD_LOCK;
+
+ return ret;
+}
+
+static int gs_dv_timings_cap(struct v4l2_subdev *sd,
+ struct v4l2_dv_timings_cap *cap)
+{
+ if (cap->pad != 0)
+ return -EINVAL;
+
+ *cap = gs_timings_cap;
+ return 0;
+}
+
+/* V4L2 core operation handlers */
+static const struct v4l2_subdev_core_ops gs_core_ops = {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .g_register = gs_g_register,
+ .s_register = gs_s_register,
+#endif
+};
+
+static const struct v4l2_subdev_video_ops gs_video_ops = {
+ .s_stream = gs_s_stream,
+ .g_input_status = gs_g_input_status,
+};
+
+static const struct v4l2_subdev_pad_ops gs_pad_ops = {
+ .s_dv_timings = gs_s_dv_timings,
+ .g_dv_timings = gs_g_dv_timings,
+ .query_dv_timings = gs_query_dv_timings,
+ .enum_dv_timings = gs_enum_dv_timings,
+ .dv_timings_cap = gs_dv_timings_cap,
+};
+
+/* V4L2 top level operation handlers */
+static const struct v4l2_subdev_ops gs_ops = {
+ .core = &gs_core_ops,
+ .video = &gs_video_ops,
+ .pad = &gs_pad_ops,
+};
+
+static int gs_probe(struct spi_device *spi)
+{
+ int ret;
+ struct gs *gs;
+ struct v4l2_subdev *sd;
+
+ gs = devm_kzalloc(&spi->dev, sizeof(struct gs), GFP_KERNEL);
+ if (!gs)
+ return -ENOMEM;
+
+ gs->pdev = spi;
+ sd = &gs->sd;
+
+ spi->mode = SPI_MODE_0;
+ spi->irq = -1;
+ spi->max_speed_hz = 10000000;
+ spi->bits_per_word = 16;
+ ret = spi_setup(spi);
+ v4l2_spi_subdev_init(sd, spi, &gs_ops);
+
+ gs->current_timings = reg_fmt[0].format;
+ gs->enabled = 0;
+
+ /* Set H_CONFIG to SMPTE timings */
+ gs_write_register(spi, 0x0, 0x300);
+
+ return ret;
+}
+
+static void gs_remove(struct spi_device *spi)
+{
+ struct v4l2_subdev *sd = spi_get_drvdata(spi);
+
+ v4l2_device_unregister_subdev(sd);
+}
+
+static struct spi_driver gs_driver = {
+ .driver = {
+ .name = "gs1662",
+ },
+
+ .probe = gs_probe,
+ .remove = gs_remove,
+ .id_table = gs_id,
+};
+
+module_spi_driver(gs_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Charles-Antoine Couret <charles-antoine.couret@nexvision.fr>");
+MODULE_DESCRIPTION("Gennum GS1662 HD/SD-SDI Serializer driver");