cregit-Linux how code gets into the kernel

Release 4.18 drivers/media/platform/rcar-vin/rcar-csi2.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Driver for Renesas R-Car MIPI CSI-2 Receiver
 *
 * Copyright (C) 2018 Renesas Electronics Corp.
 */

#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/sys_soc.h>

#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-mc.h>
#include <media/v4l2-subdev.h>

struct rcar_csi2;

/* Register offsets and bits */

/* Control Timing Select */

#define TREF_REG			0x00

#define TREF_TREF			BIT(0)

/* Software Reset */

#define SRST_REG			0x04

#define SRST_SRST			BIT(0)

/* PHY Operation Control */

#define PHYCNT_REG			0x08

#define PHYCNT_SHUTDOWNZ		BIT(17)

#define PHYCNT_RSTZ			BIT(16)

#define PHYCNT_ENABLECLK		BIT(4)

#define PHYCNT_ENABLE_3			BIT(3)

#define PHYCNT_ENABLE_2			BIT(2)

#define PHYCNT_ENABLE_1			BIT(1)

#define PHYCNT_ENABLE_0			BIT(0)

/* Checksum Control */

#define CHKSUM_REG			0x0c

#define CHKSUM_ECC_EN			BIT(1)

#define CHKSUM_CRC_EN			BIT(0)

/*
 * Channel Data Type Select
 * VCDT[0-15]:  Channel 1 VCDT[16-31]:  Channel 2
 * VCDT2[0-15]: Channel 3 VCDT2[16-31]: Channel 4
 */

#define VCDT_REG			0x10

#define VCDT2_REG			0x14

#define VCDT_VCDTN_EN			BIT(15)

#define VCDT_SEL_VC(n)			(((n) & 0x3) << 8)

#define VCDT_SEL_DTN_ON			BIT(6)

#define VCDT_SEL_DT(n)			(((n) & 0x3f) << 0)

/* Frame Data Type Select */

#define FRDT_REG			0x18

/* Field Detection Control */

#define FLD_REG				0x1c

#define FLD_FLD_NUM(n)			(((n) & 0xff) << 16)

#define FLD_FLD_EN4			BIT(3)

#define FLD_FLD_EN3			BIT(2)

#define FLD_FLD_EN2			BIT(1)

#define FLD_FLD_EN			BIT(0)

/* Automatic Standby Control */

#define ASTBY_REG			0x20

/* Long Data Type Setting 0 */

#define LNGDT0_REG			0x28

/* Long Data Type Setting 1 */

#define LNGDT1_REG			0x2c

/* Interrupt Enable */

#define INTEN_REG			0x30

/* Interrupt Source Mask */

#define INTCLOSE_REG			0x34

/* Interrupt Status Monitor */

#define INTSTATE_REG			0x38

#define INTSTATE_INT_ULPS_START		BIT(7)

#define INTSTATE_INT_ULPS_END		BIT(6)

/* Interrupt Error Status Monitor */

#define INTERRSTATE_REG			0x3c

/* Short Packet Data */

#define SHPDAT_REG			0x40

/* Short Packet Count */

#define SHPCNT_REG			0x44

/* LINK Operation Control */

#define LINKCNT_REG			0x48

#define LINKCNT_MONITOR_EN		BIT(31)

#define LINKCNT_REG_MONI_PACT_EN	BIT(25)

#define LINKCNT_ICLK_NONSTOP		BIT(24)

/* Lane Swap */

#define LSWAP_REG			0x4c

#define LSWAP_L3SEL(n)			(((n) & 0x3) << 6)

#define LSWAP_L2SEL(n)			(((n) & 0x3) << 4)

#define LSWAP_L1SEL(n)			(((n) & 0x3) << 2)

#define LSWAP_L0SEL(n)			(((n) & 0x3) << 0)

/* PHY Test Interface Write Register */

#define PHTW_REG			0x50

#define PHTW_DWEN			BIT(24)

#define PHTW_TESTDIN_DATA(n)		(((n & 0xff)) << 16)

#define PHTW_CWEN			BIT(8)

#define PHTW_TESTDIN_CODE(n)		((n & 0xff))


struct phtw_value {
	
u16 data;
	
u16 code;
};


struct rcsi2_mbps_reg {
	
u16 mbps;
	
u16 reg;
};


