| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * RP1 CSI-2 Driver |
| * |
| * Copyright (c) 2021-2024 Raspberry Pi Ltd. |
| * Copyright (c) 2023-2024 Ideas on Board Oy |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/moduleparam.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/seq_file.h> |
| |
| #include <media/videobuf2-dma-contig.h> |
| |
| #include "cfe.h" |
| #include "csi2.h" |
| |
| #include "cfe-trace.h" |
| |
| static bool csi2_track_errors; |
| module_param_named(track_csi2_errors, csi2_track_errors, bool, 0); |
| MODULE_PARM_DESC(track_csi2_errors, "track csi-2 errors"); |
| |
| #define csi2_dbg(csi2, fmt, arg...) dev_dbg((csi2)->v4l2_dev->dev, fmt, ##arg) |
| #define csi2_err(csi2, fmt, arg...) dev_err((csi2)->v4l2_dev->dev, fmt, ##arg) |
| |
| /* CSI2-DMA registers */ |
| #define CSI2_STATUS 0x000 |
| #define CSI2_QOS 0x004 |
| #define CSI2_DISCARDS_OVERFLOW 0x008 |
| #define CSI2_DISCARDS_INACTIVE 0x00c |
| #define CSI2_DISCARDS_UNMATCHED 0x010 |
| #define CSI2_DISCARDS_LEN_LIMIT 0x014 |
| |
| #define CSI2_DISCARDS_AMOUNT_SHIFT 0 |
| #define CSI2_DISCARDS_AMOUNT_MASK GENMASK(23, 0) |
| #define CSI2_DISCARDS_DT_SHIFT 24 |
| #define CSI2_DISCARDS_DT_MASK GENMASK(29, 24) |
| #define CSI2_DISCARDS_VC_SHIFT 30 |
| #define CSI2_DISCARDS_VC_MASK GENMASK(31, 30) |
| |
| #define CSI2_LLEV_PANICS 0x018 |
| #define CSI2_ULEV_PANICS 0x01c |
| #define CSI2_IRQ_MASK 0x020 |
| #define CSI2_IRQ_MASK_IRQ_OVERFLOW BIT(0) |
| #define CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW BIT(1) |
| #define CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT BIT(2) |
| #define CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED BIT(3) |
| #define CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE BIT(4) |
| #define CSI2_IRQ_MASK_IRQ_ALL \ |
| (CSI2_IRQ_MASK_IRQ_OVERFLOW | CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW | \ |
| CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT | \ |
| CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED | \ |
| CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE) |
| |
| #define CSI2_CTRL 0x024 |
| #define CSI2_CH_CTRL(x) ((x) * 0x40 + 0x28) |
| #define CSI2_CH_ADDR0(x) ((x) * 0x40 + 0x2c) |
| #define CSI2_CH_ADDR1(x) ((x) * 0x40 + 0x3c) |
| #define CSI2_CH_STRIDE(x) ((x) * 0x40 + 0x30) |
| #define CSI2_CH_LENGTH(x) ((x) * 0x40 + 0x34) |
| #define CSI2_CH_DEBUG(x) ((x) * 0x40 + 0x38) |
| #define CSI2_CH_FRAME_SIZE(x) ((x) * 0x40 + 0x40) |
| #define CSI2_CH_COMP_CTRL(x) ((x) * 0x40 + 0x44) |
| #define CSI2_CH_FE_FRAME_ID(x) ((x) * 0x40 + 0x48) |
| |
| /* CSI2_STATUS */ |
| #define CSI2_STATUS_IRQ_FS(x) (BIT(0) << (x)) |
| #define CSI2_STATUS_IRQ_FE(x) (BIT(4) << (x)) |
| #define CSI2_STATUS_IRQ_FE_ACK(x) (BIT(8) << (x)) |
| #define CSI2_STATUS_IRQ_LE(x) (BIT(12) << (x)) |
| #define CSI2_STATUS_IRQ_LE_ACK(x) (BIT(16) << (x)) |
| #define CSI2_STATUS_IRQ_CH_MASK(x) \ |
| (CSI2_STATUS_IRQ_FS(x) | CSI2_STATUS_IRQ_FE(x) | \ |
| CSI2_STATUS_IRQ_FE_ACK(x) | CSI2_STATUS_IRQ_LE(x) | \ |
| CSI2_STATUS_IRQ_LE_ACK(x)) |
| #define CSI2_STATUS_IRQ_OVERFLOW BIT(20) |
| #define CSI2_STATUS_IRQ_DISCARD_OVERFLOW BIT(21) |
| #define CSI2_STATUS_IRQ_DISCARD_LEN_LIMIT BIT(22) |
| #define CSI2_STATUS_IRQ_DISCARD_UNMATCHED BIT(23) |
| #define CSI2_STATUS_IRQ_DISCARD_INACTIVE BIT(24) |
| |
| /* CSI2_CTRL */ |
| #define CSI2_CTRL_EOP_IS_EOL BIT(0) |
| |
| /* CSI2_CH_CTRL */ |
| #define CSI2_CH_CTRL_DMA_EN BIT(0) |
| #define CSI2_CH_CTRL_FORCE BIT(3) |
| #define CSI2_CH_CTRL_AUTO_ARM BIT(4) |
| #define CSI2_CH_CTRL_IRQ_EN_FS BIT(13) |
| #define CSI2_CH_CTRL_IRQ_EN_FE BIT(14) |
| #define CSI2_CH_CTRL_IRQ_EN_FE_ACK BIT(15) |
| #define CSI2_CH_CTRL_IRQ_EN_LE BIT(16) |
| #define CSI2_CH_CTRL_IRQ_EN_LE_ACK BIT(17) |
| #define CSI2_CH_CTRL_FLUSH_FE BIT(28) |
| #define CSI2_CH_CTRL_PACK_LINE BIT(29) |
| #define CSI2_CH_CTRL_PACK_BYTES BIT(30) |
| #define CSI2_CH_CTRL_CH_MODE_MASK GENMASK(2, 1) |
| #define CSI2_CH_CTRL_VC_MASK GENMASK(6, 5) |
| #define CSI2_CH_CTRL_DT_MASK GENMASK(12, 7) |
| #define CSI2_CH_CTRL_LC_MASK GENMASK(27, 18) |
| |
| /* CHx_COMPRESSION_CONTROL */ |
| #define CSI2_CH_COMP_CTRL_OFFSET_MASK GENMASK(15, 0) |
| #define CSI2_CH_COMP_CTRL_SHIFT_MASK GENMASK(19, 16) |
| #define CSI2_CH_COMP_CTRL_MODE_MASK GENMASK(25, 24) |
| |
| static inline u32 csi2_reg_read(struct csi2_device *csi2, u32 offset) |
| { |
| return readl(csi2->base + offset); |
| } |
| |
| static inline void csi2_reg_write(struct csi2_device *csi2, u32 offset, u32 val) |
| { |
| writel(val, csi2->base + offset); |
| } |
| |
| static inline void set_field(u32 *valp, u32 field, u32 mask) |
| { |
| u32 val = *valp; |
| |
| val &= ~mask; |
| val |= (field << __ffs(mask)) & mask; |
| *valp = val; |
| } |
| |
| static int csi2_regs_show(struct seq_file *s, void *data) |
| { |
| struct csi2_device *csi2 = s->private; |
| int ret; |
| |
| ret = pm_runtime_resume_and_get(csi2->v4l2_dev->dev); |
| if (ret) |
| return ret; |
| |
| #define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", csi2_reg_read(csi2, reg)) |
| #define DUMP_CH(idx, reg) seq_printf(s, #reg "(%u) \t0x%08x\n", idx, \ |
| csi2_reg_read(csi2, reg(idx))) |
| |
| DUMP(CSI2_STATUS); |
| DUMP(CSI2_DISCARDS_OVERFLOW); |
| DUMP(CSI2_DISCARDS_INACTIVE); |
| DUMP(CSI2_DISCARDS_UNMATCHED); |
| DUMP(CSI2_DISCARDS_LEN_LIMIT); |
| DUMP(CSI2_LLEV_PANICS); |
| DUMP(CSI2_ULEV_PANICS); |
| DUMP(CSI2_IRQ_MASK); |
| DUMP(CSI2_CTRL); |
| |
| for (unsigned int i = 0; i < CSI2_NUM_CHANNELS; ++i) { |
| DUMP_CH(i, CSI2_CH_CTRL); |
| DUMP_CH(i, CSI2_CH_ADDR0); |
| DUMP_CH(i, CSI2_CH_ADDR1); |
| DUMP_CH(i, CSI2_CH_STRIDE); |
| DUMP_CH(i, CSI2_CH_LENGTH); |
| DUMP_CH(i, CSI2_CH_DEBUG); |
| DUMP_CH(i, CSI2_CH_FRAME_SIZE); |
| DUMP_CH(i, CSI2_CH_COMP_CTRL); |
| DUMP_CH(i, CSI2_CH_FE_FRAME_ID); |
| } |
| |
| #undef DUMP |
| #undef DUMP_CH |
| |
| pm_runtime_put(csi2->v4l2_dev->dev); |
| |
| return 0; |
| } |
| |
| DEFINE_SHOW_ATTRIBUTE(csi2_regs); |
| |
| static int csi2_errors_show(struct seq_file *s, void *data) |
| { |
| struct csi2_device *csi2 = s->private; |
| unsigned long flags; |
| u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES]; |
| u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES]; |
| u32 overflows; |
| |
| spin_lock_irqsave(&csi2->errors_lock, flags); |
| |
| memcpy(discards_table, csi2->discards_table, sizeof(discards_table)); |
| memcpy(discards_dt_table, csi2->discards_dt_table, |
| sizeof(discards_dt_table)); |
| overflows = csi2->overflows; |
| |
| csi2->overflows = 0; |
| memset(csi2->discards_table, 0, sizeof(discards_table)); |
| memset(csi2->discards_dt_table, 0, sizeof(discards_dt_table)); |
| |
| spin_unlock_irqrestore(&csi2->errors_lock, flags); |
| |
| seq_printf(s, "Overflows %u\n", overflows); |
| seq_puts(s, "Discards:\n"); |
| seq_puts(s, "VC OVLF LEN UNMATCHED INACTIVE\n"); |
| |
| for (unsigned int vc = 0; vc < DISCARDS_TABLE_NUM_VCS; ++vc) { |
| seq_printf(s, "%u %10u %10u %10u %10u\n", vc, |
| discards_table[vc][DISCARDS_TABLE_OVERFLOW], |
| discards_table[vc][DISCARDS_TABLE_LENGTH_LIMIT], |
| discards_table[vc][DISCARDS_TABLE_UNMATCHED], |
| discards_table[vc][DISCARDS_TABLE_INACTIVE]); |
| } |
| |
| seq_printf(s, "Last DT %10u %10u %10u %10u\n", |
| discards_dt_table[DISCARDS_TABLE_OVERFLOW], |
| discards_dt_table[DISCARDS_TABLE_LENGTH_LIMIT], |
| discards_dt_table[DISCARDS_TABLE_UNMATCHED], |
| discards_dt_table[DISCARDS_TABLE_INACTIVE]); |
| |
| return 0; |
| } |
| |
| DEFINE_SHOW_ATTRIBUTE(csi2_errors); |
| |
| static void csi2_isr_handle_errors(struct csi2_device *csi2, u32 status) |
| { |
| spin_lock(&csi2->errors_lock); |
| |
| if (status & CSI2_STATUS_IRQ_OVERFLOW) |
| csi2->overflows++; |
| |
| for (unsigned int i = 0; i < DISCARDS_TABLE_NUM_ENTRIES; ++i) { |
| static const u32 discard_bits[] = { |
| CSI2_STATUS_IRQ_DISCARD_OVERFLOW, |
| CSI2_STATUS_IRQ_DISCARD_LEN_LIMIT, |
| CSI2_STATUS_IRQ_DISCARD_UNMATCHED, |
| CSI2_STATUS_IRQ_DISCARD_INACTIVE, |
| }; |
| static const u8 discard_regs[] = { |
| CSI2_DISCARDS_OVERFLOW, |
| CSI2_DISCARDS_LEN_LIMIT, |
| CSI2_DISCARDS_UNMATCHED, |
| CSI2_DISCARDS_INACTIVE, |
| }; |
| u32 amount; |
| u8 dt, vc; |
| u32 v; |
| |
| if (!(status & discard_bits[i])) |
| continue; |
| |
| v = csi2_reg_read(csi2, discard_regs[i]); |
| csi2_reg_write(csi2, discard_regs[i], 0); |
| |
| amount = (v & CSI2_DISCARDS_AMOUNT_MASK) >> |
| CSI2_DISCARDS_AMOUNT_SHIFT; |
| dt = (v & CSI2_DISCARDS_DT_MASK) >> CSI2_DISCARDS_DT_SHIFT; |
| vc = (v & CSI2_DISCARDS_VC_MASK) >> CSI2_DISCARDS_VC_SHIFT; |
| |
| csi2->discards_table[vc][i] += amount; |
| csi2->discards_dt_table[i] = dt; |
| } |
| |
| spin_unlock(&csi2->errors_lock); |
| } |
| |
| void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof) |
| { |
| u32 status; |
| |
| status = csi2_reg_read(csi2, CSI2_STATUS); |
| |
| /* Write value back to clear the interrupts */ |
| csi2_reg_write(csi2, CSI2_STATUS, status); |
| |
| for (unsigned int i = 0; i < CSI2_NUM_CHANNELS; i++) { |
| u32 dbg; |
| |
| if ((status & CSI2_STATUS_IRQ_CH_MASK(i)) == 0) |
| continue; |
| |
| dbg = csi2_reg_read(csi2, CSI2_CH_DEBUG(i)); |
| |
| trace_csi2_irq(i, status, dbg); |
| |
| sof[i] = !!(status & CSI2_STATUS_IRQ_FS(i)); |
| eof[i] = !!(status & CSI2_STATUS_IRQ_FE_ACK(i)); |
| } |
| |
| if (csi2_track_errors) |
| csi2_isr_handle_errors(csi2, status); |
| } |
| |
| void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel, |
| dma_addr_t dmaaddr, unsigned int stride, unsigned int size) |
| { |
| u64 addr = dmaaddr; |
| /* |
| * ADDRESS0 must be written last as it triggers the double buffering |
| * mechanism for all buffer registers within the hardware. |
| */ |
| addr >>= 4; |
| csi2_reg_write(csi2, CSI2_CH_LENGTH(channel), size >> 4); |
| csi2_reg_write(csi2, CSI2_CH_STRIDE(channel), stride >> 4); |
| csi2_reg_write(csi2, CSI2_CH_ADDR1(channel), addr >> 32); |
| csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), addr & 0xffffffff); |
| } |
| |
| void csi2_set_compression(struct csi2_device *csi2, unsigned int channel, |
| enum csi2_compression_mode mode, unsigned int shift, |
| unsigned int offset) |
| { |
| u32 compression = 0; |
| |
| set_field(&compression, CSI2_CH_COMP_CTRL_OFFSET_MASK, offset); |
| set_field(&compression, CSI2_CH_COMP_CTRL_SHIFT_MASK, shift); |
| set_field(&compression, CSI2_CH_COMP_CTRL_MODE_MASK, mode); |
| csi2_reg_write(csi2, CSI2_CH_COMP_CTRL(channel), compression); |
| } |
| |
| void csi2_start_channel(struct csi2_device *csi2, unsigned int channel, |
| enum csi2_mode mode, bool auto_arm, bool pack_bytes, |
| unsigned int width, unsigned int height, |
| u8 vc, u8 dt) |
| { |
| u32 ctrl; |
| |
| csi2_dbg(csi2, "%s [%u]\n", __func__, channel); |
| |
| csi2_reg_write(csi2, CSI2_CH_CTRL(channel), 0); |
| csi2_reg_write(csi2, CSI2_CH_DEBUG(channel), 0); |
| csi2_reg_write(csi2, CSI2_STATUS, CSI2_STATUS_IRQ_CH_MASK(channel)); |
| |
| /* Enable channel and FS/FE interrupts. */ |
| ctrl = CSI2_CH_CTRL_DMA_EN | CSI2_CH_CTRL_IRQ_EN_FS | |
| CSI2_CH_CTRL_IRQ_EN_FE_ACK | CSI2_CH_CTRL_PACK_LINE; |
| /* PACK_BYTES ensures no striding for embedded data. */ |
| if (pack_bytes) |
| ctrl |= CSI2_CH_CTRL_PACK_BYTES; |
| |
| if (auto_arm) |
| ctrl |= CSI2_CH_CTRL_AUTO_ARM; |
| |
| if (width && height) { |
| set_field(&ctrl, mode, CSI2_CH_CTRL_CH_MODE_MASK); |
| csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel), |
| (height << 16) | width); |
| } else { |
| set_field(&ctrl, 0x0, CSI2_CH_CTRL_CH_MODE_MASK); |
| csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel), 0); |
| } |
| |
| set_field(&ctrl, vc, CSI2_CH_CTRL_VC_MASK); |
| set_field(&ctrl, dt, CSI2_CH_CTRL_DT_MASK); |
| csi2_reg_write(csi2, CSI2_CH_CTRL(channel), ctrl); |
| csi2->num_lines[channel] = height; |
| } |
| |
| void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel) |
| { |
| csi2_dbg(csi2, "%s [%u]\n", __func__, channel); |
| |
| /* Channel disable. Use FORCE to allow stopping mid-frame. */ |
| csi2_reg_write(csi2, CSI2_CH_CTRL(channel), CSI2_CH_CTRL_FORCE); |
| /* Latch the above change by writing to the ADDR0 register. */ |
| csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0); |
| /* Write this again, the HW needs it! */ |
| csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0); |
| } |
| |
| void csi2_open_rx(struct csi2_device *csi2) |
| { |
| csi2_reg_write(csi2, CSI2_IRQ_MASK, |
| csi2_track_errors ? CSI2_IRQ_MASK_IRQ_ALL : 0); |
| |
| dphy_start(&csi2->dphy); |
| |
| csi2_reg_write(csi2, CSI2_CTRL, CSI2_CTRL_EOP_IS_EOL); |
| } |
| |
| void csi2_close_rx(struct csi2_device *csi2) |
| { |
| dphy_stop(&csi2->dphy); |
| |
| csi2_reg_write(csi2, CSI2_IRQ_MASK, 0); |
| } |
| |
| static int csi2_init_state(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *state) |
| { |
| struct v4l2_subdev_route routes[] = { { |
| .sink_pad = CSI2_PAD_SINK, |
| .sink_stream = 0, |
| .source_pad = CSI2_PAD_FIRST_SOURCE, |
| .source_stream = 0, |
| .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, |
| } }; |
| |
| struct v4l2_subdev_krouting routing = { |
| .num_routes = ARRAY_SIZE(routes), |
| .routes = routes, |
| }; |
| |
| int ret; |
| |
| ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing, |
| &cfe_default_format); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int csi2_pad_set_fmt(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *state, |
| struct v4l2_subdev_format *format) |
| { |
| if (format->pad == CSI2_PAD_SINK) { |
| /* Store the sink format and propagate it to the source. */ |
| |
| const struct cfe_fmt *cfe_fmt; |
| |
| cfe_fmt = find_format_by_code(format->format.code); |
| if (!cfe_fmt) { |
| cfe_fmt = find_format_by_code(MEDIA_BUS_FMT_SRGGB10_1X10); |
| format->format.code = cfe_fmt->code; |
| } |
| |
| struct v4l2_mbus_framefmt *fmt; |
| |
| fmt = v4l2_subdev_state_get_format(state, format->pad, |
| format->stream); |
| if (!fmt) |
| return -EINVAL; |
| |
| *fmt = format->format; |
| |
| fmt = v4l2_subdev_state_get_opposite_stream_format(state, |
| format->pad, |
| format->stream); |
| if (!fmt) |
| return -EINVAL; |
| |
| format->format.field = V4L2_FIELD_NONE; |
| |
| *fmt = format->format; |
| } else { |
| /* Only allow changing the source pad mbus code. */ |
| |
| struct v4l2_mbus_framefmt *sink_fmt, *source_fmt; |
| u32 sink_code; |
| u32 code; |
| |
| sink_fmt = v4l2_subdev_state_get_opposite_stream_format(state, |
| format->pad, |
| format->stream); |
| if (!sink_fmt) |
| return -EINVAL; |
| |
| source_fmt = v4l2_subdev_state_get_format(state, format->pad, |
| format->stream); |
| if (!source_fmt) |
| return -EINVAL; |
| |
| sink_code = sink_fmt->code; |
| code = format->format.code; |
| |
| /* |
| * Only allow changing the mbus code to: |
| * - The sink's mbus code |
| * - The 16-bit version of the sink's mbus code |
| * - The compressed version of the sink's mbus code |
| */ |
| if (code == sink_code || |
| code == cfe_find_16bit_code(sink_code) || |
| code == cfe_find_compressed_code(sink_code)) |
| source_fmt->code = code; |
| |
| format->format.code = source_fmt->code; |
| } |
| |
| return 0; |
| } |
| |
| static int csi2_set_routing(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *state, |
| enum v4l2_subdev_format_whence which, |
| struct v4l2_subdev_krouting *routing) |
| { |
| int ret; |
| |
| ret = v4l2_subdev_routing_validate(sd, routing, |
| V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | |
| V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING); |
| if (ret) |
| return ret; |
| |
| /* Only stream ID 0 allowed on source pads */ |
| for (unsigned int i = 0; i < routing->num_routes; ++i) { |
| const struct v4l2_subdev_route *route = &routing->routes[i]; |
| |
| if (route->source_stream != 0) |
| return -EINVAL; |
| } |
| |
| ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, |
| &cfe_default_format); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static const struct v4l2_subdev_pad_ops csi2_subdev_pad_ops = { |
| .get_fmt = v4l2_subdev_get_fmt, |
| .set_fmt = csi2_pad_set_fmt, |
| .set_routing = csi2_set_routing, |
| .link_validate = v4l2_subdev_link_validate_default, |
| }; |
| |
| static const struct media_entity_operations csi2_entity_ops = { |
| .link_validate = v4l2_subdev_link_validate, |
| .has_pad_interdep = v4l2_subdev_has_pad_interdep, |
| }; |
| |
| static const struct v4l2_subdev_ops csi2_subdev_ops = { |
| .pad = &csi2_subdev_pad_ops, |
| }; |
| |
| static const struct v4l2_subdev_internal_ops csi2_internal_ops = { |
| .init_state = csi2_init_state, |
| }; |
| |
| int csi2_init(struct csi2_device *csi2, struct dentry *debugfs) |
| { |
| unsigned int ret; |
| |
| spin_lock_init(&csi2->errors_lock); |
| |
| csi2->dphy.dev = csi2->v4l2_dev->dev; |
| dphy_probe(&csi2->dphy); |
| |
| debugfs_create_file("csi2_regs", 0440, debugfs, csi2, &csi2_regs_fops); |
| |
| if (csi2_track_errors) |
| debugfs_create_file("csi2_errors", 0440, debugfs, csi2, |
| &csi2_errors_fops); |
| |
| csi2->pad[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; |
| |
| for (unsigned int i = CSI2_PAD_FIRST_SOURCE; |
| i < CSI2_PAD_FIRST_SOURCE + CSI2_PAD_NUM_SOURCES; i++) |
| csi2->pad[i].flags = MEDIA_PAD_FL_SOURCE; |
| |
| ret = media_entity_pads_init(&csi2->sd.entity, ARRAY_SIZE(csi2->pad), |
| csi2->pad); |
| if (ret) |
| return ret; |
| |
| /* Initialize subdev */ |
| v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops); |
| csi2->sd.internal_ops = &csi2_internal_ops; |
| csi2->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; |
| csi2->sd.entity.ops = &csi2_entity_ops; |
| csi2->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; |
| csi2->sd.owner = THIS_MODULE; |
| snprintf(csi2->sd.name, sizeof(csi2->sd.name), "csi2"); |
| |
| ret = v4l2_subdev_init_finalize(&csi2->sd); |
| if (ret) |
| goto err_entity_cleanup; |
| |
| ret = v4l2_device_register_subdev(csi2->v4l2_dev, &csi2->sd); |
| if (ret) { |
| csi2_err(csi2, "Failed register csi2 subdev (%d)\n", ret); |
| goto err_subdev_cleanup; |
| } |
| |
| return 0; |
| |
| err_subdev_cleanup: |
| v4l2_subdev_cleanup(&csi2->sd); |
| err_entity_cleanup: |
| media_entity_cleanup(&csi2->sd.entity); |
| |
| return ret; |
| } |
| |
| void csi2_uninit(struct csi2_device *csi2) |
| { |
| v4l2_device_unregister_subdev(&csi2->sd); |
| v4l2_subdev_cleanup(&csi2->sd); |
| media_entity_cleanup(&csi2->sd.entity); |
| } |