162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for Cadence MIPI-CSI2 TX Controller 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2017-2019 Cadence Design Systems Inc. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/io.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/mutex.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/of_graph.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <media/mipi-csi2.h> 1962306a36Sopenharmony_ci#include <media/v4l2-ctrls.h> 2062306a36Sopenharmony_ci#include <media/v4l2-device.h> 2162306a36Sopenharmony_ci#include <media/v4l2-fwnode.h> 2262306a36Sopenharmony_ci#include <media/v4l2-subdev.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define CSI2TX_DEVICE_CONFIG_REG 0x00 2562306a36Sopenharmony_ci#define CSI2TX_DEVICE_CONFIG_STREAMS_MASK GENMASK(6, 4) 2662306a36Sopenharmony_ci#define CSI2TX_DEVICE_CONFIG_HAS_DPHY BIT(3) 2762306a36Sopenharmony_ci#define CSI2TX_DEVICE_CONFIG_LANES_MASK GENMASK(2, 0) 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define CSI2TX_CONFIG_REG 0x20 3062306a36Sopenharmony_ci#define CSI2TX_CONFIG_CFG_REQ BIT(2) 3162306a36Sopenharmony_ci#define CSI2TX_CONFIG_SRST_REQ BIT(1) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_REG 0x28 3462306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_CLK_RESET BIT(16) 3562306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_LANE_RESET(n) BIT((n) + 12) 3662306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_MODE_MASK GENMASK(9, 8) 3762306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_MODE_LPDT (2 << 8) 3862306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_MODE_HS (1 << 8) 3962306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_MODE_ULPS (0 << 8) 4062306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_CLK_ENABLE BIT(4) 4162306a36Sopenharmony_ci#define CSI2TX_DPHY_CFG_LANE_ENABLE(n) BIT(n) 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#define CSI2TX_DPHY_CLK_WAKEUP_REG 0x2c 4462306a36Sopenharmony_ci#define CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(n) ((n) & 0xffff) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define CSI2TX_DT_CFG_REG(n) (0x80 + (n) * 8) 4762306a36Sopenharmony_ci#define CSI2TX_DT_CFG_DT(n) (((n) & 0x3f) << 2) 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci#define CSI2TX_DT_FORMAT_REG(n) (0x84 + (n) * 8) 5062306a36Sopenharmony_ci#define CSI2TX_DT_FORMAT_BYTES_PER_LINE(n) (((n) & 0xffff) << 16) 5162306a36Sopenharmony_ci#define CSI2TX_DT_FORMAT_MAX_LINE_NUM(n) ((n) & 0xffff) 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#define CSI2TX_STREAM_IF_CFG_REG(n) (0x100 + (n) * 4) 5462306a36Sopenharmony_ci#define CSI2TX_STREAM_IF_CFG_FILL_LEVEL(n) ((n) & 0x1f) 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* CSI2TX V2 Registers */ 5762306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_REG 0x28 5862306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_RESET BIT(16) 5962306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_CLOCK_MODE BIT(10) 6062306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_MODE_MASK GENMASK(9, 8) 6162306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_MODE_LPDT (2 << 8) 6262306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_MODE_HS (1 << 8) 6362306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_MODE_ULPS (0 << 8) 6462306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_CLK_ENABLE BIT(4) 6562306a36Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_LANE_ENABLE(n) BIT(n) 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define CSI2TX_LANES_MAX 4 6862306a36Sopenharmony_ci#define CSI2TX_STREAMS_MAX 4 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cienum csi2tx_pads { 7162306a36Sopenharmony_ci CSI2TX_PAD_SOURCE, 7262306a36Sopenharmony_ci CSI2TX_PAD_SINK_STREAM0, 7362306a36Sopenharmony_ci CSI2TX_PAD_SINK_STREAM1, 7462306a36Sopenharmony_ci CSI2TX_PAD_SINK_STREAM2, 7562306a36Sopenharmony_ci CSI2TX_PAD_SINK_STREAM3, 7662306a36Sopenharmony_ci CSI2TX_PAD_MAX, 7762306a36Sopenharmony_ci}; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistruct csi2tx_fmt { 8062306a36Sopenharmony_ci u32 mbus; 8162306a36Sopenharmony_ci u32 dt; 8262306a36Sopenharmony_ci u32 bpp; 8362306a36Sopenharmony_ci}; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistruct csi2tx_priv; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci/* CSI2TX Variant Operations */ 8862306a36Sopenharmony_cistruct csi2tx_vops { 8962306a36Sopenharmony_ci void (*dphy_setup)(struct csi2tx_priv *csi2tx); 9062306a36Sopenharmony_ci}; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistruct csi2tx_priv { 9362306a36Sopenharmony_ci struct device *dev; 9462306a36Sopenharmony_ci unsigned int count; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* 9762306a36Sopenharmony_ci * Used to prevent race conditions between multiple, 9862306a36Sopenharmony_ci * concurrent calls to start and stop. 9962306a36Sopenharmony_ci */ 10062306a36Sopenharmony_ci struct mutex lock; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci void __iomem *base; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci struct csi2tx_vops *vops; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci struct clk *esc_clk; 10762306a36Sopenharmony_ci struct clk *p_clk; 10862306a36Sopenharmony_ci struct clk *pixel_clk[CSI2TX_STREAMS_MAX]; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci struct v4l2_subdev subdev; 11162306a36Sopenharmony_ci struct media_pad pads[CSI2TX_PAD_MAX]; 11262306a36Sopenharmony_ci struct v4l2_mbus_framefmt pad_fmts[CSI2TX_PAD_MAX]; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci bool has_internal_dphy; 11562306a36Sopenharmony_ci u8 lanes[CSI2TX_LANES_MAX]; 11662306a36Sopenharmony_ci unsigned int num_lanes; 11762306a36Sopenharmony_ci unsigned int max_lanes; 11862306a36Sopenharmony_ci unsigned int max_streams; 11962306a36Sopenharmony_ci}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic const struct csi2tx_fmt csi2tx_formats[] = { 12262306a36Sopenharmony_ci { 12362306a36Sopenharmony_ci .mbus = MEDIA_BUS_FMT_UYVY8_1X16, 12462306a36Sopenharmony_ci .bpp = 2, 12562306a36Sopenharmony_ci .dt = MIPI_CSI2_DT_YUV422_8B, 12662306a36Sopenharmony_ci }, 12762306a36Sopenharmony_ci { 12862306a36Sopenharmony_ci .mbus = MEDIA_BUS_FMT_RGB888_1X24, 12962306a36Sopenharmony_ci .bpp = 3, 13062306a36Sopenharmony_ci .dt = MIPI_CSI2_DT_RGB888, 13162306a36Sopenharmony_ci }, 13262306a36Sopenharmony_ci}; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic const struct v4l2_mbus_framefmt fmt_default = { 13562306a36Sopenharmony_ci .width = 1280, 13662306a36Sopenharmony_ci .height = 720, 13762306a36Sopenharmony_ci .code = MEDIA_BUS_FMT_RGB888_1X24, 13862306a36Sopenharmony_ci .field = V4L2_FIELD_NONE, 13962306a36Sopenharmony_ci .colorspace = V4L2_COLORSPACE_DEFAULT, 14062306a36Sopenharmony_ci}; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic inline 14362306a36Sopenharmony_cistruct csi2tx_priv *v4l2_subdev_to_csi2tx(struct v4l2_subdev *subdev) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci return container_of(subdev, struct csi2tx_priv, subdev); 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic const struct csi2tx_fmt *csi2tx_get_fmt_from_mbus(u32 mbus) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci unsigned int i; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(csi2tx_formats); i++) 15362306a36Sopenharmony_ci if (csi2tx_formats[i].mbus == mbus) 15462306a36Sopenharmony_ci return &csi2tx_formats[i]; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci return NULL; 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic int csi2tx_enum_mbus_code(struct v4l2_subdev *subdev, 16062306a36Sopenharmony_ci struct v4l2_subdev_state *sd_state, 16162306a36Sopenharmony_ci struct v4l2_subdev_mbus_code_enum *code) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci if (code->pad || code->index >= ARRAY_SIZE(csi2tx_formats)) 16462306a36Sopenharmony_ci return -EINVAL; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci code->code = csi2tx_formats[code->index].mbus; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return 0; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic struct v4l2_mbus_framefmt * 17262306a36Sopenharmony_ci__csi2tx_get_pad_format(struct v4l2_subdev *subdev, 17362306a36Sopenharmony_ci struct v4l2_subdev_state *sd_state, 17462306a36Sopenharmony_ci struct v4l2_subdev_format *fmt) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) 17962306a36Sopenharmony_ci return v4l2_subdev_get_try_format(subdev, sd_state, 18062306a36Sopenharmony_ci fmt->pad); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci return &csi2tx->pad_fmts[fmt->pad]; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic int csi2tx_get_pad_format(struct v4l2_subdev *subdev, 18662306a36Sopenharmony_ci struct v4l2_subdev_state *sd_state, 18762306a36Sopenharmony_ci struct v4l2_subdev_format *fmt) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci const struct v4l2_mbus_framefmt *format; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* Multiplexed pad? */ 19262306a36Sopenharmony_ci if (fmt->pad == CSI2TX_PAD_SOURCE) 19362306a36Sopenharmony_ci return -EINVAL; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci format = __csi2tx_get_pad_format(subdev, sd_state, fmt); 19662306a36Sopenharmony_ci if (!format) 19762306a36Sopenharmony_ci return -EINVAL; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci fmt->format = *format; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci return 0; 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic int csi2tx_set_pad_format(struct v4l2_subdev *subdev, 20562306a36Sopenharmony_ci struct v4l2_subdev_state *sd_state, 20662306a36Sopenharmony_ci struct v4l2_subdev_format *fmt) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci const struct v4l2_mbus_framefmt *src_format = &fmt->format; 20962306a36Sopenharmony_ci struct v4l2_mbus_framefmt *dst_format; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci /* Multiplexed pad? */ 21262306a36Sopenharmony_ci if (fmt->pad == CSI2TX_PAD_SOURCE) 21362306a36Sopenharmony_ci return -EINVAL; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci if (!csi2tx_get_fmt_from_mbus(fmt->format.code)) 21662306a36Sopenharmony_ci src_format = &fmt_default; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci dst_format = __csi2tx_get_pad_format(subdev, sd_state, fmt); 21962306a36Sopenharmony_ci if (!dst_format) 22062306a36Sopenharmony_ci return -EINVAL; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci *dst_format = *src_format; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci return 0; 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic const struct v4l2_subdev_pad_ops csi2tx_pad_ops = { 22862306a36Sopenharmony_ci .enum_mbus_code = csi2tx_enum_mbus_code, 22962306a36Sopenharmony_ci .get_fmt = csi2tx_get_pad_format, 23062306a36Sopenharmony_ci .set_fmt = csi2tx_set_pad_format, 23162306a36Sopenharmony_ci}; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci/* Set Wake Up value in the D-PHY */ 23462306a36Sopenharmony_cistatic void csi2tx_dphy_set_wakeup(struct csi2tx_priv *csi2tx) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci writel(CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(32), 23762306a36Sopenharmony_ci csi2tx->base + CSI2TX_DPHY_CLK_WAKEUP_REG); 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci/* 24162306a36Sopenharmony_ci * Finishes the D-PHY initialization 24262306a36Sopenharmony_ci * reg dphy cfg value to be used 24362306a36Sopenharmony_ci */ 24462306a36Sopenharmony_cistatic void csi2tx_dphy_init_finish(struct csi2tx_priv *csi2tx, u32 reg) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci unsigned int i; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci udelay(10); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci /* Enable our (clock and data) lanes */ 25162306a36Sopenharmony_ci reg |= CSI2TX_DPHY_CFG_CLK_ENABLE; 25262306a36Sopenharmony_ci for (i = 0; i < csi2tx->num_lanes; i++) 25362306a36Sopenharmony_ci reg |= CSI2TX_DPHY_CFG_LANE_ENABLE(csi2tx->lanes[i] - 1); 25462306a36Sopenharmony_ci writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci udelay(10); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci /* Switch to HS mode */ 25962306a36Sopenharmony_ci reg &= ~CSI2TX_DPHY_CFG_MODE_MASK; 26062306a36Sopenharmony_ci writel(reg | CSI2TX_DPHY_CFG_MODE_HS, 26162306a36Sopenharmony_ci csi2tx->base + CSI2TX_DPHY_CFG_REG); 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci/* Configures D-PHY in CSIv1.3 */ 26562306a36Sopenharmony_cistatic void csi2tx_dphy_setup(struct csi2tx_priv *csi2tx) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci u32 reg; 26862306a36Sopenharmony_ci unsigned int i; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci csi2tx_dphy_set_wakeup(csi2tx); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci /* Put our lanes (clock and data) out of reset */ 27362306a36Sopenharmony_ci reg = CSI2TX_DPHY_CFG_CLK_RESET | CSI2TX_DPHY_CFG_MODE_LPDT; 27462306a36Sopenharmony_ci for (i = 0; i < csi2tx->num_lanes; i++) 27562306a36Sopenharmony_ci reg |= CSI2TX_DPHY_CFG_LANE_RESET(csi2tx->lanes[i] - 1); 27662306a36Sopenharmony_ci writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci csi2tx_dphy_init_finish(csi2tx, reg); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci/* Configures D-PHY in CSIv2 */ 28262306a36Sopenharmony_cistatic void csi2tx_v2_dphy_setup(struct csi2tx_priv *csi2tx) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci u32 reg; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci csi2tx_dphy_set_wakeup(csi2tx); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci /* Put our lanes (clock and data) out of reset */ 28962306a36Sopenharmony_ci reg = CSI2TX_V2_DPHY_CFG_RESET | CSI2TX_V2_DPHY_CFG_MODE_LPDT; 29062306a36Sopenharmony_ci writel(reg, csi2tx->base + CSI2TX_V2_DPHY_CFG_REG); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci csi2tx_dphy_init_finish(csi2tx, reg); 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cistatic void csi2tx_reset(struct csi2tx_priv *csi2tx) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci writel(CSI2TX_CONFIG_SRST_REQ, csi2tx->base + CSI2TX_CONFIG_REG); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci udelay(10); 30062306a36Sopenharmony_ci} 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_cistatic int csi2tx_start(struct csi2tx_priv *csi2tx) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci struct media_entity *entity = &csi2tx->subdev.entity; 30562306a36Sopenharmony_ci struct media_link *link; 30662306a36Sopenharmony_ci unsigned int i; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci csi2tx_reset(csi2tx); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci writel(CSI2TX_CONFIG_CFG_REQ, csi2tx->base + CSI2TX_CONFIG_REG); 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci udelay(10); 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if (csi2tx->vops && csi2tx->vops->dphy_setup) { 31562306a36Sopenharmony_ci csi2tx->vops->dphy_setup(csi2tx); 31662306a36Sopenharmony_ci udelay(10); 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci /* 32062306a36Sopenharmony_ci * Create a static mapping between the CSI virtual channels 32162306a36Sopenharmony_ci * and the input streams. 32262306a36Sopenharmony_ci * 32362306a36Sopenharmony_ci * This should be enhanced, but v4l2 lacks the support for 32462306a36Sopenharmony_ci * changing that mapping dynamically at the moment. 32562306a36Sopenharmony_ci * 32662306a36Sopenharmony_ci * We're protected from the userspace setting up links at the 32762306a36Sopenharmony_ci * same time by the upper layer having called 32862306a36Sopenharmony_ci * media_pipeline_start(). 32962306a36Sopenharmony_ci */ 33062306a36Sopenharmony_ci list_for_each_entry(link, &entity->links, list) { 33162306a36Sopenharmony_ci struct v4l2_mbus_framefmt *mfmt; 33262306a36Sopenharmony_ci const struct csi2tx_fmt *fmt; 33362306a36Sopenharmony_ci unsigned int stream; 33462306a36Sopenharmony_ci int pad_idx = -1; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci /* Only consider our enabled input pads */ 33762306a36Sopenharmony_ci for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) { 33862306a36Sopenharmony_ci struct media_pad *pad = &csi2tx->pads[i]; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci if ((pad == link->sink) && 34162306a36Sopenharmony_ci (link->flags & MEDIA_LNK_FL_ENABLED)) { 34262306a36Sopenharmony_ci pad_idx = i; 34362306a36Sopenharmony_ci break; 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci if (pad_idx < 0) 34862306a36Sopenharmony_ci continue; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci mfmt = &csi2tx->pad_fmts[pad_idx]; 35162306a36Sopenharmony_ci fmt = csi2tx_get_fmt_from_mbus(mfmt->code); 35262306a36Sopenharmony_ci if (!fmt) 35362306a36Sopenharmony_ci continue; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci stream = pad_idx - CSI2TX_PAD_SINK_STREAM0; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci /* 35862306a36Sopenharmony_ci * We use the stream ID there, but it's wrong. 35962306a36Sopenharmony_ci * 36062306a36Sopenharmony_ci * A stream could very well send a data type that is 36162306a36Sopenharmony_ci * not equal to its stream ID. We need to find a 36262306a36Sopenharmony_ci * proper way to address it. 36362306a36Sopenharmony_ci */ 36462306a36Sopenharmony_ci writel(CSI2TX_DT_CFG_DT(fmt->dt), 36562306a36Sopenharmony_ci csi2tx->base + CSI2TX_DT_CFG_REG(stream)); 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci writel(CSI2TX_DT_FORMAT_BYTES_PER_LINE(mfmt->width * fmt->bpp) | 36862306a36Sopenharmony_ci CSI2TX_DT_FORMAT_MAX_LINE_NUM(mfmt->height + 1), 36962306a36Sopenharmony_ci csi2tx->base + CSI2TX_DT_FORMAT_REG(stream)); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci /* 37262306a36Sopenharmony_ci * TODO: This needs to be calculated based on the 37362306a36Sopenharmony_ci * output CSI2 clock rate. 37462306a36Sopenharmony_ci */ 37562306a36Sopenharmony_ci writel(CSI2TX_STREAM_IF_CFG_FILL_LEVEL(4), 37662306a36Sopenharmony_ci csi2tx->base + CSI2TX_STREAM_IF_CFG_REG(stream)); 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci /* Disable the configuration mode */ 38062306a36Sopenharmony_ci writel(0, csi2tx->base + CSI2TX_CONFIG_REG); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci return 0; 38362306a36Sopenharmony_ci} 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cistatic void csi2tx_stop(struct csi2tx_priv *csi2tx) 38662306a36Sopenharmony_ci{ 38762306a36Sopenharmony_ci writel(CSI2TX_CONFIG_CFG_REQ | CSI2TX_CONFIG_SRST_REQ, 38862306a36Sopenharmony_ci csi2tx->base + CSI2TX_CONFIG_REG); 38962306a36Sopenharmony_ci} 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_cistatic int csi2tx_s_stream(struct v4l2_subdev *subdev, int enable) 39262306a36Sopenharmony_ci{ 39362306a36Sopenharmony_ci struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); 39462306a36Sopenharmony_ci int ret = 0; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci mutex_lock(&csi2tx->lock); 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci if (enable) { 39962306a36Sopenharmony_ci /* 40062306a36Sopenharmony_ci * If we're not the first users, there's no need to 40162306a36Sopenharmony_ci * enable the whole controller. 40262306a36Sopenharmony_ci */ 40362306a36Sopenharmony_ci if (!csi2tx->count) { 40462306a36Sopenharmony_ci ret = csi2tx_start(csi2tx); 40562306a36Sopenharmony_ci if (ret) 40662306a36Sopenharmony_ci goto out; 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci csi2tx->count++; 41062306a36Sopenharmony_ci } else { 41162306a36Sopenharmony_ci csi2tx->count--; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci /* 41462306a36Sopenharmony_ci * Let the last user turn off the lights. 41562306a36Sopenharmony_ci */ 41662306a36Sopenharmony_ci if (!csi2tx->count) 41762306a36Sopenharmony_ci csi2tx_stop(csi2tx); 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ciout: 42162306a36Sopenharmony_ci mutex_unlock(&csi2tx->lock); 42262306a36Sopenharmony_ci return ret; 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_cistatic const struct v4l2_subdev_video_ops csi2tx_video_ops = { 42662306a36Sopenharmony_ci .s_stream = csi2tx_s_stream, 42762306a36Sopenharmony_ci}; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_cistatic const struct v4l2_subdev_ops csi2tx_subdev_ops = { 43062306a36Sopenharmony_ci .pad = &csi2tx_pad_ops, 43162306a36Sopenharmony_ci .video = &csi2tx_video_ops, 43262306a36Sopenharmony_ci}; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_cistatic int csi2tx_get_resources(struct csi2tx_priv *csi2tx, 43562306a36Sopenharmony_ci struct platform_device *pdev) 43662306a36Sopenharmony_ci{ 43762306a36Sopenharmony_ci unsigned int i; 43862306a36Sopenharmony_ci u32 dev_cfg; 43962306a36Sopenharmony_ci int ret; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci csi2tx->base = devm_platform_ioremap_resource(pdev, 0); 44262306a36Sopenharmony_ci if (IS_ERR(csi2tx->base)) 44362306a36Sopenharmony_ci return PTR_ERR(csi2tx->base); 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci csi2tx->p_clk = devm_clk_get(&pdev->dev, "p_clk"); 44662306a36Sopenharmony_ci if (IS_ERR(csi2tx->p_clk)) { 44762306a36Sopenharmony_ci dev_err(&pdev->dev, "Couldn't get p_clk\n"); 44862306a36Sopenharmony_ci return PTR_ERR(csi2tx->p_clk); 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci csi2tx->esc_clk = devm_clk_get(&pdev->dev, "esc_clk"); 45262306a36Sopenharmony_ci if (IS_ERR(csi2tx->esc_clk)) { 45362306a36Sopenharmony_ci dev_err(&pdev->dev, "Couldn't get the esc_clk\n"); 45462306a36Sopenharmony_ci return PTR_ERR(csi2tx->esc_clk); 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci ret = clk_prepare_enable(csi2tx->p_clk); 45862306a36Sopenharmony_ci if (ret) { 45962306a36Sopenharmony_ci dev_err(&pdev->dev, "Couldn't prepare and enable p_clk\n"); 46062306a36Sopenharmony_ci return ret; 46162306a36Sopenharmony_ci } 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci dev_cfg = readl(csi2tx->base + CSI2TX_DEVICE_CONFIG_REG); 46462306a36Sopenharmony_ci clk_disable_unprepare(csi2tx->p_clk); 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci csi2tx->max_lanes = dev_cfg & CSI2TX_DEVICE_CONFIG_LANES_MASK; 46762306a36Sopenharmony_ci if (csi2tx->max_lanes > CSI2TX_LANES_MAX) { 46862306a36Sopenharmony_ci dev_err(&pdev->dev, "Invalid number of lanes: %u\n", 46962306a36Sopenharmony_ci csi2tx->max_lanes); 47062306a36Sopenharmony_ci return -EINVAL; 47162306a36Sopenharmony_ci } 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci csi2tx->max_streams = (dev_cfg & CSI2TX_DEVICE_CONFIG_STREAMS_MASK) >> 4; 47462306a36Sopenharmony_ci if (csi2tx->max_streams > CSI2TX_STREAMS_MAX) { 47562306a36Sopenharmony_ci dev_err(&pdev->dev, "Invalid number of streams: %u\n", 47662306a36Sopenharmony_ci csi2tx->max_streams); 47762306a36Sopenharmony_ci return -EINVAL; 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci csi2tx->has_internal_dphy = !!(dev_cfg & CSI2TX_DEVICE_CONFIG_HAS_DPHY); 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci for (i = 0; i < csi2tx->max_streams; i++) { 48362306a36Sopenharmony_ci char clk_name[16]; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci snprintf(clk_name, sizeof(clk_name), "pixel_if%u_clk", i); 48662306a36Sopenharmony_ci csi2tx->pixel_clk[i] = devm_clk_get(&pdev->dev, clk_name); 48762306a36Sopenharmony_ci if (IS_ERR(csi2tx->pixel_clk[i])) { 48862306a36Sopenharmony_ci dev_err(&pdev->dev, "Couldn't get clock %s\n", 48962306a36Sopenharmony_ci clk_name); 49062306a36Sopenharmony_ci return PTR_ERR(csi2tx->pixel_clk[i]); 49162306a36Sopenharmony_ci } 49262306a36Sopenharmony_ci } 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci return 0; 49562306a36Sopenharmony_ci} 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_cistatic int csi2tx_check_lanes(struct csi2tx_priv *csi2tx) 49862306a36Sopenharmony_ci{ 49962306a36Sopenharmony_ci struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; 50062306a36Sopenharmony_ci struct device_node *ep; 50162306a36Sopenharmony_ci int ret, i; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci ep = of_graph_get_endpoint_by_regs(csi2tx->dev->of_node, 0, 0); 50462306a36Sopenharmony_ci if (!ep) 50562306a36Sopenharmony_ci return -EINVAL; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); 50862306a36Sopenharmony_ci if (ret) { 50962306a36Sopenharmony_ci dev_err(csi2tx->dev, "Could not parse v4l2 endpoint\n"); 51062306a36Sopenharmony_ci goto out; 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci if (v4l2_ep.bus_type != V4L2_MBUS_CSI2_DPHY) { 51462306a36Sopenharmony_ci dev_err(csi2tx->dev, "Unsupported media bus type: 0x%x\n", 51562306a36Sopenharmony_ci v4l2_ep.bus_type); 51662306a36Sopenharmony_ci ret = -EINVAL; 51762306a36Sopenharmony_ci goto out; 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci csi2tx->num_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; 52162306a36Sopenharmony_ci if (csi2tx->num_lanes > csi2tx->max_lanes) { 52262306a36Sopenharmony_ci dev_err(csi2tx->dev, 52362306a36Sopenharmony_ci "Current configuration uses more lanes than supported\n"); 52462306a36Sopenharmony_ci ret = -EINVAL; 52562306a36Sopenharmony_ci goto out; 52662306a36Sopenharmony_ci } 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci for (i = 0; i < csi2tx->num_lanes; i++) { 52962306a36Sopenharmony_ci if (v4l2_ep.bus.mipi_csi2.data_lanes[i] < 1) { 53062306a36Sopenharmony_ci dev_err(csi2tx->dev, "Invalid lane[%d] number: %u\n", 53162306a36Sopenharmony_ci i, v4l2_ep.bus.mipi_csi2.data_lanes[i]); 53262306a36Sopenharmony_ci ret = -EINVAL; 53362306a36Sopenharmony_ci goto out; 53462306a36Sopenharmony_ci } 53562306a36Sopenharmony_ci } 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci memcpy(csi2tx->lanes, v4l2_ep.bus.mipi_csi2.data_lanes, 53862306a36Sopenharmony_ci sizeof(csi2tx->lanes)); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ciout: 54162306a36Sopenharmony_ci of_node_put(ep); 54262306a36Sopenharmony_ci return ret; 54362306a36Sopenharmony_ci} 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_cistatic const struct csi2tx_vops csi2tx_vops = { 54662306a36Sopenharmony_ci .dphy_setup = csi2tx_dphy_setup, 54762306a36Sopenharmony_ci}; 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_cistatic const struct csi2tx_vops csi2tx_v2_vops = { 55062306a36Sopenharmony_ci .dphy_setup = csi2tx_v2_dphy_setup, 55162306a36Sopenharmony_ci}; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_cistatic const struct of_device_id csi2tx_of_table[] = { 55462306a36Sopenharmony_ci { 55562306a36Sopenharmony_ci .compatible = "cdns,csi2tx", 55662306a36Sopenharmony_ci .data = &csi2tx_vops 55762306a36Sopenharmony_ci }, 55862306a36Sopenharmony_ci { 55962306a36Sopenharmony_ci .compatible = "cdns,csi2tx-1.3", 56062306a36Sopenharmony_ci .data = &csi2tx_vops 56162306a36Sopenharmony_ci }, 56262306a36Sopenharmony_ci { 56362306a36Sopenharmony_ci .compatible = "cdns,csi2tx-2.1", 56462306a36Sopenharmony_ci .data = &csi2tx_v2_vops 56562306a36Sopenharmony_ci }, 56662306a36Sopenharmony_ci { } 56762306a36Sopenharmony_ci}; 56862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, csi2tx_of_table); 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_cistatic int csi2tx_probe(struct platform_device *pdev) 57162306a36Sopenharmony_ci{ 57262306a36Sopenharmony_ci struct csi2tx_priv *csi2tx; 57362306a36Sopenharmony_ci const struct of_device_id *of_id; 57462306a36Sopenharmony_ci unsigned int i; 57562306a36Sopenharmony_ci int ret; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci csi2tx = kzalloc(sizeof(*csi2tx), GFP_KERNEL); 57862306a36Sopenharmony_ci if (!csi2tx) 57962306a36Sopenharmony_ci return -ENOMEM; 58062306a36Sopenharmony_ci platform_set_drvdata(pdev, csi2tx); 58162306a36Sopenharmony_ci mutex_init(&csi2tx->lock); 58262306a36Sopenharmony_ci csi2tx->dev = &pdev->dev; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci ret = csi2tx_get_resources(csi2tx, pdev); 58562306a36Sopenharmony_ci if (ret) 58662306a36Sopenharmony_ci goto err_free_priv; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci of_id = of_match_node(csi2tx_of_table, pdev->dev.of_node); 58962306a36Sopenharmony_ci csi2tx->vops = (struct csi2tx_vops *)of_id->data; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci v4l2_subdev_init(&csi2tx->subdev, &csi2tx_subdev_ops); 59262306a36Sopenharmony_ci csi2tx->subdev.owner = THIS_MODULE; 59362306a36Sopenharmony_ci csi2tx->subdev.dev = &pdev->dev; 59462306a36Sopenharmony_ci csi2tx->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; 59562306a36Sopenharmony_ci snprintf(csi2tx->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.%s", 59662306a36Sopenharmony_ci KBUILD_MODNAME, dev_name(&pdev->dev)); 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci ret = csi2tx_check_lanes(csi2tx); 59962306a36Sopenharmony_ci if (ret) 60062306a36Sopenharmony_ci goto err_free_priv; 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci /* Create our media pads */ 60362306a36Sopenharmony_ci csi2tx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; 60462306a36Sopenharmony_ci csi2tx->pads[CSI2TX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; 60562306a36Sopenharmony_ci for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) 60662306a36Sopenharmony_ci csi2tx->pads[i].flags = MEDIA_PAD_FL_SINK; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci /* 60962306a36Sopenharmony_ci * Only the input pads are considered to have a format at the 61062306a36Sopenharmony_ci * moment. The CSI link can multiplex various streams with 61162306a36Sopenharmony_ci * different formats, and we can't expose this in v4l2 right 61262306a36Sopenharmony_ci * now. 61362306a36Sopenharmony_ci */ 61462306a36Sopenharmony_ci for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) 61562306a36Sopenharmony_ci csi2tx->pad_fmts[i] = fmt_default; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci ret = media_entity_pads_init(&csi2tx->subdev.entity, CSI2TX_PAD_MAX, 61862306a36Sopenharmony_ci csi2tx->pads); 61962306a36Sopenharmony_ci if (ret) 62062306a36Sopenharmony_ci goto err_free_priv; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci ret = v4l2_async_register_subdev(&csi2tx->subdev); 62362306a36Sopenharmony_ci if (ret < 0) 62462306a36Sopenharmony_ci goto err_free_priv; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci dev_info(&pdev->dev, 62762306a36Sopenharmony_ci "Probed CSI2TX with %u/%u lanes, %u streams, %s D-PHY\n", 62862306a36Sopenharmony_ci csi2tx->num_lanes, csi2tx->max_lanes, csi2tx->max_streams, 62962306a36Sopenharmony_ci csi2tx->has_internal_dphy ? "internal" : "no"); 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci return 0; 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_cierr_free_priv: 63462306a36Sopenharmony_ci kfree(csi2tx); 63562306a36Sopenharmony_ci return ret; 63662306a36Sopenharmony_ci} 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_cistatic void csi2tx_remove(struct platform_device *pdev) 63962306a36Sopenharmony_ci{ 64062306a36Sopenharmony_ci struct csi2tx_priv *csi2tx = platform_get_drvdata(pdev); 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci v4l2_async_unregister_subdev(&csi2tx->subdev); 64362306a36Sopenharmony_ci kfree(csi2tx); 64462306a36Sopenharmony_ci} 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_cistatic struct platform_driver csi2tx_driver = { 64762306a36Sopenharmony_ci .probe = csi2tx_probe, 64862306a36Sopenharmony_ci .remove_new = csi2tx_remove, 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci .driver = { 65162306a36Sopenharmony_ci .name = "cdns-csi2tx", 65262306a36Sopenharmony_ci .of_match_table = csi2tx_of_table, 65362306a36Sopenharmony_ci }, 65462306a36Sopenharmony_ci}; 65562306a36Sopenharmony_cimodule_platform_driver(csi2tx_driver); 65662306a36Sopenharmony_ciMODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); 65762306a36Sopenharmony_ciMODULE_DESCRIPTION("Cadence CSI2-TX controller"); 65862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 659