static const struct rcsi2_mbps_reg phtw_mbps_h3_v3h_m3n[] = {
	{ .mbps =   80, .reg = 0x86 },
	{ .mbps =   90, .reg = 0x86 },
	{ .mbps =  100, .reg = 0x87 },
	{ .mbps =  110, .reg = 0x87 },
	{ .mbps =  120, .reg = 0x88 },
	{ .mbps =  130, .reg = 0x88 },
	{ .mbps =  140, .reg = 0x89 },
	{ .mbps =  150, .reg = 0x89 },
	{ .mbps =  160, .reg = 0x8a },
	{ .mbps =  170, .reg = 0x8a },
	{ .mbps =  180, .reg = 0x8b },
	{ .mbps =  190, .reg = 0x8b },
	{ .mbps =  205, .reg = 0x8c },
	{ .mbps =  220, .reg = 0x8d },
	{ .mbps =  235, .reg = 0x8e },
	{ .mbps =  250, .reg = 0x8e },
	{ /* sentinel */ },
};


static const struct rcsi2_mbps_reg phtw_mbps_v3m_e3[] = {
	{ .mbps =   80, .reg = 0x00 },
	{ .mbps =   90, .reg = 0x20 },
	{ .mbps =  100, .reg = 0x40 },
	{ .mbps =  110, .reg = 0x02 },
	{ .mbps =  130, .reg = 0x22 },
	{ .mbps =  140, .reg = 0x42 },
	{ .mbps =  150, .reg = 0x04 },
	{ .mbps =  170, .reg = 0x24 },
	{ .mbps =  180, .reg = 0x44 },
	{ .mbps =  200, .reg = 0x06 },
	{ .mbps =  220, .reg = 0x26 },
	{ .mbps =  240, .reg = 0x46 },
	{ .mbps =  250, .reg = 0x08 },
	{ .mbps =  270, .reg = 0x28 },
	{ .mbps =  300, .reg = 0x0a },
	{ .mbps =  330, .reg = 0x2a },
	{ .mbps =  360, .reg = 0x4a },
	{ .mbps =  400, .reg = 0x0c },
	{ .mbps =  450, .reg = 0x2c },
	{ .mbps =  500, .reg = 0x0e },
	{ .mbps =  550, .reg = 0x2e },
	{ .mbps =  600, .reg = 0x10 },
	{ .mbps =  650, .reg = 0x30 },
	{ .mbps =  700, .reg = 0x12 },
	{ .mbps =  750, .reg = 0x32 },
	{ .mbps =  800, .reg = 0x52 },
	{ .mbps =  850, .reg = 0x72 },
	{ .mbps =  900, .reg = 0x14 },
	{ .mbps =  950, .reg = 0x34 },
	{ .mbps = 1000, .reg = 0x54 },
	{ .mbps = 1050, .reg = 0x74 },
	{ .mbps = 1125, .reg = 0x16 },
	{ /* sentinel */ },
};

/* PHY Test Interface Clear */

#define PHTC_REG			0x58

#define PHTC_TESTCLR			BIT(0)

/* PHY Frequency Control */

#define PHYPLL_REG			0x68

#define PHYPLL_HSFREQRANGE(n)		((n) << 16)


static const struct rcsi2_mbps_reg hsfreqrange_h3_v3h_m3n[] = {
	{ .mbps =   80, .reg = 0x00 },
	{ .mbps =   90, .reg = 0x10 },
	{ .mbps =  100, .reg = 0x20 },
	{ .mbps =  110, .reg = 0x30 },
	{ .mbps =  120, .reg = 0x01 },
	{ .mbps =  130, .reg = 0x11 },
	{ .mbps =  140, .reg = 0x21 },
	{ .mbps =  150, .reg = 0x31 },
	{ .mbps =  160, .reg = 0x02 },
	{ .mbps =  170, .reg = 0x12 },
	{ .mbps =  180, .reg = 0x22 },
	{ .mbps =  190, .reg = 0x32 },
	{ .mbps =  205, .reg = 0x03 },
	{ .mbps =  220, .reg = 0x13 },
	{ .mbps =  235, .reg = 0x23 },
	{ .mbps =  250, .reg = 0x33 },
	{ .mbps =  275, .reg = 0x04 },
	{ .mbps =  300, .reg = 0x14 },
	{ .mbps =  325, .reg = 0x25 },
	{ .mbps =  350, .reg = 0x35 },
	{ .mbps =  400, .reg = 0x05 },
	{ .mbps =  450, .reg = 0x16 },
	{ .mbps =  500, .reg = 0x26 },
	{ .mbps =  550, .reg = 0x37 },
	{ .mbps =  600, .reg = 0x07 },
	{ .mbps =  650, .reg = 0x18 },
	{ .mbps =  700, .reg = 0x28 },
	{ .mbps =  750, .reg = 0x39 },
	{ .mbps =  800, .reg = 0x09 },
	{ .mbps =  850, .reg = 0x19 },
	{ .mbps =  900, .reg = 0x29 },
	{ .mbps =  950, .reg = 0x3a },
	{ .mbps = 1000, .reg = 0x0a },
	{ .mbps = 1050, .reg = 0x1a },
	{ .mbps = 1100, .reg = 0x2a },
	{ .mbps = 1150, .reg = 0x3b },
	{ .mbps = 1200, .reg = 0x0b },
	{ .mbps = 1250, .reg = 0x1b },
	{ .mbps = 1300, .reg = 0x2b },
	{ .mbps = 1350, .reg = 0x3c },
	{ .mbps = 1400, .reg = 0x0c },
	{ .mbps = 1450, .reg = 0x1c },
	{ .mbps = 1500, .reg = 0x2c },
	{ /* sentinel */ },
};


