// SPDX-License-Identifier: GPL-2.0 /* * Maxim Quad GMSL Deserializer V4L2 driver * * Copyright (C) 2023 Rockchip Electronics Co., Ltd. * * Author: Cai Wenzhong * */ #include #include #include #include #include #include #include #include #include #include #include "maxim4c_api.h" #ifndef V4L2_CID_DIGITAL_GAIN #define V4L2_CID_DIGITAL_GAIN V4L2_CID_GAIN #endif #define MIPI_PHY_FREQ_MHZ(x) ((x) * 1000000UL) /* link freq = index * MIPI_PHY_FREQ_MHZ(50) */ static const s64 link_freq_items[] = { MIPI_PHY_FREQ_MHZ(0), MIPI_PHY_FREQ_MHZ(50), MIPI_PHY_FREQ_MHZ(100), MIPI_PHY_FREQ_MHZ(150), MIPI_PHY_FREQ_MHZ(200), MIPI_PHY_FREQ_MHZ(250), MIPI_PHY_FREQ_MHZ(300), MIPI_PHY_FREQ_MHZ(350), MIPI_PHY_FREQ_MHZ(400), MIPI_PHY_FREQ_MHZ(450), MIPI_PHY_FREQ_MHZ(500), MIPI_PHY_FREQ_MHZ(550), MIPI_PHY_FREQ_MHZ(600), MIPI_PHY_FREQ_MHZ(650), MIPI_PHY_FREQ_MHZ(700), MIPI_PHY_FREQ_MHZ(750), MIPI_PHY_FREQ_MHZ(800), MIPI_PHY_FREQ_MHZ(850), MIPI_PHY_FREQ_MHZ(900), MIPI_PHY_FREQ_MHZ(950), MIPI_PHY_FREQ_MHZ(1000), MIPI_PHY_FREQ_MHZ(1050), MIPI_PHY_FREQ_MHZ(1100), MIPI_PHY_FREQ_MHZ(1150), MIPI_PHY_FREQ_MHZ(1200), MIPI_PHY_FREQ_MHZ(1250), }; static const struct maxim4c_mode maxim4c_def_mode = { .width = 1920, .height = 1080, .max_fps = { .numerator = 10000, .denominator = 300000, }, .link_freq_idx = 15, .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, .bpp = 16, #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE .vc[PAD0] = 0, .vc[PAD1] = 1, .vc[PAD2] = 2, .vc[PAD3] = 3, #else .vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_0, .vc[PAD1] = V4L2_MBUS_CSI2_CHANNEL_1, .vc[PAD2] = V4L2_MBUS_CSI2_CHANNEL_2, .vc[PAD3] = V4L2_MBUS_CSI2_CHANNEL_3, #endif /* LINUX_VERSION_CODE */ /* crop rect */ .crop_rect = { .left = 0, .width = 1920, .top = 0, .height = 1080, }, }; static struct rkmodule_csi_dphy_param rk3588_dcphy_param = { .vendor = PHY_VENDOR_SAMSUNG, .lp_vol_ref = 3, .lp_hys_sw = {3, 0, 0, 0}, .lp_escclk_pol_sel = {1, 0, 0, 0}, .skew_data_cal_clk = {0, 0, 0, 0}, .clk_hs_term_sel = 2, .data_hs_term_sel = {2, 2, 2, 2}, .reserved = {0}, }; static int maxim4c_support_mode_init(maxim4c_t *maxim4c) { struct device *dev = &maxim4c->client->dev; struct device_node *node = NULL; struct maxim4c_mode *mode = NULL; u32 value = 0, vc_array[PAD_MAX], crop_array[4]; struct maxim4c_vc_info vc_info[PAD_MAX]; int ret = 0, i = 0, array_size = 0; dev_info(dev, "=== maxim4c support mode init ===\n"); #if MAXIM4C_TEST_PATTERN return maxim4c_pattern_support_mode_init(maxim4c); #endif /* MAXIM4C_TEST_PATTERN */ maxim4c->cfg_modes_num = 1; maxim4c->cur_mode = &maxim4c->supported_mode; mode = &maxim4c->supported_mode; // init using def mode memcpy(mode, &maxim4c_def_mode, sizeof(struct maxim4c_mode)); node = of_get_child_by_name(dev->of_node, "support-mode-config"); if (IS_ERR_OR_NULL(node)) { dev_info(dev, "no mode config node, using default config.\n"); return 0; } if (!of_device_is_available(node)) { dev_info(dev, "%pOF is disabled, using default config.\n", node); of_node_put(node); return 0; } ret = of_property_read_u32(node, "sensor-width", &value); if (ret == 0) { dev_info(dev, "sensor-width property: %d\n", value); mode->width = value; } dev_info(dev, "support mode: width = %d\n", mode->width); ret = of_property_read_u32(node, "sensor-height", &value); if (ret == 0) { dev_info(dev, "sensor-height property: %d\n", value); mode->height = value; } dev_info(dev, "support mode: height = %d\n", mode->height); ret = of_property_read_u32(node, "bus-format", &value); if (ret == 0) { dev_info(dev, "bus-format property: %d\n", value); mode->bus_fmt = value; } dev_info(dev, "support mode: bus_fmt = 0x%x\n", mode->bus_fmt); ret = of_property_read_u32(node, "bpp", &value); if (ret == 0) { dev_info(dev, "bpp property: %d\n", value); mode->bpp = value; } dev_info(dev, "support mode: bpp = %d\n", mode->bpp); ret = of_property_read_u32(node, "max-fps-numerator", &value); if (ret == 0) { dev_info(dev, "max-fps-numerator property: %d\n", value); mode->max_fps.numerator = value; } dev_info(dev, "support mode: numerator = %d\n", mode->max_fps.numerator); ret = of_property_read_u32(node, "max-fps-denominator", &value); if (ret == 0) { dev_info(dev, "max-fps-denominator property: %d\n", value); mode->max_fps.denominator = value; } dev_info(dev, "support mode: denominator = %d\n", mode->max_fps.denominator); ret = of_property_read_u32(node, "link-freq-idx", &value); if (ret == 0) { dev_info(dev, "link-freq-idx property: %d\n", value); mode->link_freq_idx = value; } dev_info(dev, "support mode: link_freq_idx = %d\n", mode->link_freq_idx); ret = of_property_read_u32(node, "hts-def", &value); if (ret == 0) { dev_info(dev, "hts-def property: %d\n", value); mode->hts_def = value; } dev_info(dev, "support mode: hts_def = %d\n", mode->hts_def); ret = of_property_read_u32(node, "vts-def", &value); if (ret == 0) { dev_info(dev, "vts-def property: %d\n", value); mode->vts_def = value; } dev_info(dev, "support mode: vts_def = %d\n", mode->vts_def); ret = of_property_read_u32(node, "exp-def", &value); if (ret == 0) { dev_info(dev, "exp-def property: %d\n", value); mode->exp_def = value; } dev_info(dev, "support mode: exp_def = %d\n", mode->exp_def); array_size = of_property_read_variable_u32_array(node, "vc-array", vc_array, 1, PAD_MAX); if (array_size > 0) { if (array_size > PAD_MAX) array_size = PAD_MAX; for (i = 0; i < array_size; i++) { dev_info(dev, "vc-array[%d] property: 0x%x\n", i, vc_array[i]); mode->vc[i] = vc_array[i]; } } else { /* default vc config */ #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE for (i = 0; i < PAD_MAX; i++) mode->vc[i] = i; #else switch (PAD_MAX) { case 4: mode->vc[3] = V4L2_MBUS_CSI2_CHANNEL_3; fallthrough; case 3: mode->vc[2] = V4L2_MBUS_CSI2_CHANNEL_2; fallthrough; case 2: mode->vc[1] = V4L2_MBUS_CSI2_CHANNEL_1; fallthrough; case 1: default: mode->vc[0] = V4L2_MBUS_CSI2_CHANNEL_0; break; } #endif } for (i = 0; i < PAD_MAX; i++) dev_info(dev, "support mode: vc[%d] = 0x%x\n", i, mode->vc[i]); /* vc info */ array_size = of_property_count_u32_elems(node, "vc-info"); if ((array_size > 0) && (array_size % sizeof(struct maxim4c_vc_info) == 0) && (array_size <= sizeof(struct maxim4c_vc_info) * PAD_MAX)) { memset((char *)vc_info, 0, sizeof(vc_info)); ret = of_property_read_u32_array(node, "vc-info", (u32 *)vc_info, array_size); if (ret == 0) { /* */ for (i = 0; i < PAD_MAX; i++) { dev_info(dev, "vc-info[%d] property:\n", i); dev_info(dev, " vc-info[%d].enable = %d:\n", i, vc_info[i].enable); dev_info(dev, " vc-info[%d].width = %d:\n", i, vc_info[i].width); dev_info(dev, " vc-info[%d].height = %d:\n", i, vc_info[i].height); dev_info(dev, " vc-info[%d].bus_fmt = %d:\n", i, vc_info[i].bus_fmt); dev_info(dev, " vc-info[%d].data_type = %d:\n", i, vc_info[i].data_type); dev_info(dev, " vc-info[%d].data_bit = %d:\n", i, vc_info[i].data_bit); mode->vc_info[i].enable = vc_info[i].enable; mode->vc_info[i].width = vc_info[i].width; mode->vc_info[i].height = vc_info[i].height; mode->vc_info[i].bus_fmt = vc_info[i].bus_fmt; mode->vc_info[i].data_type = vc_info[i].data_type; mode->vc_info[i].data_bit = vc_info[i].data_bit; } } } /* crop rect */ array_size = of_property_read_variable_u32_array(node, "crop-rect", crop_array, 1, 4); if (array_size == 4) { /* [left, top, width, height] */ for (i = 0; i < array_size; i++) dev_info(dev, "crop-rect[%d] property: %d\n", i, crop_array[i]); mode->crop_rect.left = crop_array[0]; mode->crop_rect.top = crop_array[1]; mode->crop_rect.width = crop_array[2]; mode->crop_rect.height = crop_array[3]; } else { mode->crop_rect.left = 0; mode->crop_rect.width = mode->width; mode->crop_rect.top = 0; mode->crop_rect.height = mode->height; } dev_info(dev, "support mode crop rect: [ left = %d, top = %d, width = %d, height = %d ]\n", mode->crop_rect.left, mode->crop_rect.top, mode->crop_rect.width, mode->crop_rect.height); of_node_put(node); return 0; } #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API static int maxim4c_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_get_try_format(sd, fh->state, 0); #else struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_get_try_format(sd, fh->pad, 0); #endif const struct maxim4c_mode *def_mode = &maxim4c->supported_mode; mutex_lock(&maxim4c->mutex); /* Initialize try_fmt */ try_fmt->width = def_mode->width; try_fmt->height = def_mode->height; try_fmt->code = def_mode->bus_fmt; try_fmt->field = V4L2_FIELD_NONE; mutex_unlock(&maxim4c->mutex); /* No crop or compose */ return 0; } #endif static int maxim4c_s_power(struct v4l2_subdev *sd, int on) { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); struct i2c_client *client = maxim4c->client; int ret = 0; mutex_lock(&maxim4c->mutex); /* If the power state is not modified - no work to do. */ if (maxim4c->power_on == !!on) goto unlock_and_return; if (on) { ret = pm_runtime_get_sync(&client->dev); if (ret < 0) { pm_runtime_put_noidle(&client->dev); goto unlock_and_return; } maxim4c->power_on = true; } else { pm_runtime_put(&client->dev); maxim4c->power_on = false; } unlock_and_return: mutex_unlock(&maxim4c->mutex); return ret; } static void maxim4c_get_module_inf(maxim4c_t *maxim4c, struct rkmodule_inf *inf) { memset(inf, 0, sizeof(*inf)); strscpy(inf->base.sensor, maxim4c->sensor_name, sizeof(inf->base.sensor)); strscpy(inf->base.module, maxim4c->module_name, sizeof(inf->base.module)); strscpy(inf->base.lens, maxim4c->len_name, sizeof(inf->base.lens)); } static void maxim4c_get_vicap_rst_inf(maxim4c_t *maxim4c, struct rkmodule_vicap_reset_info *rst_info) { struct i2c_client *client = maxim4c->client; rst_info->is_reset = maxim4c->hot_plug; maxim4c->hot_plug = false; rst_info->src = RKCIF_RESET_SRC_ERR_HOTPLUG; dev_info(&client->dev, "%s: rst_info->is_reset:%d.\n", __func__, rst_info->is_reset); } static void maxim4c_set_vicap_rst_inf(maxim4c_t *maxim4c, struct rkmodule_vicap_reset_info rst_info) { maxim4c->is_reset = rst_info.is_reset; } static int maxim4c_get_channel_info(maxim4c_t *maxim4c, struct rkmodule_channel_info *ch_info) { const struct maxim4c_mode *mode = maxim4c->cur_mode; struct device *dev = &maxim4c->client->dev; if (ch_info->index < PAD0 || ch_info->index >= PAD_MAX) return -EINVAL; if (mode->vc_info[ch_info->index].enable) { ch_info->vc = mode->vc[ch_info->index]; ch_info->width = mode->vc_info[ch_info->index].width; ch_info->height = mode->vc_info[ch_info->index].height; ch_info->bus_fmt = mode->vc_info[ch_info->index].bus_fmt; /* optional parameters, default 0: invalid parameter */ ch_info->data_type = mode->vc_info[ch_info->index].data_type; ch_info->data_bit = mode->vc_info[ch_info->index].data_bit; } else { ch_info->vc = mode->vc[ch_info->index]; ch_info->width = mode->width; ch_info->height = mode->height; ch_info->bus_fmt = mode->bus_fmt; } dev_info(dev, "get channel info, ch_info->index = %d\n", ch_info->index); dev_info(dev, " ch_info->vc = 0x%x\n", ch_info->vc); dev_info(dev, " ch_info->width = %d\n", ch_info->width); dev_info(dev, " ch_info->height = %d\n", ch_info->height); dev_info(dev, " ch_info->bus_fmt = 0x%x\n", ch_info->bus_fmt); dev_info(dev, " ch_info->data_type = 0x%x:\n", ch_info->data_type); dev_info(dev, " ch_info->data_bit = %d\n", ch_info->data_bit); return 0; } static long maxim4c_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); struct rkmodule_csi_dphy_param *dphy_param; struct rkmodule_capture_info *capture_info; struct rkmodule_channel_info *ch_info; u32 stream = 0; long ret = 0; dev_dbg(&maxim4c->client->dev, "ioctl cmd = 0x%08x\n", cmd); switch (cmd) { case RKMODULE_GET_MODULE_INFO: maxim4c_get_module_inf(maxim4c, (struct rkmodule_inf *)arg); break; case RKMODULE_GET_VICAP_RST_INFO: maxim4c_get_vicap_rst_inf(maxim4c, (struct rkmodule_vicap_reset_info *)arg); break; case RKMODULE_SET_VICAP_RST_INFO: maxim4c_set_vicap_rst_inf(maxim4c, *(struct rkmodule_vicap_reset_info *)arg); break; case RKMODULE_SET_CSI_DPHY_PARAM: dphy_param = (struct rkmodule_csi_dphy_param *)arg; rk3588_dcphy_param = *dphy_param; dev_dbg(&maxim4c->client->dev, "set dcphy param\n"); break; case RKMODULE_GET_CSI_DPHY_PARAM: dphy_param = (struct rkmodule_csi_dphy_param *)arg; *dphy_param = rk3588_dcphy_param; dev_dbg(&maxim4c->client->dev, "get dcphy param\n"); break; case RKMODULE_GET_CAPTURE_MODE: capture_info = (struct rkmodule_capture_info *)arg; if (maxim4c->remote_routing_to_isp != 0) capture_info->mode = RKMODULE_MULTI_CH_TO_MULTI_ISP; else capture_info->mode = RKMODULE_CAPTURE_MODE_NONE; break; case RKMODULE_GET_CHANNEL_INFO: ch_info = (struct rkmodule_channel_info *)arg; ret = maxim4c_get_channel_info(maxim4c, ch_info); break; case RKMODULE_SET_QUICK_STREAM: stream = *((u32 *)arg); if (stream) ret = maxim4c_mipi_csi_output(maxim4c, true); else ret = maxim4c_mipi_csi_output(maxim4c, false); dev_info(&maxim4c->client->dev, "set quick stream = %d: mipi csi output ret = %ld\n", stream, ret); break; default: ret = -ENOIOCTLCMD; break; } return ret; } #ifdef CONFIG_COMPAT static long maxim4c_compat_ioctl32(struct v4l2_subdev *sd, unsigned int cmd, unsigned long arg) { void __user *up = compat_ptr(arg); struct rkmodule_inf *inf; struct rkmodule_vicap_reset_info *vicap_rst_inf; struct rkmodule_csi_dphy_param *dphy_param; struct rkmodule_capture_info *capture_info; struct rkmodule_channel_info *ch_info; u32 stream = 0; long ret = 0; switch (cmd) { case RKMODULE_GET_MODULE_INFO: inf = kzalloc(sizeof(*inf), GFP_KERNEL); if (!inf) { ret = -ENOMEM; return ret; } ret = maxim4c_ioctl(sd, cmd, inf); if (!ret) { ret = copy_to_user(up, inf, sizeof(*inf)); if (ret) ret = -EFAULT; } kfree(inf); break; case RKMODULE_GET_VICAP_RST_INFO: vicap_rst_inf = kzalloc(sizeof(*vicap_rst_inf), GFP_KERNEL); if (!vicap_rst_inf) { ret = -ENOMEM; return ret; } ret = maxim4c_ioctl(sd, cmd, vicap_rst_inf); if (!ret) { ret = copy_to_user(up, vicap_rst_inf, sizeof(*vicap_rst_inf)); if (ret) ret = -EFAULT; } kfree(vicap_rst_inf); break; case RKMODULE_SET_VICAP_RST_INFO: vicap_rst_inf = kzalloc(sizeof(*vicap_rst_inf), GFP_KERNEL); if (!vicap_rst_inf) { ret = -ENOMEM; return ret; } ret = copy_from_user(vicap_rst_inf, up, sizeof(*vicap_rst_inf)); if (!ret) ret = maxim4c_ioctl(sd, cmd, vicap_rst_inf); else ret = -EFAULT; kfree(vicap_rst_inf); break; case RKMODULE_SET_CSI_DPHY_PARAM: dphy_param = kzalloc(sizeof(*dphy_param), GFP_KERNEL); if (!dphy_param) { ret = -ENOMEM; return ret; } ret = copy_from_user(dphy_param, up, sizeof(*dphy_param)); if (!ret) ret = maxim4c_ioctl(sd, cmd, dphy_param); else ret = -EFAULT; kfree(dphy_param); break; case RKMODULE_GET_CSI_DPHY_PARAM: dphy_param = kzalloc(sizeof(*dphy_param), GFP_KERNEL); if (!dphy_param) { ret = -ENOMEM; return ret; } ret = maxim4c_ioctl(sd, cmd, dphy_param); if (!ret) { ret = copy_to_user(up, dphy_param, sizeof(*dphy_param)); if (ret) ret = -EFAULT; } kfree(dphy_param); break; case RKMODULE_GET_CAPTURE_MODE: capture_info = kzalloc(sizeof(*capture_info), GFP_KERNEL); if (!capture_info) { ret = -ENOMEM; return ret; } ret = maxim4c_ioctl(sd, cmd, capture_info); if (!ret) { ret = copy_to_user(up, capture_info, sizeof(*capture_info)); if (ret) ret = -EFAULT; } kfree(capture_info); break; case RKMODULE_GET_CHANNEL_INFO: ch_info = kzalloc(sizeof(*ch_info), GFP_KERNEL); if (!ch_info) { ret = -ENOMEM; return ret; } ret = maxim4c_ioctl(sd, cmd, ch_info); if (!ret) { ret = copy_to_user(up, ch_info, sizeof(*ch_info)); if (ret) ret = -EFAULT; } kfree(ch_info); break; case RKMODULE_SET_QUICK_STREAM: ret = copy_from_user(&stream, up, sizeof(u32)); if (!ret) ret = maxim4c_ioctl(sd, cmd, &stream); else ret = -EFAULT; break; default: ret = -ENOIOCTLCMD; break; } return ret; } #endif /* CONFIG_COMPAT */ static int __maxim4c_start_stream(maxim4c_t *maxim4c) { struct device *dev = &maxim4c->client->dev; int ret = 0; s64 link_freq_hz = 0; u8 link_mask = 0, link_freq_idx = 0; u8 video_pipe_mask = 0; #if MAXIM4C_LOCAL_DES_ON_OFF_EN #if MAXIM4C_TEST_PATTERN ret = maxim4c_pattern_hw_init(maxim4c); if (ret) { dev_err(dev, "test pattern hw init error\n"); return ret; } #else ret = maxim4c_module_hw_init(maxim4c); if (ret) { dev_err(dev, "maxim4c module hw init error\n"); return ret; } #endif /* MAXIM4C_TEST_PATTERN */ #endif /* MAXIM4C_LOCAL_DES_ON_OFF_EN */ link_mask = maxim4c->gmsl_link.link_enable_mask; video_pipe_mask = maxim4c->video_pipe.pipe_enable_mask; #if (MAXIM4C_TEST_PATTERN == 0) // remote devices power on if (maxim4c->remote_routing_to_isp == 0) { ret = maxim4c_remote_devices_power(maxim4c, link_mask, 1); if (ret) { dev_err(dev, "remote devices power on error\n"); return ret; } } else { dev_info(dev, "remote devices power on by cif\n"); } #endif /* MAXIM4C_TEST_PATTERN */ // disable all video pipe ret = maxim4c_video_pipe_mask_enable(maxim4c, video_pipe_mask, false); if (ret) { dev_err(dev, "video pipe disable error\n"); return ret; } ret = maxim4c_link_select_remote_enable(maxim4c, link_mask); if (ret) { dev_err(dev, "link select enable error, mask = 0x%x\n", link_mask); return ret; } link_mask = maxim4c->gmsl_link.link_locked_mask; #if (MAXIM4C_TEST_PATTERN == 0) // remote devices start stream if (maxim4c->remote_routing_to_isp == 0) { ret = maxim4c_remote_devices_s_stream(maxim4c, link_mask, 1); if (ret) { dev_err(dev, "remote devices start stream error\n"); return ret; } } else { dev_info(dev, "remote devices start stream by cif\n"); } #endif /* MAXIM4C_TEST_PATTERN */ // mipi txphy enable setting: standby or enable ret = maxim4c_mipi_txphy_enable(maxim4c, true); if (ret) { dev_err(dev, "mipi txphy enable error\n"); return ret; } // mipi txphy dpll setting link_freq_idx = maxim4c->cur_mode->link_freq_idx; link_freq_hz = link_freq_items[link_freq_idx]; ret = maxim4c_dphy_dpll_predef_set(maxim4c, link_freq_hz); if (ret) { dev_err(dev, "mipi txphy dpll setting error\n"); return ret; } // enable video pipe ret = maxim4c_video_pipe_mask_enable(maxim4c, video_pipe_mask, true); if (ret) { dev_err(dev, "video pipe enable error\n"); return ret; } /* In case these controls are set before streaming */ ret = __v4l2_ctrl_handler_setup(&maxim4c->ctrl_handler); if (ret) return ret; #if MAXIM4C_TEST_PATTERN ret = maxim4c_pattern_enable(maxim4c, true); if (ret) { dev_err(dev, "test pattern setting error\n"); return ret; } #endif /* MAXIM4C_TEST_PATTERN */ ret = maxim4c_mipi_csi_output(maxim4c, true); if (ret) { dev_err(dev, "mipi csi output error\n"); return ret; } if (maxim4c->hot_plug_irq > 0) enable_irq(maxim4c->hot_plug_irq); if (maxim4c->link_lock_state != maxim4c->gmsl_link.link_enable_mask) { dev_info(dev, "partial links are locked, start hot plug detect work.\n"); maxim4c_hot_plug_detect_work_start(maxim4c); } return 0; } static int __maxim4c_stop_stream(maxim4c_t *maxim4c) { struct device *dev = &maxim4c->client->dev; u8 link_mask = 0, pipe_mask = 0; int ret = 0; link_mask = maxim4c->gmsl_link.link_enable_mask; pipe_mask = maxim4c->video_pipe.pipe_enable_mask; if (maxim4c->hot_plug_irq > 0) disable_irq(maxim4c->hot_plug_irq); if (maxim4c->hot_plug_work.state_check_wq) cancel_delayed_work(&maxim4c->hot_plug_work.state_d_work); ret |= maxim4c_mipi_csi_output(maxim4c, false); ret |= maxim4c_mipi_txphy_enable(maxim4c, false); #if MAXIM4C_TEST_PATTERN ret |= maxim4c_pattern_enable(maxim4c, false); #endif /* MAXIM4C_TEST_PATTERN */ ret |= maxim4c_video_pipe_mask_enable(maxim4c, pipe_mask, false); #if (MAXIM4C_TEST_PATTERN == 0) if (maxim4c->remote_routing_to_isp == 0) { // remote devices stop stream ret |= maxim4c_remote_devices_s_stream(maxim4c, link_mask, 0); // remote devices power off ret |= maxim4c_remote_devices_power(maxim4c, link_mask, 0); } else { dev_info(dev, "remote devices control by cif\n"); } #endif /* MAXIM4C_TEST_PATTERN */ // i2c mux enable: default disable all remote channel ret |= maxim4c_i2c_mux_enable(maxim4c, 0x00); ret |= maxim4c_link_mask_enable(maxim4c, link_mask, false); if (ret) { dev_err(dev, "stop stream error\n"); return ret; } return 0; } static int maxim4c_s_stream(struct v4l2_subdev *sd, int on) { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); struct i2c_client *client = maxim4c->client; int ret = 0; dev_info(&client->dev, "%s: on: %d, %dx%d@%d\n", __func__, on, maxim4c->cur_mode->width, maxim4c->cur_mode->height, DIV_ROUND_CLOSEST(maxim4c->cur_mode->max_fps.denominator, maxim4c->cur_mode->max_fps.numerator)); mutex_lock(&maxim4c->mutex); on = !!on; if (on == maxim4c->streaming) goto unlock_and_return; if (on) { #if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE ret = pm_runtime_resume_and_get(&client->dev); #else ret = pm_runtime_get_sync(&client->dev); #endif if (ret < 0) goto unlock_and_return; ret = __maxim4c_start_stream(maxim4c); if (ret) { v4l2_err(sd, "start stream failed while write regs\n"); pm_runtime_put_sync(&client->dev); goto unlock_and_return; } } else { __maxim4c_stop_stream(maxim4c); pm_runtime_mark_last_busy(&client->dev); pm_runtime_put_autosuspend(&client->dev); } maxim4c->streaming = on; unlock_and_return: mutex_unlock(&maxim4c->mutex); return ret; } static int maxim4c_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi) { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); const struct maxim4c_mode *mode = maxim4c->cur_mode; fi->interval = mode->max_fps; return 0; } #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE static int maxim4c_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) #else static int maxim4c_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) #endif { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); const struct maxim4c_mode *mode = maxim4c->cur_mode; if (code->index != 0) return -EINVAL; code->code = mode->bus_fmt; return 0; } #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE static int maxim4c_enum_frame_sizes(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_size_enum *fse) #else static int maxim4c_enum_frame_sizes(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_frame_size_enum *fse) #endif { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); if (fse->index >= maxim4c->cfg_modes_num) return -EINVAL; if (fse->code != maxim4c->supported_mode.bus_fmt) return -EINVAL; fse->min_width = maxim4c->supported_mode.width; fse->max_width = maxim4c->supported_mode.width; fse->max_height = maxim4c->supported_mode.height; fse->min_height = maxim4c->supported_mode.height; return 0; } #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE static int maxim4c_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_interval_enum *fie) #else static int maxim4c_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_frame_interval_enum *fie) #endif { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); if (fie->index >= maxim4c->cfg_modes_num) return -EINVAL; fie->code = maxim4c->supported_mode.bus_fmt; fie->width = maxim4c->supported_mode.width; fie->height = maxim4c->supported_mode.height; fie->interval = maxim4c->supported_mode.max_fps; return 0; } #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE static int maxim4c_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) #else static int maxim4c_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) #endif { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); const struct maxim4c_mode *mode = maxim4c->cur_mode; mutex_lock(&maxim4c->mutex); if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE fmt->format = *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); #else fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad); #endif #else mutex_unlock(&maxim4c->mutex); return -ENOTTY; #endif } else { fmt->format.width = mode->width; fmt->format.height = mode->height; fmt->format.code = mode->bus_fmt; fmt->format.field = V4L2_FIELD_NONE; fmt->reserved[0] = mode->vc[fmt->pad]; } mutex_unlock(&maxim4c->mutex); return 0; } #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE static int maxim4c_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) #else static int maxim4c_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) #endif { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); struct device *dev = &maxim4c->client->dev; const struct maxim4c_mode *mode = NULL; u64 link_freq = 0, pixel_rate = 0; u8 data_lanes; mutex_lock(&maxim4c->mutex); mode = &maxim4c->supported_mode; fmt->format.code = mode->bus_fmt; fmt->format.width = mode->width; fmt->format.height = mode->height; fmt->format.field = V4L2_FIELD_NONE; if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; #else *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; #endif #else mutex_unlock(&maxim4c->mutex); return -ENOTTY; #endif } else { if (maxim4c->streaming) { mutex_unlock(&maxim4c->mutex); return -EBUSY; } maxim4c->cur_mode = mode; __v4l2_ctrl_s_ctrl(maxim4c->link_freq, mode->link_freq_idx); /* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */ link_freq = link_freq_items[mode->link_freq_idx]; data_lanes = maxim4c->bus_cfg.bus.mipi_csi2.num_data_lanes; pixel_rate = (u32)link_freq / mode->bpp * 2 * data_lanes; __v4l2_ctrl_s_ctrl_int64(maxim4c->pixel_rate, pixel_rate); dev_info(dev, "mipi_freq_idx = %d, mipi_link_freq = %lld\n", mode->link_freq_idx, link_freq); dev_info(dev, "pixel_rate = %lld, bpp = %d\n", pixel_rate, mode->bpp); } mutex_unlock(&maxim4c->mutex); return 0; } #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE static int maxim4c_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) #else static int maxim4c_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_selection *sel) #endif { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); int i = 0; if (sel->target == V4L2_SEL_TGT_CROP_BOUNDS) { /* if multiple channel info enable, get_selection isn't support */ for (i = 0; i < PAD_MAX; i++) { if (maxim4c->cur_mode->vc_info[i].enable) { v4l2_warn(sd, "Multi-channel enable, get_selection isn't support\n"); return -EINVAL; } } sel->r.left = maxim4c->cur_mode->crop_rect.left; sel->r.width = maxim4c->cur_mode->crop_rect.width; sel->r.top = maxim4c->cur_mode->crop_rect.top; sel->r.height = maxim4c->cur_mode->crop_rect.height; return 0; } return -EINVAL; } #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE static int maxim4c_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_config *config) { struct maxim4c *maxim4c = v4l2_get_subdevdata(sd); config->type = V4L2_MBUS_CSI2_DPHY; config->bus.mipi_csi2 = maxim4c->bus_cfg.bus.mipi_csi2; return 0; } #elif KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE static int maxim4c_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_config *config) { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); u32 val = 0; const struct maxim4c_mode *mode = maxim4c->cur_mode; u8 data_lanes = maxim4c->bus_cfg.bus.mipi_csi2.num_data_lanes; int i = 0; val |= V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; val |= (1 << (data_lanes - 1)); for (i = 0; i < PAD_MAX; i++) val |= (mode->vc[i] & V4L2_MBUS_CSI2_CHANNELS); config->type = V4L2_MBUS_CSI2_DPHY; config->flags = val; return 0; } #else static int maxim4c_g_mbus_config(struct v4l2_subdev *sd, struct v4l2_mbus_config *config) { maxim4c_t *maxim4c = v4l2_get_subdevdata(sd); u32 val = 0; const struct maxim4c_mode *mode = maxim4c->cur_mode; u8 data_lanes = maxim4c->bus_cfg.bus.mipi_csi2.num_data_lanes; int i = 0; val |= V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; val |= (1 << (data_lanes - 1)); for (i = 0; i < PAD_MAX; i++) val |= (mode->vc[i] & V4L2_MBUS_CSI2_CHANNELS); config->type = V4L2_MBUS_CSI2; config->flags = val; return 0; } #endif /* LINUX_VERSION_CODE */ static int maxim4c_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub) { switch (sub->type) { case V4L2_EVENT_HOT_PLUG: return v4l2_event_subscribe(fh, sub, 0, NULL); default: return -EINVAL; } } #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API static const struct v4l2_subdev_internal_ops maxim4c_internal_ops = { .open = maxim4c_open, }; #endif static const struct v4l2_subdev_core_ops maxim4c_core_ops = { .s_power = maxim4c_s_power, .subscribe_event = maxim4c_subscribe_event, .unsubscribe_event = v4l2_event_subdev_unsubscribe, .ioctl = maxim4c_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl32 = maxim4c_compat_ioctl32, #endif }; static const struct v4l2_subdev_video_ops maxim4c_video_ops = { .s_stream = maxim4c_s_stream, .g_frame_interval = maxim4c_g_frame_interval, #if KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE .g_mbus_config = maxim4c_g_mbus_config, #endif }; static const struct v4l2_subdev_pad_ops maxim4c_pad_ops = { .enum_mbus_code = maxim4c_enum_mbus_code, .enum_frame_size = maxim4c_enum_frame_sizes, .enum_frame_interval = maxim4c_enum_frame_interval, .get_fmt = maxim4c_get_fmt, .set_fmt = maxim4c_set_fmt, .get_selection = maxim4c_get_selection, #if KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE .get_mbus_config = maxim4c_g_mbus_config, #endif }; static const struct v4l2_subdev_ops maxim4c_subdev_ops = { .core = &maxim4c_core_ops, .video = &maxim4c_video_ops, .pad = &maxim4c_pad_ops, }; static int maxim4c_initialize_controls(maxim4c_t *maxim4c) { struct device *dev = &maxim4c->client->dev; const struct maxim4c_mode *mode; struct v4l2_ctrl_handler *handler; u64 link_freq = 0, pixel_rate = 0; u8 data_lanes; int ret = 0; handler = &maxim4c->ctrl_handler; ret = v4l2_ctrl_handler_init(handler, 2); if (ret) return ret; handler->lock = &maxim4c->mutex; mode = maxim4c->cur_mode; maxim4c->link_freq = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq_items) - 1, 0, link_freq_items); __v4l2_ctrl_s_ctrl(maxim4c->link_freq, mode->link_freq_idx); link_freq = link_freq_items[mode->link_freq_idx]; dev_info(dev, "mipi_freq_idx = %d, mipi_link_freq = %lld\n", mode->link_freq_idx, link_freq); /* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */ data_lanes = maxim4c->bus_cfg.bus.mipi_csi2.num_data_lanes; pixel_rate = (u32)link_freq / mode->bpp * 2 * data_lanes; maxim4c->pixel_rate = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 0, pixel_rate, 1, pixel_rate); dev_info(dev, "pixel_rate = %lld, bpp = %d\n", pixel_rate, mode->bpp); if (handler->error) { ret = handler->error; dev_err(dev, "Failed to init controls(%d)\n", ret); goto err_free_handler; } maxim4c->subdev.ctrl_handler = handler; return 0; err_free_handler: v4l2_ctrl_handler_free(handler); return ret; } static int maxim4c_mipi_data_lanes_parse(maxim4c_t *maxim4c) { struct device *dev = &maxim4c->client->dev; struct device_node *endpoint; u8 mipi_data_lanes; int ret = 0; endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); if (!endpoint) { dev_err(dev, "Failed to get endpoint\n"); return -EINVAL; } ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &maxim4c->bus_cfg); if (ret) { dev_err(dev, "Failed to get bus config\n"); return -EINVAL; } mipi_data_lanes = maxim4c->bus_cfg.bus.mipi_csi2.num_data_lanes; dev_info(dev, "mipi csi2 phy data lanes = %d\n", mipi_data_lanes); return 0; } int maxim4c_v4l2_subdev_init(maxim4c_t *maxim4c) { struct i2c_client *client = maxim4c->client; struct device *dev = &client->dev; struct v4l2_subdev *sd = NULL; char facing[2]; int ret = 0; maxim4c_mipi_data_lanes_parse(maxim4c); maxim4c_support_mode_init(maxim4c); sd = &maxim4c->subdev; v4l2_i2c_subdev_init(sd, client, &maxim4c_subdev_ops); ret = maxim4c_initialize_controls(maxim4c); if (ret) goto err_free_handler; #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API sd->internal_ops = &maxim4c_internal_ops; sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; #endif #if defined(CONFIG_MEDIA_CONTROLLER) maxim4c->pad.flags = MEDIA_PAD_FL_SOURCE; sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; ret = media_entity_pads_init(&sd->entity, 1, &maxim4c->pad); if (ret < 0) goto err_free_handler; #endif v4l2_set_subdevdata(sd, maxim4c); memset(facing, 0, sizeof(facing)); if (strcmp(maxim4c->module_facing, "back") == 0) facing[0] = 'b'; else facing[0] = 'f'; snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", maxim4c->module_index, facing, maxim4c->sensor_name, dev_name(sd->dev)); #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE ret = v4l2_async_register_subdev_sensor(sd); #else ret = v4l2_async_register_subdev_sensor_common(sd); #endif if (ret) { dev_err(dev, "v4l2 async register subdev failed\n"); goto err_clean_entity; } return 0; err_clean_entity: #if defined(CONFIG_MEDIA_CONTROLLER) media_entity_cleanup(&sd->entity); #endif err_free_handler: v4l2_ctrl_handler_free(&maxim4c->ctrl_handler); return ret; } EXPORT_SYMBOL(maxim4c_v4l2_subdev_init); void maxim4c_v4l2_subdev_deinit(maxim4c_t *maxim4c) { struct v4l2_subdev *sd = &maxim4c->subdev; v4l2_async_unregister_subdev(sd); #if defined(CONFIG_MEDIA_CONTROLLER) media_entity_cleanup(&sd->entity); #endif v4l2_ctrl_handler_free(&maxim4c->ctrl_handler); } EXPORT_SYMBOL(maxim4c_v4l2_subdev_deinit);