773 lines
21 KiB
C
773 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2021-2023 Digiteq Automotive
|
|
* author: Martin Tuma <martin.tuma@digiteqautomotive.com>
|
|
*
|
|
* This module handles all the sysfs info/configuration that is related to the
|
|
* v4l2 input devices.
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include "mgb4_core.h"
|
|
#include "mgb4_i2c.h"
|
|
#include "mgb4_vin.h"
|
|
#include "mgb4_cmt.h"
|
|
#include "mgb4_sysfs.h"
|
|
|
|
/* Common for both FPDL3 and GMSL */
|
|
|
|
static ssize_t input_id_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
|
|
return sprintf(buf, "%d\n", vindev->config->id);
|
|
}
|
|
|
|
static ssize_t oldi_lane_width_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
struct mgb4_dev *mgbdev = vindev->mgbdev;
|
|
u16 i2c_reg;
|
|
u8 i2c_mask, i2c_single_val, i2c_dual_val;
|
|
u32 config;
|
|
int ret;
|
|
|
|
i2c_reg = MGB4_IS_GMSL(mgbdev) ? 0x1CE : 0x49;
|
|
i2c_mask = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x03;
|
|
i2c_single_val = MGB4_IS_GMSL(mgbdev) ? 0x00 : 0x02;
|
|
i2c_dual_val = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x00;
|
|
|
|
mutex_lock(&mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_read_byte(&vindev->deser, i2c_reg);
|
|
mutex_unlock(&mgbdev->i2c_lock);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
config = mgb4_read_reg(&mgbdev->video, vindev->config->regs.config);
|
|
|
|
if (((config & (1U << 9)) && ((ret & i2c_mask) != i2c_dual_val)) ||
|
|
(!(config & (1U << 9)) && ((ret & i2c_mask) != i2c_single_val))) {
|
|
dev_err(dev, "I2C/FPGA register value mismatch\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return sprintf(buf, "%s\n", config & (1U << 9) ? "1" : "0");
|
|
}
|
|
|
|
/*
|
|
* OLDI lane width change is expected to be called on live streams. Video device
|
|
* locking/queue check is not needed.
|
|
*/
|
|
static ssize_t oldi_lane_width_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
struct mgb4_dev *mgbdev = vindev->mgbdev;
|
|
u32 fpga_data;
|
|
u16 i2c_reg;
|
|
u8 i2c_mask, i2c_data;
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (val) {
|
|
case 0: /* single */
|
|
fpga_data = 0;
|
|
i2c_data = MGB4_IS_GMSL(mgbdev) ? 0x00 : 0x02;
|
|
break;
|
|
case 1: /* dual */
|
|
fpga_data = 1U << 9;
|
|
i2c_data = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x00;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
i2c_reg = MGB4_IS_GMSL(mgbdev) ? 0x1CE : 0x49;
|
|
i2c_mask = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x03;
|
|
|
|
mutex_lock(&mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_mask_byte(&vindev->deser, i2c_reg, i2c_mask, i2c_data);
|
|
mutex_unlock(&mgbdev->i2c_lock);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
mgb4_mask_reg(&mgbdev->video, vindev->config->regs.config, 1U << 9,
|
|
fpga_data);
|
|
if (MGB4_IS_GMSL(mgbdev)) {
|
|
/* reset input link */
|
|
mutex_lock(&mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_mask_byte(&vindev->deser, 0x10, 1U << 5, 1U << 5);
|
|
mutex_unlock(&mgbdev->i2c_lock);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t color_mapping_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 config = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.config);
|
|
|
|
return sprintf(buf, "%s\n", config & (1U << 8) ? "0" : "1");
|
|
}
|
|
|
|
/*
|
|
* Color mapping change is expected to be called on live streams. Video device
|
|
* locking/queue check is not needed.
|
|
*/
|
|
static ssize_t color_mapping_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 fpga_data;
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (val) {
|
|
case 0: /* OLDI/JEIDA */
|
|
fpga_data = (1U << 8);
|
|
break;
|
|
case 1: /* SPWG/VESA */
|
|
fpga_data = 0;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config,
|
|
1U << 8, fpga_data);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t link_status_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 status = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.status);
|
|
|
|
return sprintf(buf, "%s\n", status & (1U << 2) ? "1" : "0");
|
|
}
|
|
|
|
static ssize_t stream_status_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 status = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.status);
|
|
|
|
return sprintf(buf, "%s\n", ((status & (1 << 14)) &&
|
|
(status & (1 << 2)) && (status & (3 << 9))) ? "1" : "0");
|
|
}
|
|
|
|
static ssize_t video_width_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 config = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.resolution);
|
|
|
|
return sprintf(buf, "%u\n", config >> 16);
|
|
}
|
|
|
|
static ssize_t video_height_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 config = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.resolution);
|
|
|
|
return sprintf(buf, "%u\n", config & 0xFFFF);
|
|
}
|
|
|
|
static ssize_t hsync_status_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 status = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.status);
|
|
u32 res;
|
|
|
|
if (!(status & (1U << 11)))
|
|
res = 0x02; // not available
|
|
else if (status & (1U << 12))
|
|
res = 0x01; // active high
|
|
else
|
|
res = 0x00; // active low
|
|
|
|
return sprintf(buf, "%u\n", res);
|
|
}
|
|
|
|
static ssize_t vsync_status_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 status = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.status);
|
|
u32 res;
|
|
|
|
if (!(status & (1U << 11)))
|
|
res = 0x02; // not available
|
|
else if (status & (1U << 13))
|
|
res = 0x01; // active high
|
|
else
|
|
res = 0x00; // active low
|
|
|
|
return sprintf(buf, "%u\n", res);
|
|
}
|
|
|
|
static ssize_t hsync_gap_length_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 sync = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.sync);
|
|
|
|
return sprintf(buf, "%u\n", sync >> 16);
|
|
}
|
|
|
|
/*
|
|
* HSYNC gap length change is expected to be called on live streams. Video
|
|
* device locking/queue check is not needed.
|
|
*/
|
|
static ssize_t hsync_gap_length_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
if (val > 0xFFFF)
|
|
return -EINVAL;
|
|
|
|
mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync,
|
|
0xFFFF0000, val << 16);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t vsync_gap_length_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 sync = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.sync);
|
|
|
|
return sprintf(buf, "%u\n", sync & 0xFFFF);
|
|
}
|
|
|
|
/*
|
|
* VSYNC gap length change is expected to be called on live streams. Video
|
|
* device locking/queue check is not needed.
|
|
*/
|
|
static ssize_t vsync_gap_length_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
if (val > 0xFFFF)
|
|
return -EINVAL;
|
|
|
|
mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync, 0xFFFF,
|
|
val);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t pclk_frequency_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 freq = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.pclk);
|
|
|
|
return sprintf(buf, "%u\n", freq);
|
|
}
|
|
|
|
static ssize_t hsync_width_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 sig = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.signal);
|
|
|
|
return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16);
|
|
}
|
|
|
|
static ssize_t vsync_width_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 sig = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.signal2);
|
|
|
|
return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16);
|
|
}
|
|
|
|
static ssize_t hback_porch_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 sig = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.signal);
|
|
|
|
return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8);
|
|
}
|
|
|
|
static ssize_t hfront_porch_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 sig = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.signal);
|
|
|
|
return sprintf(buf, "%u\n", (sig & 0x000000FF));
|
|
}
|
|
|
|
static ssize_t vback_porch_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 sig = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.signal2);
|
|
|
|
return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8);
|
|
}
|
|
|
|
static ssize_t vfront_porch_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u32 sig = mgb4_read_reg(&vindev->mgbdev->video,
|
|
vindev->config->regs.signal2);
|
|
|
|
return sprintf(buf, "%u\n", (sig & 0x000000FF));
|
|
}
|
|
|
|
static ssize_t frequency_range_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
|
|
return sprintf(buf, "%d\n", vindev->freq_range);
|
|
}
|
|
|
|
static ssize_t frequency_range_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
if (val > 1)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(vindev->vdev.lock);
|
|
if (vb2_is_busy(vindev->vdev.queue)) {
|
|
mutex_unlock(vindev->vdev.lock);
|
|
return -EBUSY;
|
|
}
|
|
|
|
mgb4_cmt_set_vin_freq_range(vindev, val);
|
|
vindev->freq_range = val;
|
|
|
|
mutex_unlock(vindev->vdev.lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
/* FPDL3 only */
|
|
|
|
static ssize_t fpdl3_input_width_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
s32 ret;
|
|
|
|
mutex_lock(&vindev->mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_read_byte(&vindev->deser, 0x34);
|
|
mutex_unlock(&vindev->mgbdev->i2c_lock);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
switch ((u8)ret & 0x18) {
|
|
case 0:
|
|
return sprintf(buf, "0\n");
|
|
case 0x10:
|
|
return sprintf(buf, "1\n");
|
|
case 0x08:
|
|
return sprintf(buf, "2\n");
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* FPD-Link width change is expected to be called on live streams. Video device
|
|
* locking/queue check is not needed.
|
|
*/
|
|
static ssize_t fpdl3_input_width_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
u8 i2c_data;
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (val) {
|
|
case 0: /* auto */
|
|
i2c_data = 0x00;
|
|
break;
|
|
case 1: /* single */
|
|
i2c_data = 0x10;
|
|
break;
|
|
case 2: /* dual */
|
|
i2c_data = 0x08;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&vindev->mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_mask_byte(&vindev->deser, 0x34, 0x18, i2c_data);
|
|
mutex_unlock(&vindev->mgbdev->i2c_lock);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
return count;
|
|
}
|
|
|
|
/* GMSL only */
|
|
|
|
static ssize_t gmsl_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
s32 r1, r300, r3;
|
|
|
|
mutex_lock(&vindev->mgbdev->i2c_lock);
|
|
r1 = mgb4_i2c_read_byte(&vindev->deser, 0x01);
|
|
r300 = mgb4_i2c_read_byte(&vindev->deser, 0x300);
|
|
r3 = mgb4_i2c_read_byte(&vindev->deser, 0x03);
|
|
mutex_unlock(&vindev->mgbdev->i2c_lock);
|
|
if (r1 < 0 || r300 < 0 || r3 < 0)
|
|
return -EIO;
|
|
|
|
if ((r1 & 0x03) == 0x03 && (r300 & 0x0C) == 0x0C && (r3 & 0xC0) == 0xC0)
|
|
return sprintf(buf, "0\n");
|
|
else if ((r1 & 0x03) == 0x02 && (r300 & 0x0C) == 0x08 && (r3 & 0xC0) == 0x00)
|
|
return sprintf(buf, "1\n");
|
|
else if ((r1 & 0x03) == 0x01 && (r300 & 0x0C) == 0x04 && (r3 & 0xC0) == 0x00)
|
|
return sprintf(buf, "2\n");
|
|
else if ((r1 & 0x03) == 0x00 && (r300 & 0x0C) == 0x00 && (r3 & 0xC0) == 0x00)
|
|
return sprintf(buf, "3\n");
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* GMSL mode change is expected to be called on live streams. Video device
|
|
* locking/queue check is not needed.
|
|
*/
|
|
static ssize_t gmsl_mode_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf,
|
|
size_t count)
|
|
{
|
|
static const struct mgb4_i2c_kv G12[] = {
|
|
{0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0}};
|
|
static const struct mgb4_i2c_kv G6[] = {
|
|
{0x01, 0x03, 0x02}, {0x300, 0x0C, 0x08}, {0x03, 0xC0, 0x00}};
|
|
static const struct mgb4_i2c_kv G3[] = {
|
|
{0x01, 0x03, 0x01}, {0x300, 0x0C, 0x04}, {0x03, 0xC0, 0x00}};
|
|
static const struct mgb4_i2c_kv G1[] = {
|
|
{0x01, 0x03, 0x00}, {0x300, 0x0C, 0x00}, {0x03, 0xC0, 0x00}};
|
|
static const struct mgb4_i2c_kv reset[] = {
|
|
{0x10, 1U << 5, 1U << 5}, {0x300, 1U << 6, 1U << 6}};
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
const struct mgb4_i2c_kv *values;
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (val) {
|
|
case 0: /* 12Gb/s */
|
|
values = G12;
|
|
break;
|
|
case 1: /* 6Gb/s */
|
|
values = G6;
|
|
break;
|
|
case 2: /* 3Gb/s */
|
|
values = G3;
|
|
break;
|
|
case 3: /* 1.5Gb/s */
|
|
values = G1;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&vindev->mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_configure(&vindev->deser, values, 3);
|
|
ret |= mgb4_i2c_configure(&vindev->deser, reset, 2);
|
|
mutex_unlock(&vindev->mgbdev->i2c_lock);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t gmsl_stream_id_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
s32 ret;
|
|
|
|
mutex_lock(&vindev->mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_read_byte(&vindev->deser, 0xA0);
|
|
mutex_unlock(&vindev->mgbdev->i2c_lock);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
return sprintf(buf, "%d\n", ret & 0x03);
|
|
}
|
|
|
|
static ssize_t gmsl_stream_id_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
if (val > 3)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(vindev->vdev.lock);
|
|
if (vb2_is_busy(vindev->vdev.queue)) {
|
|
mutex_unlock(vindev->vdev.lock);
|
|
return -EBUSY;
|
|
}
|
|
|
|
mutex_lock(&vindev->mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_mask_byte(&vindev->deser, 0xA0, 0x03, (u8)val);
|
|
mutex_unlock(&vindev->mgbdev->i2c_lock);
|
|
|
|
mutex_unlock(vindev->vdev.lock);
|
|
|
|
return (ret < 0) ? -EIO : count;
|
|
}
|
|
|
|
static ssize_t gmsl_fec_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
s32 r3e0, r308;
|
|
|
|
mutex_lock(&vindev->mgbdev->i2c_lock);
|
|
r3e0 = mgb4_i2c_read_byte(&vindev->deser, 0x3E0);
|
|
r308 = mgb4_i2c_read_byte(&vindev->deser, 0x308);
|
|
mutex_unlock(&vindev->mgbdev->i2c_lock);
|
|
if (r3e0 < 0 || r308 < 0)
|
|
return -EIO;
|
|
|
|
if ((r3e0 & 0x07) == 0x00 && (r308 & 0x01) == 0x00)
|
|
return sprintf(buf, "0\n");
|
|
else if ((r3e0 & 0x07) == 0x07 && (r308 & 0x01) == 0x01)
|
|
return sprintf(buf, "1\n");
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* GMSL FEC change is expected to be called on live streams. Video device
|
|
* locking/queue check is not needed.
|
|
*/
|
|
static ssize_t gmsl_fec_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct video_device *vdev = to_video_device(dev);
|
|
struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
|
|
static const struct mgb4_i2c_kv enable[] = {
|
|
{0x3E0, 0x07, 0x07}, {0x308, 0x01, 0x01}};
|
|
static const struct mgb4_i2c_kv disable[] = {
|
|
{0x3E0, 0x07, 0x00}, {0x308, 0x01, 0x00}};
|
|
static const struct mgb4_i2c_kv reset[] = {
|
|
{0x10, 1U << 5, 1U << 5}, {0x300, 1U << 6, 1U << 6}};
|
|
const struct mgb4_i2c_kv *values;
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (val) {
|
|
case 0: /* disabled */
|
|
values = disable;
|
|
break;
|
|
case 1: /* enabled */
|
|
values = enable;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&vindev->mgbdev->i2c_lock);
|
|
ret = mgb4_i2c_configure(&vindev->deser, values, 2);
|
|
ret |= mgb4_i2c_configure(&vindev->deser, reset, 2);
|
|
mutex_unlock(&vindev->mgbdev->i2c_lock);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(input_id);
|
|
static DEVICE_ATTR_RW(oldi_lane_width);
|
|
static DEVICE_ATTR_RW(color_mapping);
|
|
static DEVICE_ATTR_RO(link_status);
|
|
static DEVICE_ATTR_RO(stream_status);
|
|
static DEVICE_ATTR_RO(video_width);
|
|
static DEVICE_ATTR_RO(video_height);
|
|
static DEVICE_ATTR_RO(hsync_status);
|
|
static DEVICE_ATTR_RO(vsync_status);
|
|
static DEVICE_ATTR_RW(hsync_gap_length);
|
|
static DEVICE_ATTR_RW(vsync_gap_length);
|
|
static DEVICE_ATTR_RO(pclk_frequency);
|
|
static DEVICE_ATTR_RO(hsync_width);
|
|
static DEVICE_ATTR_RO(vsync_width);
|
|
static DEVICE_ATTR_RO(hback_porch);
|
|
static DEVICE_ATTR_RO(hfront_porch);
|
|
static DEVICE_ATTR_RO(vback_porch);
|
|
static DEVICE_ATTR_RO(vfront_porch);
|
|
static DEVICE_ATTR_RW(frequency_range);
|
|
|
|
static DEVICE_ATTR_RW(fpdl3_input_width);
|
|
|
|
static DEVICE_ATTR_RW(gmsl_mode);
|
|
static DEVICE_ATTR_RW(gmsl_stream_id);
|
|
static DEVICE_ATTR_RW(gmsl_fec);
|
|
|
|
struct attribute *mgb4_fpdl3_in_attrs[] = {
|
|
&dev_attr_input_id.attr,
|
|
&dev_attr_link_status.attr,
|
|
&dev_attr_stream_status.attr,
|
|
&dev_attr_video_width.attr,
|
|
&dev_attr_video_height.attr,
|
|
&dev_attr_hsync_status.attr,
|
|
&dev_attr_vsync_status.attr,
|
|
&dev_attr_oldi_lane_width.attr,
|
|
&dev_attr_color_mapping.attr,
|
|
&dev_attr_hsync_gap_length.attr,
|
|
&dev_attr_vsync_gap_length.attr,
|
|
&dev_attr_pclk_frequency.attr,
|
|
&dev_attr_hsync_width.attr,
|
|
&dev_attr_vsync_width.attr,
|
|
&dev_attr_hback_porch.attr,
|
|
&dev_attr_hfront_porch.attr,
|
|
&dev_attr_vback_porch.attr,
|
|
&dev_attr_vfront_porch.attr,
|
|
&dev_attr_frequency_range.attr,
|
|
&dev_attr_fpdl3_input_width.attr,
|
|
NULL
|
|
};
|
|
|
|
struct attribute *mgb4_gmsl_in_attrs[] = {
|
|
&dev_attr_input_id.attr,
|
|
&dev_attr_link_status.attr,
|
|
&dev_attr_stream_status.attr,
|
|
&dev_attr_video_width.attr,
|
|
&dev_attr_video_height.attr,
|
|
&dev_attr_hsync_status.attr,
|
|
&dev_attr_vsync_status.attr,
|
|
&dev_attr_oldi_lane_width.attr,
|
|
&dev_attr_color_mapping.attr,
|
|
&dev_attr_hsync_gap_length.attr,
|
|
&dev_attr_vsync_gap_length.attr,
|
|
&dev_attr_pclk_frequency.attr,
|
|
&dev_attr_hsync_width.attr,
|
|
&dev_attr_vsync_width.attr,
|
|
&dev_attr_hback_porch.attr,
|
|
&dev_attr_hfront_porch.attr,
|
|
&dev_attr_vback_porch.attr,
|
|
&dev_attr_vfront_porch.attr,
|
|
&dev_attr_frequency_range.attr,
|
|
&dev_attr_gmsl_mode.attr,
|
|
&dev_attr_gmsl_stream_id.attr,
|
|
&dev_attr_gmsl_fec.attr,
|
|
NULL
|
|
};
|