static const struct rcsi2_mbps_reg hsfreqrange_m3w_h3es1[] = {
	{ .mbps =   80,	.reg = 0x00 },
	{ .mbps =   90,	.reg = 0x10 },
	{ .mbps =  100,	.reg = 0x20 },
	{ .mbps =  110,	.reg = 0x30 },
	{ .mbps =  120,	.reg = 0x01 },
	{ .mbps =  130,	.reg = 0x11 },
	{ .mbps =  140,	.reg = 0x21 },
	{ .mbps =  150,	.reg = 0x31 },
	{ .mbps =  160,	.reg = 0x02 },
	{ .mbps =  170,	.reg = 0x12 },
	{ .mbps =  180,	.reg = 0x22 },
	{ .mbps =  190,	.reg = 0x32 },
	{ .mbps =  205,	.reg = 0x03 },
	{ .mbps =  220,	.reg = 0x13 },
	{ .mbps =  235,	.reg = 0x23 },
	{ .mbps =  250,	.reg = 0x33 },
	{ .mbps =  275,	.reg = 0x04 },
	{ .mbps =  300,	.reg = 0x14 },
	{ .mbps =  325,	.reg = 0x05 },
	{ .mbps =  350,	.reg = 0x15 },
	{ .mbps =  400,	.reg = 0x25 },
	{ .mbps =  450,	.reg = 0x06 },
	{ .mbps =  500,	.reg = 0x16 },
	{ .mbps =  550,	.reg = 0x07 },
	{ .mbps =  600,	.reg = 0x17 },
	{ .mbps =  650,	.reg = 0x08 },
	{ .mbps =  700,	.reg = 0x18 },
	{ .mbps =  750,	.reg = 0x09 },
	{ .mbps =  800,	.reg = 0x19 },
	{ .mbps =  850,	.reg = 0x29 },
	{ .mbps =  900,	.reg = 0x39 },
	{ .mbps =  950,	.reg = 0x0a },
	{ .mbps = 1000,	.reg = 0x1a },
	{ .mbps = 1050,	.reg = 0x2a },
	{ .mbps = 1100,	.reg = 0x3a },
	{ .mbps = 1150,	.reg = 0x0b },
	{ .mbps = 1200,	.reg = 0x1b },
	{ .mbps = 1250,	.reg = 0x2b },
	{ .mbps = 1300,	.reg = 0x3b },
	{ .mbps = 1350,	.reg = 0x0c },
	{ .mbps = 1400,	.reg = 0x1c },
	{ .mbps = 1450,	.reg = 0x2c },
	{ .mbps = 1500,	.reg = 0x3c },
	{ /* sentinel */ },
};

/* PHY ESC Error Monitor */

#define PHEERM_REG			0x74

/* PHY Clock Lane Monitor */

#define PHCLM_REG			0x78

#define PHCLM_STOPSTATECKL		BIT(0)

/* PHY Data Lane Monitor */

#define PHDLM_REG			0x7c

/* CSI0CLK Frequency Configuration Preset Register */

#define CSI0CLKFCPR_REG			0x260

#define CSI0CLKFREQRANGE(n)		((n & 0x3f) << 16)


struct rcar_csi2_format {
	
u32 code;
	
unsigned int datatype;
	
unsigned int bpp;
};


static const struct rcar_csi2_format rcar_csi2_formats[] = {
	{ .code = MEDIA_BUS_FMT_RGB888_1X24,	.datatype = 0x24, .bpp = 24 },
	{ .code = MEDIA_BUS_FMT_UYVY8_1X16,	.datatype = 0x1e, .bpp = 16 },
	{ .code = MEDIA_BUS_FMT_YUYV8_1X16,	.datatype = 0x1e, .bpp = 16 },
	{ .code = MEDIA_BUS_FMT_UYVY8_2X8,	.datatype = 0x1e, .bpp = 16 },
	{ .code = MEDIA_BUS_FMT_YUYV10_2X10,	.datatype = 0x1e, .bpp = 20 },
};


