text: // SPDX-License-Identifier: GPL-2.0
  /*
   * Support for Medifield PNW Camera Imaging ISP subsystem.
   *
   * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License version
   * 2 as published by the Free Software Foundation.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   *
   */
  
  #include <media/v4l2-event.h>
  #include <media/v4l2-mediabus.h>
  #include "atomisp_cmd.h"
  #include "atomisp_internal.h"
  #include "atomisp-regs.h"
  
  static struct v4l2_mbus_framefmt *__csi2_get_format(struct
  	atomisp_mipi_csi2_device
  	* csi2,
  	struct
  	v4l2_subdev_pad_config *cfg,
  	enum
  	v4l2_subdev_format_whence
  	which, unsigned int pad) {
  	if (which == V4L2_SUBDEV_FORMAT_TRY)
  		return v4l2_subdev_get_try_format(&csi2->subdev, cfg, pad);
  	else
  		return &csi2->formats[pad];
  }
  
  /*
   * csi2_enum_mbus_code - Handle pixel format enumeration
   * @sd     : pointer to v4l2 subdev structure
   * @fh     : V4L2 subdev file handle
   * @code   : pointer to v4l2_subdev_pad_mbus_code_enum structure
   * return -EINVAL or zero on success
  */
  static int csi2_enum_mbus_code(struct v4l2_subdev *sd,
  			       struct v4l2_subdev_pad_config *cfg,
  			       struct v4l2_subdev_mbus_code_enum *code)
  {
  	const struct atomisp_in_fmt_conv *ic = atomisp_in_fmt_conv;
  	unsigned int i = 0;
  
  	while (ic->code) {
  		if (i == code->index) {
  			code->code = ic->code;
  			return 0;
  		}
  		i++, ic++;
  	}
  
  	return -EINVAL;
  }
  
  /*
   * csi2_get_format - Handle get format by pads subdev method
   * @sd : pointer to v4l2 subdev structure
   * @fh : V4L2 subdev file handle
   * @pad: pad num
   * @fmt: pointer to v4l2 format structure
   * return -EINVAL or zero on success
  */
  static int csi2_get_format(struct v4l2_subdev *sd,
  			   struct v4l2_subdev_pad_config *cfg,
  			   struct v4l2_subdev_format *fmt)
  {
  	struct atomisp_mipi_csi2_device *csi2 = v4l2_get_subdevdata(sd);
  	struct v4l2_mbus_framefmt *format;
  
  	format = __csi2_get_format(csi2, cfg, fmt->which, fmt->pad);
  
  	fmt->format = *format;
  
  	return 0;
  }
  
  int atomisp_csi2_set_ffmt(struct v4l2_subdev *sd,
  			  struct v4l2_subdev_pad_config *cfg,
  			  unsigned int which, uint16_t pad,
  			  struct v4l2_mbus_framefmt *ffmt)
  {
  	struct atomisp_mipi_csi2_device *csi2 = v4l2_get_subdevdata(sd);
  	struct v4l2_mbus_framefmt *actual_ffmt = __csi2_get_format(csi2, cfg, which, pad);
  
  	if (pad == CSI2_PAD_SINK) {
  		const struct atomisp_in_fmt_conv *ic;
  		struct v4l2_mbus_framefmt tmp_ffmt;
  
  		ic = atomisp_find_in_fmt_conv(ffmt->code);
  		if (ic)
  			actual_ffmt->code = ic->code;
  		else
  			actual_ffmt->code = atomisp_in_fmt_conv[0].code;
  
  		actual_ffmt->width = clamp_t(
  					 u32, ffmt->width, ATOM_ISP_MIN_WIDTH,
  					 ATOM_ISP_MAX_WIDTH);
  		actual_ffmt->height = clamp_t(
  					  u32, ffmt->height, ATOM_ISP_MIN_HEIGHT,
  					  ATOM_ISP_MAX_HEIGHT);
  
  		tmp_ffmt = *ffmt = *actual_ffmt;
  
  		return atomisp_csi2_set_ffmt(sd, cfg, which, CSI2_PAD_SOURCE,
  					     &tmp_ffmt);
  	}
  
  	/* FIXME: DPCM decompression */
  	*actual_ffmt = *ffmt = *__csi2_get_format(csi2, cfg, which, CSI2_PAD_SINK);
  
  	return 0;
  }
  
  /*
   * csi2_set_format - Handle set format by pads subdev method
   * @sd : pointer to v4l2 subdev structure
   * @fh : V4L2 subdev file handle
   * @pad: pad num
   * @fmt: pointer to v4l2 format structure
   * return -EINVAL or zero on success
  */
  static int csi2_set_format(struct v4l2_subdev *sd,
  			   struct v4l2_subdev_pad_config *cfg,
  			   struct v4l2_subdev_format *fmt)
  {
  	return atomisp_csi2_set_ffmt(sd, cfg, fmt->which, fmt->pad,
  				     &fmt->format);
  }
  
  /*
   * csi2_set_stream - Enable/Disable streaming on the CSI2 module
   * @sd: ISP CSI2 V4L2 subdevice
   * @enable: Enable/disable stream (1/0)
   *
   * Return 0 on success or a negative error code otherwise.
  */
  static int csi2_set_stream(struct v4l2_subdev *sd, int enable)
  {
  	return 0;
  }
  
  /* subdev core operations */
  static const struct v4l2_subdev_core_ops csi2_core_ops = {
  };
  
  /* subdev video operations */
  static const struct v4l2_subdev_video_ops csi2_video_ops = {
  	.s_stream = csi2_set_stream,
  };
  
  /* subdev pad operations */
  static const struct v4l2_subdev_pad_ops csi2_pad_ops = {
  	.enum_mbus_code = csi2_enum_mbus_code,
  	.get_fmt = csi2_get_format,
  	.set_fmt = csi2_set_format,
  	.link_validate = v4l2_subdev_link_validate_default,
  };
  
  /* subdev operations */
  static const struct v4l2_subdev_ops csi2_ops = {
  	.core = &csi2_core_ops,
  	.video = &csi2_video_ops,
  	.pad = &csi2_pad_ops,
  };
  
  /*
   * csi2_link_setup - Setup CSI2 connections.
   * @entity : Pointer to media entity structure
   * @local  : Pointer to local pad array
   * @remote : Pointer to remote pad array
   * @flags  : Link flags
   * return -EINVAL or zero on success
  */
  static int csi2_link_setup(struct media_entity *entity,
  			   const struct media_pad *local,
  			   const struct media_pad *remote, u32 flags)
  {
  	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
  	struct atomisp_mipi_csi2_device *csi2 = v4l2_get_subdevdata(sd);
  	u32 result = local->index | is_media_entity_v4l2_subdev(remote->entity);
  
  	switch (result) {
  	case CSI2_PAD_SOURCE | MEDIA_ENT_F_OLD_BASE:
  		/* not supported yet */
  		return -EINVAL;
  
  	case CSI2_PAD_SOURCE | MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN:
  		if (flags & MEDIA_LNK_FL_ENABLED) {
  			if (csi2->output & ~CSI2_OUTPUT_ISP_SUBDEV)
  				return -EBUSY;
  			csi2->output |= CSI2_OUTPUT_ISP_SUBDEV;
  		} else {
  			csi2->output &= ~CSI2_OUTPUT_ISP_SUBDEV;
  		}
  		break;
  
  	default:
  		/* Link from camera to CSI2 is fixed... */
  		return -EINVAL;
  	}
  	return 0;
  }
  
  /* media operations */
  static const struct media_entity_operations csi2_media_ops = {
  	.link_setup = csi2_link_setup,
  	.link_validate = v4l2_subdev_link_validate,
  };
  
  /*
  * ispcsi2_init_entities - Initialize subdev and media entity.
  * @csi2: Pointer to ispcsi2 structure.
  * return -ENOMEM or zero on success
  */
  static int mipi_csi2_init_entities(struct atomisp_mipi_csi2_device *csi2,
  				   int port)
  {
  	struct v4l2_subdev *sd = &csi2->subdev;
  	struct media_pad *pads = csi2->pads;
  	struct media_entity *me = &sd->entity;
  	int ret;
  
  	v4l2_subdev_init(sd, &csi2_ops);
  	snprintf(sd->name, sizeof(sd->name), "ATOM ISP CSI2-port%d", port);
  
  	v4l2_set_subdevdata(sd, csi2);
  	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
  
  	pads[CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
  	pads[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
  
  	me->ops = &csi2_media_ops;
  	me->function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
  	ret = media_entity_pads_init(me, CSI2_PADS_NUM, pads);
  	if (ret < 0)
  		return ret;
  
  	csi2->formats[CSI2_PAD_SINK].code =
  	    csi2->formats[CSI2_PAD_SOURCE].code =
  		atomisp_in_fmt_conv[0].code;
  
  	return 0;
  }
  
  void
  atomisp_mipi_csi2_unregister_entities(struct atomisp_mipi_csi2_device *csi2)
  {
  	media_entity_cleanup(&csi2->subdev.entity);
  	v4l2_device_unregister_subdev(&csi2->subdev);
  }
  
  int atomisp_mipi_csi2_register_entities(struct atomisp_mipi_csi2_device *csi2,
  					struct v4l2_device *vdev)
  {
  	int ret;
  
  	/* Register the subdev and video nodes. */
  	ret = v4l2_device_register_subdev(vdev, &csi2->subdev);
  	if (ret < 0)
  		goto error;
  
  	return 0;
  
  error:
  	atomisp_mipi_csi2_unregister_entities(csi2);
  	return ret;
  }
  
  static const int LIMIT_SHIFT = 6;	/* Limit numeric range into 31 bits */
  
  static int
  atomisp_csi2_configure_calc(const short int coeffs[2], int mipi_freq, int def)
  {
  	/* Delay counter accuracy, 1/0.0625 for ANN/CHT, 1/0.125 for BXT */
  	static const int accinv = 16;		/* 1 / COUNT_ACC */
  	int r;
  
  	if (mipi_freq >> LIMIT_SHIFT <= 0)
  		return def;
  
  	r = accinv * coeffs[1] * (500000000 >> LIMIT_SHIFT);
  	r /= mipi_freq >> LIMIT_SHIFT;
  	r += accinv * coeffs[0];
  
  	return r;
  }
  
  static void atomisp_csi2_configure_isp2401(struct atomisp_sub_device *asd)
  {
  	/*
  	 * The ISP2401 new input system CSI2+ receiver has several
  	 * parameters affecting the receiver timings. These depend
  	 * on the MIPI bus frequency F in Hz (sensor transmitter rate)
  	 * as follows:
  	 *	register value = (A/1e9 + B * UI) / COUNT_ACC
  	 * where
  	 *	UI = 1 / (2 * F) in seconds
  	 *	COUNT_ACC = counter accuracy in seconds
  	 *	For ANN and CHV, COUNT_ACC = 0.0625 ns
  	 *	For BXT,  COUNT_ACC = 0.125 ns
  	 * A and B are coefficients from the table below,
  	 * depending whether the register minimum or maximum value is
  	 * calculated.
  	 *				       Minimum     Maximum
  	 * Clock lane			       A     B     A     B
  	 * reg_rx_csi_dly_cnt_termen_clane     0     0    38     0
  	 * reg_rx_csi_dly_cnt_settle_clane    95    -8   300   -16
  	 * Data lanes
  	 * reg_rx_csi_dly_cnt_termen_dlane0    0     0    35     4
  	 * reg_rx_csi_dly_cnt_settle_dlane0   85    -2   145    -6
  	 * reg_rx_csi_dly_cnt_termen_dlane1    0     0    35     4
  	 * reg_rx_csi_dly_cnt_settle_dlane1   85    -2   145    -6
  	 * reg_rx_csi_dly_cnt_termen_dlane2    0     0    35     4
  	 * reg_rx_csi_dly_cnt_settle_dlane2   85    -2   145    -6
  	 * reg_rx_csi_dly_cnt_termen_dlane3    0     0    35     4
  	 * reg_rx_csi_dly_cnt_settle_dlane3   85    -2   145    -6
  	 *
  	 * We use the minimum values in the calculations below.
  	 */
  	static const short int coeff_clk_termen[] = { 0, 0 };
  	static const short int coeff_clk_settle[] = { 95, -8 };
  	static const short int coeff_dat_termen[] = { 0, 0 };
  	static const short int coeff_dat_settle[] = { 85, -2 };
  	static const int TERMEN_DEFAULT		  = 0 * 0;
  	static const int SETTLE_DEFAULT		  = 0x480;
  
  	static const hrt_address csi2_port_base[] = {
  		[ATOMISP_CAMERA_PORT_PRIMARY]     = CSI2_PORT_A_BASE,
  		[ATOMISP_CAMERA_PORT_SECONDARY]   = CSI2_PORT_B_BASE,
  		[ATOMISP_CAMERA_PORT_TERTIARY]    = CSI2_PORT_C_BASE,
  	};
  	/* Number of lanes on each port, excluding clock lane */
  	static const unsigned char csi2_port_lanes[] = {
  		[ATOMISP_CAMERA_PORT_PRIMARY]     = 4,
  		[ATOMISP_CAMERA_PORT_SECONDARY]   = 2,
  		[ATOMISP_CAMERA_PORT_TERTIARY]    = 2,
  	};
  	static const hrt_address csi2_lane_base[] = {
  		CSI2_LANE_CL_BASE,
  		CSI2_LANE_D0_BASE,
  		CSI2_LANE_D1_BASE,
  		CSI2_LANE_D2_BASE,
  		CSI2_LANE_D3_BASE,
  	};
  
  	int clk_termen;
  	int clk_settle;
  	int dat_termen;
  	int dat_settle;
  
  	struct v4l2_control ctrl;
  	struct atomisp_device *isp = asd->isp;
  	struct camera_mipi_info *mipi_info;
  	int mipi_freq = 0;
  	enum atomisp_camera_port port;
  
  	int n;
  
  	mipi_info = atomisp_to_sensor_mipi_info(
  			isp->inputs[asd->input_curr].camera);
  	port = mipi_info->port;
  
  	ctrl.id = V4L2_CID_LINK_FREQ;
  	if (v4l2_g_ctrl
  	    (isp->inputs[asd->input_curr].camera->ctrl_handler, &ctrl) == 0)
  		mipi_freq = ctrl.value;
  
  	clk_termen = atomisp_csi2_configure_calc(coeff_clk_termen,
  		     mipi_freq, TERMEN_DEFAULT);
  	clk_settle = atomisp_csi2_configure_calc(coeff_clk_settle,
  		     mipi_freq, SETTLE_DEFAULT);
  	dat_termen = atomisp_csi2_configure_calc(coeff_dat_termen,
  		     mipi_freq, TERMEN_DEFAULT);
  	dat_settle = atomisp_csi2_configure_calc(coeff_dat_settle,
  		     mipi_freq, SETTLE_DEFAULT);
  	for (n = 0; n < csi2_port_lanes[port] + 1; n++) {
  		hrt_address base = csi2_port_base[port] + csi2_lane_base[n];
  
  		atomisp_css2_hw_store_32(base + CSI2_REG_RX_CSI_DLY_CNT_TERMEN,
  				     n == 0 ? clk_termen : dat_termen);
  		atomisp_css2_hw_store_32(base + CSI2_REG_RX_CSI_DLY_CNT_SETTLE,
  				     n == 0 ? clk_settle : dat_settle);
  	}
  }
  
  void atomisp_csi2_configure(struct atomisp_sub_device *asd)
  {
  	if (IS_HWREVISION(asd->isp, ATOMISP_HW_REVISION_ISP2401))
  		atomisp_csi2_configure_isp2401(asd);
  }
  
  /*
   * atomisp_mipi_csi2_cleanup - Routine for module driver cleanup
  */
  void atomisp_mipi_csi2_cleanup(struct atomisp_device *isp)
  {
  }
  
  int atomisp_mipi_csi2_init(struct atomisp_device *isp)
  {
  	struct atomisp_mipi_csi2_device *csi2_port;
  	unsigned int i;
  	int ret;
  
  	for (i = 0; i < ATOMISP_CAMERA_NR_PORTS; i++) {
  		csi2_port = &isp->csi2_port[i];
  		csi2_port->isp = isp;
  		ret = mipi_csi2_init_entities(csi2_port, i);
  		if (ret < 0)
  			goto fail;
  	}
  
  	return 0;
  
  fail:
  	atomisp_mipi_csi2_cleanup(isp);
  	return ret;
  }
  