// 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 #include #include #include #include #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); }