static const struct rcar_csi2_format *rcsi2_code_to_fmt(unsigned int code) { unsigned int i; for (i = 0; i < ARRAY_SIZE(rcar_csi2_formats); i++) if (rcar_csi2_formats[i].code == code) return &rcar_csi2_formats[i]; return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund54100.00%1100.00%
Total54100.00%1100.00%

enum rcar_csi2_pads { RCAR_CSI2_SINK, RCAR_CSI2_SOURCE_VC0, RCAR_CSI2_SOURCE_VC1, RCAR_CSI2_SOURCE_VC2, RCAR_CSI2_SOURCE_VC3, NR_OF_RCAR_CSI2_PAD, }; struct rcar_csi2_info { int (*init_phtw)(struct rcar_csi2 *priv, unsigned int mbps); const struct rcsi2_mbps_reg *hsfreqrange; unsigned int csi0clkfreqrange; bool clear_ulps; }; struct rcar_csi2 { struct device *dev; void __iomem *base; const struct rcar_csi2_info *info; struct v4l2_subdev subdev; struct media_pad pads[NR_OF_RCAR_CSI2_PAD]; struct v4l2_async_notifier notifier; struct v4l2_async_subdev asd; struct v4l2_subdev *remote; struct v4l2_mbus_framefmt mf; struct mutex lock; int stream_count; unsigned short lanes; unsigned char lane_swap[4]; };
static inline struct rcar_csi2 *sd_to_csi2(struct v4l2_subdev *sd) { return container_of(sd, struct rcar_csi2, subdev); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund25100.00%1100.00%
Total25100.00%1100.00%


static inline struct rcar_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) { return container_of(n, struct rcar_csi2, notifier); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund25100.00%1100.00%
Total25100.00%1100.00%


static u32 rcsi2_read(struct rcar_csi2 *priv, unsigned int reg) { return ioread32(priv->base + reg); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund25100.00%1100.00%
Total25100.00%1100.00%


static void rcsi2_write(struct rcar_csi2 *priv, unsigned int reg, u32 data) { iowrite32(data, priv->base + reg); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund29100.00%1100.00%
Total29100.00%1100.00%


static void rcsi2_reset(struct rcar_csi2 *priv) { rcsi2_write(priv, SRST_REG, SRST_SRST); usleep_range(100, 150); rcsi2_write(priv, SRST_REG, 0); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund36100.00%1100.00%
Total36100.00%1100.00%


static int rcsi2_wait_phy_start(struct rcar_csi2 *priv) { unsigned int timeout; /* Wait for the clock and data lanes to enter LP-11 state. */ for (timeout = 0; timeout <= 20; timeout++) { const u32 lane_mask = (1 << priv->lanes) - 1; if ((rcsi2_read(priv, PHCLM_REG) & PHCLM_STOPSTATECKL) && (rcsi2_read(priv, PHDLM_REG) & lane_mask) == lane_mask) return 0; usleep_range(1000, 2000); } dev_err(priv->dev, "Timeout waiting for LP-11 state\n"); return -ETIMEDOUT; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund94100.00%1100.00%
Total94100.00%1100.00%


static int rcsi2_set_phypll(struct rcar_csi2 *priv, unsigned int mbps) { const struct rcsi2_mbps_reg *hsfreq; for (hsfreq = priv->info->hsfreqrange; hsfreq->mbps != 0; hsfreq++) if (hsfreq->mbps >= mbps) break; if (!hsfreq->mbps) { dev_err(priv->dev, "Unsupported PHY speed (%u Mbps)", mbps); return -ERANGE; } rcsi2_write(priv, PHYPLL_REG, PHYPLL_HSFREQRANGE(hsfreq->reg)); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund90100.00%1100.00%
Total90100.00%1100.00%


static int rcsi2_calc_mbps(struct rcar_csi2 *priv, unsigned int bpp) { struct v4l2_subdev *source; struct v4l2_ctrl *ctrl; u64 mbps; if (!priv->remote) return -ENODEV; source = priv->remote; /* Read the pixel rate control from remote. */ ctrl = v4l2_ctrl_find(source->ctrl_handler, V4L2_CID_PIXEL_RATE); if (!ctrl) { dev_err(priv->dev, "no pixel rate control in subdev %s\n", source->name); return -EINVAL; } /* * Calculate the phypll in mbps. * link_freq = (pixel_rate * bits_per_sample) / (2 * nr_of_lanes) * bps = link_freq * 2 */ mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * bpp; do_div(mbps, priv->lanes * 1000000); return mbps; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund105100.00%1100.00%
Total105100.00%1100.00%


static int rcsi2_start(struct rcar_csi2 *priv) { const struct rcar_csi2_format *format; u32 phycnt, vcdt = 0, vcdt2 = 0; unsigned int i; int mbps, ret; dev_dbg(priv->dev, "Input size (%ux%u%c)\n", priv->mf.width, priv->mf.height, priv->mf.field == V4L2_FIELD_NONE ? 'p' : 'i'); /* Code is validated in set_fmt. */ format = rcsi2_code_to_fmt(priv->mf.code); /* * Enable all Virtual Channels. * * NOTE: It's not possible to get individual datatype for each * source virtual channel. Once this is possible in V4L2 * it should be used here. */ for (i = 0; i < 4; i++) { u32 vcdt_part; vcdt_part = VCDT_SEL_VC(i) | VCDT_VCDTN_EN | VCDT_SEL_DTN_ON | VCDT_SEL_DT(format->datatype); /* Store in correct reg and offset. */ if (i < 2) vcdt |= vcdt_part << ((i % 2) * 16); else vcdt2 |= vcdt_part << ((i % 2) * 16); } phycnt = PHYCNT_ENABLECLK; phycnt |= (1 << priv->lanes) - 1; mbps = rcsi2_calc_mbps(priv, format->bpp); if (mbps < 0) return mbps; /* Init */ rcsi2_write(priv, TREF_REG, TREF_TREF); rcsi2_reset(priv); rcsi2_write(priv, PHTC_REG, 0); /* Configure */ rcsi2_write(priv, FLD_REG, FLD_FLD_NUM(2) | FLD_FLD_EN4 | FLD_FLD_EN3 | FLD_FLD_EN2 | FLD_FLD_EN); rcsi2_write(priv, VCDT_REG, vcdt); rcsi2_write(priv, VCDT2_REG, vcdt2); /* Lanes are zero indexed. */ rcsi2_write(priv, LSWAP_REG, LSWAP_L0SEL(priv->lane_swap[0] - 1) | LSWAP_L1SEL(priv->lane_swap[1] - 1) | LSWAP_L2SEL(priv->lane_swap[2] - 1) | LSWAP_L3SEL(priv->lane_swap[3] - 1)); /* Start */ if (priv->info->init_phtw) { ret = priv->info->init_phtw(priv, mbps); if (ret) return ret; } if (priv->info->hsfreqrange) { ret = rcsi2_set_phypll(priv, mbps); if (ret) return ret; } if (priv->info->csi0clkfreqrange) rcsi2_write(priv, CSI0CLKFCPR_REG, CSI0CLKFREQRANGE(priv->info->csi0clkfreqrange)); rcsi2_write(priv, PHYCNT_REG, phycnt); rcsi2_write(priv, LINKCNT_REG, LINKCNT_MONITOR_EN | LINKCNT_REG_MONI_PACT_EN | LINKCNT_ICLK_NONSTOP); rcsi2_write(priv, PHYCNT_REG, phycnt | PHYCNT_SHUTDOWNZ); rcsi2_write(priv, PHYCNT_REG, phycnt | PHYCNT_SHUTDOWNZ | PHYCNT_RSTZ); ret = rcsi2_wait_phy_start(priv); if (ret) return ret; /* Clear Ultra Low Power interrupt. */ if (priv->info->clear_ulps) rcsi2_write(priv, INTSTATE_REG, INTSTATE_INT_ULPS_START | INTSTATE_INT_ULPS_END); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund474100.00%1100.00%
Total474100.00%1100.00%


static void rcsi2_stop(struct rcar_csi2 *priv) { rcsi2_write(priv, PHYCNT_REG, 0); rcsi2_reset(priv); rcsi2_write(priv, PHTC_REG, PHTC_TESTCLR); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund34100.00%1100.00%
Total34100.00%1100.00%


static int rcsi2_s_stream(struct v4l2_subdev *sd, int enable) { struct rcar_csi2 *priv = sd_to_csi2(sd); struct v4l2_subdev *nextsd; int ret = 0; mutex_lock(&priv->lock); if (!priv->remote) { ret = -ENODEV; goto out; } nextsd = priv->remote; if (enable && priv->stream_count == 0) { pm_runtime_get_sync(priv->dev); ret = rcsi2_start(priv); if (ret) { pm_runtime_put(priv->dev); goto out; } ret = v4l2_subdev_call(nextsd, video, s_stream, 1); if (ret) { rcsi2_stop(priv); pm_runtime_put(priv->dev); goto out; } } else if (!enable && priv->stream_count == 1) { rcsi2_stop(priv); v4l2_subdev_call(nextsd, video, s_stream, 0); pm_runtime_put(priv->dev); } priv->stream_count += enable ? 1 : -1; out: mutex_unlock(&priv->lock); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund202100.00%1100.00%
Total202100.00%1100.00%


static int rcsi2_set_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *format) { struct rcar_csi2 *priv = sd_to_csi2(sd); struct v4l2_mbus_framefmt *framefmt; if (!rcsi2_code_to_fmt(format->format.code)) format->format.code = rcar_csi2_formats[0].code; if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { priv->mf = format->format; } else { framefmt = v4l2_subdev_get_try_format(sd, cfg, 0); *framefmt = format->format; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund103100.00%2100.00%
Total103100.00%2100.00%


static int rcsi2_get_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *format) { struct rcar_csi2 *priv = sd_to_csi2(sd); if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) format->format = priv->mf; else format->format = *v4l2_subdev_get_try_format(sd, cfg, 0); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund65100.00%1100.00%
Total65100.00%1100.00%

static const struct v4l2_subdev_video_ops rcar_csi2_video_ops = { .s_stream = rcsi2_s_stream, }; static const struct v4l2_subdev_pad_ops rcar_csi2_pad_ops = { .set_fmt = rcsi2_set_pad_format, .get_fmt = rcsi2_get_pad_format, }; static const struct v4l2_subdev_ops rcar_csi2_subdev_ops = { .video = &rcar_csi2_video_ops, .pad = &rcar_csi2_pad_ops, }; /* ----------------------------------------------------------------------------- * Async handling and registration of subdevices and links. */
static int rcsi2_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd) { struct rcar_csi2 *priv = notifier_to_csi2(notifier); int pad; pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, MEDIA_PAD_FL_SOURCE); if (pad < 0) { dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name); return pad; } priv->remote = subdev; dev_dbg(priv->dev, "Bound %s pad: %d\n", subdev->name, pad); return media_create_pad_link(&subdev->entity, pad, &priv->subdev.entity, 0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund121100.00%1100.00%
Total121100.00%1100.00%


static void rcsi2_notify_unbind(struct v4l2_async_notifier *notifier, struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd) { struct rcar_csi2 *priv = notifier_to_csi2(notifier); priv->remote = NULL; dev_dbg(priv->dev, "Unbind %s\n", subdev->name); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund50100.00%1100.00%
Total50100.00%1100.00%

static const struct v4l2_async_notifier_operations rcar_csi2_notify_ops = { .bound = rcsi2_notify_bound, .unbind = rcsi2_notify_unbind, };
static int rcsi2_parse_v4l2(struct rcar_csi2 *priv, struct v4l2_fwnode_endpoint *vep) { unsigned int i; /* Only port 0 endpoint 0 is valid. */ if (vep->base.port || vep->base.id) return -ENOTCONN; if (vep->bus_type != V4L2_MBUS_CSI2) { dev_err(priv->dev, "Unsupported bus: %u\n", vep->bus_type); return -EINVAL; } priv->lanes = vep->bus.mipi_csi2.num_data_lanes; if (priv->lanes != 1 && priv->lanes != 2 && priv->lanes != 4) { dev_err(priv->dev, "Unsupported number of data-lanes: %u\n", priv->lanes); return -EINVAL; } for (i = 0; i < ARRAY_SIZE(priv->lane_swap); i++) { priv->lane_swap[i] = i < priv->lanes ? vep->bus.mipi_csi2.data_lanes[i] : i; /* Check for valid lane number. */ if (priv->lane_swap[i] < 1 || priv->lane_swap[i] > 4) { dev_err(priv->dev, "data-lanes must be in 1-4 range\n"); return -EINVAL; } } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund202100.00%1100.00%
Total202100.00%1100.00%


static int rcsi2_parse_dt(struct rcar_csi2 *priv) { struct device_node *ep; struct v4l2_fwnode_endpoint v4l2_ep; int ret; ep = of_graph_get_endpoint_by_regs(priv->dev->of_node, 0, 0); if (!ep) { dev_err(priv->dev, "Not connected to subdevice\n"); return -EINVAL; } ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); if (ret) { dev_err(priv->dev, "Could not parse v4l2 endpoint\n"); of_node_put(ep); return -EINVAL; } ret = rcsi2_parse_v4l2(priv, &v4l2_ep); if (ret) { of_node_put(ep); return ret; } priv->asd.match.fwnode = fwnode_graph_get_remote_endpoint(of_fwnode_handle(ep)); priv->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; of_node_put(ep); priv->notifier.subdevs = devm_kzalloc(priv->dev, sizeof(*priv->notifier.subdevs), GFP_KERNEL); if (!priv->notifier.subdevs) return -ENOMEM; priv->notifier.num_subdevs = 1; priv->notifier.subdevs[0] = &priv->asd; priv->notifier.ops = &rcar_csi2_notify_ops; dev_dbg(priv->dev, "Found '%pOF'\n", to_of_node(priv->asd.match.fwnode)); return v4l2_async_subdev_notifier_register(&priv->subdev, &priv->notifier); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund251100.00%1100.00%
Total251100.00%1100.00%

/* ----------------------------------------------------------------------------- * PHTW initialization sequences. * * NOTE: Magic values are from the datasheet and lack documentation. */
static int rcsi2_phtw_write(struct rcar_csi2 *priv, u16 data, u16 code) { unsigned int timeout; rcsi2_write(priv, PHTW_REG, PHTW_DWEN | PHTW_TESTDIN_DATA(data) | PHTW_CWEN | PHTW_TESTDIN_CODE(code)); /* Wait for DWEN and CWEN to be cleared by hardware. */ for (timeout = 0; timeout <= 20; timeout++) { if (!(rcsi2_read(priv, PHTW_REG) & (PHTW_DWEN | PHTW_CWEN))) return 0; usleep_range(1000, 2000); } dev_err(priv->dev, "Timeout waiting for PHTW_DWEN and/or PHTW_CWEN\n"); return -ETIMEDOUT; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund99100.00%1100.00%
Total99100.00%1100.00%


static int rcsi2_phtw_write_array(struct rcar_csi2 *priv, const struct phtw_value *values) { const struct phtw_value *value; int ret; for (value = values; value->data || value->code; value++) { ret = rcsi2_phtw_write(priv, value->data, value->code); if (ret) return ret; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund70100.00%1100.00%
Total70100.00%1100.00%


static int rcsi2_phtw_write_mbps(struct rcar_csi2 *priv, unsigned int mbps, const struct rcsi2_mbps_reg *values, u16 code) { const struct rcsi2_mbps_reg *value; for (value = values; value->mbps; value++) if (value->mbps >= mbps) break; if (!value->mbps) { dev_err(priv->dev, "Unsupported PHY speed (%u Mbps)", mbps); return -ERANGE; } return rcsi2_phtw_write(priv, value->reg, code); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund88100.00%1100.00%
Total88100.00%1100.00%


static int rcsi2_init_phtw_h3_v3h_m3n(struct rcar_csi2 *priv, unsigned int mbps) { static const struct phtw_value step1[] = { { .data = 0xcc, .code = 0xe2 }, { .data = 0x01, .code = 0xe3 }, { .data = 0x11, .code = 0xe4 }, { .data = 0x01, .code = 0xe5 }, { .data = 0x10, .code = 0x04 }, { /* sentinel */ }, }; static const struct phtw_value step2[] = { { .data = 0x38, .code = 0x08 }, { .data = 0x01, .code = 0x00 }, { .data = 0x4b, .code = 0xac }, { .data = 0x03, .code = 0x00 }, { .data = 0x80, .code = 0x07 }, { /* sentinel */ }, }; int ret; ret = rcsi2_phtw_write_array(priv, step1); if (ret) return ret; if (mbps <= 250) { ret = rcsi2_phtw_write(priv, 0x39, 0x05); if (ret) return ret; ret = rcsi2_phtw_write_mbps(priv, mbps, phtw_mbps_h3_v3h_m3n, 0xf1); if (ret) return ret; } return rcsi2_phtw_write_array(priv, step2); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund234100.00%1100.00%
Total234100.00%1100.00%


static int rcsi2_init_phtw_v3m_e3(struct rcar_csi2 *priv, unsigned int mbps) { static const struct phtw_value step1[] = { { .data = 0xed, .code = 0x34 }, { .data = 0xed, .code = 0x44 }, { .data = 0xed, .code = 0x54 }, { .data = 0xed, .code = 0x84 }, { .data = 0xed, .code = 0x94 }, { /* sentinel */ }, }; int ret; ret = rcsi2_phtw_write_mbps(priv, mbps, phtw_mbps_v3m_e3, 0x44); if (ret) return ret; return rcsi2_phtw_write_array(priv, step1); }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund119100.00%1100.00%
Total119100.00%1100.00%

/* ----------------------------------------------------------------------------- * Platform Device Driver. */ static const struct media_entity_operations rcar_csi2_entity_ops = { .link_validate = v4l2_subdev_link_validate, };
static int rcsi2_probe_resources(struct rcar_csi2 *priv, struct platform_device *pdev) { struct resource *res; int irq; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund87100.00%1100.00%
Total87100.00%1100.00%

static const struct rcar_csi2_info rcar_csi2_info_r8a7795 = { .init_phtw = rcsi2_init_phtw_h3_v3h_m3n, .hsfreqrange = hsfreqrange_h3_v3h_m3n, .csi0clkfreqrange = 0x20, .clear_ulps = true, }; static const struct rcar_csi2_info rcar_csi2_info_r8a7795es1 = { .hsfreqrange = hsfreqrange_m3w_h3es1, }; static const struct rcar_csi2_info rcar_csi2_info_r8a7796 = { .hsfreqrange = hsfreqrange_m3w_h3es1, }; static const struct rcar_csi2_info rcar_csi2_info_r8a77965 = { .init_phtw = rcsi2_init_phtw_h3_v3h_m3n, .hsfreqrange = hsfreqrange_h3_v3h_m3n, .csi0clkfreqrange = 0x20, .clear_ulps = true, }; static const struct rcar_csi2_info rcar_csi2_info_r8a77970 = { .init_phtw = rcsi2_init_phtw_v3m_e3, }; static const struct of_device_id rcar_csi2_of_table[] = { { .compatible = "renesas,r8a7795-csi2", .data = &rcar_csi2_info_r8a7795, }, { .compatible = "renesas,r8a7796-csi2", .data = &rcar_csi2_info_r8a7796, }, { .compatible = "renesas,r8a77965-csi2", .data = &rcar_csi2_info_r8a77965, }, { .compatible = "renesas,r8a77970-csi2", .data = &rcar_csi2_info_r8a77970, }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, rcar_csi2_of_table); static const struct soc_device_attribute r8a7795es1[] = { { .soc_id = "r8a7795", .revision = "ES1.*", .data = &rcar_csi2_info_r8a7795es1, }, { /* sentinel */ }, };
static int rcsi2_probe(struct platform_device *pdev) { const struct soc_device_attribute *attr; struct rcar_csi2 *priv; unsigned int i; int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->info = of_device_get_match_data(&pdev->dev); /* * r8a7795 ES1.x behaves differently than the ES2.0+ but doesn't * have it's own compatible string. */ attr = soc_device_match(r8a7795es1); if (attr) priv->info = attr->data; priv->dev = &pdev->dev; mutex_init(&priv->lock); priv->stream_count = 0; ret = rcsi2_probe_resources(priv, pdev); if (ret) { dev_err(priv->dev, "Failed to get resources\n"); return ret; } platform_set_drvdata(pdev, priv); ret = rcsi2_parse_dt(priv); if (ret) return ret; priv->subdev.owner = THIS_MODULE; priv->subdev.dev = &pdev->dev; v4l2_subdev_init(&priv->subdev, &rcar_csi2_subdev_ops); v4l2_set_subdevdata(&priv->subdev, &pdev->dev); snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s %s", KBUILD_MODNAME, dev_name(&pdev->dev)); priv->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; priv->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; priv->subdev.entity.ops = &rcar_csi2_entity_ops; priv->pads[RCAR_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; for (i = RCAR_CSI2_SOURCE_VC0; i < NR_OF_RCAR_CSI2_PAD; i++) priv->pads[i].flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&priv->subdev.entity, NR_OF_RCAR_CSI2_PAD, priv->pads); if (ret) goto error; pm_runtime_enable(&pdev->dev); ret = v4l2_async_register_subdev(&priv->subdev); if (ret < 0) goto error; dev_info(priv->dev, "%d lanes found\n", priv->lanes); return 0; error: v4l2_async_notifier_unregister(&priv->notifier); v4l2_async_notifier_cleanup(&priv->notifier); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund378100.00%1100.00%
Total378100.00%1100.00%


static int rcsi2_remove(struct platform_device *pdev) { struct rcar_csi2 *priv = platform_get_drvdata(pdev); v4l2_async_notifier_unregister(&priv->notifier); v4l2_async_notifier_cleanup(&priv->notifier); v4l2_async_unregister_subdev(&priv->subdev); pm_runtime_disable(&pdev->dev); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund56100.00%1100.00%
Total56100.00%1100.00%

static struct platform_driver rcar_csi2_pdrv = { .remove = rcsi2_remove, .probe = rcsi2_probe, .driver = { .name = "rcar-csi2", .of_match_table = rcar_csi2_of_table, }, }; module_platform_driver(rcar_csi2_pdrv); MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>"); MODULE_DESCRIPTION("Renesas R-Car MIPI CSI-2 receiver driver"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Niklas Söderlund5721100.00%2100.00%
Total5721100.00%2100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.