b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * Driver for Cadence MIPI-CSI2 TX Controller |
| 4 | * |
| 5 | * Copyright (C) 2017-2019 Cadence Design Systems Inc. |
| 6 | */ |
| 7 | |
| 8 | #include <linux/clk.h> |
| 9 | #include <linux/delay.h> |
| 10 | #include <linux/io.h> |
| 11 | #include <linux/module.h> |
| 12 | #include <linux/mutex.h> |
| 13 | #include <linux/of.h> |
| 14 | #include <linux/of_graph.h> |
| 15 | #include <linux/platform_device.h> |
| 16 | #include <linux/slab.h> |
| 17 | |
| 18 | #include <media/v4l2-ctrls.h> |
| 19 | #include <media/v4l2-device.h> |
| 20 | #include <media/v4l2-fwnode.h> |
| 21 | #include <media/v4l2-subdev.h> |
| 22 | |
| 23 | #define CSI2TX_DEVICE_CONFIG_REG 0x00 |
| 24 | #define CSI2TX_DEVICE_CONFIG_STREAMS_MASK GENMASK(6, 4) |
| 25 | #define CSI2TX_DEVICE_CONFIG_HAS_DPHY BIT(3) |
| 26 | #define CSI2TX_DEVICE_CONFIG_LANES_MASK GENMASK(2, 0) |
| 27 | |
| 28 | #define CSI2TX_CONFIG_REG 0x20 |
| 29 | #define CSI2TX_CONFIG_CFG_REQ BIT(2) |
| 30 | #define CSI2TX_CONFIG_SRST_REQ BIT(1) |
| 31 | |
| 32 | #define CSI2TX_DPHY_CFG_REG 0x28 |
| 33 | #define CSI2TX_DPHY_CFG_CLK_RESET BIT(16) |
| 34 | #define CSI2TX_DPHY_CFG_LANE_RESET(n) BIT((n) + 12) |
| 35 | #define CSI2TX_DPHY_CFG_MODE_MASK GENMASK(9, 8) |
| 36 | #define CSI2TX_DPHY_CFG_MODE_LPDT (2 << 8) |
| 37 | #define CSI2TX_DPHY_CFG_MODE_HS (1 << 8) |
| 38 | #define CSI2TX_DPHY_CFG_MODE_ULPS (0 << 8) |
| 39 | #define CSI2TX_DPHY_CFG_CLK_ENABLE BIT(4) |
| 40 | #define CSI2TX_DPHY_CFG_LANE_ENABLE(n) BIT(n) |
| 41 | |
| 42 | #define CSI2TX_DPHY_CLK_WAKEUP_REG 0x2c |
| 43 | #define CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(n) ((n) & 0xffff) |
| 44 | |
| 45 | #define CSI2TX_DT_CFG_REG(n) (0x80 + (n) * 8) |
| 46 | #define CSI2TX_DT_CFG_DT(n) (((n) & 0x3f) << 2) |
| 47 | |
| 48 | #define CSI2TX_DT_FORMAT_REG(n) (0x84 + (n) * 8) |
| 49 | #define CSI2TX_DT_FORMAT_BYTES_PER_LINE(n) (((n) & 0xffff) << 16) |
| 50 | #define CSI2TX_DT_FORMAT_MAX_LINE_NUM(n) ((n) & 0xffff) |
| 51 | |
| 52 | #define CSI2TX_STREAM_IF_CFG_REG(n) (0x100 + (n) * 4) |
| 53 | #define CSI2TX_STREAM_IF_CFG_FILL_LEVEL(n) ((n) & 0x1f) |
| 54 | |
| 55 | /* CSI2TX V2 Registers */ |
| 56 | #define CSI2TX_V2_DPHY_CFG_REG 0x28 |
| 57 | #define CSI2TX_V2_DPHY_CFG_RESET BIT(16) |
| 58 | #define CSI2TX_V2_DPHY_CFG_CLOCK_MODE BIT(10) |
| 59 | #define CSI2TX_V2_DPHY_CFG_MODE_MASK GENMASK(9, 8) |
| 60 | #define CSI2TX_V2_DPHY_CFG_MODE_LPDT (2 << 8) |
| 61 | #define CSI2TX_V2_DPHY_CFG_MODE_HS (1 << 8) |
| 62 | #define CSI2TX_V2_DPHY_CFG_MODE_ULPS (0 << 8) |
| 63 | #define CSI2TX_V2_DPHY_CFG_CLK_ENABLE BIT(4) |
| 64 | #define CSI2TX_V2_DPHY_CFG_LANE_ENABLE(n) BIT(n) |
| 65 | |
| 66 | #define CSI2TX_LANES_MAX 4 |
| 67 | #define CSI2TX_STREAMS_MAX 4 |
| 68 | |
| 69 | enum csi2tx_pads { |
| 70 | CSI2TX_PAD_SOURCE, |
| 71 | CSI2TX_PAD_SINK_STREAM0, |
| 72 | CSI2TX_PAD_SINK_STREAM1, |
| 73 | CSI2TX_PAD_SINK_STREAM2, |
| 74 | CSI2TX_PAD_SINK_STREAM3, |
| 75 | CSI2TX_PAD_MAX, |
| 76 | }; |
| 77 | |
| 78 | struct csi2tx_fmt { |
| 79 | u32 mbus; |
| 80 | u32 dt; |
| 81 | u32 bpp; |
| 82 | }; |
| 83 | |
| 84 | struct csi2tx_priv; |
| 85 | |
| 86 | /* CSI2TX Variant Operations */ |
| 87 | struct csi2tx_vops { |
| 88 | void (*dphy_setup)(struct csi2tx_priv *csi2tx); |
| 89 | }; |
| 90 | |
| 91 | struct csi2tx_priv { |
| 92 | struct device *dev; |
| 93 | unsigned int count; |
| 94 | |
| 95 | /* |
| 96 | * Used to prevent race conditions between multiple, |
| 97 | * concurrent calls to start and stop. |
| 98 | */ |
| 99 | struct mutex lock; |
| 100 | |
| 101 | void __iomem *base; |
| 102 | |
| 103 | struct csi2tx_vops *vops; |
| 104 | |
| 105 | struct clk *esc_clk; |
| 106 | struct clk *p_clk; |
| 107 | struct clk *pixel_clk[CSI2TX_STREAMS_MAX]; |
| 108 | |
| 109 | struct v4l2_subdev subdev; |
| 110 | struct media_pad pads[CSI2TX_PAD_MAX]; |
| 111 | struct v4l2_mbus_framefmt pad_fmts[CSI2TX_PAD_MAX]; |
| 112 | |
| 113 | bool has_internal_dphy; |
| 114 | u8 lanes[CSI2TX_LANES_MAX]; |
| 115 | unsigned int num_lanes; |
| 116 | unsigned int max_lanes; |
| 117 | unsigned int max_streams; |
| 118 | }; |
| 119 | |
| 120 | static const struct csi2tx_fmt csi2tx_formats[] = { |
| 121 | { |
| 122 | .mbus = MEDIA_BUS_FMT_UYVY8_1X16, |
| 123 | .bpp = 2, |
| 124 | .dt = 0x1e, |
| 125 | }, |
| 126 | { |
| 127 | .mbus = MEDIA_BUS_FMT_RGB888_1X24, |
| 128 | .bpp = 3, |
| 129 | .dt = 0x24, |
| 130 | }, |
| 131 | }; |
| 132 | |
| 133 | static const struct v4l2_mbus_framefmt fmt_default = { |
| 134 | .width = 1280, |
| 135 | .height = 720, |
| 136 | .code = MEDIA_BUS_FMT_RGB888_1X24, |
| 137 | .field = V4L2_FIELD_NONE, |
| 138 | .colorspace = V4L2_COLORSPACE_DEFAULT, |
| 139 | }; |
| 140 | |
| 141 | static inline |
| 142 | struct csi2tx_priv *v4l2_subdev_to_csi2tx(struct v4l2_subdev *subdev) |
| 143 | { |
| 144 | return container_of(subdev, struct csi2tx_priv, subdev); |
| 145 | } |
| 146 | |
| 147 | static const struct csi2tx_fmt *csi2tx_get_fmt_from_mbus(u32 mbus) |
| 148 | { |
| 149 | unsigned int i; |
| 150 | |
| 151 | for (i = 0; i < ARRAY_SIZE(csi2tx_formats); i++) |
| 152 | if (csi2tx_formats[i].mbus == mbus) |
| 153 | return &csi2tx_formats[i]; |
| 154 | |
| 155 | return NULL; |
| 156 | } |
| 157 | |
| 158 | static int csi2tx_enum_mbus_code(struct v4l2_subdev *subdev, |
| 159 | struct v4l2_subdev_pad_config *cfg, |
| 160 | struct v4l2_subdev_mbus_code_enum *code) |
| 161 | { |
| 162 | if (code->pad || code->index >= ARRAY_SIZE(csi2tx_formats)) |
| 163 | return -EINVAL; |
| 164 | |
| 165 | code->code = csi2tx_formats[code->index].mbus; |
| 166 | |
| 167 | return 0; |
| 168 | } |
| 169 | |
| 170 | static struct v4l2_mbus_framefmt * |
| 171 | __csi2tx_get_pad_format(struct v4l2_subdev *subdev, |
| 172 | struct v4l2_subdev_pad_config *cfg, |
| 173 | struct v4l2_subdev_format *fmt) |
| 174 | { |
| 175 | struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); |
| 176 | |
| 177 | if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) |
| 178 | return v4l2_subdev_get_try_format(subdev, cfg, |
| 179 | fmt->pad); |
| 180 | |
| 181 | return &csi2tx->pad_fmts[fmt->pad]; |
| 182 | } |
| 183 | |
| 184 | static int csi2tx_get_pad_format(struct v4l2_subdev *subdev, |
| 185 | struct v4l2_subdev_pad_config *cfg, |
| 186 | struct v4l2_subdev_format *fmt) |
| 187 | { |
| 188 | const struct v4l2_mbus_framefmt *format; |
| 189 | |
| 190 | /* Multiplexed pad? */ |
| 191 | if (fmt->pad == CSI2TX_PAD_SOURCE) |
| 192 | return -EINVAL; |
| 193 | |
| 194 | format = __csi2tx_get_pad_format(subdev, cfg, fmt); |
| 195 | if (!format) |
| 196 | return -EINVAL; |
| 197 | |
| 198 | fmt->format = *format; |
| 199 | |
| 200 | return 0; |
| 201 | } |
| 202 | |
| 203 | static int csi2tx_set_pad_format(struct v4l2_subdev *subdev, |
| 204 | struct v4l2_subdev_pad_config *cfg, |
| 205 | struct v4l2_subdev_format *fmt) |
| 206 | { |
| 207 | const struct v4l2_mbus_framefmt *src_format = &fmt->format; |
| 208 | struct v4l2_mbus_framefmt *dst_format; |
| 209 | |
| 210 | /* Multiplexed pad? */ |
| 211 | if (fmt->pad == CSI2TX_PAD_SOURCE) |
| 212 | return -EINVAL; |
| 213 | |
| 214 | if (!csi2tx_get_fmt_from_mbus(fmt->format.code)) |
| 215 | src_format = &fmt_default; |
| 216 | |
| 217 | dst_format = __csi2tx_get_pad_format(subdev, cfg, fmt); |
| 218 | if (!dst_format) |
| 219 | return -EINVAL; |
| 220 | |
| 221 | *dst_format = *src_format; |
| 222 | |
| 223 | return 0; |
| 224 | } |
| 225 | |
| 226 | static const struct v4l2_subdev_pad_ops csi2tx_pad_ops = { |
| 227 | .enum_mbus_code = csi2tx_enum_mbus_code, |
| 228 | .get_fmt = csi2tx_get_pad_format, |
| 229 | .set_fmt = csi2tx_set_pad_format, |
| 230 | }; |
| 231 | |
| 232 | /* Set Wake Up value in the D-PHY */ |
| 233 | static void csi2tx_dphy_set_wakeup(struct csi2tx_priv *csi2tx) |
| 234 | { |
| 235 | writel(CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(32), |
| 236 | csi2tx->base + CSI2TX_DPHY_CLK_WAKEUP_REG); |
| 237 | } |
| 238 | |
| 239 | /* |
| 240 | * Finishes the D-PHY initialization |
| 241 | * reg dphy cfg value to be used |
| 242 | */ |
| 243 | static void csi2tx_dphy_init_finish(struct csi2tx_priv *csi2tx, u32 reg) |
| 244 | { |
| 245 | unsigned int i; |
| 246 | |
| 247 | udelay(10); |
| 248 | |
| 249 | /* Enable our (clock and data) lanes */ |
| 250 | reg |= CSI2TX_DPHY_CFG_CLK_ENABLE; |
| 251 | for (i = 0; i < csi2tx->num_lanes; i++) |
| 252 | reg |= CSI2TX_DPHY_CFG_LANE_ENABLE(csi2tx->lanes[i] - 1); |
| 253 | writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); |
| 254 | |
| 255 | udelay(10); |
| 256 | |
| 257 | /* Switch to HS mode */ |
| 258 | reg &= ~CSI2TX_DPHY_CFG_MODE_MASK; |
| 259 | writel(reg | CSI2TX_DPHY_CFG_MODE_HS, |
| 260 | csi2tx->base + CSI2TX_DPHY_CFG_REG); |
| 261 | } |
| 262 | |
| 263 | /* Configures D-PHY in CSIv1.3 */ |
| 264 | static void csi2tx_dphy_setup(struct csi2tx_priv *csi2tx) |
| 265 | { |
| 266 | u32 reg; |
| 267 | unsigned int i; |
| 268 | |
| 269 | csi2tx_dphy_set_wakeup(csi2tx); |
| 270 | |
| 271 | /* Put our lanes (clock and data) out of reset */ |
| 272 | reg = CSI2TX_DPHY_CFG_CLK_RESET | CSI2TX_DPHY_CFG_MODE_LPDT; |
| 273 | for (i = 0; i < csi2tx->num_lanes; i++) |
| 274 | reg |= CSI2TX_DPHY_CFG_LANE_RESET(csi2tx->lanes[i] - 1); |
| 275 | writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); |
| 276 | |
| 277 | csi2tx_dphy_init_finish(csi2tx, reg); |
| 278 | } |
| 279 | |
| 280 | /* Configures D-PHY in CSIv2 */ |
| 281 | static void csi2tx_v2_dphy_setup(struct csi2tx_priv *csi2tx) |
| 282 | { |
| 283 | u32 reg; |
| 284 | |
| 285 | csi2tx_dphy_set_wakeup(csi2tx); |
| 286 | |
| 287 | /* Put our lanes (clock and data) out of reset */ |
| 288 | reg = CSI2TX_V2_DPHY_CFG_RESET | CSI2TX_V2_DPHY_CFG_MODE_LPDT; |
| 289 | writel(reg, csi2tx->base + CSI2TX_V2_DPHY_CFG_REG); |
| 290 | |
| 291 | csi2tx_dphy_init_finish(csi2tx, reg); |
| 292 | } |
| 293 | |
| 294 | static void csi2tx_reset(struct csi2tx_priv *csi2tx) |
| 295 | { |
| 296 | writel(CSI2TX_CONFIG_SRST_REQ, csi2tx->base + CSI2TX_CONFIG_REG); |
| 297 | |
| 298 | udelay(10); |
| 299 | } |
| 300 | |
| 301 | static int csi2tx_start(struct csi2tx_priv *csi2tx) |
| 302 | { |
| 303 | struct media_entity *entity = &csi2tx->subdev.entity; |
| 304 | struct media_link *link; |
| 305 | unsigned int i; |
| 306 | |
| 307 | csi2tx_reset(csi2tx); |
| 308 | |
| 309 | writel(CSI2TX_CONFIG_CFG_REQ, csi2tx->base + CSI2TX_CONFIG_REG); |
| 310 | |
| 311 | udelay(10); |
| 312 | |
| 313 | if (csi2tx->vops && csi2tx->vops->dphy_setup) { |
| 314 | csi2tx->vops->dphy_setup(csi2tx); |
| 315 | udelay(10); |
| 316 | } |
| 317 | |
| 318 | /* |
| 319 | * Create a static mapping between the CSI virtual channels |
| 320 | * and the input streams. |
| 321 | * |
| 322 | * This should be enhanced, but v4l2 lacks the support for |
| 323 | * changing that mapping dynamically at the moment. |
| 324 | * |
| 325 | * We're protected from the userspace setting up links at the |
| 326 | * same time by the upper layer having called |
| 327 | * media_pipeline_start(). |
| 328 | */ |
| 329 | list_for_each_entry(link, &entity->links, list) { |
| 330 | struct v4l2_mbus_framefmt *mfmt; |
| 331 | const struct csi2tx_fmt *fmt; |
| 332 | unsigned int stream; |
| 333 | int pad_idx = -1; |
| 334 | |
| 335 | /* Only consider our enabled input pads */ |
| 336 | for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) { |
| 337 | struct media_pad *pad = &csi2tx->pads[i]; |
| 338 | |
| 339 | if ((pad == link->sink) && |
| 340 | (link->flags & MEDIA_LNK_FL_ENABLED)) { |
| 341 | pad_idx = i; |
| 342 | break; |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | if (pad_idx < 0) |
| 347 | continue; |
| 348 | |
| 349 | mfmt = &csi2tx->pad_fmts[pad_idx]; |
| 350 | fmt = csi2tx_get_fmt_from_mbus(mfmt->code); |
| 351 | if (!fmt) |
| 352 | continue; |
| 353 | |
| 354 | stream = pad_idx - CSI2TX_PAD_SINK_STREAM0; |
| 355 | |
| 356 | /* |
| 357 | * We use the stream ID there, but it's wrong. |
| 358 | * |
| 359 | * A stream could very well send a data type that is |
| 360 | * not equal to its stream ID. We need to find a |
| 361 | * proper way to address it. |
| 362 | */ |
| 363 | writel(CSI2TX_DT_CFG_DT(fmt->dt), |
| 364 | csi2tx->base + CSI2TX_DT_CFG_REG(stream)); |
| 365 | |
| 366 | writel(CSI2TX_DT_FORMAT_BYTES_PER_LINE(mfmt->width * fmt->bpp) | |
| 367 | CSI2TX_DT_FORMAT_MAX_LINE_NUM(mfmt->height + 1), |
| 368 | csi2tx->base + CSI2TX_DT_FORMAT_REG(stream)); |
| 369 | |
| 370 | /* |
| 371 | * TODO: This needs to be calculated based on the |
| 372 | * output CSI2 clock rate. |
| 373 | */ |
| 374 | writel(CSI2TX_STREAM_IF_CFG_FILL_LEVEL(4), |
| 375 | csi2tx->base + CSI2TX_STREAM_IF_CFG_REG(stream)); |
| 376 | } |
| 377 | |
| 378 | /* Disable the configuration mode */ |
| 379 | writel(0, csi2tx->base + CSI2TX_CONFIG_REG); |
| 380 | |
| 381 | return 0; |
| 382 | } |
| 383 | |
| 384 | static void csi2tx_stop(struct csi2tx_priv *csi2tx) |
| 385 | { |
| 386 | writel(CSI2TX_CONFIG_CFG_REQ | CSI2TX_CONFIG_SRST_REQ, |
| 387 | csi2tx->base + CSI2TX_CONFIG_REG); |
| 388 | } |
| 389 | |
| 390 | static int csi2tx_s_stream(struct v4l2_subdev *subdev, int enable) |
| 391 | { |
| 392 | struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); |
| 393 | int ret = 0; |
| 394 | |
| 395 | mutex_lock(&csi2tx->lock); |
| 396 | |
| 397 | if (enable) { |
| 398 | /* |
| 399 | * If we're not the first users, there's no need to |
| 400 | * enable the whole controller. |
| 401 | */ |
| 402 | if (!csi2tx->count) { |
| 403 | ret = csi2tx_start(csi2tx); |
| 404 | if (ret) |
| 405 | goto out; |
| 406 | } |
| 407 | |
| 408 | csi2tx->count++; |
| 409 | } else { |
| 410 | csi2tx->count--; |
| 411 | |
| 412 | /* |
| 413 | * Let the last user turn off the lights. |
| 414 | */ |
| 415 | if (!csi2tx->count) |
| 416 | csi2tx_stop(csi2tx); |
| 417 | } |
| 418 | |
| 419 | out: |
| 420 | mutex_unlock(&csi2tx->lock); |
| 421 | return ret; |
| 422 | } |
| 423 | |
| 424 | static const struct v4l2_subdev_video_ops csi2tx_video_ops = { |
| 425 | .s_stream = csi2tx_s_stream, |
| 426 | }; |
| 427 | |
| 428 | static const struct v4l2_subdev_ops csi2tx_subdev_ops = { |
| 429 | .pad = &csi2tx_pad_ops, |
| 430 | .video = &csi2tx_video_ops, |
| 431 | }; |
| 432 | |
| 433 | static int csi2tx_get_resources(struct csi2tx_priv *csi2tx, |
| 434 | struct platform_device *pdev) |
| 435 | { |
| 436 | struct resource *res; |
| 437 | unsigned int i; |
| 438 | u32 dev_cfg; |
| 439 | |
| 440 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| 441 | csi2tx->base = devm_ioremap_resource(&pdev->dev, res); |
| 442 | if (IS_ERR(csi2tx->base)) |
| 443 | return PTR_ERR(csi2tx->base); |
| 444 | |
| 445 | csi2tx->p_clk = devm_clk_get(&pdev->dev, "p_clk"); |
| 446 | if (IS_ERR(csi2tx->p_clk)) { |
| 447 | dev_err(&pdev->dev, "Couldn't get p_clk\n"); |
| 448 | return PTR_ERR(csi2tx->p_clk); |
| 449 | } |
| 450 | |
| 451 | csi2tx->esc_clk = devm_clk_get(&pdev->dev, "esc_clk"); |
| 452 | if (IS_ERR(csi2tx->esc_clk)) { |
| 453 | dev_err(&pdev->dev, "Couldn't get the esc_clk\n"); |
| 454 | return PTR_ERR(csi2tx->esc_clk); |
| 455 | } |
| 456 | |
| 457 | clk_prepare_enable(csi2tx->p_clk); |
| 458 | dev_cfg = readl(csi2tx->base + CSI2TX_DEVICE_CONFIG_REG); |
| 459 | clk_disable_unprepare(csi2tx->p_clk); |
| 460 | |
| 461 | csi2tx->max_lanes = dev_cfg & CSI2TX_DEVICE_CONFIG_LANES_MASK; |
| 462 | if (csi2tx->max_lanes > CSI2TX_LANES_MAX) { |
| 463 | dev_err(&pdev->dev, "Invalid number of lanes: %u\n", |
| 464 | csi2tx->max_lanes); |
| 465 | return -EINVAL; |
| 466 | } |
| 467 | |
| 468 | csi2tx->max_streams = (dev_cfg & CSI2TX_DEVICE_CONFIG_STREAMS_MASK) >> 4; |
| 469 | if (csi2tx->max_streams > CSI2TX_STREAMS_MAX) { |
| 470 | dev_err(&pdev->dev, "Invalid number of streams: %u\n", |
| 471 | csi2tx->max_streams); |
| 472 | return -EINVAL; |
| 473 | } |
| 474 | |
| 475 | csi2tx->has_internal_dphy = !!(dev_cfg & CSI2TX_DEVICE_CONFIG_HAS_DPHY); |
| 476 | |
| 477 | for (i = 0; i < csi2tx->max_streams; i++) { |
| 478 | char clk_name[16]; |
| 479 | |
| 480 | snprintf(clk_name, sizeof(clk_name), "pixel_if%u_clk", i); |
| 481 | csi2tx->pixel_clk[i] = devm_clk_get(&pdev->dev, clk_name); |
| 482 | if (IS_ERR(csi2tx->pixel_clk[i])) { |
| 483 | dev_err(&pdev->dev, "Couldn't get clock %s\n", |
| 484 | clk_name); |
| 485 | return PTR_ERR(csi2tx->pixel_clk[i]); |
| 486 | } |
| 487 | } |
| 488 | |
| 489 | return 0; |
| 490 | } |
| 491 | |
| 492 | static int csi2tx_check_lanes(struct csi2tx_priv *csi2tx) |
| 493 | { |
| 494 | struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; |
| 495 | struct device_node *ep; |
| 496 | int ret, i; |
| 497 | |
| 498 | ep = of_graph_get_endpoint_by_regs(csi2tx->dev->of_node, 0, 0); |
| 499 | if (!ep) |
| 500 | return -EINVAL; |
| 501 | |
| 502 | ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); |
| 503 | if (ret) { |
| 504 | dev_err(csi2tx->dev, "Could not parse v4l2 endpoint\n"); |
| 505 | goto out; |
| 506 | } |
| 507 | |
| 508 | if (v4l2_ep.bus_type != V4L2_MBUS_CSI2_DPHY) { |
| 509 | dev_err(csi2tx->dev, "Unsupported media bus type: 0x%x\n", |
| 510 | v4l2_ep.bus_type); |
| 511 | ret = -EINVAL; |
| 512 | goto out; |
| 513 | } |
| 514 | |
| 515 | csi2tx->num_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; |
| 516 | if (csi2tx->num_lanes > csi2tx->max_lanes) { |
| 517 | dev_err(csi2tx->dev, |
| 518 | "Current configuration uses more lanes than supported\n"); |
| 519 | ret = -EINVAL; |
| 520 | goto out; |
| 521 | } |
| 522 | |
| 523 | for (i = 0; i < csi2tx->num_lanes; i++) { |
| 524 | if (v4l2_ep.bus.mipi_csi2.data_lanes[i] < 1) { |
| 525 | dev_err(csi2tx->dev, "Invalid lane[%d] number: %u\n", |
| 526 | i, v4l2_ep.bus.mipi_csi2.data_lanes[i]); |
| 527 | ret = -EINVAL; |
| 528 | goto out; |
| 529 | } |
| 530 | } |
| 531 | |
| 532 | memcpy(csi2tx->lanes, v4l2_ep.bus.mipi_csi2.data_lanes, |
| 533 | sizeof(csi2tx->lanes)); |
| 534 | |
| 535 | out: |
| 536 | of_node_put(ep); |
| 537 | return ret; |
| 538 | } |
| 539 | |
| 540 | static const struct csi2tx_vops csi2tx_vops = { |
| 541 | .dphy_setup = csi2tx_dphy_setup, |
| 542 | }; |
| 543 | |
| 544 | static const struct csi2tx_vops csi2tx_v2_vops = { |
| 545 | .dphy_setup = csi2tx_v2_dphy_setup, |
| 546 | }; |
| 547 | |
| 548 | static const struct of_device_id csi2tx_of_table[] = { |
| 549 | { |
| 550 | .compatible = "cdns,csi2tx", |
| 551 | .data = &csi2tx_vops |
| 552 | }, |
| 553 | { |
| 554 | .compatible = "cdns,csi2tx-1.3", |
| 555 | .data = &csi2tx_vops |
| 556 | }, |
| 557 | { |
| 558 | .compatible = "cdns,csi2tx-2.1", |
| 559 | .data = &csi2tx_v2_vops |
| 560 | }, |
| 561 | { } |
| 562 | }; |
| 563 | MODULE_DEVICE_TABLE(of, csi2tx_of_table); |
| 564 | |
| 565 | static int csi2tx_probe(struct platform_device *pdev) |
| 566 | { |
| 567 | struct csi2tx_priv *csi2tx; |
| 568 | const struct of_device_id *of_id; |
| 569 | unsigned int i; |
| 570 | int ret; |
| 571 | |
| 572 | csi2tx = kzalloc(sizeof(*csi2tx), GFP_KERNEL); |
| 573 | if (!csi2tx) |
| 574 | return -ENOMEM; |
| 575 | platform_set_drvdata(pdev, csi2tx); |
| 576 | mutex_init(&csi2tx->lock); |
| 577 | csi2tx->dev = &pdev->dev; |
| 578 | |
| 579 | ret = csi2tx_get_resources(csi2tx, pdev); |
| 580 | if (ret) |
| 581 | goto err_free_priv; |
| 582 | |
| 583 | of_id = of_match_node(csi2tx_of_table, pdev->dev.of_node); |
| 584 | csi2tx->vops = (struct csi2tx_vops *)of_id->data; |
| 585 | |
| 586 | v4l2_subdev_init(&csi2tx->subdev, &csi2tx_subdev_ops); |
| 587 | csi2tx->subdev.owner = THIS_MODULE; |
| 588 | csi2tx->subdev.dev = &pdev->dev; |
| 589 | csi2tx->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; |
| 590 | snprintf(csi2tx->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.%s", |
| 591 | KBUILD_MODNAME, dev_name(&pdev->dev)); |
| 592 | |
| 593 | ret = csi2tx_check_lanes(csi2tx); |
| 594 | if (ret) |
| 595 | goto err_free_priv; |
| 596 | |
| 597 | /* Create our media pads */ |
| 598 | csi2tx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; |
| 599 | csi2tx->pads[CSI2TX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; |
| 600 | for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) |
| 601 | csi2tx->pads[i].flags = MEDIA_PAD_FL_SINK; |
| 602 | |
| 603 | /* |
| 604 | * Only the input pads are considered to have a format at the |
| 605 | * moment. The CSI link can multiplex various streams with |
| 606 | * different formats, and we can't expose this in v4l2 right |
| 607 | * now. |
| 608 | */ |
| 609 | for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) |
| 610 | csi2tx->pad_fmts[i] = fmt_default; |
| 611 | |
| 612 | ret = media_entity_pads_init(&csi2tx->subdev.entity, CSI2TX_PAD_MAX, |
| 613 | csi2tx->pads); |
| 614 | if (ret) |
| 615 | goto err_free_priv; |
| 616 | |
| 617 | ret = v4l2_async_register_subdev(&csi2tx->subdev); |
| 618 | if (ret < 0) |
| 619 | goto err_free_priv; |
| 620 | |
| 621 | dev_info(&pdev->dev, |
| 622 | "Probed CSI2TX with %u/%u lanes, %u streams, %s D-PHY\n", |
| 623 | csi2tx->num_lanes, csi2tx->max_lanes, csi2tx->max_streams, |
| 624 | csi2tx->has_internal_dphy ? "internal" : "no"); |
| 625 | |
| 626 | return 0; |
| 627 | |
| 628 | err_free_priv: |
| 629 | kfree(csi2tx); |
| 630 | return ret; |
| 631 | } |
| 632 | |
| 633 | static int csi2tx_remove(struct platform_device *pdev) |
| 634 | { |
| 635 | struct csi2tx_priv *csi2tx = platform_get_drvdata(pdev); |
| 636 | |
| 637 | v4l2_async_unregister_subdev(&csi2tx->subdev); |
| 638 | kfree(csi2tx); |
| 639 | |
| 640 | return 0; |
| 641 | } |
| 642 | |
| 643 | static struct platform_driver csi2tx_driver = { |
| 644 | .probe = csi2tx_probe, |
| 645 | .remove = csi2tx_remove, |
| 646 | |
| 647 | .driver = { |
| 648 | .name = "cdns-csi2tx", |
| 649 | .of_match_table = csi2tx_of_table, |
| 650 | }, |
| 651 | }; |
| 652 | module_platform_driver(csi2tx_driver); |
| 653 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); |
| 654 | MODULE_DESCRIPTION("Cadence CSI2-TX controller"); |
| 655 | MODULE_LICENSE("GPL"); |