From ef3128dda213b885229c8c1e0c8a095cf521345e Mon Sep 17 00:00:00 2001 From: Shawn Bai Date: Fri, 5 Sep 2025 10:20:33 +0000 Subject: [PATCH] [MAX96712][MAX96726]add max96712/726 TPG driver max96712, run in fixed configuration 1. resolution: 3840x2160 2. FPS: 30 3. data rate 1900Mbps 4. pixel format: RAW10 5. PHY mode: DPHY 4Lane max96726, run in fixed configuration 1. resolution: 3840x2160 2. FPS: 30 3. data rate: DPHY 2500Mbps, CPHY 4500Msps 4. pixel format: RAW16 Bug 5060621 Change-Id: I0ab8139f3b8e4756b7558653ea516e5fbc2f719d Signed-off-by: Shawn Bai Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3447885 GVS: buildbot_gerritrpt Reviewed-by: Jukka Kaartinen Reviewed-by: Narendra Kondapalli --- drivers/media/i2c/Makefile | 2 + drivers/media/i2c/max96712_tpg.c | 761 +++++++++++++++++++++++++++ drivers/media/i2c/max96726_tpg.c | 865 +++++++++++++++++++++++++++++++ 3 files changed, 1628 insertions(+) create mode 100644 drivers/media/i2c/max96712_tpg.c create mode 100644 drivers/media/i2c/max96726_tpg.c diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index fdf09df6..3b3a3c14 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -33,6 +33,8 @@ obj-m += nv_ov5693.o obj-m += nv_ar0234.o obj-m += nv_hawk_owl.o obj-m += max929x.o +obj-m += max96712_tpg.o +obj-m += max96726_tpg.o endif obj-m += pca9570.o diff --git a/drivers/media/i2c/max96712_tpg.c b/drivers/media/i2c/max96712_tpg.c new file mode 100644 index 00000000..7c0d2a56 --- /dev/null +++ b/drivers/media/i2c/max96712_tpg.c @@ -0,0 +1,761 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* Kernel version compatibility checks */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + + +#define MAX96712_ID 0xA0 +#define MAX96712_DPLL_FREQ 1000 +#define DT_RAW_10 0x2B + +enum max96712_pattern { + MAX96712_PATTERN_CHECKERBOARD = 0, + MAX96712_PATTERN_GRADIENT, +}; + +struct max96712_priv { + struct i2c_client *client; + struct regmap *regmap; + struct gpio_desc *gpiod_pwdn[2]; + + bool cphy; + + struct v4l2_subdev sd; + struct v4l2_ctrl_handler ctrl_handler; + struct media_pad pads[1]; + + enum max96712_pattern pattern; + + const struct i2c_device_id *id; + struct v4l2_subdev *subdev; + u32 frame_length; + struct camera_common_data *s_data; + struct tegracam_device *tc_dev; + u32 channel; + const char *sensor_name; + + /* Debugfs interface */ + struct { + struct dentry *dir; + struct dentry *reg; + struct dentry *val; + u16 reg_addr; + u8 reg_val; + } debugfs; +}; + +struct index_reg_8 { + u16 addr; + u16 val; +}; + +enum { + MAX96712_MODE_1920X1080_CROP_30FPS, + MAX96712_MODE_3840X2160_CROP_30FPS, + MAX96712_MODE_START_STREAM, + MAX96712_MODE_STOP_STREAM, + MAX96712_MODE_QUADLINK_DSER_SER, + MAX96712_MODE_TEST_PATTERN +}; + +#define MAX96712_TABLE_WAIT_MS 0xff00 +#define MAX96712_TABLE_END 0xff01 + +static const struct of_device_id max96712_of_table[] = { + { .compatible = "maxim,max96712-tpg" }, + { /* sentinel */ }, +}; + +static int max96712_raw16_3840_2160_x4(struct max96712_priv *priv); + +/* Global definition begin */ +static const int max96712_30fps[] = { + 30, +}; + +#define MAX96712_FRAME_RATE_30_FPS 30000000 /* 30 fps */ +#define MAX96712_FRAME_RATE_DEFAULT MAX96712_FRAME_RATE_30_FPS +static const struct camera_common_frmfmt max96712_frmfmt[] = { + {{3840, 2160}, + max96712_30fps, + 1, + 0, + MAX96712_MODE_3840X2160_CROP_30FPS, + }, +}; + +/* Global definition end */ + +static int max96712_read(struct max96712_priv *priv, int reg) +{ + int ret, val; + + if (!priv || !priv->regmap) { + pr_err("Invalid regmap pointer\n"); + return -EINVAL; + } + + dev_dbg(&priv->client->dev, "to read 0x%04x\n", reg); + + ret = regmap_read(priv->regmap, reg, &val); + if (ret) { + dev_err(&priv->client->dev, "read 0x%04x failed\n", reg); + return ret; + } + + return val; +} + +static int max96712_write(struct max96712_priv *priv, unsigned int reg, u8 val) +{ + int ret; + + if (!priv || !priv->regmap) { + pr_err("Invalid regmap pointer\n"); + return -EINVAL; + } + + dev_dbg(&priv->client->dev, "to write 0x%04x with 0x%x\n", reg, val); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(&priv->client->dev, "write 0x%04x failed\n", reg); + + return ret; +} + +/* Debugfs interface implementation */ +static int max96712_debugfs_reg_get(void *data, u64 *val) +{ + struct max96712_priv *priv = data; + int ret; + + ret = max96712_read(priv, priv->debugfs.reg_addr); + if (ret < 0) { + dev_err(&priv->client->dev, "Failed to read reg 0x%04x\n", + priv->debugfs.reg_addr); + return ret; + } + + *val = ret; + return 0; +} + +static int max96712_debugfs_reg_set(void *data, u64 val) +{ + struct max96712_priv *priv = data; + int ret; + + if (val > 0xFF) { + dev_err(&priv->client->dev, "Value 0x%llx out of range (max 0xFF)\n", val); + return -EINVAL; + } + + ret = max96712_write(priv, priv->debugfs.reg_addr, val); + if (ret) { + dev_err(&priv->client->dev, "Failed to wr reg 0x%04x = 0x%02llx\n", + priv->debugfs.reg_addr, val); + return ret; + } + + dev_info(&priv->client->dev, "Successfully wr reg 0x%04x = 0x%02llx\n", + priv->debugfs.reg_addr, val); + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(max96712_debugfs_reg_fops, + max96712_debugfs_reg_get, + max96712_debugfs_reg_set, + "0x%02llx\n"); + +static void max96712_debugfs_init(struct max96712_priv *priv) +{ + /* Create debugfs directory */ + priv->debugfs.dir = debugfs_create_dir("max96712", NULL); + if (IS_ERR(priv->debugfs.dir)) { + dev_err(&priv->client->dev, "Failed to create debugfs directory\n"); + return; + } + + /* Create register address entry */ + debugfs_create_x16("reg_addr", 0644, priv->debugfs.dir, + &priv->debugfs.reg_addr); + + /* Create register value entry */ + debugfs_create_file("reg_val", 0644, priv->debugfs.dir, + priv, &max96712_debugfs_reg_fops); +} + +static void max96712_debugfs_remove(struct max96712_priv *priv) +{ + debugfs_remove_recursive(priv->debugfs.dir); +} + +static int max96712_update_bits(struct max96712_priv *priv, unsigned int reg, + u8 mask, u8 val) +{ + int ret; + + dev_dbg(&priv->client->dev, "%s: update 0x%04x mask 0x%x, val 0x%x\n", + __func__, reg, mask, val); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(&priv->client->dev, "update 0x%04x failed\n", reg); + + return ret; +} + +static void max96712_reset(struct max96712_priv *priv) +{ + pr_info("MAX96712 reset\n"); + + max96712_update_bits(priv, 0x13, 0x40, 0x40); + msleep(20); +} + +static int max96712_raw16_3840_2160_x4(struct max96712_priv *priv) +{ + int regBpp = 0xE0; + int dt = DT_RAW_10; + int bpp = 10; + int freq = 19; /* 1900 / 100 => DPLL */ + + pr_info("%s: Configuring MAX96712 for RAW10 3840x2160@30fps\n", __func__); + + /* Enable Video PG 3, Generate VS, HS, DE, Invert VS */ + max96712_write(priv, 0x1050, 0xF3); + /* Set Pattern to be checkerboard pattern. */ + max96712_write(priv, 0x1051, 0x10); + /* VS (Vertical Sync) Configuration */ + /* Set VS_DLY = 0x000000 */ + max96712_write(priv, 0x1052, 0x00); + max96712_write(priv, 0x1053, 0x00); + max96712_write(priv, 0x1054, 0x00); + /* Adjust VS timing for proper frame rate */ + max96712_write(priv, 0x1055, 0x00); + max96712_write(priv, 0x1056, 0x44); /* Reduced from 0x52 */ + max96712_write(priv, 0x1057, 0x04); /* Reduced from 0x08 */ + /* Set VS Low = 0x8ED280, 9,360,000 */ + max96712_write(priv, 0x1058, 0x8E); + max96712_write(priv, 0x1059, 0xD2); + max96712_write(priv, 0x105A, 0x80); + /* HS (Horizontal Sync) Configuration */ + /* Set HS Delay V2H = 0x015FC4, 90,052 */ + max96712_write(priv, 0x105B, 0x01); + max96712_write(priv, 0x105C, 0x5F); + max96712_write(priv, 0x105D, 0xC4); + + /* Adjust horizontal timing */ + max96712_write(priv, 0x105E, 0x0F); /* HS_HIGH = 0x0F80 (reduced) */ + max96712_write(priv, 0x105F, 0x80); + + /* Optimize blanking periods */ + max96712_write(priv, 0x1060, 0x00); + max96712_write(priv, 0x1061, 0x18); /* Reduced H-sync width */ + + /* Set HS_CNT = 0x094C, 2,380, V total */ + max96712_write(priv, 0x1062, 0x09); + max96712_write(priv, 0x1063, 0x4C); + /* DE (Data Enable) Configuration */ + /* Set DE Delay V2D = 0x015F90, 90,000, VS delay, VS width, plus Vertical back porch. */ + max96712_write(priv, 0x1064, 0x01); + max96712_write(priv, 0x1065, 0x5F); + max96712_write(priv, 0x1066, 0x90); + + /* Adjust DE timing for proper active region */ + max96712_write(priv, 0x1067, 0x0F); /* DE_HIGH = 0x0F00 (3,840 pixels) */ + max96712_write(priv, 0x1068, 0x00); + max96712_write(priv, 0x1069, 0x01); /* DE_LOW = 0x0180 (reduced blanking) */ + max96712_write(priv, 0x106A, 0x80); + + /* Set proper vertical resolution */ + max96712_write(priv, 0x106B, 0x08); /* DE_CNT = 0x0870 (2,160) */ + max96712_write(priv, 0x106C, 0x70); + + /* Set color gradient increment ratio */ + max96712_write(priv, 0x106D, 0x06); + /* Checkerboard mode color A low/mid/high byte, RGB888 color R/G/B (0-255) */ + max96712_write(priv, 0x106E, 0x80); + max96712_write(priv, 0x106F, 0x00); + max96712_write(priv, 0x1070, 0x04); + /* Checkerboard mode color B low/mid/high byte, RGB888 color R/G/B (0-255) */ + max96712_write(priv, 0x1071, 0x00); + max96712_write(priv, 0x1072, 0x08); + max96712_write(priv, 0x1073, 0x80); + /* Checkerboard mode color A repeat count. */ + max96712_write(priv, 0x1074, 0x50); + /* Checkerboard mode color B repeat count */ + max96712_write(priv, 0x1075, 0xA0); + /* Checkerboard mode alternate line count */ + max96712_write(priv, 0x1076, 0x50); + /* Pattern CLK Freq Register: + * Bit [1:0] : Set video pattern PCLK frequency. + * 2'b00 = 25MHz + * 2'b01 = 75MHz + * 2'b10 = 150MHz (PATGEN_CLK_SRC = 1'b0) + * 2'b11 = 375MHz (PATGEN_CLK_SRC = 1'b1, by default) + * For 3840x2160@30fps with RAW10, let's verify the required pixel clock: + * Data Rate = 3840 * 2160 * 30 * 10 = 2,488,320,000 bits/sec + * Minimum Pixel Clock = 2,488,320,000 / 10 = 248.832 MHz + */ + max96712_write(priv, 0x0009, 0x03); /* new 0x03, old 0x01 */ + /* 0b0: GMSL1, 0b1: GMSL2 */ + max96712_write(priv, 0x0006, 0xF0); /* GMSL2 for Links D/C/B/A */ + + /* dt >= DT_RAW_8 && dt <= DT_RAW_20 */ + regBpp = 0xE0; + max96712_write(priv, 0x40B, 0x02 | (bpp << 3)); /* 10bpp 0x52 */ + max96712_write(priv, 0x40C, 0x00); /* VC0 */ + max96712_write(priv, 0x40D, 0x00); /* VC0 */ + /* Data type RAW10 0xAB */ + max96712_write(priv, 0x40E, dt | ((dt & 0x30)<<2)); + max96712_write(priv, 0x40F, (dt & 0x0F) | ((dt & 0x3C) << 2)); /* Data type RAW10 0xAB */ + max96712_write(priv, 0x410, (dt & 0x03) | (dt << 2)); /* Data type RAW10 0xAF */ + max96712_write(priv, 0x411, bpp | ((bpp & 0x1C) << 3)); /* 10bpp 0x4A */ + max96712_write(priv, 0x412, (bpp & 0x03) | bpp << 2); /* 10bpp 0x2A */ + + /* s_sensor_cfg.DataRate <= 3100 && s_sensor_cfg.DataRate >= 100 */ + max96712_write(priv, 0x415, regBpp + freq); /* Port A Override DT/VC/BPP */ + max96712_write(priv, 0x418, regBpp + freq); /* Port B Override DT/VC/BPP */ + max96712_write(priv, 0x41B, regBpp + freq); /* Port C */ + max96712_write(priv, 0x41E, 0x20 + freq); /* Port D */ + + max96712_write(priv, 0x1DD, 0x9B); /* Port A pipe config */ + max96712_write(priv, 0x1FD, 0x9B); /* Port B pipe config */ + max96712_write(priv, 0x21D, 0x9B); /* Port C pipe config */ + max96712_write(priv, 0x23D, 0x9B); /* Port D pipe config */ + + pr_info("Enable Deskew in Sensor\n"); + max96712_write(priv, 0x903, 0x87); /* Port A Deskew */ + max96712_write(priv, 0x943, 0x87); /* Port B Deskew */ + max96712_write(priv, 0x983, 0x87); /* Port C Deskew */ + max96712_write(priv, 0x9c3, 0x87); /* Port D Deskew */ + + /* s_sensor_cfg.LaneMode == Lanes_4x2 */ + max96712_write(priv, 0x90A, 0xC0); /* Port A Lane count */ + max96712_write(priv, 0x94A, 0xC0); /* Port B Lane count */ + max96712_write(priv, 0x98A, 0xC0); /* Port C Lane count */ + max96712_write(priv, 0x9CA, 0xC0); /* Port D Lane count */ + max96712_write(priv, 0x8A3, 0xE4); /* lane mapping */ + max96712_write(priv, 0x8A4, 0xE4); + /* PHY Mode 2x4, force clock on PHY0, CIL A, (required by RCE FW) */ + max96712_write(priv, 0x8A0, 0xA4); + + max96712_write(priv, 0x8A1, 0x55); /* Optimize HS timing */ + + max96712_write(priv, 0x8AD, 0x28); + max96712_write(priv, 0x8AE, 0x1c); + max96712_write(priv, 0x8A2, 0xF0); + max96712_write(priv, 0x018, 0x01); + + return 0; +} + +static int max96712_s_stream(struct v4l2_subdev *sd, int enable) +{ + pr_info("%s: enable %d\n", __func__, enable); + return 0; +} + +static const struct v4l2_subdev_video_ops max96712_video_ops = { + .s_stream = max96712_s_stream, +}; + +static int max96712_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + format->format.width = 3840; + format->format.height = 2160; + format->format.code = MEDIA_BUS_FMT_SBGGR10_1X10; + format->format.field = V4L2_FIELD_NONE; + + return 0; +} + +static const struct v4l2_subdev_pad_ops max96712_pad_ops = { + .get_fmt = max96712_get_pad_format, + .set_fmt = max96712_get_pad_format, +}; + +static const struct v4l2_subdev_ops max96712_subdev_ops = { + .video = &max96712_video_ops, + .pad = &max96712_pad_ops, +}; + +static const char * const max96712_test_pattern[] = { + "Checkerboard", + "Gradient", +}; + +static const u32 ctrl_cid_list[] = { + TEGRA_CAMERA_CID_FRAME_RATE + /* TEGRA_CAMERA_CID_GROUP_HOLD */ +}; + +static int max96712_set_frame_rate(struct tegracam_device *tc_dev, s64 val) +{ + pr_info("%s: val %lld\n", __func__, val); + + return 0; +} + +static int max96712_set_group_hold(struct tegracam_device *tc_dev, bool val) +{ + /* Group hold is not used for this sensor */ + return 0; +} + +static struct tegracam_ctrl_ops max96712_ctrl_ops = { + .numctrls = ARRAY_SIZE(ctrl_cid_list), + .ctrl_cid_list = ctrl_cid_list, + .set_frame_rate = max96712_set_frame_rate, + .set_group_hold = max96712_set_group_hold, +}; + +static const struct regmap_config max96712_i2c_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x1f00, +}; + +static int max96712_power_on(struct camera_common_data *s_data) +{ + int err = 0; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + + if (pdata && pdata->power_on) { + err = pdata->power_on(pw); + if (err) + dev_err(dev, "%s failed.\n", __func__); + else + pw->state = SWITCH_ON; + return err; + } + if (pw->reset_gpio > 0) + gpio_set_value(pw->reset_gpio, 1); + usleep_range(1000, 2000); + pw->state = SWITCH_ON; + return err; +} +static int max96712_power_off(struct camera_common_data *s_data) +{ + int err = 0; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + + if (pdata && pdata->power_off) { + err = pdata->power_off(pw); + if (!err) + goto power_off_done; + else + dev_err(dev, "%s failed.\n", __func__); + return err; + } +power_off_done: + pw->state = SWITCH_OFF; + return 0; +} + +static struct camera_common_pdata *max96712_parse_dt(struct tegracam_device *tc_dev) +{ + struct device *dev = tc_dev->dev; + struct device_node *node = dev->of_node; + struct camera_common_pdata *board_priv_pdata; + const struct of_device_id *match; + int err; + + if (!node) + return NULL; + match = of_match_device(max96712_of_table, dev); + if (!match) { + dev_err(dev, "Failed to find matching dt id\n"); + return NULL; + } + board_priv_pdata = devm_kzalloc(dev, sizeof(*board_priv_pdata), GFP_KERNEL); + err = of_property_read_string(node, "mclk", + &board_priv_pdata->mclk_name); + if (err) + dev_err(dev, "mclk not in DT\n"); + + board_priv_pdata->reset_gpio = of_get_named_gpio(node, "reset-gpios", 0); + if (board_priv_pdata->reset_gpio > 0) + gpio_direction_output(board_priv_pdata->reset_gpio, 1); + else + dev_err(dev, "failed to read reset_gpio\n"); + + board_priv_pdata->pwdn_gpio = 0; + if (board_priv_pdata->pwdn_gpio > 0) { + dev_err(dev, "board_priv_pdata->pwdn_gpio %d\n", board_priv_pdata->pwdn_gpio); + gpio_direction_output(board_priv_pdata->pwdn_gpio, 1); + } else + dev_warn(dev, "failed to read pwdn_gpio\n"); + + return board_priv_pdata; +} + +static int max96712_power_get(struct tegracam_device *tc_dev) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + int err = 0; + + pw->reset_gpio = pdata->reset_gpio; + + pw->state = SWITCH_OFF; + + return err; +} + +static int max96712_power_put(struct tegracam_device *tc_dev) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + + if (unlikely(!pw)) + return -EFAULT; + return 0; +} + +static int max96712_set_mode(struct tegracam_device *tc_dev) +{ + return 0; +} + +static int max96712_start_streaming(struct tegracam_device *tc_dev) +{ + struct max96712_priv *priv = (struct max96712_priv *)tegracam_get_privdata(tc_dev); + + dev_dbg(tc_dev->dev, ">> %s on channel %d\n", __func__, priv->channel); + + return 0; +} +static int max96712_stop_streaming(struct tegracam_device *tc_dev) +{ + struct max96712_priv *priv = (struct max96712_priv *)tegracam_get_privdata(tc_dev); + + dev_dbg(tc_dev->dev, ">> %s on channel %d\n", __func__, priv->channel); + + return 0; +} + +static struct camera_common_sensor_ops max96712_common_ops = { + .numfrmfmts = ARRAY_SIZE(max96712_frmfmt), + .frmfmt_table = max96712_frmfmt, + .power_on = max96712_power_on, + .power_off = max96712_power_off, + .parse_dt = max96712_parse_dt, + .power_get = max96712_power_get, + .power_put = max96712_power_put, + .set_mode = max96712_set_mode, + .start_streaming = max96712_start_streaming, + .stop_streaming = max96712_stop_streaming, +}; + +static int max96712_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return 0; +} + +static const struct v4l2_subdev_internal_ops max96712_subdev_internal_ops = { + .open = max96712_open, +}; + +static int max96712_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *node = dev->of_node; + struct tegracam_device *tc_dev; + struct max96712_priv *priv; + int tmpid; + int err; + + dev_info(dev, "%s: probing v4l2 sensor:%s\n", __func__, dev_name(dev)); + + if (!IS_ENABLED(CONFIG_OF) || !node) + return -EINVAL; + + /* Allocate private data */ + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + tc_dev = devm_kzalloc(dev, + sizeof(struct tegracam_device), GFP_KERNEL); + if (!tc_dev) + return -ENOMEM; + + /* Initialize basic structures */ + priv->client = tc_dev->client = client; + tc_dev->dev = dev; + i2c_set_clientdata(client, priv); + strncpy(tc_dev->name, "96712-tpg", sizeof(tc_dev->name)); + + /* Configure tegracam device */ + tc_dev->dev_regmap_config = &max96712_i2c_regmap; + tc_dev->sensor_ops = &max96712_common_ops; + tc_dev->v4l2sd_internal_ops = &max96712_subdev_internal_ops; + tc_dev->tcctrl_ops = &max96712_ctrl_ops; + + /* Register tegracam device */ + err = tegracam_device_register(tc_dev); + if (err) { + dev_err(dev, "tegra camera driver registration failed\n"); + return err; + } + + priv->regmap = tc_dev->s_data->regmap; + priv->tc_dev = tc_dev; + priv->s_data = tc_dev->s_data; + priv->subdev = &tc_dev->s_data->subdev; + tegracam_set_privdata(tc_dev, (void *)priv); + + /* Initialize debugfs interface */ + max96712_debugfs_init(priv); + + /* GPIO setup */ + /* max96712_0_pd and max96712_1_pd */ + priv->gpiod_pwdn[0] = devm_gpiod_get_optional(&client->dev, "max96712_0_pd", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_pwdn[0])) { + err = PTR_ERR(priv->gpiod_pwdn[0]); + goto un_register; + } + + gpiod_set_consumer_name(priv->gpiod_pwdn[0], "max96712-0-pwdn"); + gpiod_set_value_cansleep(priv->gpiod_pwdn[0], 1); + + priv->gpiod_pwdn[1] = devm_gpiod_get_optional(&client->dev, "max96712_1_pd", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_pwdn[1])) { + err = PTR_ERR(priv->gpiod_pwdn[1]); + goto un_register; + } + + gpiod_set_consumer_name(priv->gpiod_pwdn[1], "max96712-1-pwdn"); + gpiod_set_value_cansleep(priv->gpiod_pwdn[1], 1); + + if (priv->gpiod_pwdn[0] || priv->gpiod_pwdn[1]) + usleep_range(4000, 5000); + + tmpid = max96712_read(priv, 0x0d); + if (tmpid != MAX96712_ID) { + pr_err("max96712-tpg: ID 0x%x mismatch\n", tmpid); + return -ENODEV; + } + + max96712_reset(priv); + + max96712_raw16_3840_2160_x4(priv); + + /* Register V4L2 subdevice */ + err = tegracam_v4l2subdev_register(tc_dev, true); + if (err) { + dev_err(dev, "tegra camera subdev registration failed\n"); + goto un_register; + } + dev_err(&client->dev, "Detected max96712 sensor\n"); + return 0; + +un_register: + max96712_debugfs_remove(priv); + tegracam_device_unregister(tc_dev); + return err; +} + +/* Remove function with version-dependent return type */ +#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */ +static int max96712_remove(struct i2c_client *client) +#else +static void max96712_remove(struct i2c_client *client) +#endif +{ + struct camera_common_data *s_data = to_camera_common_data(&client->dev); + struct max96712_priv *priv; + + if (!s_data) { +#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */ + return -EINVAL; +#else + return; +#endif + } + + priv = (struct max96712_priv *)s_data->priv; + + /* Clean up debugfs interface */ + max96712_debugfs_remove(priv); + + tegracam_v4l2subdev_unregister(priv->tc_dev); + tegracam_device_unregister(priv->tc_dev); + +#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */ + return 0; +#endif +} + +static const struct i2c_device_id max96712_id[] = { + { "max96712-tpg", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max96712_id); +static struct i2c_driver max96712_i2c_driver = { + .driver = { + .name = "max96712-tpg", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(max96712_of_table), + }, + .probe = max96712_probe, + .remove = max96712_remove, + .id_table = max96712_id, +}; + +module_i2c_driver(max96712_i2c_driver); + +MODULE_DESCRIPTION("Maxim MAX96712 Quad GMSL2 Deserializer TPG Driver"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/max96726_tpg.c b/drivers/media/i2c/max96726_tpg.c new file mode 100644 index 00000000..805270ce --- /dev/null +++ b/drivers/media/i2c/max96726_tpg.c @@ -0,0 +1,865 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define MAX96726_I2C_SLAVE_ADDR_CHIP1 0x21 +#define MAX96726_I2C_SLAVE_ADDR_CHIP2 0x33 +#define MAX96726_I2C_SLAVE_ADDR_BRD_MUX 0x25 + +/* + * Resolution and FPS modes: + * 0b000: 852 x 480 @ 30fps RAW16 + * 0b001: 1280 x 720 @ 30fps RAW16 + * 0b010: 1920 x 1080 @ 30fps RAW16 (default) + * 0b011: 3840 x 2160 @ 30fps RAW16 + * 0b100: 852 x 480 @ 60fps RAW16 + * 0b101: 1280 x 720 @ 60fps RAW16 + * 0b110: 1920 x 1080 @ 60fps RAW16 + */ +#define MAX96726_RESO_FPS_852x480_30 0x0 +#define MAX96726_RESO_FPS_1280x720_30 0x1 +#define MAX96726_RESO_FPS_1920x1080_30 0x2 +#define MAX96726_RESO_FPS_3840x2160_30 0x3 +#define MAX96726_RESO_FPS_852x480_60 0x4 +#define MAX96726_RESO_FPS_1280x720_60 0x5 +#define MAX96726_RESO_FPS_1920x1080_60 0x6 + + +#define MAX96726_DATA_RATE_2500 2500 +#define MAX96726_DATA_RATE_4500 4500 + +#define MAX96726_ID 0x30 +#define MAX96726_ID1 0x31 + +#define MAX96726_DPLL_FREQ 1000 + +#define MAX96726_DT_RAW_10 43 + +#define MAX96726_PHY_MODE_DPHY 0 +#define MAX96726_PHY_MODE_CPHY 1 + +enum max96726_pattern { + MAX96726_PATTERN_CHECKERBOARD = 0, + MAX96726_PATTERN_GRADIENT, +}; + +struct max96726_priv { + struct i2c_client *client; + struct regmap *regmap; + struct gpio_desc *gpiod_pwdn[2]; + + bool cphy; + + struct v4l2_subdev sd; + struct v4l2_ctrl_handler ctrl_handler; + struct media_pad pads[1]; + + enum max96726_pattern pattern; + + const struct i2c_device_id *id; + struct v4l2_subdev *subdev; + u32 frame_length; + struct camera_common_data *s_data; + struct tegracam_device *tc_dev; + u32 channel; + const char *sensor_name; +}; + +struct index_reg_8 { + u16 addr; + u16 val; +}; + +enum { + MAX96726_MODE_3840X2160_CROP_30FPS, + MAX96726_MODE_START_STREAM, + MAX96726_MODE_STOP_STREAM, + MAX96726_MODE_QUADLINK_DSER_SER, + MAX96726_MODE_TEST_PATTERN +}; + +#define MAX96726_TABLE_WAIT_MS 0xff00 +#define MAX96726_TABLE_END 0xff01 + +static const struct of_device_id max96726_of_table[] = { + { .compatible = "maxim,max96726-tpg" }, + { /* sentinel */ }, +}; + +/* Module parameters */ +static int phy_mode = MAX96726_PHY_MODE_CPHY; +module_param(phy_mode, int, 0644); +MODULE_PARM_DESC(phy_mode, "PHY mode (0:DPHY,1:CPHY)"); + +static int data_rate; +module_param(data_rate, int, 0644); +MODULE_PARM_DESC(data_rate, "Data rate (0:2500, 1:4500)"); + +static int reso_fps = MAX96726_RESO_FPS_3840x2160_30; +module_param(reso_fps, int, 0644); +MODULE_PARM_DESC(reso_fps, "Resolution and FPS (3:3840x2160_30)"); + +/* Global definition begin */ +static const int max96726_30fps[] = { + 30, +}; + +#define MAX96726_FRAME_RATE_30_FPS 30000000 /* 30 fps */ +#define MAX96726_FRAME_RATE_DEFAULT MAX96726_FRAME_RATE_30_FPS + +static const struct camera_common_frmfmt max96726_frmfmt[] = { + {{3840, 2160}, + max96726_30fps, + 1, + 0, + MAX96726_MODE_3840X2160_CROP_30FPS, + }, +}; + +static int use_csi_standby; +module_param(use_csi_standby, int, 0644); + +/* Global definition end */ + +static int max96726_read(struct max96726_priv *priv, int reg) +{ + int ret, val; + + if (!priv || !priv->regmap) { + pr_err("Invalid regmap pointer\n"); + return -EINVAL; + } + + dev_dbg(&priv->client->dev, "to read 0x%04x\n", reg); + + ret = regmap_read(priv->regmap, reg, &val); + if (ret) { + dev_err(&priv->client->dev, "read 0x%04x failed\n", reg); + return ret; + } + + return val; +} + +static int max96726_write(struct max96726_priv *priv, unsigned int reg, u8 val) +{ + int ret; + + if (!priv || !priv->regmap) { + pr_err("Invalid regmap pointer\n"); + return -EINVAL; + } + + dev_dbg(&priv->client->dev, "to write 0x%04x with 0x%x\n", reg, val); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(&priv->client->dev, "write 0x%04x failed\n", reg); + + return ret; +} + +static int max96726_update_bits(struct max96726_priv *priv, unsigned int reg, + u8 mask, u8 val) +{ + int ret; + + dev_dbg(&priv->client->dev, "%s: update 0x%04x mask 0x%x, val 0x%x\n", + __func__, reg, mask, val); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(&priv->client->dev, "update 0x%04x failed\n", reg); + + return ret; +} + +static void max96726_reset(struct max96726_priv *priv) +{ + pr_info("MAX96726 reset\n"); + + max96726_update_bits(priv, 0x19, 0x40, 0x40); + msleep(20); +} + +/* + * CamIndex:1/2/3/4 + * Bit[3]: Enable MIPI PHY3 + * Bit[2]: Enable MIPI PHY2 + * Bit[1]: Enable MIPI PHY1 + * Bit[0]: Enable MIPI PHY0 + * ~(0x3 << ((CamIndex-1) * 2)) + * Use the phy_Stdby_n bits to enable/disable the ports. Port A uses PHYs 0/1, Port B uses PHYs 2/3. + * Reg 0x08B0 = 0x0F: All ports enabled + * Reg 0x08B0 = 0x0C: Port A disabled + * Reg 0x08B0 = 0x03: Port B disabled + * Use CSI2_LANE_CNT bits to change number of lanes + * Reg 0x0A06 for port A + * Reg 0x0A46 for port B + */ +static int max96726_CsiPhyStandby(struct max96726_priv *priv, + u32 CamIndex, int enable_phy_ports) +{ + int csiPhyShift = 0; + int val = 0; + + if (CamIndex == 1 || CamIndex == 3) + csiPhyShift = 0; + else + csiPhyShift = 2; + + val = max96726_read(priv, 0x08B0); + dev_dbg(priv->tc_dev->dev, + "channel: 0x%x, read, Register=0x08B0, Data=0x%02x\n", + CamIndex, val); + + if (enable_phy_ports) + val = val & ~(0x3 << csiPhyShift); + else { + if (true) /* not enable the PHYs while we want the Tx Off */ + val = val | (0x3 << csiPhyShift); + else + pr_info("Note: CSI TX forced to be off\n"); + } + + max96726_write(priv, 0x08B0, val); + + return 0; +} + +/* + * MAX96726A, 8pipes enabled + * CSI Port A: 3 lanes, C-PHY, 2500 Msps/lane, RAW16 VC0 + * Pipe 0: RAW16 VC0 -> RAW16 VC0 -> Port A + * Pipe 1: RAW16 VC0 -> RAW16 VC8 -> Port A + * Pipe 2: RAW16 VC0 -> RAW16 VC16 -> Port A + * Pipe 3: RAW16 VC0 -> RAW16 VC24 -> Port A + * Pipe 4: RAW16 VC0 -> RAW16 VC0 -> Port B + * Pipe 5: RAW16 VC0 -> RAW16 VC8 -> Port B + * Pipe 6: RAW16 VC0 -> RAW16 VC16 -> Port B + * Pipe 7: RAW16 VC0 -> RAW16 VC24 -> Port B + */ +static int max96726_I2cSequence_Raw16_8pipes(struct max96726_priv *priv) +{ + int val = 0, valwr = 0; + int reg = 0; + + if (data_rate == 0) { + if (phy_mode == MAX96726_PHY_MODE_DPHY) + data_rate = MAX96726_DATA_RATE_2500; + else + data_rate = MAX96726_DATA_RATE_4500; + } + + pr_info("%s: Dev 0x%x, phy_mode %d(0:DPHY,1:CPHY), data_rate %d, reso_fps %d\n", + __func__, priv->client->addr, phy_mode, data_rate, reso_fps); + + /* [Number of bytes],[Slave adr],[Reg adr MSB],[Reg adr LSB],[Data] */ + /* RESET_ALL=1b with waitmsec 2000 to re-initiate the chip */ + /* 0x04,0x42,0x00,0x19,0x7F, [6]RESET_ALL=1b */ + /* max96726_write(priv, 0x0019, 0x7F); */ + + /* + * Select C-PHY/D-PHY mode with the CSI2_CPHY_EN bits + * in regs 0x0A06 (port A) and 0x0A46 (port B) + */ + if (phy_mode == MAX96726_PHY_MODE_DPHY) { + /* 0x04,0x42,0x0A,0x06,0xC0, bit[7:6]=0b11 4lane, [5]=0b1 CPHY_EN */ + /* 0x04,0x42,0x0A,0x46,0xC0, bit[7:6]=0b11 4lane, [5]=0b1 CPHY_EN */ + max96726_write(priv, 0x0A06, 0xC0); + max96726_write(priv, 0x0A46, 0xC0); + + /* Disable Periodic Deskew by default */ + pr_info("Disable Periodic Deskew on sensor by default\n"); + max96726_write(priv, 0x0A03, 0x0); + max96726_write(priv, 0x0A43, 0x0); + + if (data_rate >= 1500) { + pr_info("Enable Initial Deskew on sensor\n"); + valwr = 0x80; + } else { + pr_info("Disable Initial Deskew on sensor\n"); + valwr = 0x00; + } + + reg = 0x0A02; + val = max96726_read(priv, reg); + max96726_write(priv, reg, valwr); + dev_dbg(priv->tc_dev->dev, + "sensor@0x%x, Reg(0x%x)=0x%02x, new 0x%02x to wr\n", + priv->client->addr, reg, val, valwr); + reg = 0x0A42; + val = max96726_read(priv, reg); + max96726_write(priv, reg, valwr); + pr_info("sensor@0x%x, Reg(0x%x)=0x%02x, new 0x%02x to wr\n", + priv->client->addr, reg, val, valwr); + } else { + /* 0x04,0x42,0x0A,0x06,0xE0, bit[7:6]=0b11 4lane, [5]=0b1 CPHY_EN */ + /* 0x04,0x42,0x0A,0x46,0xE0, bit[7:6]=0b11 4lane, [5]=0b1 CPHY_EN */ + max96726_write(priv, 0x0A06, 0xE0); + max96726_write(priv, 0x0A46, 0xE0); + } + + /* GM30 Patgen script, 4-lanes at 2500Mbps, PHY_COPY enabled */ + /* 0x04,0x42,0x09,0x8F,0x99, 0xAD:4500Mb/sps, 0x99:2500Mb/sps */ + /* 0x04,0x42,0x09,0x92,0x99, 0xAD:4500Mb/sps, 0x99:2500Mb/sps */ + /* + * bit7 + * MIPI PHY1 software-override disable for frequency fine tuning + * 0b0: Enable software override for frequency fine tuning + * 0b1: Disable software override for frequency fine tuning + * bit [5:0] + * 0x00: D-PHY: 80MHz DPLL, 80Mbps/lane + * C-PHY: 80MHz DPLL, 182Mbps/lane + * 0x01: D-PHY: 100MHz DPLL, 100Mbps/lane + * C-PHY: 100MHz DPLL, 228Mbps/lane + * 0x02: D-PHY: 200MHz DPLL, 200Mbps/lane + * C-PHY: 200MHz DPLL, 456Mbps/lane + * 0x2D: D-PHY: 4500MHz DPLL, 4.5Gbps/lane + * C-PHY: 4500MHz DPLL, 10.26Gbps/lane + */ + /* s_sensor_cfg.DataRate */ + max96726_write(priv, 0x098F, (1 << 7) | ((data_rate / 100) & 0x3F)); + max96726_write(priv, 0x0992, (1 << 7) | ((data_rate / 100) & 0x3F)); + + /* DES patgen configuration + * Resolution and FPS modes: + * 0b000: 852 x 480 @ 30fps RAW16 + * 0b001: 1280 x 720 @ 30fps RAW16 + * 0b010: 1920 x 1080 @ 30fps RAW16 (default) + * 0b011: 3840 x 2160 @ 30fps RAW16 + * 0b100: 852 x 480 @ 60fps RAW16 + * 0b101: 1280 x 720 @ 60fps RAW16 + * 0b110: 1920 x 1080 @ 60fps RAW16 + */ + max96726_write(priv, 0x010F, reso_fps); /* Pipe 0 PattGen */ + reg = 0x010E; + val = max96726_read(priv, reg); + val |= (1 << 4); + pr_info("write, 0x%x <= 0x%x\n", reg, val); + max96726_write(priv, reg, val); /* Enable color bars in pipe 0 */ + max96726_write(priv, 0x0420, 0x41); /* Pipe 0 to ctrl 1 (port A) */ + /* Override VC according to VC_MAP_SRC/DST registers */ + max96726_write(priv, 0x0426, 0x20); + max96726_write(priv, 0x0427, 0x00); /* remap0, src VC */ + max96726_write(priv, 0x0428, 0x00); /* remap0, dst VC */ + max96726_write(priv, 0x0429, 0x08); /* remap1, src VC */ + max96726_write(priv, 0x042A, 0x00); /* remap1, dst VC */ + max96726_write(priv, 0x042B, 0x10); /* remap2, src VC */ + max96726_write(priv, 0x042C, 0x00); /* remap2, dst VC */ + max96726_write(priv, 0x042D, 0x18); /* remap3, src VC */ + max96726_write(priv, 0x042E, 0x00); /* remap3, dst VC */ + + max96726_write(priv, 0x012B, reso_fps); /* Pipe 1 PattGen */ + reg = 0x012A; + val = max96726_read(priv, reg); + val |= (1 << 4); + dev_dbg(priv->tc_dev->dev, "write, 0x%x <= 0x%x\n", reg, val); + max96726_write(priv, reg, val); /* Enable color bars in pipe 1 */ + max96726_write(priv, 0x0438, 0x40); /* Pipe 1 to ctrl 1 (port A) */ + + max96726_write(priv, 0x0147, reso_fps); /* Pipe 2 PattGen */ + reg = 0x0146; + val = max96726_read(priv, reg); + val |= (1 << 4); + dev_dbg(priv->tc_dev->dev, "write, 0x%x <= 0x%x\n", reg, val); + max96726_write(priv, reg, val); /* Enable color bars in pipe 2 */ + max96726_write(priv, 0x0450, 0x40); /* Pipe 2 to ctrl 1 (port A) */ + + max96726_write(priv, 0x0163, reso_fps); /* Pipe 3 PattGen */ + reg = 0x0162; + val = max96726_read(priv, reg); + val |= (1 << 4); + dev_dbg(priv->tc_dev->dev, "write, 0x%x <= 0x%x\n", reg, val); + max96726_write(priv, reg, val); /* Enable color bars in pipe 3 */ + max96726_write(priv, 0x0468, 0x40); /* Pipe 3 to ctrl 1 (port A) */ + + max96726_write(priv, 0x017F, reso_fps); /* Pipe 4 PattGen */ + reg = 0x017E; + val = max96726_read(priv, reg); + val |= (1 << 4); + dev_dbg(priv->tc_dev->dev, "write, 0x%x <= 0x%x\n", reg, val); + max96726_write(priv, reg, val); /* Enable color bars in pipe 4 */ + max96726_write(priv, 0x0480, 0x42); /* Pipe 4 to ctrl 2 (port B) */ + /* Override VC according to VC_MAP_SRC/DST registers */ + max96726_write(priv, 0x0486, 0x20); + max96726_write(priv, 0x0487, 0x00); /* remap0, src VC */ + max96726_write(priv, 0x0488, 0x00); /* remap0, dst VC */ + max96726_write(priv, 0x0489, 0x08); /* remap1, src VC */ + max96726_write(priv, 0x048A, 0x00); /* remap1, dst VC */ + max96726_write(priv, 0x048B, 0x10); /* remap2, src VC */ + max96726_write(priv, 0x048C, 0x00); /* remap2, dst VC */ + max96726_write(priv, 0x048D, 0x18); /* remap3, src VC */ + max96726_write(priv, 0x048E, 0x00); /* remap3, dst VC */ + + max96726_write(priv, 0x019B, reso_fps); /* Pipe 5 PattGen */ + reg = 0x019A; + val = max96726_read(priv, reg); + val |= (1 << 4); + dev_dbg(priv->tc_dev->dev, "write, 0x%x <= 0x%x\n", reg, val); + max96726_write(priv, reg, val); /* Enable color bars in pipe 5 */ + max96726_write(priv, 0x0498, 0x40); /* Pipe 5 to ctrl 2 (port B) */ + + max96726_write(priv, 0x01B7, reso_fps); /* Pipe 6 PattGen */ + reg = 0x01B6; + val = max96726_read(priv, reg); + val |= (1 << 4); + dev_dbg(priv->tc_dev->dev, "write, 0x%x <= 0x%x\n", reg, val); + max96726_write(priv, reg, val); /* Enable color bars in pipe 6 */ + max96726_write(priv, 0x04B0, 0x40); /* Pipe 6 to ctrl 2 (port B) */ + + max96726_write(priv, 0x01D3, reso_fps); /* Pipe 7 PattGen */ + reg = 0x01D2; + val = max96726_read(priv, reg); + val |= (1 << 4); + dev_dbg(priv->tc_dev->dev, "write, 0x%x <= 0x%x\n", reg, val); + max96726_write(priv, reg, val); /* Enable color bars in pipe 7 */ + max96726_write(priv, 0x04C8, 0x40); /* Pipe 7 to ctrl 2 (port B) */ + + /* 0x04,0x42,0x08,0xB0,0x8F, [7]=0b1 force_csi_out */ + max96726_write(priv, 0x08B0, 0x8F); /* Enable port A */ + + usleep_range(100000, 200000); + + return 0; +} + +static int max96726_s_stream(struct v4l2_subdev *sd, int enable) +{ + pr_info("%s: enable %d\n", __func__, enable); + return 0; +} + +static const struct v4l2_subdev_video_ops max96726_video_ops = { + .s_stream = max96726_s_stream, +}; + +static int max96726_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + switch (reso_fps) { + case MAX96726_RESO_FPS_852x480_30: + case MAX96726_RESO_FPS_852x480_60: + format->format.width = 852; + format->format.height = 480; + break; + case MAX96726_RESO_FPS_1280x720_30: + case MAX96726_RESO_FPS_1280x720_60: + format->format.width = 1280; + format->format.height = 720; + break; + case MAX96726_RESO_FPS_1920x1080_30: + case MAX96726_RESO_FPS_1920x1080_60: + format->format.width = 1920; + format->format.height = 1080; + break; + case MAX96726_RESO_FPS_3840x2160_30: + format->format.width = 3840; + format->format.height = 2160; + break; + default: + pr_warn("Invalid reso_fps %d, using default 3840x2160\n", reso_fps); + format->format.width = 3840; + format->format.height = 2160; + break; + } + + format->format.code = MEDIA_BUS_FMT_SBGGR16_1X16; + format->format.field = V4L2_FIELD_NONE; + + return 0; +} + +static const struct v4l2_subdev_pad_ops max96726_pad_ops = { + .get_fmt = max96726_get_pad_format, + .set_fmt = max96726_get_pad_format, +}; + +static const struct v4l2_subdev_ops max96726_subdev_ops = { + .video = &max96726_video_ops, + .pad = &max96726_pad_ops, +}; + +static const char * const max96726_test_pattern[] = { + "Checkerboard", + "Gradient", +}; + +static const u32 ctrl_cid_list[] = { + TEGRA_CAMERA_CID_FRAME_RATE +}; + +static int max96726_set_frame_rate(struct tegracam_device *tc_dev, s64 val) +{ + pr_info("%s: val %lld\n", __func__, val); + + return 0; +} + +static int max96726_set_group_hold(struct tegracam_device *tc_dev, bool val) +{ + /* Group hold is not used for this sensor */ + return 0; +} + +static struct tegracam_ctrl_ops max96726_ctrl_ops = { + .numctrls = ARRAY_SIZE(ctrl_cid_list), + .ctrl_cid_list = ctrl_cid_list, + .set_frame_rate = max96726_set_frame_rate, + .set_group_hold = max96726_set_group_hold +}; + +static const struct regmap_config max96726_i2c_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x13aa, +}; + +static int max96726_power_on(struct camera_common_data *s_data) +{ + int err = 0; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + int tmpid, tmpid0, tmpid1; + struct max96726_priv *priv = (struct max96726_priv *) s_data->priv; + + dev_dbg(dev, "%s priv %p\n", __func__, priv); + + if (pdata && pdata->power_on) { + err = pdata->power_on(pw); + if (err) + dev_err(dev, "%s failed.\n", __func__); + else + pw->state = SWITCH_ON; + return err; + } + if (pw->reset_gpio > 0) + gpio_set_value(pw->reset_gpio, 1); + usleep_range(1000, 2000); + pw->state = SWITCH_ON; + + tmpid = max96726_read(priv, 0x0d); + tmpid0 = max96726_read(priv, 0x0e); + tmpid1 = max96726_read(priv, 0x0f); + pr_info("max96726-tpg: tmpid (0xd): 0x%x\n", tmpid); + pr_info("max96726-tpg: tmpid0(0xe): 0x%x\n", tmpid0); + pr_info("max96726-tpg: tmpid1(0xf): 0x%x\n", tmpid1); + + return err; +} +static int max96726_power_off(struct camera_common_data *s_data) +{ + int err = 0; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + + if (pdata && pdata->power_off) { + err = pdata->power_off(pw); + if (!err) + goto power_off_done; + else + dev_err(dev, "%s failed.\n", __func__); + return err; + } +power_off_done: + pw->state = SWITCH_OFF; + return 0; +} + +static struct camera_common_pdata *max96726_parse_dt(struct tegracam_device *tc_dev) +{ + struct device *dev = tc_dev->dev; + struct device_node *node = dev->of_node; + struct camera_common_pdata *board_priv_pdata; + const struct of_device_id *match; + int err; + + if (!node) + return NULL; + match = of_match_device(max96726_of_table, dev); + if (!match) { + dev_err(dev, "Failed to find matching dt id\n"); + return NULL; + } + board_priv_pdata = devm_kzalloc(dev, sizeof(*board_priv_pdata), GFP_KERNEL); + err = of_property_read_string(node, "mclk", + &board_priv_pdata->mclk_name); + if (err) + dev_err(dev, "mclk not in DT\n"); + + board_priv_pdata->reset_gpio = of_get_named_gpio(node, "reset-gpios", 0); + if (board_priv_pdata->reset_gpio > 0) + gpio_direction_output(board_priv_pdata->reset_gpio, 1); + else + dev_err(dev, "failed to read reset_gpio\n"); + + board_priv_pdata->pwdn_gpio = 0; /* of_get_named_gpio(node, "pwdn-gpios", 0); */ + if (board_priv_pdata->pwdn_gpio > 0) { + dev_err(dev, "board_priv_pdata->pwdn_gpio %d\n", board_priv_pdata->pwdn_gpio); + gpio_direction_output(board_priv_pdata->pwdn_gpio, 1); + } else + dev_warn(dev, "failed to read pwdn_gpio\n"); + + return board_priv_pdata; +} + +static int max96726_power_get(struct tegracam_device *tc_dev) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + int err = 0; + + pw->reset_gpio = pdata->reset_gpio; + + pw->state = SWITCH_OFF; + + return err; +} + +static int max96726_power_put(struct tegracam_device *tc_dev) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + + if (unlikely(!pw)) + return -EFAULT; + return 0; +} + +static int max96726_set_mode(struct tegracam_device *tc_dev) +{ + return 0; +} + +static int max96726_start_streaming(struct tegracam_device *tc_dev) +{ + struct max96726_priv *priv = (struct max96726_priv *)tegracam_get_privdata(tc_dev); + + dev_dbg(tc_dev->dev, ">> %s on channel %d\n", __func__, priv->channel); + + /* CSI exit from standby */ + if (use_csi_standby) + max96726_CsiPhyStandby(priv, priv->channel+1, false); + + return 0; +} +static int max96726_stop_streaming(struct tegracam_device *tc_dev) +{ + struct max96726_priv *priv = (struct max96726_priv *)tegracam_get_privdata(tc_dev); + + dev_dbg(tc_dev->dev, ">> %s on channel %d\n", __func__, priv->channel); + + /* CSI enter into standby */ + if (use_csi_standby) + max96726_CsiPhyStandby(priv, priv->channel+1, true); + + return 0; +} + +static struct camera_common_sensor_ops max96726_common_ops = { + .numfrmfmts = ARRAY_SIZE(max96726_frmfmt), + .frmfmt_table = max96726_frmfmt, + .power_on = max96726_power_on, + .power_off = max96726_power_off, + .parse_dt = max96726_parse_dt, + .power_get = max96726_power_get, + .power_put = max96726_power_put, + .set_mode = max96726_set_mode, + .start_streaming = max96726_start_streaming, + .stop_streaming = max96726_stop_streaming, +}; + +static int max96726_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return 0; +} + +static const struct v4l2_subdev_internal_ops max96726_subdev_internal_ops = { + .open = max96726_open, +}; + +static int max96726_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *node = dev->of_node; + struct tegracam_device *tc_dev; + struct max96726_priv *priv; + int tmpid, tmpid0, tmpid1; + int err; + + dev_info(dev, "%s: probing v4l2 sensor:%s\n", __func__, dev_name(dev)); + + if (!IS_ENABLED(CONFIG_OF) || !node) + return -EINVAL; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + tc_dev = devm_kzalloc(dev, + sizeof(struct tegracam_device), GFP_KERNEL); + if (!tc_dev) + return -ENOMEM; + + priv->client = tc_dev->client = client; + tc_dev->dev = dev; + i2c_set_clientdata(client, priv); + strncpy(tc_dev->name, "96726-tpg", sizeof(tc_dev->name)); + + tc_dev->dev_regmap_config = &max96726_i2c_regmap; + tc_dev->sensor_ops = &max96726_common_ops; + tc_dev->v4l2sd_internal_ops = &max96726_subdev_internal_ops; + tc_dev->tcctrl_ops = &max96726_ctrl_ops; + + err = tegracam_device_register(tc_dev); + if (err) { + dev_err(dev, "tegra camera driver registration failed\n"); + return err; + } + + priv->regmap = tc_dev->s_data->regmap; + priv->tc_dev = tc_dev; + priv->s_data = tc_dev->s_data; + priv->subdev = &tc_dev->s_data->subdev; + tegracam_set_privdata(tc_dev, (void *)priv); + + /* max96726_0_pd and max96726_1_pd */ + priv->gpiod_pwdn[0] = devm_gpiod_get_optional(&client->dev, "max96726_0_pd", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_pwdn[0])) { + err = PTR_ERR(priv->gpiod_pwdn[0]); + goto un_register; + } + + gpiod_set_consumer_name(priv->gpiod_pwdn[0], "max96726-0-pwdn"); + gpiod_set_value_cansleep(priv->gpiod_pwdn[0], 1); + + priv->gpiod_pwdn[1] = devm_gpiod_get_optional(&client->dev, "max96726_1_pd", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_pwdn[1])) { + err = PTR_ERR(priv->gpiod_pwdn[1]); + goto un_register; + } + + gpiod_set_consumer_name(priv->gpiod_pwdn[1], "max96726-1-pwdn"); + gpiod_set_value_cansleep(priv->gpiod_pwdn[1], 1); + + if (priv->gpiod_pwdn[0] || priv->gpiod_pwdn[1]) + usleep_range(4000, 5000); + + tmpid = max96726_read(priv, 0x0d); + tmpid0 = max96726_read(priv, 0x0e); + tmpid1 = max96726_read(priv, 0x0f); + pr_info("max96726-tpg: tmpid (0xd): 0x%x\n", tmpid); + pr_info("max96726-tpg: tmpid0(0xe): 0x%x\n", tmpid0); + pr_info("max96726-tpg: tmpid1(0xf): 0x%x\n", tmpid1); + if ((tmpid0 != MAX96726_ID) && (tmpid0 != MAX96726_ID1)) { + pr_err("max96726-tpg: ID 0x%x mismatch\n", tmpid0); + return -ENODEV; + } + + max96726_reset(priv); + + max96726_I2cSequence_Raw16_8pipes(priv); + + err = tegracam_v4l2subdev_register(tc_dev, true); + if (err) { + dev_err(dev, "tegra camera subdev registration failed\n"); + goto un_register; + } + dev_err(&client->dev, "Detected max96726 sensor\n"); + return 0; + +un_register: + tegracam_device_unregister(tc_dev); + return err; +} + +#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */ +static int max96726_remove(struct i2c_client *client) +#else +static void max96726_remove(struct i2c_client *client) +#endif +{ + struct camera_common_data *s_data = to_camera_common_data(&client->dev); + struct max96726_priv *priv = (struct max96726_priv *)s_data->priv; + + if (!s_data) { +#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */ + return -EINVAL; +#else + return; +#endif + } + + tegracam_v4l2subdev_unregister(priv->tc_dev); + tegracam_device_unregister(priv->tc_dev); + +#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */ + return 0; +#endif +} + +static const struct i2c_device_id max96726_id[] = { + { "max96726-tpg", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max96726_id); +static struct i2c_driver max96726_i2c_driver = { + .driver = { + .name = "max96726-tpg", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(max96726_of_table), + }, + .probe = max96726_probe, + .remove = max96726_remove, + .id_table = max96726_id, +}; + +module_i2c_driver(max96726_i2c_driver); + +MODULE_DESCRIPTION("Maxim max96726 Quad GMSL2 Deserializer Driver"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_LICENSE("GPL");