18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for Cadence MIPI-CSI2 TX Controller 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2017-2019 Cadence Design Systems Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/clk.h> 98c2ecf20Sopenharmony_ci#include <linux/delay.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/mutex.h> 138c2ecf20Sopenharmony_ci#include <linux/of.h> 148c2ecf20Sopenharmony_ci#include <linux/of_graph.h> 158c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h> 198c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 208c2ecf20Sopenharmony_ci#include <media/v4l2-fwnode.h> 218c2ecf20Sopenharmony_ci#include <media/v4l2-subdev.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define CSI2TX_DEVICE_CONFIG_REG 0x00 248c2ecf20Sopenharmony_ci#define CSI2TX_DEVICE_CONFIG_STREAMS_MASK GENMASK(6, 4) 258c2ecf20Sopenharmony_ci#define CSI2TX_DEVICE_CONFIG_HAS_DPHY BIT(3) 268c2ecf20Sopenharmony_ci#define CSI2TX_DEVICE_CONFIG_LANES_MASK GENMASK(2, 0) 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define CSI2TX_CONFIG_REG 0x20 298c2ecf20Sopenharmony_ci#define CSI2TX_CONFIG_CFG_REQ BIT(2) 308c2ecf20Sopenharmony_ci#define CSI2TX_CONFIG_SRST_REQ BIT(1) 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_REG 0x28 338c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_CLK_RESET BIT(16) 348c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_LANE_RESET(n) BIT((n) + 12) 358c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_MODE_MASK GENMASK(9, 8) 368c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_MODE_LPDT (2 << 8) 378c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_MODE_HS (1 << 8) 388c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_MODE_ULPS (0 << 8) 398c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_CLK_ENABLE BIT(4) 408c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CFG_LANE_ENABLE(n) BIT(n) 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CLK_WAKEUP_REG 0x2c 438c2ecf20Sopenharmony_ci#define CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(n) ((n) & 0xffff) 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#define CSI2TX_DT_CFG_REG(n) (0x80 + (n) * 8) 468c2ecf20Sopenharmony_ci#define CSI2TX_DT_CFG_DT(n) (((n) & 0x3f) << 2) 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci#define CSI2TX_DT_FORMAT_REG(n) (0x84 + (n) * 8) 498c2ecf20Sopenharmony_ci#define CSI2TX_DT_FORMAT_BYTES_PER_LINE(n) (((n) & 0xffff) << 16) 508c2ecf20Sopenharmony_ci#define CSI2TX_DT_FORMAT_MAX_LINE_NUM(n) ((n) & 0xffff) 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#define CSI2TX_STREAM_IF_CFG_REG(n) (0x100 + (n) * 4) 538c2ecf20Sopenharmony_ci#define CSI2TX_STREAM_IF_CFG_FILL_LEVEL(n) ((n) & 0x1f) 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci/* CSI2TX V2 Registers */ 568c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_REG 0x28 578c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_RESET BIT(16) 588c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_CLOCK_MODE BIT(10) 598c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_MODE_MASK GENMASK(9, 8) 608c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_MODE_LPDT (2 << 8) 618c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_MODE_HS (1 << 8) 628c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_MODE_ULPS (0 << 8) 638c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_CLK_ENABLE BIT(4) 648c2ecf20Sopenharmony_ci#define CSI2TX_V2_DPHY_CFG_LANE_ENABLE(n) BIT(n) 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci#define CSI2TX_LANES_MAX 4 678c2ecf20Sopenharmony_ci#define CSI2TX_STREAMS_MAX 4 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cienum csi2tx_pads { 708c2ecf20Sopenharmony_ci CSI2TX_PAD_SOURCE, 718c2ecf20Sopenharmony_ci CSI2TX_PAD_SINK_STREAM0, 728c2ecf20Sopenharmony_ci CSI2TX_PAD_SINK_STREAM1, 738c2ecf20Sopenharmony_ci CSI2TX_PAD_SINK_STREAM2, 748c2ecf20Sopenharmony_ci CSI2TX_PAD_SINK_STREAM3, 758c2ecf20Sopenharmony_ci CSI2TX_PAD_MAX, 768c2ecf20Sopenharmony_ci}; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistruct csi2tx_fmt { 798c2ecf20Sopenharmony_ci u32 mbus; 808c2ecf20Sopenharmony_ci u32 dt; 818c2ecf20Sopenharmony_ci u32 bpp; 828c2ecf20Sopenharmony_ci}; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistruct csi2tx_priv; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci/* CSI2TX Variant Operations */ 878c2ecf20Sopenharmony_cistruct csi2tx_vops { 888c2ecf20Sopenharmony_ci void (*dphy_setup)(struct csi2tx_priv *csi2tx); 898c2ecf20Sopenharmony_ci}; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistruct csi2tx_priv { 928c2ecf20Sopenharmony_ci struct device *dev; 938c2ecf20Sopenharmony_ci unsigned int count; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci /* 968c2ecf20Sopenharmony_ci * Used to prevent race conditions between multiple, 978c2ecf20Sopenharmony_ci * concurrent calls to start and stop. 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci struct mutex lock; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci void __iomem *base; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci struct csi2tx_vops *vops; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci struct clk *esc_clk; 1068c2ecf20Sopenharmony_ci struct clk *p_clk; 1078c2ecf20Sopenharmony_ci struct clk *pixel_clk[CSI2TX_STREAMS_MAX]; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci struct v4l2_subdev subdev; 1108c2ecf20Sopenharmony_ci struct media_pad pads[CSI2TX_PAD_MAX]; 1118c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt pad_fmts[CSI2TX_PAD_MAX]; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci bool has_internal_dphy; 1148c2ecf20Sopenharmony_ci u8 lanes[CSI2TX_LANES_MAX]; 1158c2ecf20Sopenharmony_ci unsigned int num_lanes; 1168c2ecf20Sopenharmony_ci unsigned int max_lanes; 1178c2ecf20Sopenharmony_ci unsigned int max_streams; 1188c2ecf20Sopenharmony_ci}; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic const struct csi2tx_fmt csi2tx_formats[] = { 1218c2ecf20Sopenharmony_ci { 1228c2ecf20Sopenharmony_ci .mbus = MEDIA_BUS_FMT_UYVY8_1X16, 1238c2ecf20Sopenharmony_ci .bpp = 2, 1248c2ecf20Sopenharmony_ci .dt = 0x1e, 1258c2ecf20Sopenharmony_ci }, 1268c2ecf20Sopenharmony_ci { 1278c2ecf20Sopenharmony_ci .mbus = MEDIA_BUS_FMT_RGB888_1X24, 1288c2ecf20Sopenharmony_ci .bpp = 3, 1298c2ecf20Sopenharmony_ci .dt = 0x24, 1308c2ecf20Sopenharmony_ci }, 1318c2ecf20Sopenharmony_ci}; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic const struct v4l2_mbus_framefmt fmt_default = { 1348c2ecf20Sopenharmony_ci .width = 1280, 1358c2ecf20Sopenharmony_ci .height = 720, 1368c2ecf20Sopenharmony_ci .code = MEDIA_BUS_FMT_RGB888_1X24, 1378c2ecf20Sopenharmony_ci .field = V4L2_FIELD_NONE, 1388c2ecf20Sopenharmony_ci .colorspace = V4L2_COLORSPACE_DEFAULT, 1398c2ecf20Sopenharmony_ci}; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic inline 1428c2ecf20Sopenharmony_cistruct csi2tx_priv *v4l2_subdev_to_csi2tx(struct v4l2_subdev *subdev) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci return container_of(subdev, struct csi2tx_priv, subdev); 1458c2ecf20Sopenharmony_ci} 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic const struct csi2tx_fmt *csi2tx_get_fmt_from_mbus(u32 mbus) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci unsigned int i; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(csi2tx_formats); i++) 1528c2ecf20Sopenharmony_ci if (csi2tx_formats[i].mbus == mbus) 1538c2ecf20Sopenharmony_ci return &csi2tx_formats[i]; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci return NULL; 1568c2ecf20Sopenharmony_ci} 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic int csi2tx_enum_mbus_code(struct v4l2_subdev *subdev, 1598c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 1608c2ecf20Sopenharmony_ci struct v4l2_subdev_mbus_code_enum *code) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci if (code->pad || code->index >= ARRAY_SIZE(csi2tx_formats)) 1638c2ecf20Sopenharmony_ci return -EINVAL; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci code->code = csi2tx_formats[code->index].mbus; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci return 0; 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic struct v4l2_mbus_framefmt * 1718c2ecf20Sopenharmony_ci__csi2tx_get_pad_format(struct v4l2_subdev *subdev, 1728c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 1738c2ecf20Sopenharmony_ci struct v4l2_subdev_format *fmt) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) 1788c2ecf20Sopenharmony_ci return v4l2_subdev_get_try_format(subdev, cfg, 1798c2ecf20Sopenharmony_ci fmt->pad); 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci return &csi2tx->pad_fmts[fmt->pad]; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic int csi2tx_get_pad_format(struct v4l2_subdev *subdev, 1858c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 1868c2ecf20Sopenharmony_ci struct v4l2_subdev_format *fmt) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci const struct v4l2_mbus_framefmt *format; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci /* Multiplexed pad? */ 1918c2ecf20Sopenharmony_ci if (fmt->pad == CSI2TX_PAD_SOURCE) 1928c2ecf20Sopenharmony_ci return -EINVAL; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci format = __csi2tx_get_pad_format(subdev, cfg, fmt); 1958c2ecf20Sopenharmony_ci if (!format) 1968c2ecf20Sopenharmony_ci return -EINVAL; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci fmt->format = *format; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return 0; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic int csi2tx_set_pad_format(struct v4l2_subdev *subdev, 2048c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 2058c2ecf20Sopenharmony_ci struct v4l2_subdev_format *fmt) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci const struct v4l2_mbus_framefmt *src_format = &fmt->format; 2088c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt *dst_format; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci /* Multiplexed pad? */ 2118c2ecf20Sopenharmony_ci if (fmt->pad == CSI2TX_PAD_SOURCE) 2128c2ecf20Sopenharmony_ci return -EINVAL; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci if (!csi2tx_get_fmt_from_mbus(fmt->format.code)) 2158c2ecf20Sopenharmony_ci src_format = &fmt_default; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci dst_format = __csi2tx_get_pad_format(subdev, cfg, fmt); 2188c2ecf20Sopenharmony_ci if (!dst_format) 2198c2ecf20Sopenharmony_ci return -EINVAL; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci *dst_format = *src_format; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci return 0; 2248c2ecf20Sopenharmony_ci} 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_pad_ops csi2tx_pad_ops = { 2278c2ecf20Sopenharmony_ci .enum_mbus_code = csi2tx_enum_mbus_code, 2288c2ecf20Sopenharmony_ci .get_fmt = csi2tx_get_pad_format, 2298c2ecf20Sopenharmony_ci .set_fmt = csi2tx_set_pad_format, 2308c2ecf20Sopenharmony_ci}; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci/* Set Wake Up value in the D-PHY */ 2338c2ecf20Sopenharmony_cistatic void csi2tx_dphy_set_wakeup(struct csi2tx_priv *csi2tx) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci writel(CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(32), 2368c2ecf20Sopenharmony_ci csi2tx->base + CSI2TX_DPHY_CLK_WAKEUP_REG); 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci/* 2408c2ecf20Sopenharmony_ci * Finishes the D-PHY initialization 2418c2ecf20Sopenharmony_ci * reg dphy cfg value to be used 2428c2ecf20Sopenharmony_ci */ 2438c2ecf20Sopenharmony_cistatic void csi2tx_dphy_init_finish(struct csi2tx_priv *csi2tx, u32 reg) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci unsigned int i; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci udelay(10); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci /* Enable our (clock and data) lanes */ 2508c2ecf20Sopenharmony_ci reg |= CSI2TX_DPHY_CFG_CLK_ENABLE; 2518c2ecf20Sopenharmony_ci for (i = 0; i < csi2tx->num_lanes; i++) 2528c2ecf20Sopenharmony_ci reg |= CSI2TX_DPHY_CFG_LANE_ENABLE(csi2tx->lanes[i] - 1); 2538c2ecf20Sopenharmony_ci writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci udelay(10); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci /* Switch to HS mode */ 2588c2ecf20Sopenharmony_ci reg &= ~CSI2TX_DPHY_CFG_MODE_MASK; 2598c2ecf20Sopenharmony_ci writel(reg | CSI2TX_DPHY_CFG_MODE_HS, 2608c2ecf20Sopenharmony_ci csi2tx->base + CSI2TX_DPHY_CFG_REG); 2618c2ecf20Sopenharmony_ci} 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci/* Configures D-PHY in CSIv1.3 */ 2648c2ecf20Sopenharmony_cistatic void csi2tx_dphy_setup(struct csi2tx_priv *csi2tx) 2658c2ecf20Sopenharmony_ci{ 2668c2ecf20Sopenharmony_ci u32 reg; 2678c2ecf20Sopenharmony_ci unsigned int i; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci csi2tx_dphy_set_wakeup(csi2tx); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci /* Put our lanes (clock and data) out of reset */ 2728c2ecf20Sopenharmony_ci reg = CSI2TX_DPHY_CFG_CLK_RESET | CSI2TX_DPHY_CFG_MODE_LPDT; 2738c2ecf20Sopenharmony_ci for (i = 0; i < csi2tx->num_lanes; i++) 2748c2ecf20Sopenharmony_ci reg |= CSI2TX_DPHY_CFG_LANE_RESET(csi2tx->lanes[i] - 1); 2758c2ecf20Sopenharmony_ci writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci csi2tx_dphy_init_finish(csi2tx, reg); 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci/* Configures D-PHY in CSIv2 */ 2818c2ecf20Sopenharmony_cistatic void csi2tx_v2_dphy_setup(struct csi2tx_priv *csi2tx) 2828c2ecf20Sopenharmony_ci{ 2838c2ecf20Sopenharmony_ci u32 reg; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci csi2tx_dphy_set_wakeup(csi2tx); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci /* Put our lanes (clock and data) out of reset */ 2888c2ecf20Sopenharmony_ci reg = CSI2TX_V2_DPHY_CFG_RESET | CSI2TX_V2_DPHY_CFG_MODE_LPDT; 2898c2ecf20Sopenharmony_ci writel(reg, csi2tx->base + CSI2TX_V2_DPHY_CFG_REG); 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci csi2tx_dphy_init_finish(csi2tx, reg); 2928c2ecf20Sopenharmony_ci} 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_cistatic void csi2tx_reset(struct csi2tx_priv *csi2tx) 2958c2ecf20Sopenharmony_ci{ 2968c2ecf20Sopenharmony_ci writel(CSI2TX_CONFIG_SRST_REQ, csi2tx->base + CSI2TX_CONFIG_REG); 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci udelay(10); 2998c2ecf20Sopenharmony_ci} 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_cistatic int csi2tx_start(struct csi2tx_priv *csi2tx) 3028c2ecf20Sopenharmony_ci{ 3038c2ecf20Sopenharmony_ci struct media_entity *entity = &csi2tx->subdev.entity; 3048c2ecf20Sopenharmony_ci struct media_link *link; 3058c2ecf20Sopenharmony_ci unsigned int i; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci csi2tx_reset(csi2tx); 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci writel(CSI2TX_CONFIG_CFG_REQ, csi2tx->base + CSI2TX_CONFIG_REG); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci udelay(10); 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci if (csi2tx->vops && csi2tx->vops->dphy_setup) { 3148c2ecf20Sopenharmony_ci csi2tx->vops->dphy_setup(csi2tx); 3158c2ecf20Sopenharmony_ci udelay(10); 3168c2ecf20Sopenharmony_ci } 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci /* 3198c2ecf20Sopenharmony_ci * Create a static mapping between the CSI virtual channels 3208c2ecf20Sopenharmony_ci * and the input streams. 3218c2ecf20Sopenharmony_ci * 3228c2ecf20Sopenharmony_ci * This should be enhanced, but v4l2 lacks the support for 3238c2ecf20Sopenharmony_ci * changing that mapping dynamically at the moment. 3248c2ecf20Sopenharmony_ci * 3258c2ecf20Sopenharmony_ci * We're protected from the userspace setting up links at the 3268c2ecf20Sopenharmony_ci * same time by the upper layer having called 3278c2ecf20Sopenharmony_ci * media_pipeline_start(). 3288c2ecf20Sopenharmony_ci */ 3298c2ecf20Sopenharmony_ci list_for_each_entry(link, &entity->links, list) { 3308c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt *mfmt; 3318c2ecf20Sopenharmony_ci const struct csi2tx_fmt *fmt; 3328c2ecf20Sopenharmony_ci unsigned int stream; 3338c2ecf20Sopenharmony_ci int pad_idx = -1; 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci /* Only consider our enabled input pads */ 3368c2ecf20Sopenharmony_ci for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) { 3378c2ecf20Sopenharmony_ci struct media_pad *pad = &csi2tx->pads[i]; 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci if ((pad == link->sink) && 3408c2ecf20Sopenharmony_ci (link->flags & MEDIA_LNK_FL_ENABLED)) { 3418c2ecf20Sopenharmony_ci pad_idx = i; 3428c2ecf20Sopenharmony_ci break; 3438c2ecf20Sopenharmony_ci } 3448c2ecf20Sopenharmony_ci } 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci if (pad_idx < 0) 3478c2ecf20Sopenharmony_ci continue; 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci mfmt = &csi2tx->pad_fmts[pad_idx]; 3508c2ecf20Sopenharmony_ci fmt = csi2tx_get_fmt_from_mbus(mfmt->code); 3518c2ecf20Sopenharmony_ci if (!fmt) 3528c2ecf20Sopenharmony_ci continue; 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci stream = pad_idx - CSI2TX_PAD_SINK_STREAM0; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci /* 3578c2ecf20Sopenharmony_ci * We use the stream ID there, but it's wrong. 3588c2ecf20Sopenharmony_ci * 3598c2ecf20Sopenharmony_ci * A stream could very well send a data type that is 3608c2ecf20Sopenharmony_ci * not equal to its stream ID. We need to find a 3618c2ecf20Sopenharmony_ci * proper way to address it. 3628c2ecf20Sopenharmony_ci */ 3638c2ecf20Sopenharmony_ci writel(CSI2TX_DT_CFG_DT(fmt->dt), 3648c2ecf20Sopenharmony_ci csi2tx->base + CSI2TX_DT_CFG_REG(stream)); 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci writel(CSI2TX_DT_FORMAT_BYTES_PER_LINE(mfmt->width * fmt->bpp) | 3678c2ecf20Sopenharmony_ci CSI2TX_DT_FORMAT_MAX_LINE_NUM(mfmt->height + 1), 3688c2ecf20Sopenharmony_ci csi2tx->base + CSI2TX_DT_FORMAT_REG(stream)); 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci /* 3718c2ecf20Sopenharmony_ci * TODO: This needs to be calculated based on the 3728c2ecf20Sopenharmony_ci * output CSI2 clock rate. 3738c2ecf20Sopenharmony_ci */ 3748c2ecf20Sopenharmony_ci writel(CSI2TX_STREAM_IF_CFG_FILL_LEVEL(4), 3758c2ecf20Sopenharmony_ci csi2tx->base + CSI2TX_STREAM_IF_CFG_REG(stream)); 3768c2ecf20Sopenharmony_ci } 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci /* Disable the configuration mode */ 3798c2ecf20Sopenharmony_ci writel(0, csi2tx->base + CSI2TX_CONFIG_REG); 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci return 0; 3828c2ecf20Sopenharmony_ci} 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_cistatic void csi2tx_stop(struct csi2tx_priv *csi2tx) 3858c2ecf20Sopenharmony_ci{ 3868c2ecf20Sopenharmony_ci writel(CSI2TX_CONFIG_CFG_REQ | CSI2TX_CONFIG_SRST_REQ, 3878c2ecf20Sopenharmony_ci csi2tx->base + CSI2TX_CONFIG_REG); 3888c2ecf20Sopenharmony_ci} 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_cistatic int csi2tx_s_stream(struct v4l2_subdev *subdev, int enable) 3918c2ecf20Sopenharmony_ci{ 3928c2ecf20Sopenharmony_ci struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); 3938c2ecf20Sopenharmony_ci int ret = 0; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci mutex_lock(&csi2tx->lock); 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci if (enable) { 3988c2ecf20Sopenharmony_ci /* 3998c2ecf20Sopenharmony_ci * If we're not the first users, there's no need to 4008c2ecf20Sopenharmony_ci * enable the whole controller. 4018c2ecf20Sopenharmony_ci */ 4028c2ecf20Sopenharmony_ci if (!csi2tx->count) { 4038c2ecf20Sopenharmony_ci ret = csi2tx_start(csi2tx); 4048c2ecf20Sopenharmony_ci if (ret) 4058c2ecf20Sopenharmony_ci goto out; 4068c2ecf20Sopenharmony_ci } 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci csi2tx->count++; 4098c2ecf20Sopenharmony_ci } else { 4108c2ecf20Sopenharmony_ci csi2tx->count--; 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci /* 4138c2ecf20Sopenharmony_ci * Let the last user turn off the lights. 4148c2ecf20Sopenharmony_ci */ 4158c2ecf20Sopenharmony_ci if (!csi2tx->count) 4168c2ecf20Sopenharmony_ci csi2tx_stop(csi2tx); 4178c2ecf20Sopenharmony_ci } 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ciout: 4208c2ecf20Sopenharmony_ci mutex_unlock(&csi2tx->lock); 4218c2ecf20Sopenharmony_ci return ret; 4228c2ecf20Sopenharmony_ci} 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_video_ops csi2tx_video_ops = { 4258c2ecf20Sopenharmony_ci .s_stream = csi2tx_s_stream, 4268c2ecf20Sopenharmony_ci}; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_ops csi2tx_subdev_ops = { 4298c2ecf20Sopenharmony_ci .pad = &csi2tx_pad_ops, 4308c2ecf20Sopenharmony_ci .video = &csi2tx_video_ops, 4318c2ecf20Sopenharmony_ci}; 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_cistatic int csi2tx_get_resources(struct csi2tx_priv *csi2tx, 4348c2ecf20Sopenharmony_ci struct platform_device *pdev) 4358c2ecf20Sopenharmony_ci{ 4368c2ecf20Sopenharmony_ci struct resource *res; 4378c2ecf20Sopenharmony_ci unsigned int i; 4388c2ecf20Sopenharmony_ci u32 dev_cfg; 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 4418c2ecf20Sopenharmony_ci csi2tx->base = devm_ioremap_resource(&pdev->dev, res); 4428c2ecf20Sopenharmony_ci if (IS_ERR(csi2tx->base)) 4438c2ecf20Sopenharmony_ci return PTR_ERR(csi2tx->base); 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci csi2tx->p_clk = devm_clk_get(&pdev->dev, "p_clk"); 4468c2ecf20Sopenharmony_ci if (IS_ERR(csi2tx->p_clk)) { 4478c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Couldn't get p_clk\n"); 4488c2ecf20Sopenharmony_ci return PTR_ERR(csi2tx->p_clk); 4498c2ecf20Sopenharmony_ci } 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci csi2tx->esc_clk = devm_clk_get(&pdev->dev, "esc_clk"); 4528c2ecf20Sopenharmony_ci if (IS_ERR(csi2tx->esc_clk)) { 4538c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Couldn't get the esc_clk\n"); 4548c2ecf20Sopenharmony_ci return PTR_ERR(csi2tx->esc_clk); 4558c2ecf20Sopenharmony_ci } 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci clk_prepare_enable(csi2tx->p_clk); 4588c2ecf20Sopenharmony_ci dev_cfg = readl(csi2tx->base + CSI2TX_DEVICE_CONFIG_REG); 4598c2ecf20Sopenharmony_ci clk_disable_unprepare(csi2tx->p_clk); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci csi2tx->max_lanes = dev_cfg & CSI2TX_DEVICE_CONFIG_LANES_MASK; 4628c2ecf20Sopenharmony_ci if (csi2tx->max_lanes > CSI2TX_LANES_MAX) { 4638c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Invalid number of lanes: %u\n", 4648c2ecf20Sopenharmony_ci csi2tx->max_lanes); 4658c2ecf20Sopenharmony_ci return -EINVAL; 4668c2ecf20Sopenharmony_ci } 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci csi2tx->max_streams = (dev_cfg & CSI2TX_DEVICE_CONFIG_STREAMS_MASK) >> 4; 4698c2ecf20Sopenharmony_ci if (csi2tx->max_streams > CSI2TX_STREAMS_MAX) { 4708c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Invalid number of streams: %u\n", 4718c2ecf20Sopenharmony_ci csi2tx->max_streams); 4728c2ecf20Sopenharmony_ci return -EINVAL; 4738c2ecf20Sopenharmony_ci } 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci csi2tx->has_internal_dphy = !!(dev_cfg & CSI2TX_DEVICE_CONFIG_HAS_DPHY); 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci for (i = 0; i < csi2tx->max_streams; i++) { 4788c2ecf20Sopenharmony_ci char clk_name[16]; 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci snprintf(clk_name, sizeof(clk_name), "pixel_if%u_clk", i); 4818c2ecf20Sopenharmony_ci csi2tx->pixel_clk[i] = devm_clk_get(&pdev->dev, clk_name); 4828c2ecf20Sopenharmony_ci if (IS_ERR(csi2tx->pixel_clk[i])) { 4838c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Couldn't get clock %s\n", 4848c2ecf20Sopenharmony_ci clk_name); 4858c2ecf20Sopenharmony_ci return PTR_ERR(csi2tx->pixel_clk[i]); 4868c2ecf20Sopenharmony_ci } 4878c2ecf20Sopenharmony_ci } 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ci return 0; 4908c2ecf20Sopenharmony_ci} 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_cistatic int csi2tx_check_lanes(struct csi2tx_priv *csi2tx) 4938c2ecf20Sopenharmony_ci{ 4948c2ecf20Sopenharmony_ci struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; 4958c2ecf20Sopenharmony_ci struct device_node *ep; 4968c2ecf20Sopenharmony_ci int ret, i; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci ep = of_graph_get_endpoint_by_regs(csi2tx->dev->of_node, 0, 0); 4998c2ecf20Sopenharmony_ci if (!ep) 5008c2ecf20Sopenharmony_ci return -EINVAL; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); 5038c2ecf20Sopenharmony_ci if (ret) { 5048c2ecf20Sopenharmony_ci dev_err(csi2tx->dev, "Could not parse v4l2 endpoint\n"); 5058c2ecf20Sopenharmony_ci goto out; 5068c2ecf20Sopenharmony_ci } 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_ci if (v4l2_ep.bus_type != V4L2_MBUS_CSI2_DPHY) { 5098c2ecf20Sopenharmony_ci dev_err(csi2tx->dev, "Unsupported media bus type: 0x%x\n", 5108c2ecf20Sopenharmony_ci v4l2_ep.bus_type); 5118c2ecf20Sopenharmony_ci ret = -EINVAL; 5128c2ecf20Sopenharmony_ci goto out; 5138c2ecf20Sopenharmony_ci } 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci csi2tx->num_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; 5168c2ecf20Sopenharmony_ci if (csi2tx->num_lanes > csi2tx->max_lanes) { 5178c2ecf20Sopenharmony_ci dev_err(csi2tx->dev, 5188c2ecf20Sopenharmony_ci "Current configuration uses more lanes than supported\n"); 5198c2ecf20Sopenharmony_ci ret = -EINVAL; 5208c2ecf20Sopenharmony_ci goto out; 5218c2ecf20Sopenharmony_ci } 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci for (i = 0; i < csi2tx->num_lanes; i++) { 5248c2ecf20Sopenharmony_ci if (v4l2_ep.bus.mipi_csi2.data_lanes[i] < 1) { 5258c2ecf20Sopenharmony_ci dev_err(csi2tx->dev, "Invalid lane[%d] number: %u\n", 5268c2ecf20Sopenharmony_ci i, v4l2_ep.bus.mipi_csi2.data_lanes[i]); 5278c2ecf20Sopenharmony_ci ret = -EINVAL; 5288c2ecf20Sopenharmony_ci goto out; 5298c2ecf20Sopenharmony_ci } 5308c2ecf20Sopenharmony_ci } 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ci memcpy(csi2tx->lanes, v4l2_ep.bus.mipi_csi2.data_lanes, 5338c2ecf20Sopenharmony_ci sizeof(csi2tx->lanes)); 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_ciout: 5368c2ecf20Sopenharmony_ci of_node_put(ep); 5378c2ecf20Sopenharmony_ci return ret; 5388c2ecf20Sopenharmony_ci} 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_cistatic const struct csi2tx_vops csi2tx_vops = { 5418c2ecf20Sopenharmony_ci .dphy_setup = csi2tx_dphy_setup, 5428c2ecf20Sopenharmony_ci}; 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_cistatic const struct csi2tx_vops csi2tx_v2_vops = { 5458c2ecf20Sopenharmony_ci .dphy_setup = csi2tx_v2_dphy_setup, 5468c2ecf20Sopenharmony_ci}; 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_cistatic const struct of_device_id csi2tx_of_table[] = { 5498c2ecf20Sopenharmony_ci { 5508c2ecf20Sopenharmony_ci .compatible = "cdns,csi2tx", 5518c2ecf20Sopenharmony_ci .data = &csi2tx_vops 5528c2ecf20Sopenharmony_ci }, 5538c2ecf20Sopenharmony_ci { 5548c2ecf20Sopenharmony_ci .compatible = "cdns,csi2tx-1.3", 5558c2ecf20Sopenharmony_ci .data = &csi2tx_vops 5568c2ecf20Sopenharmony_ci }, 5578c2ecf20Sopenharmony_ci { 5588c2ecf20Sopenharmony_ci .compatible = "cdns,csi2tx-2.1", 5598c2ecf20Sopenharmony_ci .data = &csi2tx_v2_vops 5608c2ecf20Sopenharmony_ci }, 5618c2ecf20Sopenharmony_ci { } 5628c2ecf20Sopenharmony_ci}; 5638c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, csi2tx_of_table); 5648c2ecf20Sopenharmony_ci 5658c2ecf20Sopenharmony_cistatic int csi2tx_probe(struct platform_device *pdev) 5668c2ecf20Sopenharmony_ci{ 5678c2ecf20Sopenharmony_ci struct csi2tx_priv *csi2tx; 5688c2ecf20Sopenharmony_ci const struct of_device_id *of_id; 5698c2ecf20Sopenharmony_ci unsigned int i; 5708c2ecf20Sopenharmony_ci int ret; 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci csi2tx = kzalloc(sizeof(*csi2tx), GFP_KERNEL); 5738c2ecf20Sopenharmony_ci if (!csi2tx) 5748c2ecf20Sopenharmony_ci return -ENOMEM; 5758c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, csi2tx); 5768c2ecf20Sopenharmony_ci mutex_init(&csi2tx->lock); 5778c2ecf20Sopenharmony_ci csi2tx->dev = &pdev->dev; 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_ci ret = csi2tx_get_resources(csi2tx, pdev); 5808c2ecf20Sopenharmony_ci if (ret) 5818c2ecf20Sopenharmony_ci goto err_free_priv; 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_ci of_id = of_match_node(csi2tx_of_table, pdev->dev.of_node); 5848c2ecf20Sopenharmony_ci csi2tx->vops = (struct csi2tx_vops *)of_id->data; 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_ci v4l2_subdev_init(&csi2tx->subdev, &csi2tx_subdev_ops); 5878c2ecf20Sopenharmony_ci csi2tx->subdev.owner = THIS_MODULE; 5888c2ecf20Sopenharmony_ci csi2tx->subdev.dev = &pdev->dev; 5898c2ecf20Sopenharmony_ci csi2tx->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; 5908c2ecf20Sopenharmony_ci snprintf(csi2tx->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.%s", 5918c2ecf20Sopenharmony_ci KBUILD_MODNAME, dev_name(&pdev->dev)); 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci ret = csi2tx_check_lanes(csi2tx); 5948c2ecf20Sopenharmony_ci if (ret) 5958c2ecf20Sopenharmony_ci goto err_free_priv; 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_ci /* Create our media pads */ 5988c2ecf20Sopenharmony_ci csi2tx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; 5998c2ecf20Sopenharmony_ci csi2tx->pads[CSI2TX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; 6008c2ecf20Sopenharmony_ci for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) 6018c2ecf20Sopenharmony_ci csi2tx->pads[i].flags = MEDIA_PAD_FL_SINK; 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci /* 6048c2ecf20Sopenharmony_ci * Only the input pads are considered to have a format at the 6058c2ecf20Sopenharmony_ci * moment. The CSI link can multiplex various streams with 6068c2ecf20Sopenharmony_ci * different formats, and we can't expose this in v4l2 right 6078c2ecf20Sopenharmony_ci * now. 6088c2ecf20Sopenharmony_ci */ 6098c2ecf20Sopenharmony_ci for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) 6108c2ecf20Sopenharmony_ci csi2tx->pad_fmts[i] = fmt_default; 6118c2ecf20Sopenharmony_ci 6128c2ecf20Sopenharmony_ci ret = media_entity_pads_init(&csi2tx->subdev.entity, CSI2TX_PAD_MAX, 6138c2ecf20Sopenharmony_ci csi2tx->pads); 6148c2ecf20Sopenharmony_ci if (ret) 6158c2ecf20Sopenharmony_ci goto err_free_priv; 6168c2ecf20Sopenharmony_ci 6178c2ecf20Sopenharmony_ci ret = v4l2_async_register_subdev(&csi2tx->subdev); 6188c2ecf20Sopenharmony_ci if (ret < 0) 6198c2ecf20Sopenharmony_ci goto err_free_priv; 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_ci dev_info(&pdev->dev, 6228c2ecf20Sopenharmony_ci "Probed CSI2TX with %u/%u lanes, %u streams, %s D-PHY\n", 6238c2ecf20Sopenharmony_ci csi2tx->num_lanes, csi2tx->max_lanes, csi2tx->max_streams, 6248c2ecf20Sopenharmony_ci csi2tx->has_internal_dphy ? "internal" : "no"); 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci return 0; 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_cierr_free_priv: 6298c2ecf20Sopenharmony_ci kfree(csi2tx); 6308c2ecf20Sopenharmony_ci return ret; 6318c2ecf20Sopenharmony_ci} 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_cistatic int csi2tx_remove(struct platform_device *pdev) 6348c2ecf20Sopenharmony_ci{ 6358c2ecf20Sopenharmony_ci struct csi2tx_priv *csi2tx = platform_get_drvdata(pdev); 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ci v4l2_async_unregister_subdev(&csi2tx->subdev); 6388c2ecf20Sopenharmony_ci kfree(csi2tx); 6398c2ecf20Sopenharmony_ci 6408c2ecf20Sopenharmony_ci return 0; 6418c2ecf20Sopenharmony_ci} 6428c2ecf20Sopenharmony_ci 6438c2ecf20Sopenharmony_cistatic struct platform_driver csi2tx_driver = { 6448c2ecf20Sopenharmony_ci .probe = csi2tx_probe, 6458c2ecf20Sopenharmony_ci .remove = csi2tx_remove, 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_ci .driver = { 6488c2ecf20Sopenharmony_ci .name = "cdns-csi2tx", 6498c2ecf20Sopenharmony_ci .of_match_table = csi2tx_of_table, 6508c2ecf20Sopenharmony_ci }, 6518c2ecf20Sopenharmony_ci}; 6528c2ecf20Sopenharmony_cimodule_platform_driver(csi2tx_driver); 6538c2ecf20Sopenharmony_ciMODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); 6548c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Cadence CSI2-TX controller"); 6558c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 656