diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2022-12-13 11:36:58 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2022-12-13 11:36:58 -0800 |
commit | cdb9d3537711939e4d8fd0de2889c966f88346eb (patch) | |
tree | 2a118e0930d3e08c6303b579e89f0df388cba4e6 /drivers/media | |
parent | 102f9d3d455870844c47b82322c2dfc0a35eb745 (diff) | |
parent | 3178804c64ef7c8c87a53cd5bba0b2942dd64fec (diff) |
Merge tag 'media/v6.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
Pull media updates from Mauro Carvalho Chehab:
- DVB core changes to avoid refcount troubles and UAF
- DVB API/core has gained support for DVB-C2 and DVB-S2X
- New sensor drivers: ov08x40, ov4689.c, st-vgxy61 and tc358746.c
- Removal of an unused sensor driver: s5k4ecgx
- Move microchip_csi2dc to a new directory, named after the
manufacturer
- Add media controller support to Microship drivers
- Old Atmel/Microship drivers that don't use media controler got moved
to staging
- New drivers added for Renesas RZ/G2L CRU and MIPI CSI-2 support
- Allwinner A31 camera sensor driver code was now split into a bridge
and a separate processor driver
- Added a virtual stateless decoder driver in order to test core
support for stateless drivers and test userspace apps using it
- removed platform-based support for ov9650, as this is not used
anymore
- atomisp now uses videobuf2 and supports normal mmap mode
- the imx7-media-csi driver got promoted from staging
- rcar-vin driver has gained support for gen3 UDS (Up Down Scaler)
- most i2c drivers now use I2C .probe_new() kAPI
- lots of drivers fixes, cleanups and improvements
* tag 'media/v6.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (544 commits)
media: s5c73m3: Switch to GPIO descriptors
media: i2c: s5k5baf: switch to using gpiod API
media: i2c: s5k6a3: switch to using gpiod API
media: imx: remove code for non-existing config IMX_GPT_ICAP
media: si470x: Fix use-after-free in si470x_int_in_callback()
media: staging: stkwebcam: Restore MEDIA_{USB,CAMERA}_SUPPORT dependencies
media: coda: Add check for kmalloc
media: coda: Add check for dcoda_iram_alloc
dt-bindings: media: s5c73m3: Fix reset-gpio descriptor
media: dt-bindings: allwinner: h6-vpu-g2: Add IOMMU reference property
media: s5k4ecgx: Delete driver
media: s5k4ecgx: Switch to GPIO descriptors
media: Switch to use dev_err_probe() helper
headers: Remove some left-over license text in include/uapi/linux/v4l2-*
headers: Remove some left-over license text in include/uapi/linux/dvb/
media: usb: pwc-uncompress: Use flex array destination for memcpy()
media: s5p-mfc: Fix to handle reference queue during finishing
media: s5p-mfc: Clear workbit to handle error condition
media: s5p-mfc: Fix in register read and write for H264
media: imx: Use get_mbus_config instead of parsing upstream DT endpoints
...
Diffstat (limited to 'drivers/media')
310 files changed, 25149 insertions, 5449 deletions
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 283b78b5766e..6abc9302cd84 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -182,7 +182,7 @@ config MEDIA_CONTROLLER # config DVB_CORE - tristate + tristate "DVB Core" depends on MEDIA_DIGITAL_TV_SUPPORT depends on (I2C || I2C=n) default MEDIA_DIGITAL_TV_SUPPORT diff --git a/drivers/media/cec/platform/stm32/stm32-cec.c b/drivers/media/cec/platform/stm32/stm32-cec.c index 40db7911b437..7b2db46a5722 100644 --- a/drivers/media/cec/platform/stm32/stm32-cec.c +++ b/drivers/media/cec/platform/stm32/stm32-cec.c @@ -288,12 +288,9 @@ static int stm32_cec_probe(struct platform_device *pdev) return ret; cec->clk_cec = devm_clk_get(&pdev->dev, "cec"); - if (IS_ERR(cec->clk_cec)) { - if (PTR_ERR(cec->clk_cec) != -EPROBE_DEFER) - dev_err(&pdev->dev, "Cannot get cec clock\n"); - - return PTR_ERR(cec->clk_cec); - } + if (IS_ERR(cec->clk_cec)) + return dev_err_probe(&pdev->dev, PTR_ERR(cec->clk_cec), + "Cannot get cec clock\n"); ret = clk_prepare(cec->clk_cec); if (ret) { diff --git a/drivers/media/common/videobuf2/frame_vector.c b/drivers/media/common/videobuf2/frame_vector.c index 144027035892..89eebc3341b8 100644 --- a/drivers/media/common/videobuf2/frame_vector.c +++ b/drivers/media/common/videobuf2/frame_vector.c @@ -14,6 +14,7 @@ * get_vaddr_frames() - map virtual addresses to pfns * @start: starting user address * @nr_frames: number of pages / pfns from start to map + * @write: the mapped address has write permission * @vec: structure which receives pages / pfns of the addresses mapped. * It should have space for at least nr_frames entries. * @@ -32,10 +33,11 @@ * * This function takes care of grabbing mmap_lock as necessary. */ -int get_vaddr_frames(unsigned long start, unsigned int nr_frames, +int get_vaddr_frames(unsigned long start, unsigned int nr_frames, bool write, struct frame_vector *vec) { int ret; + unsigned int gup_flags = FOLL_FORCE | FOLL_LONGTERM; if (nr_frames == 0) return 0; @@ -45,8 +47,10 @@ int get_vaddr_frames(unsigned long start, unsigned int nr_frames, start = untagged_addr(start); - ret = pin_user_pages_fast(start, nr_frames, - FOLL_FORCE | FOLL_WRITE | FOLL_LONGTERM, + if (write) + gup_flags |= FOLL_WRITE; + + ret = pin_user_pages_fast(start, nr_frames, gup_flags, (struct page **)(vec->ptrs)); vec->got_ref = true; vec->is_pfns = false; diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c index 92efc4676df6..fc3758a5bc1c 100644 --- a/drivers/media/common/videobuf2/videobuf2-core.c +++ b/drivers/media/common/videobuf2/videobuf2-core.c @@ -544,6 +544,7 @@ static int __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) */ if (q->num_buffers) { bool unbalanced = q->cnt_start_streaming != q->cnt_stop_streaming || + q->cnt_prepare_streaming != q->cnt_unprepare_streaming || q->cnt_wait_prepare != q->cnt_wait_finish; if (unbalanced || debug) { @@ -552,14 +553,18 @@ static int __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) pr_info(" setup: %u start_streaming: %u stop_streaming: %u\n", q->cnt_queue_setup, q->cnt_start_streaming, q->cnt_stop_streaming); + pr_info(" prepare_streaming: %u unprepare_streaming: %u\n", + q->cnt_prepare_streaming, q->cnt_unprepare_streaming); pr_info(" wait_prepare: %u wait_finish: %u\n", q->cnt_wait_prepare, q->cnt_wait_finish); } q->cnt_queue_setup = 0; q->cnt_wait_prepare = 0; q->cnt_wait_finish = 0; + q->cnt_prepare_streaming = 0; q->cnt_start_streaming = 0; q->cnt_stop_streaming = 0; + q->cnt_unprepare_streaming = 0; } for (buffer = 0; buffer < q->num_buffers; ++buffer) { struct vb2_buffer *vb = q->bufs[buffer]; @@ -2026,6 +2031,9 @@ static void __vb2_queue_cancel(struct vb2_queue *q) if (q->start_streaming_called) call_void_qop(q, stop_streaming, q); + if (q->streaming) + call_void_qop(q, unprepare_streaming, q); + /* * If you see this warning, then the driver isn't cleaning up properly * in stop_streaming(). See the stop_streaming() documentation in @@ -2137,23 +2145,29 @@ int vb2_core_streamon(struct vb2_queue *q, unsigned int type) return -EINVAL; } + ret = call_qop(q, prepare_streaming, q); + if (ret) + return ret; + + q->streaming = 1; + /* * Tell driver to start streaming provided sufficient buffers * are available. */ if (q->queued_count >= q->min_buffers_needed) { - ret = v4l_vb2q_enable_media_source(q); - if (ret) - return ret; ret = vb2_start_streaming(q); if (ret) - return ret; + goto unprepare; } - q->streaming = 1; - dprintk(q, 3, "successful\n"); return 0; + +unprepare: + call_void_qop(q, unprepare_streaming, q); + q->streaming = 0; + return ret; } EXPORT_SYMBOL_GPL(vb2_core_streamon); diff --git a/drivers/media/common/videobuf2/videobuf2-dma-contig.c b/drivers/media/common/videobuf2/videobuf2-dma-contig.c index 678b359717c4..8e55468cb60d 100644 --- a/drivers/media/common/videobuf2/videobuf2-dma-contig.c +++ b/drivers/media/common/videobuf2/videobuf2-dma-contig.c @@ -603,7 +603,8 @@ static void *vb2_dc_get_userptr(struct vb2_buffer *vb, struct device *dev, buf->vb = vb; offset = lower_32_bits(offset_in_page(vaddr)); - vec = vb2_create_framevec(vaddr, size); + vec = vb2_create_framevec(vaddr, size, buf->dma_dir == DMA_FROM_DEVICE || + buf->dma_dir == DMA_BIDIRECTIONAL); if (IS_ERR(vec)) { ret = PTR_ERR(vec); goto fail_buf; diff --git a/drivers/media/common/videobuf2/videobuf2-dma-sg.c b/drivers/media/common/videobuf2/videobuf2-dma-sg.c index fa69158a65b1..099693e42bc6 100644 --- a/drivers/media/common/videobuf2/videobuf2-dma-sg.c +++ b/drivers/media/common/videobuf2/videobuf2-dma-sg.c @@ -241,7 +241,9 @@ static void *vb2_dma_sg_get_userptr(struct vb2_buffer *vb, struct device *dev, buf->size = size; buf->dma_sgt = &buf->sg_table; buf->vb = vb; - vec = vb2_create_framevec(vaddr, size); + vec = vb2_create_framevec(vaddr, size, + buf->dma_dir == DMA_FROM_DEVICE || + buf->dma_dir == DMA_BIDIRECTIONAL); if (IS_ERR(vec)) goto userptr_fail_pfnvec; buf->vec = vec; diff --git a/drivers/media/common/videobuf2/videobuf2-memops.c b/drivers/media/common/videobuf2/videobuf2-memops.c index 9dd6c27162f4..f9a4ec44422e 100644 --- a/drivers/media/common/videobuf2/videobuf2-memops.c +++ b/drivers/media/common/videobuf2/videobuf2-memops.c @@ -26,6 +26,7 @@ * vb2_create_framevec() - map virtual addresses to pfns * @start: Virtual user address where we start mapping * @length: Length of a range to map + * @write: Should we map for writing into the area * * This function allocates and fills in a vector with pfns corresponding to * virtual address range passed in arguments. If pfns have corresponding pages, @@ -34,7 +35,8 @@ * failure. Returned vector needs to be freed via vb2_destroy_pfnvec(). */ struct frame_vector *vb2_create_framevec(unsigned long start, - unsigned long length) + unsigned long length, + bool write) { int ret; unsigned long first, last; @@ -47,7 +49,7 @@ struct frame_vector *vb2_create_framevec(unsigned long start, vec = frame_vector_create(nr); if (!vec) return ERR_PTR(-ENOMEM); - ret = get_vaddr_frames(start & PAGE_MASK, nr, vec); + ret = get_vaddr_frames(start & PAGE_MASK, nr, write, vec); if (ret < 0) goto out_destroy; /* We accept only complete set of PFNs */ diff --git a/drivers/media/common/videobuf2/videobuf2-vmalloc.c b/drivers/media/common/videobuf2/videobuf2-vmalloc.c index 948152f1596b..67d0b89e701b 100644 --- a/drivers/media/common/videobuf2/videobuf2-vmalloc.c +++ b/drivers/media/common/videobuf2/videobuf2-vmalloc.c @@ -85,7 +85,9 @@ static void *vb2_vmalloc_get_userptr(struct vb2_buffer *vb, struct device *dev, buf->dma_dir = vb->vb2_queue->dma_dir; offset = vaddr & ~PAGE_MASK; buf->size = size; - vec = vb2_create_framevec(vaddr, size); + vec = vb2_create_framevec(vaddr, size, + buf->dma_dir == DMA_FROM_DEVICE || + buf->dma_dir == DMA_BIDIRECTIONAL); if (IS_ERR(vec)) { ret = PTR_ERR(vec); goto fail_pfnvec_create; diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c index f6ee678107d3..9ce5f010de3f 100644 --- a/drivers/media/dvb-core/dmxdev.c +++ b/drivers/media/dvb-core/dmxdev.c @@ -790,6 +790,11 @@ static int dvb_demux_open(struct inode *inode, struct file *file) if (mutex_lock_interruptible(&dmxdev->mutex)) return -ERESTARTSYS; + if (dmxdev->exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + for (i = 0; i < dmxdev->filternum; i++) if (dmxdev->filter[i].state == DMXDEV_STATE_FREE) break; @@ -1448,7 +1453,10 @@ EXPORT_SYMBOL(dvb_dmxdev_init); void dvb_dmxdev_release(struct dmxdev *dmxdev) { + mutex_lock(&dmxdev->mutex); dmxdev->exit = 1; + mutex_unlock(&dmxdev->mutex); + if (dmxdev->dvbdev->users > 1) { wait_event(dmxdev->dvbdev->wait_queue, dmxdev->dvbdev->users == 1); diff --git a/drivers/media/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb-core/dvb_ca_en50221.c index 15a08d8c69ef..c2d2792227f8 100644 --- a/drivers/media/dvb-core/dvb_ca_en50221.c +++ b/drivers/media/dvb-core/dvb_ca_en50221.c @@ -157,7 +157,7 @@ static void dvb_ca_private_free(struct dvb_ca_private *ca) { unsigned int i; - dvb_free_device(ca->dvbdev); + dvb_device_put(ca->dvbdev); for (i = 0; i < ca->slot_count; i++) vfree(ca->slot_info[i].rx_buffer.data); diff --git a/drivers/media/dvb-core/dvb_demux.c b/drivers/media/dvb-core/dvb_demux.c index 83cc32ad7e12..398c86279b5b 100644 --- a/drivers/media/dvb-core/dvb_demux.c +++ b/drivers/media/dvb-core/dvb_demux.c @@ -233,7 +233,7 @@ static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, { struct dvb_demux *demux = feed->demux; struct dmx_section_feed *sec = &feed->feed.sec; - u16 limit, seclen, n; + u16 limit, seclen; if (sec->tsfeedp >= DMX_MAX_SECFEED_SIZE) return 0; @@ -262,7 +262,7 @@ static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, /* to be sure always set secbuf */ sec->secbuf = sec->secbuf_base + sec->secbufp; - for (n = 0; sec->secbufp + 2 < limit; n++) { + while (sec->secbufp + 2 < limit) { seclen = section_length(sec->secbuf); if (seclen <= 0 || seclen > DMX_MAX_SECTION_SIZE || seclen + sec->secbufp > limit) diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c index 48e735cdbe6b..cc0a789f09ae 100644 --- a/drivers/media/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -136,7 +136,7 @@ static void __dvb_frontend_free(struct dvb_frontend *fe) struct dvb_frontend_private *fepriv = fe->frontend_priv; if (fepriv) - dvb_free_device(fepriv->dvbdev); + dvb_device_put(fepriv->dvbdev); dvb_frontend_invoke_release(fe, fe->ops.release); @@ -918,6 +918,7 @@ static void dvb_frontend_get_frequency_limits(struct dvb_frontend *fe, /* If the standard is for satellite, convert frequencies to kHz */ switch (c->delivery_system) { + case SYS_DSS: case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: @@ -943,6 +944,7 @@ static u32 dvb_frontend_get_stepsize(struct dvb_frontend *fe) u32 step = max(fe_step, tuner_step); switch (c->delivery_system) { + case SYS_DSS: case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: @@ -974,6 +976,7 @@ static int dvb_frontend_check_parameters(struct dvb_frontend *fe) /* range check: symbol rate */ switch (c->delivery_system) { + case SYS_DSS: case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: @@ -1040,6 +1043,10 @@ static int dvb_frontend_clear_cache(struct dvb_frontend *fe) c->scrambling_sequence_index = 0;/* default sequence */ switch (c->delivery_system) { + case SYS_DSS: + c->modulation = QPSK; + c->rolloff = ROLLOFF_20; + break; case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: @@ -1821,6 +1828,7 @@ static void prepare_tuning_algo_parameters(struct dvb_frontend *fe) } else { /* default values */ switch (c->delivery_system) { + case SYS_DSS: case SYS_DVBS: case SYS_DVBS2: case SYS_ISDBS: @@ -2288,6 +2296,9 @@ static int dtv_set_frontend(struct dvb_frontend *fe) case SYS_DVBC_ANNEX_C: rolloff = 113; break; + case SYS_DSS: + rolloff = 120; + break; case SYS_DVBS: case SYS_TURBO: case SYS_ISDBS: @@ -2754,7 +2765,17 @@ static int dvb_frontend_open(struct inode *inode, struct file *file) if (fe->exit == DVB_FE_DEVICE_REMOVED) return -ENODEV; - if (adapter->mfe_shared) { + if (adapter->mfe_shared == 2) { + mutex_lock(&adapter->mfe_lock); + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + if (adapter->mfe_dvbdev && + !adapter->mfe_dvbdev->writers) { + mutex_unlock(&adapter->mfe_lock); + return -EBUSY; + } + adapter->mfe_dvbdev = dvbdev; + } + } else if (adapter->mfe_shared) { mutex_lock(&adapter->mfe_lock); if (!adapter->mfe_dvbdev) @@ -2986,6 +3007,7 @@ int dvb_register_frontend(struct dvb_adapter *dvb, .name = fe->ops.info.name, #endif }; + int ret; dev_dbg(dvb->device, "%s:\n", __func__); @@ -3019,8 +3041,13 @@ int dvb_register_frontend(struct dvb_adapter *dvb, "DVB: registering adapter %i frontend %i (%s)...\n", fe->dvb->num, fe->id, fe->ops.info.name); - dvb_register_device(fe->dvb, &fepriv->dvbdev, &dvbdev_template, + ret = dvb_register_device(fe->dvb, &fepriv->dvbdev, &dvbdev_template, fe, DVB_DEVICE_FRONTEND, 0); + if (ret) { + dvb_frontend_put(fe); + mutex_unlock(&frontend_mutex); + return ret; + } /* * Initialize the cache to the proper values according with the diff --git a/drivers/media/dvb-core/dvb_ringbuffer.c b/drivers/media/dvb-core/dvb_ringbuffer.c index d1d471af0636..7d4558de8e83 100644 --- a/drivers/media/dvb-core/dvb_ringbuffer.c +++ b/drivers/media/dvb-core/dvb_ringbuffer.c @@ -335,7 +335,9 @@ ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* idx = (idx + curpktlen + DVB_RINGBUFFER_PKTHDRSIZE) % rbuf->size; } - consumed = (idx - rbuf->pread) % rbuf->size; + consumed = (idx - rbuf->pread); + if (consumed < 0) + consumed += rbuf->size; while((dvb_ringbuffer_avail(rbuf) - consumed) > DVB_RINGBUFFER_PKTHDRSIZE) { diff --git a/drivers/media/dvb-core/dvbdev.c b/drivers/media/dvb-core/dvbdev.c index 675d877a67b2..2a857cf70c94 100644 --- a/drivers/media/dvb-core/dvbdev.c +++ b/drivers/media/dvb-core/dvbdev.c @@ -97,7 +97,7 @@ static int dvb_device_open(struct inode *inode, struct file *file) new_fops = fops_get(dvbdev->fops); if (!new_fops) goto fail; - file->private_data = dvbdev; + file->private_data = dvb_device_get(dvbdev); replace_fops(file, new_fops); if (file->f_op->open) err = file->f_op->open(inode, file); @@ -161,6 +161,9 @@ int dvb_generic_release(struct inode *inode, struct file *file) } dvbdev->users++; + + dvb_device_put(dvbdev); + return 0; } EXPORT_SYMBOL(dvb_generic_release); @@ -243,7 +246,7 @@ static void dvb_media_device_free(struct dvb_device *dvbdev) static int dvb_create_tsout_entity(struct dvb_device *dvbdev, const char *name, int npads) { - int i, ret = 0; + int i; dvbdev->tsout_pads = kcalloc(npads, sizeof(*dvbdev->tsout_pads), GFP_KERNEL); @@ -260,6 +263,7 @@ static int dvb_create_tsout_entity(struct dvb_device *dvbdev, for (i = 0; i < npads; i++) { struct media_pad *pads = &dvbdev->tsout_pads[i]; struct media_entity *entity = &dvbdev->tsout_entity[i]; + int ret; entity->name = kasprintf(GFP_KERNEL, "%s #%d", name, i); if (!entity->name) @@ -332,6 +336,7 @@ static int dvb_create_media_entity(struct dvb_device *dvbdev, GFP_KERNEL); if (!dvbdev->pads) { kfree(dvbdev->entity); + dvbdev->entity = NULL; return -ENOMEM; } } @@ -478,6 +483,7 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, } memcpy(dvbdev, template, sizeof(struct dvb_device)); + kref_init(&dvbdev->ref); dvbdev->type = type; dvbdev->id = id; dvbdev->adapter = adap; @@ -508,7 +514,7 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, #endif dvbdev->minor = minor; - dvb_minors[minor] = dvbdev; + dvb_minors[minor] = dvb_device_get(dvbdev); up_write(&minor_rwsem); ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads); @@ -553,6 +559,7 @@ void dvb_remove_device(struct dvb_device *dvbdev) down_write(&minor_rwsem); dvb_minors[dvbdev->minor] = NULL; + dvb_device_put(dvbdev); up_write(&minor_rwsem); dvb_media_device_free(dvbdev); @@ -564,21 +571,34 @@ void dvb_remove_device(struct dvb_device *dvbdev) EXPORT_SYMBOL(dvb_remove_device); -void dvb_free_device(struct dvb_device *dvbdev) +static void dvb_free_device(struct kref *ref) { - if (!dvbdev) - return; + struct dvb_device *dvbdev = container_of(ref, struct dvb_device, ref); kfree (dvbdev->fops); kfree (dvbdev); } -EXPORT_SYMBOL(dvb_free_device); + + +struct dvb_device *dvb_device_get(struct dvb_device *dvbdev) +{ + kref_get(&dvbdev->ref); + return dvbdev; +} +EXPORT_SYMBOL(dvb_device_get); + + +void dvb_device_put(struct dvb_device *dvbdev) +{ + if (dvbdev) + kref_put(&dvbdev->ref, dvb_free_device); +} void dvb_unregister_device(struct dvb_device *dvbdev) { dvb_remove_device(dvbdev); - dvb_free_device(dvbdev); + dvb_device_put(dvbdev); } EXPORT_SYMBOL(dvb_unregister_device); diff --git a/drivers/media/dvb-frontends/a8293.c b/drivers/media/dvb-frontends/a8293.c index ba38783b2b4f..cca7cbdd4c7c 100644 --- a/drivers/media/dvb-frontends/a8293.c +++ b/drivers/media/dvb-frontends/a8293.c @@ -7,20 +7,148 @@ #include "a8293.h" +#define A8293_FLAG_ODT 0x10 + struct a8293_dev { struct i2c_client *client; u8 reg[2]; + int volt_slew_nanos_per_mv; }; -static int a8293_set_voltage(struct dvb_frontend *fe, - enum fe_sec_voltage fe_sec_voltage) +/* + * When increasing voltage, do so in minimal steps over time, minimizing + * risk of vIN undervoltage. + */ + +static int a8293_set_voltage_slew(struct a8293_dev *dev, + struct i2c_client *client, + enum fe_sec_voltage fe_sec_voltage, + int min_nanos_per_mv) +{ + int ret; + u8 reg0, reg1; + int new_volt_idx; + const int idx_to_mv[] = { + 0, 12709, 13042, 13375, 14042, 15042, 18042, 18709, 19042 + }; + const u8 idx_to_reg[] = { + 0x00, 0x20, 0x21, 0x22, 0x24, 0x27, 0x28, 0x2A, 0x2B + }; + int this_volt_idx; + u8 status; + int prev_volt_idx; + + dev_dbg(&client->dev, "set_voltage_slew fe_sec_voltage=%d\n", + fe_sec_voltage); + + /* Read status register to clear any stale faults. */ + ret = i2c_master_recv(client, &status, 1); + if (ret < 0) + goto err; + + /* Determine previous voltage */ + switch (dev->reg[0] & 0x2F) { + case 0x00: + prev_volt_idx = 0; + break; + case 0x20: + prev_volt_idx = 1; + break; + case 0x21: + prev_volt_idx = 2; + break; + case 0x22: + prev_volt_idx = 3; + break; + case 0x24: + prev_volt_idx = 4; + break; + case 0x27: + prev_volt_idx = 5; + break; + case 0x28: + prev_volt_idx = 6; + break; + case 0x2A: + prev_volt_idx = 7; + break; + case 0x2B: + prev_volt_idx = 8; + break; + default: + prev_volt_idx = 0; + } + + /* Determine new voltage */ + switch (fe_sec_voltage) { + case SEC_VOLTAGE_OFF: + new_volt_idx = 0; + break; + case SEC_VOLTAGE_13: + new_volt_idx = 2; + break; + case SEC_VOLTAGE_18: + new_volt_idx = 6; + break; + default: + ret = -EINVAL; + goto err; + } + + /* Slew to new voltage if new voltage is greater than current voltage */ + this_volt_idx = prev_volt_idx; + if (this_volt_idx < new_volt_idx) { + while (this_volt_idx < new_volt_idx) { + int delta_mv = idx_to_mv[this_volt_idx+1] - idx_to_mv[this_volt_idx]; + int min_wait_time = delta_mv * min_nanos_per_mv; + + reg0 = idx_to_reg[this_volt_idx+1]; + reg0 |= A8293_FLAG_ODT; + + ret = i2c_master_send(client, ®0, 1); + if (ret < 0) + goto err; + dev->reg[0] = reg0; + this_volt_idx++; + usleep_range(min_wait_time, min_wait_time * 2); + } + } else { /* Else just set the voltage */ + reg0 = idx_to_reg[new_volt_idx]; + reg0 |= A8293_FLAG_ODT; + ret = i2c_master_send(client, ®0, 1); + if (ret < 0) + goto err; + dev->reg[0] = reg0; + } + + /* TMODE=0, TGATE=1 */ + reg1 = 0x82; + if (reg1 != dev->reg[1]) { + ret = i2c_master_send(client, ®1, 1); + if (ret < 0) + goto err; + dev->reg[1] = reg1; + } + + usleep_range(1500, 5000); + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + + +static int a8293_set_voltage_noslew(struct dvb_frontend *fe, + enum fe_sec_voltage fe_sec_voltage) { struct a8293_dev *dev = fe->sec_priv; struct i2c_client *client = dev->client; int ret; u8 reg0, reg1; - dev_dbg(&client->dev, "fe_sec_voltage=%d\n", fe_sec_voltage); + dev_dbg(&client->dev, "set_voltage_noslew fe_sec_voltage=%d\n", + fe_sec_voltage); switch (fe_sec_voltage) { case SEC_VOLTAGE_OFF: @@ -62,8 +190,27 @@ err: return ret; } -static int a8293_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int a8293_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage fe_sec_voltage) +{ + struct a8293_dev *dev = fe->sec_priv; + struct i2c_client *client = dev->client; + int volt_slew_nanos_per_mv = dev->volt_slew_nanos_per_mv; + + dev_dbg(&client->dev, "set_voltage volt_slew_nanos_per_mv=%d\n", + volt_slew_nanos_per_mv); + + /* Use slew version if slew rate is set to a sane value */ + if (volt_slew_nanos_per_mv > 0 && volt_slew_nanos_per_mv < 1600) + a8293_set_voltage_slew(dev, client, fe_sec_voltage, + volt_slew_nanos_per_mv); + else + a8293_set_voltage_noslew(fe, fe_sec_voltage); + + return 0; +} + +static int a8293_probe(struct i2c_client *client) { struct a8293_dev *dev; struct a8293_platform_data *pdata = client->dev.platform_data; @@ -78,6 +225,7 @@ static int a8293_probe(struct i2c_client *client, } dev->client = client; + dev->volt_slew_nanos_per_mv = pdata->volt_slew_nanos_per_mv; /* check if the SEC is there */ ret = i2c_master_recv(client, buf, 2); @@ -118,7 +266,7 @@ static struct i2c_driver a8293_driver = { .name = "a8293", .suppress_bind_attrs = true, }, - .probe = a8293_probe, + .probe_new = a8293_probe, .remove = a8293_remove, .id_table = a8293_id_table, }; diff --git a/drivers/media/dvb-frontends/a8293.h b/drivers/media/dvb-frontends/a8293.h index 8c09635ef8a4..7fbac9735f7b 100644 --- a/drivers/media/dvb-frontends/a8293.h +++ b/drivers/media/dvb-frontends/a8293.h @@ -18,9 +18,12 @@ /** * struct a8293_platform_data - Platform data for the a8293 driver * @dvb_frontend: DVB frontend. + * @volt_slew_nanos_per_mv: Slew rate when increasing LNB voltage, + * in nanoseconds per millivolt. */ struct a8293_platform_data { struct dvb_frontend *dvb_frontend; + int volt_slew_nanos_per_mv; }; #endif /* A8293_H */ diff --git a/drivers/media/dvb-frontends/af9013.c b/drivers/media/dvb-frontends/af9013.c index d85929582c3f..206758a73ae2 100644 --- a/drivers/media/dvb-frontends/af9013.c +++ b/drivers/media/dvb-frontends/af9013.c @@ -1430,8 +1430,7 @@ err: return ret; } -static int af9013_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int af9013_probe(struct i2c_client *client) { struct af9013_state *state; struct af9013_platform_data *pdata = client->dev.platform_data; @@ -1564,7 +1563,7 @@ static struct i2c_driver af9013_driver = { .name = "af9013", .suppress_bind_attrs = true, }, - .probe = af9013_probe, + .probe_new = af9013_probe, .remove = af9013_remove, .id_table = af9013_id_table, }; diff --git a/drivers/media/dvb-frontends/af9033.c b/drivers/media/dvb-frontends/af9033.c index 808da7a9ffe7..a30773f62006 100644 --- a/drivers/media/dvb-frontends/af9033.c +++ b/drivers/media/dvb-frontends/af9033.c @@ -1049,8 +1049,7 @@ static const struct dvb_frontend_ops af9033_ops = { .i2c_gate_ctrl = af9033_i2c_gate_ctrl, }; -static int af9033_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int af9033_probe(struct i2c_client *client) { struct af9033_config *cfg = client->dev.platform_data; struct af9033_dev *dev; @@ -1184,7 +1183,7 @@ static struct i2c_driver af9033_driver = { .name = "af9033", .suppress_bind_attrs = true, }, - .probe = af9033_probe, + .probe_new = af9033_probe, .remove = af9033_remove, .id_table = af9033_id_table, }; diff --git a/drivers/media/dvb-frontends/au8522_decoder.c b/drivers/media/dvb-frontends/au8522_decoder.c index e4f99bd468cb..0f748cf46089 100644 --- a/drivers/media/dvb-frontends/au8522_decoder.c +++ b/drivers/media/dvb-frontends/au8522_decoder.c @@ -669,8 +669,7 @@ static const struct v4l2_ctrl_ops au8522_ctrl_ops = { /* ----------------------------------------------------------------------- */ -static int au8522_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int au8522_probe(struct i2c_client *client) { struct au8522_state *state; struct v4l2_ctrl_handler *hdl; @@ -777,7 +776,7 @@ static struct i2c_driver au8522_driver = { .driver = { .name = "au8522", }, - .probe = au8522_probe, + .probe_new = au8522_probe, .remove = au8522_remove, .id_table = au8522_id, }; diff --git a/drivers/media/dvb-frontends/bcm3510.c b/drivers/media/dvb-frontends/bcm3510.c index da0ff7b44da4..68b92b4419cf 100644 --- a/drivers/media/dvb-frontends/bcm3510.c +++ b/drivers/media/dvb-frontends/bcm3510.c @@ -649,6 +649,7 @@ static int bcm3510_download_firmware(struct dvb_frontend* fe) deb_info("firmware chunk, addr: 0x%04x, len: 0x%04x, total length: 0x%04zx\n",addr,len,fw->size); if ((ret = bcm3510_write_ram(st,addr,&b[i+4],len)) < 0) { err("firmware download failed: %d\n",ret); + release_firmware(fw); return ret; } i += 4 + len; diff --git a/drivers/media/dvb-frontends/cxd2099.c b/drivers/media/dvb-frontends/cxd2099.c index fbc666fa04ec..c0967ad95220 100644 --- a/drivers/media/dvb-frontends/cxd2099.c +++ b/drivers/media/dvb-frontends/cxd2099.c @@ -598,8 +598,7 @@ static const struct dvb_ca_en50221 en_templ = { .write_data = write_data, }; -static int cxd2099_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cxd2099_probe(struct i2c_client *client) { struct cxd *ci; struct cxd2099_cfg *cfg = client->dev.platform_data; @@ -682,7 +681,7 @@ static struct i2c_driver cxd2099_driver = { .driver = { .name = "cxd2099", }, - .probe = cxd2099_probe, + .probe_new = cxd2099_probe, .remove = cxd2099_remove, .id_table = cxd2099_id, }; diff --git a/drivers/media/dvb-frontends/cxd2820r_core.c b/drivers/media/dvb-frontends/cxd2820r_core.c index 5d98222f9df0..47aa40967171 100644 --- a/drivers/media/dvb-frontends/cxd2820r_core.c +++ b/drivers/media/dvb-frontends/cxd2820r_core.c @@ -547,8 +547,7 @@ static struct dvb_frontend *cxd2820r_get_dvb_frontend(struct i2c_client *client) return &priv->fe; } -static int cxd2820r_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cxd2820r_probe(struct i2c_client *client) { struct cxd2820r_platform_data *pdata = client->dev.platform_data; struct cxd2820r_priv *priv; @@ -629,7 +628,7 @@ static int cxd2820r_probe(struct i2c_client *client, /* * Chip has two I2C addresses for different register banks. We register - * one dummy I2C client in in order to get own I2C client for each + * one dummy I2C client in order to get own I2C client for each * register bank. */ priv->client[1] = i2c_new_dummy_device(client->adapter, client->addr | (1 << 1)); @@ -734,7 +733,7 @@ static struct i2c_driver cxd2820r_driver = { .name = "cxd2820r", .suppress_bind_attrs = true, }, - .probe = cxd2820r_probe, + .probe_new = cxd2820r_probe, .remove = cxd2820r_remove, .id_table = cxd2820r_id_table, }; diff --git a/drivers/media/dvb-frontends/cxd2820r_priv.h b/drivers/media/dvb-frontends/cxd2820r_priv.h index 09c42bcef971..9b4d9cf8563d 100644 --- a/drivers/media/dvb-frontends/cxd2820r_priv.h +++ b/drivers/media/dvb-frontends/cxd2820r_priv.h @@ -52,8 +52,6 @@ struct cxd2820r_priv { /* cxd2820r_core.c */ -extern int cxd2820r_debug; - int cxd2820r_gpio(struct dvb_frontend *fe, u8 *gpio); int cxd2820r_wr_reg_val_mask_tab(struct cxd2820r_priv *priv, diff --git a/drivers/media/dvb-frontends/drx39xyj/drx_dap_fasi.h b/drivers/media/dvb-frontends/drx39xyj/drx_dap_fasi.h index 739dc5590fa4..9df34c10d22b 100644 --- a/drivers/media/dvb-frontends/drx39xyj/drx_dap_fasi.h +++ b/drivers/media/dvb-frontends/drx39xyj/drx_dap_fasi.h @@ -234,8 +234,6 @@ /*-------- Public API functions ----------------------------------------------*/ -extern struct drx_access_func drx_dap_fasi_funct_g; - #define DRXDAP_FASI_RMW 0x10000000 #define DRXDAP_FASI_BROADCAST 0x20000000 #define DRXDAP_FASI_CLEARCRC 0x80000000 diff --git a/drivers/media/dvb-frontends/drx39xyj/drxj.c b/drivers/media/dvb-frontends/drx39xyj/drxj.c index bf9e4ef35684..1dff59ca21a1 100644 --- a/drivers/media/dvb-frontends/drx39xyj/drxj.c +++ b/drivers/media/dvb-frontends/drx39xyj/drxj.c @@ -2347,6 +2347,7 @@ hi_command(struct i2c_device_addr *dev_addr, const struct drxj_hi_cmd *cmd, u16 do { nr_retries++; if (nr_retries > DRXJ_MAX_RETRIES) { + rc = -ETIMEDOUT; pr_err("timeout\n"); goto rw_error; } diff --git a/drivers/media/dvb-frontends/helene.c b/drivers/media/dvb-frontends/helene.c index 8c1310c6b0bc..e4bbf6a51a2b 100644 --- a/drivers/media/dvb-frontends/helene.c +++ b/drivers/media/dvb-frontends/helene.c @@ -1063,8 +1063,7 @@ struct dvb_frontend *helene_attach(struct dvb_frontend *fe, } EXPORT_SYMBOL(helene_attach); -static int helene_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int helene_probe(struct i2c_client *client) { struct helene_config *config = client->dev.platform_data; struct dvb_frontend *fe = config->fe; @@ -1111,7 +1110,7 @@ static struct i2c_driver helene_driver = { .driver = { .name = "helene", }, - .probe = helene_probe, + .probe_new = helene_probe, .id_table = helene_id, }; module_i2c_driver(helene_driver); diff --git a/drivers/media/dvb-frontends/lgdt3306a.c b/drivers/media/dvb-frontends/lgdt3306a.c index 424311afb2bf..6bf723b5ffad 100644 --- a/drivers/media/dvb-frontends/lgdt3306a.c +++ b/drivers/media/dvb-frontends/lgdt3306a.c @@ -2169,8 +2169,7 @@ static int lgdt3306a_deselect(struct i2c_mux_core *muxc, u32 chan) return lgdt3306a_i2c_gate_ctrl(&state->frontend, 0); } -static int lgdt3306a_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int lgdt3306a_probe(struct i2c_client *client) { struct lgdt3306a_config *config; struct lgdt3306a_state *state; @@ -2250,7 +2249,7 @@ static struct i2c_driver lgdt3306a_driver = { .name = "lgdt3306a", .suppress_bind_attrs = true, }, - .probe = lgdt3306a_probe, + .probe_new = lgdt3306a_probe, .remove = lgdt3306a_remove, .id_table = lgdt3306a_id_table, }; diff --git a/drivers/media/dvb-frontends/lgdt330x.c b/drivers/media/dvb-frontends/lgdt330x.c index ea9ae22fd201..1d6932d8e497 100644 --- a/drivers/media/dvb-frontends/lgdt330x.c +++ b/drivers/media/dvb-frontends/lgdt330x.c @@ -857,8 +857,7 @@ static struct dvb_frontend *lgdt330x_get_dvb_frontend(struct i2c_client *client) static const struct dvb_frontend_ops lgdt3302_ops; static const struct dvb_frontend_ops lgdt3303_ops; -static int lgdt330x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int lgdt330x_probe(struct i2c_client *client) { struct lgdt330x_state *state = NULL; u8 buf[1]; @@ -994,7 +993,7 @@ static struct i2c_driver lgdt330x_driver = { .name = "lgdt330x", .suppress_bind_attrs = true, }, - .probe = lgdt330x_probe, + .probe_new = lgdt330x_probe, .remove = lgdt330x_remove, .id_table = lgdt330x_id_table, }; diff --git a/drivers/media/dvb-frontends/mn88472.c b/drivers/media/dvb-frontends/mn88472.c index 2b01cc678f7e..4a71f1c6371a 100644 --- a/drivers/media/dvb-frontends/mn88472.c +++ b/drivers/media/dvb-frontends/mn88472.c @@ -572,8 +572,7 @@ static struct dvb_frontend *mn88472_get_dvb_frontend(struct i2c_client *client) return &dev->fe; } -static int mn88472_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mn88472_probe(struct i2c_client *client) { struct mn88472_config *pdata = client->dev.platform_data; struct mn88472_dev *dev; @@ -719,7 +718,7 @@ static struct i2c_driver mn88472_driver = { .name = "mn88472", .suppress_bind_attrs = true, }, - .probe = mn88472_probe, + .probe_new = mn88472_probe, .remove = mn88472_remove, .id_table = mn88472_id_table, }; diff --git a/drivers/media/dvb-frontends/mn88473.c b/drivers/media/dvb-frontends/mn88473.c index f0ecf5910c02..205b14ae584e 100644 --- a/drivers/media/dvb-frontends/mn88473.c +++ b/drivers/media/dvb-frontends/mn88473.c @@ -606,8 +606,7 @@ static const struct dvb_frontend_ops mn88473_ops = { .read_status = mn88473_read_status, }; -static int mn88473_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mn88473_probe(struct i2c_client *client) { struct mn88473_config *config = client->dev.platform_data; struct mn88473_dev *dev; @@ -754,7 +753,7 @@ static struct i2c_driver mn88473_driver = { .name = "mn88473", .suppress_bind_attrs = true, }, - .probe = mn88473_probe, + .probe_new = mn88473_probe, .remove = mn88473_remove, .id_table = mn88473_id_table, }; diff --git a/drivers/media/dvb-frontends/mxl5xx.c b/drivers/media/dvb-frontends/mxl5xx.c index 934d1c0b214a..4ebbcf05cc09 100644 --- a/drivers/media/dvb-frontends/mxl5xx.c +++ b/drivers/media/dvb-frontends/mxl5xx.c @@ -1644,8 +1644,6 @@ static int validate_sku(struct mxl *state) default: return -1; } - } else { - } return -1; } diff --git a/drivers/media/dvb-frontends/mxl692.c b/drivers/media/dvb-frontends/mxl692.c index 129630cbffff..9858e11943a0 100644 --- a/drivers/media/dvb-frontends/mxl692.c +++ b/drivers/media/dvb-frontends/mxl692.c @@ -1308,8 +1308,7 @@ static const struct dvb_frontend_ops mxl692_ops = { .read_snr = mxl692_read_snr, }; -static int mxl692_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mxl692_probe(struct i2c_client *client) { struct mxl692_config *config = client->dev.platform_data; struct mxl692_dev *dev; @@ -1356,7 +1355,7 @@ static struct i2c_driver mxl692_driver = { .driver = { .name = "mxl692", }, - .probe = mxl692_probe, + .probe_new = mxl692_probe, .remove = mxl692_remove, .id_table = mxl692_id_table, }; diff --git a/drivers/media/dvb-frontends/rtl2830.c b/drivers/media/dvb-frontends/rtl2830.c index e0fbf41316ae..db3254950147 100644 --- a/drivers/media/dvb-frontends/rtl2830.c +++ b/drivers/media/dvb-frontends/rtl2830.c @@ -768,8 +768,7 @@ static int rtl2830_regmap_gather_write(void *context, const void *reg, return 0; } -static int rtl2830_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int rtl2830_probe(struct i2c_client *client) { struct rtl2830_platform_data *pdata = client->dev.platform_data; struct rtl2830_dev *dev; @@ -887,7 +886,7 @@ static struct i2c_driver rtl2830_driver = { .name = "rtl2830", .suppress_bind_attrs = true, }, - .probe = rtl2830_probe, + .probe_new = rtl2830_probe, .remove = rtl2830_remove, .id_table = rtl2830_id_table, }; diff --git a/drivers/media/dvb-frontends/rtl2832.c b/drivers/media/dvb-frontends/rtl2832.c index 4fa884eda5d5..900d4db8b922 100644 --- a/drivers/media/dvb-frontends/rtl2832.c +++ b/drivers/media/dvb-frontends/rtl2832.c @@ -1021,8 +1021,7 @@ err: return ret; } -static int rtl2832_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int rtl2832_probe(struct i2c_client *client) { struct rtl2832_platform_data *pdata = client->dev.platform_data; struct i2c_adapter *i2c = client->adapter; @@ -1136,7 +1135,7 @@ static struct i2c_driver rtl2832_driver = { .name = "rtl2832", .suppress_bind_attrs = true, }, - .probe = rtl2832_probe, + .probe_new = rtl2832_probe, .remove = rtl2832_remove, .id_table = rtl2832_id_table, }; diff --git a/drivers/media/dvb-frontends/si2165.c b/drivers/media/dvb-frontends/si2165.c index 86b0d59169dd..cc07e965c34c 100644 --- a/drivers/media/dvb-frontends/si2165.c +++ b/drivers/media/dvb-frontends/si2165.c @@ -1144,8 +1144,7 @@ static const struct dvb_frontend_ops si2165_ops = { .read_ber = si2165_read_ber, }; -static int si2165_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int si2165_probe(struct i2c_client *client) { struct si2165_state *state = NULL; struct si2165_platform_data *pdata = client->dev.platform_data; @@ -1293,7 +1292,7 @@ static struct i2c_driver si2165_driver = { .driver = { .name = "si2165", }, - .probe = si2165_probe, + .probe_new = si2165_probe, .remove = si2165_remove, .id_table = si2165_id_table, }; diff --git a/drivers/media/dvb-frontends/si2168.c b/drivers/media/dvb-frontends/si2168.c index 8157df4570d1..2a0e108c5eb0 100644 --- a/drivers/media/dvb-frontends/si2168.c +++ b/drivers/media/dvb-frontends/si2168.c @@ -672,8 +672,7 @@ static const struct dvb_frontend_ops si2168_ops = { .read_status = si2168_read_status, }; -static int si2168_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int si2168_probe(struct i2c_client *client) { struct si2168_config *config = client->dev.platform_data; struct si2168_dev *dev; @@ -799,7 +798,7 @@ static struct i2c_driver si2168_driver = { .name = "si2168", .suppress_bind_attrs = true, }, - .probe = si2168_probe, + .probe_new = si2168_probe, .remove = si2168_remove, .id_table = si2168_id_table, }; diff --git a/drivers/media/dvb-frontends/sp2.c b/drivers/media/dvb-frontends/sp2.c index 27e7037e130e..3395f6b5b948 100644 --- a/drivers/media/dvb-frontends/sp2.c +++ b/drivers/media/dvb-frontends/sp2.c @@ -363,8 +363,7 @@ static int sp2_exit(struct i2c_client *client) return 0; } -static int sp2_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int sp2_probe(struct i2c_client *client) { struct sp2_config *cfg = client->dev.platform_data; struct sp2 *s; @@ -417,7 +416,7 @@ static struct i2c_driver sp2_driver = { .driver = { .name = "sp2", }, - .probe = sp2_probe, + .probe_new = sp2_probe, .remove = sp2_remove, .id_table = sp2_id, }; diff --git a/drivers/media/dvb-frontends/stv090x.c b/drivers/media/dvb-frontends/stv090x.c index 0a600c1d7d1b..9bde0ad6f26e 100644 --- a/drivers/media/dvb-frontends/stv090x.c +++ b/drivers/media/dvb-frontends/stv090x.c @@ -4990,8 +4990,7 @@ static struct dvb_frontend *stv090x_get_dvb_frontend(struct i2c_client *client) return &state->frontend; } -static int stv090x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int stv090x_probe(struct i2c_client *client) { int ret = 0; struct stv090x_config *config = client->dev.platform_data; @@ -5085,7 +5084,7 @@ static struct i2c_driver stv090x_driver = { .name = "stv090x", .suppress_bind_attrs = true, }, - .probe = stv090x_probe, + .probe_new = stv090x_probe, .remove = stv090x_remove, .id_table = stv090x_id_table, }; diff --git a/drivers/media/dvb-frontends/stv6110x.c b/drivers/media/dvb-frontends/stv6110x.c index fbc4dbd62151..b2f456116c60 100644 --- a/drivers/media/dvb-frontends/stv6110x.c +++ b/drivers/media/dvb-frontends/stv6110x.c @@ -406,8 +406,7 @@ static struct stv6110x_devctl *stv6110x_get_devctl(struct i2c_client *client) return stv6110x->devctl; } -static int stv6110x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int stv6110x_probe(struct i2c_client *client) { struct stv6110x_config *config = client->dev.platform_data; @@ -481,7 +480,7 @@ static struct i2c_driver stv6110x_driver = { .name = "stv6110x", .suppress_bind_attrs = true, }, - .probe = stv6110x_probe, + .probe_new = stv6110x_probe, .remove = stv6110x_remove, .id_table = stv6110x_id_table, }; diff --git a/drivers/media/dvb-frontends/tda10071.c b/drivers/media/dvb-frontends/tda10071.c index d1098ef20a8b..c8e5617d08c0 100644 --- a/drivers/media/dvb-frontends/tda10071.c +++ b/drivers/media/dvb-frontends/tda10071.c @@ -1145,8 +1145,7 @@ static struct dvb_frontend *tda10071_get_dvb_frontend(struct i2c_client *client) return &dev->fe; } -static int tda10071_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda10071_probe(struct i2c_client *client) { struct tda10071_dev *dev; struct tda10071_platform_data *pdata = client->dev.platform_data; @@ -1241,7 +1240,7 @@ static struct i2c_driver tda10071_driver = { .name = "tda10071", .suppress_bind_attrs = true, }, - .probe = tda10071_probe, + .probe_new = tda10071_probe, .remove = tda10071_remove, .id_table = tda10071_id_table, }; diff --git a/drivers/media/dvb-frontends/ts2020.c b/drivers/media/dvb-frontends/ts2020.c index 02338256b974..c28fee7509cd 100644 --- a/drivers/media/dvb-frontends/ts2020.c +++ b/drivers/media/dvb-frontends/ts2020.c @@ -550,8 +550,7 @@ static void ts2020_regmap_unlock(void *__dev) mutex_unlock(&dev->regmap_mutex); } -static int ts2020_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ts2020_probe(struct i2c_client *client) { struct ts2020_config *pdata = client->dev.platform_data; struct dvb_frontend *fe = pdata->fe; @@ -721,7 +720,7 @@ static struct i2c_driver ts2020_driver = { .driver = { .name = "ts2020", }, - .probe = ts2020_probe, + .probe_new = ts2020_probe, .remove = ts2020_remove, .id_table = ts2020_id_table, }; diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7806d4b81716..833241897d63 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -364,6 +364,19 @@ config VIDEO_OV08D10 To compile this driver as a module, choose M here: the module will be called ov08d10. +config VIDEO_OV08X40 + tristate "OmniVision OV08X40 sensor support" + depends on VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV08X40 camera. + + To compile this driver as a module, choose M here: the + module will be called ov08x40. + config VIDEO_OV13858 tristate "OmniVision OV13858 sensor support" depends on I2C && VIDEO_DEV @@ -445,6 +458,19 @@ config VIDEO_OV2740 To compile this driver as a module, choose M here: the module will be called ov2740. +config VIDEO_OV4689 + tristate "OmniVision OV4689 sensor support" + depends on GPIOLIB && VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor-level driver for the OmniVision + OV4689 camera. + + To compile this driver as a module, choose M here: the + module will be called ov4689. + config VIDEO_OV5640 tristate "OmniVision OV5640 sensor support" depends on OF @@ -725,16 +751,6 @@ config VIDEO_S5C73M3 This is a V4L2 sensor driver for Samsung S5C73M3 8 Mpixel camera. -config VIDEO_S5K4ECGX - tristate "Samsung S5K4ECGX sensor support" - depends on I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select CRC32 - help - This is a V4L2 sensor driver for Samsung S5K4ECGX 5M - camera sensor with an embedded SoC image signal processor. - config VIDEO_S5K5BAF tristate "Samsung S5K5BAF sensor support" depends on I2C && VIDEO_DEV @@ -769,6 +785,16 @@ config VIDEO_SR030PC30 help This driver supports SR030PC30 VGA camera from Siliconfile +config VIDEO_ST_VGXY61 + tristate "ST VGXY61 sensor support" + depends on OF && GPIOLIB && VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the ST VGXY61 + camera sensor. + config VIDEO_VS6624 tristate "ST VS6624 sensor support" depends on VIDEO_DEV && I2C @@ -1272,6 +1298,22 @@ config VIDEO_TC358743_CEC When selected the tc358743 will support the optional HDMI CEC feature. +config VIDEO_TC358746 + tristate "Toshiba TC358746 parallel-CSI2 bridge" + depends on VIDEO_DEV && PM && I2C + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select V4L2_FWNODE + select GENERIC_PHY_MIPI_DPHY + select REGMAP_I2C + help + Support for the Toshiba TC358746 parallel to MIPI CSI-2 bridge. + The bridge can work in both directions but currently only the + parallel-in / csi-out path is supported. + + To compile this driver as a module, choose M here: the + module will be called tc358746. + config VIDEO_TVP514X tristate "Texas Instruments TVP514x video decoder" depends on VIDEO_DEV && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0a2933103dd9..4d6c052bb5a7 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o +obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o obj-$(CONFIG_VIDEO_OV13858) += ov13858.o obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o obj-$(CONFIG_VIDEO_OV2640) += ov2640.o @@ -79,6 +80,7 @@ obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_OV2680) += ov2680.o obj-$(CONFIG_VIDEO_OV2685) += ov2685.o obj-$(CONFIG_VIDEO_OV2740) += ov2740.o +obj-$(CONFIG_VIDEO_OV4689) += ov4689.o obj-$(CONFIG_VIDEO_OV5640) += ov5640.o obj-$(CONFIG_VIDEO_OV5645) += ov5645.o obj-$(CONFIG_VIDEO_OV5647) += ov5647.o @@ -103,7 +105,6 @@ obj-$(CONFIG_VIDEO_RDACM20) += rdacm20.o obj-$(CONFIG_VIDEO_RDACM21) += rdacm21.o obj-$(CONFIG_VIDEO_RJ54N1) += rj54n1cb0c.o obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/ -obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o @@ -117,7 +118,9 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o +obj-$(CONFIG_VIDEO_TC358746) += tc358746.o obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o diff --git a/drivers/media/i2c/ad5820.c b/drivers/media/i2c/ad5820.c index 516de278cc49..44c26af49071 100644 --- a/drivers/media/i2c/ad5820.c +++ b/drivers/media/i2c/ad5820.c @@ -290,8 +290,7 @@ static int __maybe_unused ad5820_resume(struct device *dev) return ad5820_power_on(coil, true); } -static int ad5820_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int ad5820_probe(struct i2c_client *client) { struct ad5820_device *coil; int ret; @@ -301,21 +300,15 @@ static int ad5820_probe(struct i2c_client *client, return -ENOMEM; coil->vana = devm_regulator_get(&client->dev, "VANA"); - if (IS_ERR(coil->vana)) { - ret = PTR_ERR(coil->vana); - if (ret != -EPROBE_DEFER) - dev_err(&client->dev, "could not get regulator for vana\n"); - return ret; - } + if (IS_ERR(coil->vana)) + return dev_err_probe(&client->dev, PTR_ERR(coil->vana), + "could not get regulator for vana\n"); coil->enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW); - if (IS_ERR(coil->enable_gpio)) { - ret = PTR_ERR(coil->enable_gpio); - if (ret != -EPROBE_DEFER) - dev_err(&client->dev, "could not get enable gpio\n"); - return ret; - } + if (IS_ERR(coil->enable_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(coil->enable_gpio), + "could not get enable gpio\n"); mutex_init(&coil->power_lock); @@ -327,18 +320,18 @@ static int ad5820_probe(struct i2c_client *client, ret = media_entity_pads_init(&coil->subdev.entity, 0, NULL); if (ret < 0) - goto cleanup2; + goto clean_mutex; ret = v4l2_async_register_subdev(&coil->subdev); if (ret < 0) - goto cleanup; + goto clean_entity; return ret; -cleanup2: - mutex_destroy(&coil->power_lock); -cleanup: +clean_entity: media_entity_cleanup(&coil->subdev.entity); +clean_mutex: + mutex_destroy(&coil->power_lock); return ret; } @@ -377,7 +370,7 @@ static struct i2c_driver ad5820_i2c_driver = { .pm = &ad5820_pm, .of_match_table = ad5820_of_table, }, - .probe = ad5820_probe, + .probe_new = ad5820_probe, .remove = ad5820_remove, .id_table = ad5820_id_table, }; diff --git a/drivers/media/i2c/ad9389b.c b/drivers/media/i2c/ad9389b.c index 4a255a492918..ad17097a2d25 100644 --- a/drivers/media/i2c/ad9389b.c +++ b/drivers/media/i2c/ad9389b.c @@ -1080,7 +1080,7 @@ static void ad9389b_init_setup(struct v4l2_subdev *sd) ad9389b_set_isr(sd, false); } -static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int ad9389b_probe(struct i2c_client *client) { const struct v4l2_dv_timings dv1080p60 = V4L2_DV_BT_CEA_1920X1080P60; struct ad9389b_state *state; @@ -1207,7 +1207,7 @@ static struct i2c_driver ad9389b_driver = { .driver = { .name = "ad9389b", }, - .probe = ad9389b_probe, + .probe_new = ad9389b_probe, .remove = ad9389b_remove, .id_table = ad9389b_id, }; diff --git a/drivers/media/i2c/adp1653.c b/drivers/media/i2c/adp1653.c index 1f353157df07..a61a77de6eee 100644 --- a/drivers/media/i2c/adp1653.c +++ b/drivers/media/i2c/adp1653.c @@ -463,8 +463,7 @@ err: } -static int adp1653_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int adp1653_probe(struct i2c_client *client) { struct adp1653_flash *flash; int ret; @@ -536,7 +535,7 @@ static struct i2c_driver adp1653_i2c_driver = { .name = ADP1653_NAME, .pm = &adp1653_pm_ops, }, - .probe = adp1653_probe, + .probe_new = adp1653_probe, .remove = adp1653_remove, .id_table = adp1653_id_table, }; diff --git a/drivers/media/i2c/adv7170.c b/drivers/media/i2c/adv7170.c index 61a2f87d3c62..aa0f80e299b3 100644 --- a/drivers/media/i2c/adv7170.c +++ b/drivers/media/i2c/adv7170.c @@ -334,8 +334,7 @@ static const struct v4l2_subdev_ops adv7170_ops = { /* ----------------------------------------------------------------------- */ -static int adv7170_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7170_probe(struct i2c_client *client) { struct adv7170 *encoder; struct v4l2_subdev *sd; @@ -388,7 +387,7 @@ static struct i2c_driver adv7170_driver = { .driver = { .name = "adv7170", }, - .probe = adv7170_probe, + .probe_new = adv7170_probe, .remove = adv7170_remove, .id_table = adv7170_id, }; diff --git a/drivers/media/i2c/adv7175.c b/drivers/media/i2c/adv7175.c index b58689728243..d9bea2b9ec33 100644 --- a/drivers/media/i2c/adv7175.c +++ b/drivers/media/i2c/adv7175.c @@ -389,8 +389,7 @@ static const struct v4l2_subdev_ops adv7175_ops = { /* ----------------------------------------------------------------------- */ -static int adv7175_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7175_probe(struct i2c_client *client) { int i; struct adv7175 *encoder; @@ -443,7 +442,7 @@ static struct i2c_driver adv7175_driver = { .driver = { .name = "adv7175", }, - .probe = adv7175_probe, + .probe_new = adv7175_probe, .remove = adv7175_remove, .id_table = adv7175_id, }; diff --git a/drivers/media/i2c/adv7183.c b/drivers/media/i2c/adv7183.c index 313c706e8335..98b63d79d33d 100644 --- a/drivers/media/i2c/adv7183.c +++ b/drivers/media/i2c/adv7183.c @@ -521,8 +521,7 @@ static const struct v4l2_subdev_ops adv7183_ops = { .pad = &adv7183_pad_ops, }; -static int adv7183_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7183_probe(struct i2c_client *client) { struct adv7183 *decoder; struct v4l2_subdev *sd; @@ -632,7 +631,7 @@ static struct i2c_driver adv7183_driver = { .driver = { .name = "adv7183", }, - .probe = adv7183_probe, + .probe_new = adv7183_probe, .remove = adv7183_remove, .id_table = adv7183_id, }; diff --git a/drivers/media/i2c/adv7393.c b/drivers/media/i2c/adv7393.c index fb5fefa83b18..61e916cbe651 100644 --- a/drivers/media/i2c/adv7393.c +++ b/drivers/media/i2c/adv7393.c @@ -381,8 +381,7 @@ static int adv7393_initialize(struct v4l2_subdev *sd) return err; } -static int adv7393_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7393_probe(struct i2c_client *client) { struct adv7393_state *state; int err; @@ -456,7 +455,7 @@ static struct i2c_driver adv7393_driver = { .driver = { .name = "adv7393", }, - .probe = adv7393_probe, + .probe_new = adv7393_probe, .remove = adv7393_remove, .id_table = adv7393_id, }; diff --git a/drivers/media/i2c/adv748x/adv748x-afe.c b/drivers/media/i2c/adv748x/adv748x-afe.c index 02eabe10ab97..00095c7762c2 100644 --- a/drivers/media/i2c/adv748x/adv748x-afe.c +++ b/drivers/media/i2c/adv748x/adv748x-afe.c @@ -521,6 +521,10 @@ int adv748x_afe_init(struct adv748x_afe *afe) } } + adv748x_afe_s_input(afe, afe->input); + + adv_dbg(state, "AFE Default input set to %d\n", afe->input); + /* Entity pads and sinks are 0-indexed to match the pads */ for (i = ADV748X_AFE_SINK_AIN0; i <= ADV748X_AFE_SINK_AIN7; i++) afe->pads[i].flags = MEDIA_PAD_FL_SINK; diff --git a/drivers/media/i2c/adv748x/adv748x.h b/drivers/media/i2c/adv748x/adv748x.h index d75eb3d8be5a..6f90f78f58cf 100644 --- a/drivers/media/i2c/adv748x/adv748x.h +++ b/drivers/media/i2c/adv748x/adv748x.h @@ -428,9 +428,6 @@ void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state, const struct v4l2_subdev_ops *ops, u32 function, const char *ident); -int adv748x_register_subdevs(struct adv748x_state *state, - struct v4l2_device *v4l2_dev); - int adv748x_tx_power(struct adv748x_csi2 *tx, bool on); int adv748x_afe_init(struct adv748x_afe *afe); diff --git a/drivers/media/i2c/adv7511-v4l2.c b/drivers/media/i2c/adv7511-v4l2.c index 0d5ce69f12e7..3999fa524cab 100644 --- a/drivers/media/i2c/adv7511-v4l2.c +++ b/drivers/media/i2c/adv7511-v4l2.c @@ -1763,7 +1763,7 @@ static void adv7511_init_setup(struct v4l2_subdev *sd) adv7511_cec_write(sd, 0x4e, ratio << 2); } -static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int adv7511_probe(struct i2c_client *client) { struct adv7511_state *state; struct adv7511_platform_data *pdata = client->dev.platform_data; @@ -1957,7 +1957,7 @@ static struct i2c_driver adv7511_driver = { .driver = { .name = "adv7511-v4l2", }, - .probe = adv7511_probe, + .probe_new = adv7511_probe, .remove = adv7511_remove, .id_table = adv7511_id, }; diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index 7731cc1887e6..cb8655574119 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -3441,8 +3441,7 @@ static int adv7842_register_clients(struct v4l2_subdev *sd) return 0; } -static int adv7842_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7842_probe(struct i2c_client *client) { struct adv7842_state *state; static const struct v4l2_dv_timings cea640x480 = @@ -3620,7 +3619,7 @@ static struct i2c_driver adv7842_driver = { .driver = { .name = "adv7842", }, - .probe = adv7842_probe, + .probe_new = adv7842_probe, .remove = adv7842_remove, .id_table = adv7842_id, }; diff --git a/drivers/media/i2c/ak881x.c b/drivers/media/i2c/ak881x.c index 0370ad6b6811..7c9ab76e2448 100644 --- a/drivers/media/i2c/ak881x.c +++ b/drivers/media/i2c/ak881x.c @@ -226,8 +226,7 @@ static const struct v4l2_subdev_ops ak881x_subdev_ops = { .pad = &ak881x_subdev_pad_ops, }; -static int ak881x_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int ak881x_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct ak881x *ak881x; @@ -315,7 +314,7 @@ static struct i2c_driver ak881x_i2c_driver = { .driver = { .name = "ak881x", }, - .probe = ak881x_probe, + .probe_new = ak881x_probe, .remove = ak881x_remove, .id_table = ak881x_id, }; diff --git a/drivers/media/i2c/aptina-pll.c b/drivers/media/i2c/aptina-pll.c index 1423c04a1c90..b1f89bbf9d47 100644 --- a/drivers/media/i2c/aptina-pll.c +++ b/drivers/media/i2c/aptina-pll.c @@ -8,7 +8,6 @@ #include <linux/device.h> #include <linux/gcd.h> #include <linux/kernel.h> -#include <linux/lcm.h> #include <linux/module.h> #include "aptina-pll.h" diff --git a/drivers/media/i2c/ar0521.c b/drivers/media/i2c/ar0521.c index e408049f6312..77f597571167 100644 --- a/drivers/media/i2c/ar0521.c +++ b/drivers/media/i2c/ar0521.c @@ -14,26 +14,39 @@ #include <media/v4l2-subdev.h> /* External clock (extclk) frequencies */ -#define AR0521_EXTCLK_MIN (10 * 1000 * 1000) -#define AR0521_EXTCLK_MAX (48 * 1000 * 1000) +#define AR0521_EXTCLK_MIN (10 * 1000 * 1000) +#define AR0521_EXTCLK_MAX (48 * 1000 * 1000) /* PLL and PLL2 */ -#define AR0521_PLL_MIN (320 * 1000 * 1000) -#define AR0521_PLL_MAX (1280 * 1000 * 1000) - -/* Effective pixel clocks, the registers may be DDR */ -#define AR0521_PIXEL_CLOCK_RATE (184 * 1000 * 1000) -#define AR0521_PIXEL_CLOCK_MIN (168 * 1000 * 1000) -#define AR0521_PIXEL_CLOCK_MAX (414 * 1000 * 1000) - -#define AR0521_WIDTH_MIN 8u -#define AR0521_WIDTH_MAX 2608u -#define AR0521_HEIGHT_MIN 8u -#define AR0521_HEIGHT_MAX 1958u - -#define AR0521_WIDTH_BLANKING_MIN 572u -#define AR0521_HEIGHT_BLANKING_MIN 38u /* must be even */ -#define AR0521_TOTAL_WIDTH_MIN 2968u +#define AR0521_PLL_MIN (320 * 1000 * 1000) +#define AR0521_PLL_MAX (1280 * 1000 * 1000) + +/* Effective pixel sample rate on the pixel array. */ +#define AR0521_PIXEL_CLOCK_RATE (184 * 1000 * 1000) +#define AR0521_PIXEL_CLOCK_MIN (168 * 1000 * 1000) +#define AR0521_PIXEL_CLOCK_MAX (414 * 1000 * 1000) + +#define AR0521_NATIVE_WIDTH 2604u +#define AR0521_NATIVE_HEIGHT 1964u +#define AR0521_MIN_X_ADDR_START 0u +#define AR0521_MIN_Y_ADDR_START 0u +#define AR0521_MAX_X_ADDR_END 2603u +#define AR0521_MAX_Y_ADDR_END 1955u + +#define AR0521_WIDTH_MIN 8u +#define AR0521_WIDTH_MAX 2592u +#define AR0521_HEIGHT_MIN 8u +#define AR0521_HEIGHT_MAX 1944u + +#define AR0521_WIDTH_BLANKING_MIN 572u +#define AR0521_HEIGHT_BLANKING_MIN 38u /* must be even */ +#define AR0521_TOTAL_HEIGHT_MAX 65535u /* max_frame_length_lines */ +#define AR0521_TOTAL_WIDTH_MAX 65532u /* max_line_length_pck */ + +#define AR0521_ANA_GAIN_MIN 0x00 +#define AR0521_ANA_GAIN_MAX 0x3f +#define AR0521_ANA_GAIN_STEP 0x01 +#define AR0521_ANA_GAIN_DEFAULT 0x00 /* AR0521 registers */ #define AR0521_REG_VT_PIX_CLK_DIV 0x0300 @@ -50,6 +63,8 @@ #define AR0521_REG_RESET_RESTART BIT(1) #define AR0521_REG_RESET_INIT BIT(0) +#define AR0521_REG_ANA_GAIN_CODE_GLOBAL 0x3028 + #define AR0521_REG_GREEN1_GAIN 0x3056 #define AR0521_REG_BLUE_GAIN 0x3058 #define AR0521_REG_RED_GAIN 0x305A @@ -75,6 +90,10 @@ static const char * const ar0521_supply_names[] = { "vaa", /* Analog (2.7V) supply */ }; +static const s64 ar0521_link_frequencies[] = { + 184000000, +}; + struct ar0521_ctrls { struct v4l2_ctrl_handler handler; struct { @@ -107,12 +126,14 @@ struct ar0521_dev { struct v4l2_mbus_framefmt fmt; struct ar0521_ctrls ctrls; unsigned int lane_count; - u16 total_width; - u16 total_height; - u16 pll_pre; - u16 pll_mult; - u16 pll_pre2; - u16 pll_mult2; + struct { + u16 pre; + u16 mult; + u16 pre2; + u16 mult2; + u16 vt_pix; + } pll; + bool streaming; }; @@ -137,6 +158,16 @@ static u32 div64_round_up(u64 v, u32 d) return div_u64(v + d - 1, d); } +static int ar0521_code_to_bpp(struct ar0521_dev *sensor) +{ + switch (sensor->fmt.code) { + case MEDIA_BUS_FMT_SGRBG8_1X8: + return 8; + } + + return -EINVAL; +} + /* Data must be BE16, the first value is the register address */ static int ar0521_write_regs(struct ar0521_dev *sensor, const __be16 *data, unsigned int count) @@ -169,13 +200,17 @@ static int ar0521_write_reg(struct ar0521_dev *sensor, u16 reg, u16 val) static int ar0521_set_geometry(struct ar0521_dev *sensor) { + /* Center the image in the visible output window. */ + u16 x = clamp((AR0521_WIDTH_MAX - sensor->fmt.width) / 2, + AR0521_MIN_X_ADDR_START, AR0521_MAX_X_ADDR_END); + u16 y = clamp(((AR0521_HEIGHT_MAX - sensor->fmt.height) / 2) & ~1, + AR0521_MIN_Y_ADDR_START, AR0521_MAX_Y_ADDR_END); + /* All dimensions are unsigned 12-bit integers */ - u16 x = (AR0521_WIDTH_MAX - sensor->fmt.width) / 2; - u16 y = ((AR0521_HEIGHT_MAX - sensor->fmt.height) / 2) & ~1; __be16 regs[] = { be(AR0521_REG_FRAME_LENGTH_LINES), - be(sensor->total_height), - be(sensor->total_width), + be(sensor->fmt.height + sensor->ctrls.vblank->val), + be(sensor->fmt.width + sensor->ctrls.hblank->val), be(x), be(y), be(x + sensor->fmt.width - 1), @@ -208,8 +243,7 @@ static int ar0521_set_gains(struct ar0521_dev *sensor) return ar0521_write_regs(sensor, regs, ARRAY_SIZE(regs)); } -static u32 calc_pll(struct ar0521_dev *sensor, int num, u32 freq, u16 *pre_ptr, - u16 *mult_ptr) +static u32 calc_pll(struct ar0521_dev *sensor, u32 freq, u16 *pre_ptr, u16 *mult_ptr) { u16 pre = 1, mult = 1, new_pre; u32 pll = AR0521_PLL_MAX + 1; @@ -244,69 +278,84 @@ static u32 calc_pll(struct ar0521_dev *sensor, int num, u32 freq, u16 *pre_ptr, return pll; } -#define DIV 4 -static void ar0521_calc_mode(struct ar0521_dev *sensor) +static void ar0521_calc_pll(struct ar0521_dev *sensor) { - unsigned int speed_mod = 4 / sensor->lane_count; /* 1 with 4 DDR lanes */ - u16 total_width = max(sensor->fmt.width + AR0521_WIDTH_BLANKING_MIN, - AR0521_TOTAL_WIDTH_MIN); - u16 total_height = sensor->fmt.height + AR0521_HEIGHT_BLANKING_MIN; - - /* Calculate approximate pixel clock first */ - u64 pix_clk = AR0521_PIXEL_CLOCK_RATE; - - /* PLL1 drives pixel clock - dual rate */ - pix_clk = calc_pll(sensor, 1, pix_clk * (DIV / 2), &sensor->pll_pre, - &sensor->pll_mult); - pix_clk = div64_round(pix_clk, (DIV / 2)); - calc_pll(sensor, 2, pix_clk * (DIV / 2) * speed_mod, &sensor->pll_pre2, - &sensor->pll_mult2); - - sensor->total_width = total_width; - sensor->total_height = total_height; + unsigned int pixel_clock; + u16 pre, mult; + u32 vco; + int bpp; + + /* + * PLL1 and PLL2 are computed equally even if the application note + * suggests a slower PLL1 clock. Maintain pll1 and pll2 divider and + * multiplier separated to later specialize the calculation procedure. + * + * PLL1: + * - mclk -> / pre_div1 * pre_mul1 = VCO1 = COUNTER_CLOCK + * + * PLL2: + * - mclk -> / pre_div * pre_mul = VCO + * + * VCO -> / vt_pix = PIXEL_CLOCK + * VCO -> / vt_pix / 2 = WORD_CLOCK + * VCO -> / op_sys = SERIAL_CLOCK + * + * With: + * - vt_pix = bpp / 2 + * - WORD_CLOCK = PIXEL_CLOCK / 2 + * - SERIAL_CLOCK = MIPI data rate (Mbps / lane) = WORD_CLOCK * bpp + * NOTE: this implies the MIPI clock is divided internally by 2 + * to account for DDR. + * + * As op_sys_div is fixed to 1: + * + * SERIAL_CLOCK = VCO + * VCO = 2 * MIPI_CLK + * VCO = PIXEL_CLOCK * bpp / 2 + * + * In the clock tree: + * MIPI_CLK = PIXEL_CLOCK * bpp / 2 / 2 + * + * Generic pixel_rate to bus clock frequencey equation: + * MIPI_CLK = V4L2_CID_PIXEL_RATE * bpp / lanes / 2 + * + * From which we derive the PIXEL_CLOCK to use in the clock tree: + * PIXEL_CLOCK = V4L2_CID_PIXEL_RATE * 2 / lanes + * + * Documented clock ranges: + * WORD_CLOCK = (35MHz - 120 MHz) + * PIXEL_CLOCK = (84MHz - 207MHz) + * VCO = (320MHz - 1280MHz) + * + * TODO: in case we have less data lanes we have to reduce the desired + * VCO not to exceed the limits specified by the datasheet and + * consequentially reduce the obtained pixel clock. + */ + pixel_clock = AR0521_PIXEL_CLOCK_RATE * 2 / sensor->lane_count; + bpp = ar0521_code_to_bpp(sensor); + sensor->pll.vt_pix = bpp / 2; + vco = pixel_clock * sensor->pll.vt_pix; + + calc_pll(sensor, vco, &pre, &mult); + + sensor->pll.pre = sensor->pll.pre2 = pre; + sensor->pll.mult = sensor->pll.mult2 = mult; } -static int ar0521_write_mode(struct ar0521_dev *sensor) +static int ar0521_pll_config(struct ar0521_dev *sensor) { __be16 pll_regs[] = { be(AR0521_REG_VT_PIX_CLK_DIV), - /* 0x300 */ be(4), /* vt_pix_clk_div = number of bits / 2 */ + /* 0x300 */ be(sensor->pll.vt_pix), /* vt_pix_clk_div = bpp / 2 */ /* 0x302 */ be(1), /* vt_sys_clk_div */ - /* 0x304 */ be((sensor->pll_pre2 << 8) | sensor->pll_pre), - /* 0x306 */ be((sensor->pll_mult2 << 8) | sensor->pll_mult), - /* 0x308 */ be(8), /* op_pix_clk_div = 2 * vt_pix_clk_div */ + /* 0x304 */ be((sensor->pll.pre2 << 8) | sensor->pll.pre), + /* 0x306 */ be((sensor->pll.mult2 << 8) | sensor->pll.mult), + /* 0x308 */ be(sensor->pll.vt_pix * 2), /* op_pix_clk_div = 2 * vt_pix_clk_div */ /* 0x30A */ be(1) /* op_sys_clk_div */ }; - int ret; - - /* Stop streaming for just a moment */ - ret = ar0521_write_reg(sensor, AR0521_REG_RESET, - AR0521_REG_RESET_DEFAULTS); - if (ret) - return ret; - ret = ar0521_set_geometry(sensor); - if (ret) - return ret; - - ret = ar0521_write_regs(sensor, pll_regs, ARRAY_SIZE(pll_regs)); - if (ret) - return ret; - - ret = ar0521_write_reg(sensor, AR0521_REG_COARSE_INTEGRATION_TIME, - sensor->ctrls.exposure->val); - if (ret) - return ret; - - ret = ar0521_write_reg(sensor, AR0521_REG_RESET, - AR0521_REG_RESET_DEFAULTS | - AR0521_REG_RESET_STREAM); - if (ret) - return ret; - - ret = ar0521_write_reg(sensor, AR0521_REG_TEST_PATTERN_MODE, - sensor->ctrls.test_pattern->val); - return ret; + ar0521_calc_pll(sensor); + return ar0521_write_regs(sensor, pll_regs, ARRAY_SIZE(pll_regs)); } static int ar0521_set_stream(struct ar0521_dev *sensor, bool on) @@ -318,12 +367,21 @@ static int ar0521_set_stream(struct ar0521_dev *sensor, bool on) if (ret < 0) return ret; - ar0521_calc_mode(sensor); - ret = ar0521_write_mode(sensor); + /* Stop streaming for just a moment */ + ret = ar0521_write_reg(sensor, AR0521_REG_RESET, + AR0521_REG_RESET_DEFAULTS); + if (ret) + return ret; + + ret = ar0521_set_geometry(sensor); + if (ret) + return ret; + + ret = ar0521_pll_config(sensor); if (ret) goto err; - ret = ar0521_set_gains(sensor); + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); if (ret) goto err; @@ -406,6 +464,8 @@ static int ar0521_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_format *format) { struct ar0521_dev *sensor = to_ar0521_dev(sd); + int max_vblank, max_hblank, exposure_max; + int ret; ar0521_adj_fmt(&format->format); @@ -416,33 +476,73 @@ static int ar0521_set_fmt(struct v4l2_subdev *sd, fmt = v4l2_subdev_get_try_format(sd, sd_state, 0 /* pad */); *fmt = format->format; - } else { - sensor->fmt = format->format; - ar0521_calc_mode(sensor); + + mutex_unlock(&sensor->lock); + + return 0; } + sensor->fmt = format->format; + ar0521_calc_pll(sensor); + + /* + * Update the exposure and blankings limits. Blankings are also reset + * to the minimum. + */ + max_hblank = AR0521_TOTAL_WIDTH_MAX - sensor->fmt.width; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.hblank, + sensor->ctrls.hblank->minimum, + max_hblank, sensor->ctrls.hblank->step, + sensor->ctrls.hblank->minimum); + if (ret) + goto unlock; + + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.hblank, + sensor->ctrls.hblank->minimum); + if (ret) + goto unlock; + + max_vblank = AR0521_TOTAL_HEIGHT_MAX - sensor->fmt.height; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.vblank, + sensor->ctrls.vblank->minimum, + max_vblank, sensor->ctrls.vblank->step, + sensor->ctrls.vblank->minimum); + if (ret) + goto unlock; + + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.vblank, + sensor->ctrls.vblank->minimum); + if (ret) + goto unlock; + + exposure_max = sensor->fmt.height + AR0521_HEIGHT_BLANKING_MIN - 4; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.exposure, + sensor->ctrls.exposure->minimum, + exposure_max, + sensor->ctrls.exposure->step, + sensor->ctrls.exposure->default_value); +unlock: mutex_unlock(&sensor->lock); - return 0; + + return ret; } static int ar0521_s_ctrl(struct v4l2_ctrl *ctrl) { struct v4l2_subdev *sd = ctrl_to_sd(ctrl); struct ar0521_dev *sensor = to_ar0521_dev(sd); + int exp_max; int ret; /* v4l2_ctrl_lock() locks our own mutex */ switch (ctrl->id) { - case V4L2_CID_HBLANK: case V4L2_CID_VBLANK: - sensor->total_width = sensor->fmt.width + - sensor->ctrls.hblank->val; - sensor->total_height = sensor->fmt.width + - sensor->ctrls.vblank->val; - break; - default: - ret = -EINVAL; + exp_max = sensor->fmt.height + ctrl->val - 4; + __v4l2_ctrl_modify_range(sensor->ctrls.exposure, + sensor->ctrls.exposure->minimum, + exp_max, sensor->ctrls.exposure->step, + sensor->ctrls.exposure->default_value); break; } @@ -455,6 +555,10 @@ static int ar0521_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_VBLANK: ret = ar0521_set_geometry(sensor); break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ar0521_write_reg(sensor, AR0521_REG_ANA_GAIN_CODE_GLOBAL, + ctrl->val); + break; case V4L2_CID_GAIN: case V4L2_CID_RED_BALANCE: case V4L2_CID_BLUE_BALANCE: @@ -469,6 +573,11 @@ static int ar0521_s_ctrl(struct v4l2_ctrl *ctrl) ret = ar0521_write_reg(sensor, AR0521_REG_TEST_PATTERN_MODE, ctrl->val); break; + default: + dev_err(&sensor->i2c_client->dev, + "Unsupported control %x\n", ctrl->id); + ret = -EINVAL; + break; } pm_runtime_put(&sensor->i2c_client->dev); @@ -491,6 +600,8 @@ static int ar0521_init_controls(struct ar0521_dev *sensor) const struct v4l2_ctrl_ops *ops = &ar0521_ctrl_ops; struct ar0521_ctrls *ctrls = &sensor->ctrls; struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int max_vblank, max_hblank, exposure_max; + struct v4l2_ctrl *link_freq; int ret; v4l2_ctrl_handler_init(hdl, 32); @@ -498,6 +609,11 @@ static int ar0521_init_controls(struct ar0521_dev *sensor) /* We can use our own mutex for the ctrl lock */ hdl->lock = &sensor->lock; + /* Analog gain */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, + AR0521_ANA_GAIN_MIN, AR0521_ANA_GAIN_MAX, + AR0521_ANA_GAIN_STEP, AR0521_ANA_GAIN_DEFAULT); + /* Manual gain */ ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, 0, 511, 1, 0); ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, @@ -506,11 +622,17 @@ static int ar0521_init_controls(struct ar0521_dev *sensor) -512, 511, 1, 0); v4l2_ctrl_cluster(3, &ctrls->gain); + /* Initialize blanking limits using the default 2592x1944 format. */ + max_hblank = AR0521_TOTAL_WIDTH_MAX - AR0521_WIDTH_MAX; ctrls->hblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, - AR0521_WIDTH_BLANKING_MIN, 4094, 1, + AR0521_WIDTH_BLANKING_MIN, + max_hblank, 1, AR0521_WIDTH_BLANKING_MIN); + + max_vblank = AR0521_TOTAL_HEIGHT_MAX - AR0521_HEIGHT_MAX; ctrls->vblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, - AR0521_HEIGHT_BLANKING_MIN, 4094, 2, + AR0521_HEIGHT_BLANKING_MIN, + max_vblank, 2, AR0521_HEIGHT_BLANKING_MIN); v4l2_ctrl_cluster(2, &ctrls->hblank); @@ -520,9 +642,16 @@ static int ar0521_init_controls(struct ar0521_dev *sensor) AR0521_PIXEL_CLOCK_MAX, 1, AR0521_PIXEL_CLOCK_RATE); - /* Manual exposure time */ + /* Manual exposure time: max exposure time = visible + blank - 4 */ + exposure_max = AR0521_HEIGHT_MAX + AR0521_HEIGHT_BLANKING_MIN - 4; ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 0, - 65535, 1, 360); + exposure_max, 1, 0x70); + + link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(ar0521_link_frequencies) - 1, + 0, ar0521_link_frequencies); + if (link_freq) + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ctrls->test_pattern = v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, @@ -799,6 +928,24 @@ static int ar0521_enum_mbus_code(struct v4l2_subdev *sd, return 0; } +static int ar0521_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG8_1X8) + return -EINVAL; + + fse->min_width = AR0521_WIDTH_MIN; + fse->max_width = AR0521_WIDTH_MAX; + fse->min_height = AR0521_HEIGHT_MIN; + fse->max_height = AR0521_HEIGHT_MAX; + + return 0; +} + static int ar0521_pre_streamon(struct v4l2_subdev *sd, u32 flags) { struct ar0521_dev *sensor = to_ar0521_dev(sd); @@ -865,6 +1012,7 @@ static const struct v4l2_subdev_video_ops ar0521_video_ops = { static const struct v4l2_subdev_pad_ops ar0521_pad_ops = { .enum_mbus_code = ar0521_enum_mbus_code, + .enum_frame_size = ar0521_enum_frame_size, .get_fmt = ar0521_get_fmt, .set_fmt = ar0521_set_fmt, }; diff --git a/drivers/media/i2c/bt819.c b/drivers/media/i2c/bt819.c index 4d9bb6eb7d65..39f8a5361166 100644 --- a/drivers/media/i2c/bt819.c +++ b/drivers/media/i2c/bt819.c @@ -380,8 +380,7 @@ static const struct v4l2_subdev_ops bt819_ops = { /* ----------------------------------------------------------------------- */ -static int bt819_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int bt819_probe(struct i2c_client *client) { int i, ver; struct bt819 *decoder; @@ -469,7 +468,7 @@ static struct i2c_driver bt819_driver = { .driver = { .name = "bt819", }, - .probe = bt819_probe, + .probe_new = bt819_probe, .remove = bt819_remove, .id_table = bt819_id, }; diff --git a/drivers/media/i2c/bt856.c b/drivers/media/i2c/bt856.c index 70443ef1ac46..d1d397b15b85 100644 --- a/drivers/media/i2c/bt856.c +++ b/drivers/media/i2c/bt856.c @@ -181,8 +181,7 @@ static const struct v4l2_subdev_ops bt856_ops = { /* ----------------------------------------------------------------------- */ -static int bt856_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int bt856_probe(struct i2c_client *client) { struct bt856 *encoder; struct v4l2_subdev *sd; @@ -240,7 +239,7 @@ static struct i2c_driver bt856_driver = { .driver = { .name = "bt856", }, - .probe = bt856_probe, + .probe_new = bt856_probe, .remove = bt856_remove, .id_table = bt856_id, }; diff --git a/drivers/media/i2c/bt866.c b/drivers/media/i2c/bt866.c index c2508cbafd02..d632d9a07f04 100644 --- a/drivers/media/i2c/bt866.c +++ b/drivers/media/i2c/bt866.c @@ -173,8 +173,7 @@ static const struct v4l2_subdev_ops bt866_ops = { .video = &bt866_video_ops, }; -static int bt866_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int bt866_probe(struct i2c_client *client) { struct bt866 *encoder; struct v4l2_subdev *sd; @@ -207,7 +206,7 @@ static struct i2c_driver bt866_driver = { .driver = { .name = "bt866", }, - .probe = bt866_probe, + .probe_new = bt866_probe, .remove = bt866_remove, .id_table = bt866_id, }; diff --git a/drivers/media/i2c/cs3308.c b/drivers/media/i2c/cs3308.c index d901a59883a9..a0b66c04fe25 100644 --- a/drivers/media/i2c/cs3308.c +++ b/drivers/media/i2c/cs3308.c @@ -64,8 +64,7 @@ static const struct v4l2_subdev_ops cs3308_ops = { /* ----------------------------------------------------------------------- */ -static int cs3308_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cs3308_probe(struct i2c_client *client) { struct v4l2_subdev *sd; unsigned i; @@ -119,7 +118,7 @@ static struct i2c_driver cs3308_driver = { .driver = { .name = "cs3308", }, - .probe = cs3308_probe, + .probe_new = cs3308_probe, .remove = cs3308_remove, .id_table = cs3308_id, }; diff --git a/drivers/media/i2c/cs5345.c b/drivers/media/i2c/cs5345.c index 591b1e7b24ee..ac4b5632fc46 100644 --- a/drivers/media/i2c/cs5345.c +++ b/drivers/media/i2c/cs5345.c @@ -136,8 +136,7 @@ static const struct v4l2_subdev_ops cs5345_ops = { /* ----------------------------------------------------------------------- */ -static int cs5345_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cs5345_probe(struct i2c_client *client) { struct cs5345_state *state; struct v4l2_subdev *sd; @@ -199,7 +198,7 @@ static struct i2c_driver cs5345_driver = { .driver = { .name = "cs5345", }, - .probe = cs5345_probe, + .probe_new = cs5345_probe, .remove = cs5345_remove, .id_table = cs5345_id, }; diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c index f1a978af82ef..46cf422270b2 100644 --- a/drivers/media/i2c/cx25840/cx25840-core.c +++ b/drivers/media/i2c/cx25840/cx25840-core.c @@ -5825,8 +5825,7 @@ static u32 get_cx2388x_ident(struct i2c_client *client) return ret; } -static int cx25840_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int cx25840_probe(struct i2c_client *client) { struct cx25840_state *state; struct v4l2_subdev *sd; @@ -6046,7 +6045,7 @@ static struct i2c_driver cx25840_driver = { .driver = { .name = "cx25840", }, - .probe = cx25840_probe, + .probe_new = cx25840_probe, .remove = cx25840_remove, .id_table = cx25840_id, }; diff --git a/drivers/media/i2c/dw9768.c b/drivers/media/i2c/dw9768.c index 0f47ef015a1d..83a3ee275bbe 100644 --- a/drivers/media/i2c/dw9768.c +++ b/drivers/media/i2c/dw9768.c @@ -414,6 +414,7 @@ static int dw9768_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct dw9768 *dw9768; + bool full_power; unsigned int i; int ret; @@ -469,13 +470,23 @@ static int dw9768_probe(struct i2c_client *client) dw9768->sd.entity.function = MEDIA_ENT_F_LENS; + /* + * Figure out whether we're going to power up the device here. Generally + * this is done if CONFIG_PM is disabled in a DT system or the device is + * to be powered on in an ACPI system. Similarly for power off in + * remove. + */ pm_runtime_enable(dev); - if (!pm_runtime_enabled(dev)) { + full_power = (is_acpi_node(dev_fwnode(dev)) && + acpi_dev_state_d0(dev)) || + (is_of_node(dev_fwnode(dev)) && !pm_runtime_enabled(dev)); + if (full_power) { ret = dw9768_runtime_resume(dev); if (ret < 0) { dev_err(dev, "failed to power on: %d\n", ret); goto err_clean_entity; } + pm_runtime_set_active(dev); } ret = v4l2_async_register_subdev(&dw9768->sd); @@ -484,14 +495,17 @@ static int dw9768_probe(struct i2c_client *client) goto err_power_off; } + pm_runtime_idle(dev); + return 0; err_power_off: - if (pm_runtime_enabled(dev)) - pm_runtime_disable(dev); - else + if (full_power) { dw9768_runtime_suspend(dev); + pm_runtime_set_suspended(dev); + } err_clean_entity: + pm_runtime_disable(dev); media_entity_cleanup(&dw9768->sd.entity); err_free_handler: v4l2_ctrl_handler_free(&dw9768->ctrls); @@ -503,14 +517,17 @@ static void dw9768_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9768 *dw9768 = sd_to_dw9768(sd); + struct device *dev = &client->dev; v4l2_async_unregister_subdev(&dw9768->sd); v4l2_ctrl_handler_free(&dw9768->ctrls); media_entity_cleanup(&dw9768->sd.entity); - pm_runtime_disable(&client->dev); - if (!pm_runtime_status_suspended(&client->dev)) - dw9768_runtime_suspend(&client->dev); - pm_runtime_set_suspended(&client->dev); + if ((is_acpi_node(dev_fwnode(dev)) && acpi_dev_state_d0(dev)) || + (is_of_node(dev_fwnode(dev)) && !pm_runtime_enabled(dev))) { + dw9768_runtime_suspend(dev); + pm_runtime_set_suspended(dev); + } + pm_runtime_disable(dev); } static const struct of_device_id dw9768_of_table[] = { diff --git a/drivers/media/i2c/hi846.c b/drivers/media/i2c/hi846.c index c5b69823f257..7c61873b7198 100644 --- a/drivers/media/i2c/hi846.c +++ b/drivers/media/i2c/hi846.c @@ -2008,22 +2008,24 @@ static int hi846_parse_dt(struct hi846 *hi846, struct device *dev) bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { dev_err(dev, "number of CSI2 data lanes %d is not supported", bus_cfg.bus.mipi_csi2.num_data_lanes); - v4l2_fwnode_endpoint_free(&bus_cfg); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } hi846->nr_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; if (!bus_cfg.nr_of_link_frequencies) { dev_err(dev, "link-frequency property not found in DT\n"); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } /* Check that link frequences for all the modes are in device tree */ fq = hi846_check_link_freqs(hi846, &bus_cfg); if (fq) { dev_err(dev, "Link frequency of %lld is not supported\n", fq); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } v4l2_fwnode_endpoint_free(&bus_cfg); @@ -2044,6 +2046,10 @@ static int hi846_parse_dt(struct hi846 *hi846, struct device *dev) } return 0; + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + return ret; } static int hi846_probe(struct i2c_client *client) diff --git a/drivers/media/i2c/imx208.c b/drivers/media/i2c/imx208.c index a0e17bb9d4ca..64c70ebf9869 100644 --- a/drivers/media/i2c/imx208.c +++ b/drivers/media/i2c/imx208.c @@ -937,8 +937,12 @@ static int imx208_init_controls(struct imx208 *imx208) imx208->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx208->hflip) + imx208->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; imx208->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx208->vflip) + imx208->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, IMX208_ANA_GAIN_MIN, IMX208_ANA_GAIN_MAX, diff --git a/drivers/media/i2c/imx274.c b/drivers/media/i2c/imx274.c index a00761b1e18c..9219f3c9594b 100644 --- a/drivers/media/i2c/imx274.c +++ b/drivers/media/i2c/imx274.c @@ -2060,9 +2060,8 @@ static int imx274_probe(struct i2c_client *client) imx274->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(imx274->reset_gpio)) { - if (PTR_ERR(imx274->reset_gpio) != -EPROBE_DEFER) - dev_err(dev, "Reset GPIO not setup in DT"); - ret = PTR_ERR(imx274->reset_gpio); + ret = dev_err_probe(dev, PTR_ERR(imx274->reset_gpio), + "Reset GPIO not setup in DT\n"); goto err_me; } diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 1ce64dcdf7f0..218ded13fd80 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -22,22 +22,135 @@ #include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> -#define IMX290_STANDBY 0x3000 -#define IMX290_REGHOLD 0x3001 -#define IMX290_XMSTA 0x3002 -#define IMX290_FR_FDG_SEL 0x3009 -#define IMX290_BLKLEVEL_LOW 0x300a -#define IMX290_BLKLEVEL_HIGH 0x300b -#define IMX290_GAIN 0x3014 -#define IMX290_HMAX_LOW 0x301c -#define IMX290_HMAX_HIGH 0x301d -#define IMX290_PGCTRL 0x308c -#define IMX290_PHY_LANE_NUM 0x3407 -#define IMX290_CSI_LANE_MODE 0x3443 - -#define IMX290_PGCTRL_REGEN BIT(0) -#define IMX290_PGCTRL_THRU BIT(1) -#define IMX290_PGCTRL_MODE(n) ((n) << 4) +#define IMX290_REG_SIZE_SHIFT 16 +#define IMX290_REG_ADDR_MASK 0xffff +#define IMX290_REG_8BIT(n) ((1U << IMX290_REG_SIZE_SHIFT) | (n)) +#define IMX290_REG_16BIT(n) ((2U << IMX290_REG_SIZE_SHIFT) | (n)) +#define IMX290_REG_24BIT(n) ((3U << IMX290_REG_SIZE_SHIFT) | (n)) + +#define IMX290_STANDBY IMX290_REG_8BIT(0x3000) +#define IMX290_REGHOLD IMX290_REG_8BIT(0x3001) +#define IMX290_XMSTA IMX290_REG_8BIT(0x3002) +#define IMX290_ADBIT IMX290_REG_8BIT(0x3005) +#define IMX290_ADBIT_10BIT (0 << 0) +#define IMX290_ADBIT_12BIT (1 << 0) +#define IMX290_CTRL_07 IMX290_REG_8BIT(0x3007) +#define IMX290_VREVERSE BIT(0) +#define IMX290_HREVERSE BIT(1) +#define IMX290_WINMODE_1080P (0 << 4) +#define IMX290_WINMODE_720P (1 << 4) +#define IMX290_WINMODE_CROP (4 << 4) +#define IMX290_FR_FDG_SEL IMX290_REG_8BIT(0x3009) +#define IMX290_BLKLEVEL IMX290_REG_16BIT(0x300a) +#define IMX290_GAIN IMX290_REG_8BIT(0x3014) +#define IMX290_VMAX IMX290_REG_24BIT(0x3018) +#define IMX290_HMAX IMX290_REG_16BIT(0x301c) +#define IMX290_SHS1 IMX290_REG_24BIT(0x3020) +#define IMX290_WINWV_OB IMX290_REG_8BIT(0x303a) +#define IMX290_WINPV IMX290_REG_16BIT(0x303c) +#define IMX290_WINWV IMX290_REG_16BIT(0x303e) +#define IMX290_WINPH IMX290_REG_16BIT(0x3040) +#define IMX290_WINWH IMX290_REG_16BIT(0x3042) +#define IMX290_OUT_CTRL IMX290_REG_8BIT(0x3046) +#define IMX290_ODBIT_10BIT (0 << 0) +#define IMX290_ODBIT_12BIT (1 << 0) +#define IMX290_OPORTSEL_PARALLEL (0x0 << 4) +#define IMX290_OPORTSEL_LVDS_2CH (0xd << 4) +#define IMX290_OPORTSEL_LVDS_4CH (0xe << 4) +#define IMX290_OPORTSEL_LVDS_8CH (0xf << 4) +#define IMX290_XSOUTSEL IMX290_REG_8BIT(0x304b) +#define IMX290_XSOUTSEL_XVSOUTSEL_HIGH (0 << 0) +#define IMX290_XSOUTSEL_XVSOUTSEL_VSYNC (2 << 0) +#define IMX290_XSOUTSEL_XHSOUTSEL_HIGH (0 << 2) +#define IMX290_XSOUTSEL_XHSOUTSEL_HSYNC (2 << 2) +#define IMX290_INCKSEL1 IMX290_REG_8BIT(0x305c) +#define IMX290_INCKSEL2 IMX290_REG_8BIT(0x305d) +#define IMX290_INCKSEL3 IMX290_REG_8BIT(0x305e) +#define IMX290_INCKSEL4 IMX290_REG_8BIT(0x305f) +#define IMX290_PGCTRL IMX290_REG_8BIT(0x308c) +#define IMX290_ADBIT1 IMX290_REG_8BIT(0x3129) +#define IMX290_ADBIT1_10BIT 0x1d +#define IMX290_ADBIT1_12BIT 0x00 +#define IMX290_INCKSEL5 IMX290_REG_8BIT(0x315e) +#define IMX290_INCKSEL6 IMX290_REG_8BIT(0x3164) +#define IMX290_ADBIT2 IMX290_REG_8BIT(0x317c) +#define IMX290_ADBIT2_10BIT 0x12 +#define IMX290_ADBIT2_12BIT 0x00 +#define IMX290_CHIP_ID IMX290_REG_16BIT(0x319a) +#define IMX290_ADBIT3 IMX290_REG_8BIT(0x31ec) +#define IMX290_ADBIT3_10BIT 0x37 +#define IMX290_ADBIT3_12BIT 0x0e +#define IMX290_REPETITION IMX290_REG_8BIT(0x3405) +#define IMX290_PHY_LANE_NUM IMX290_REG_8BIT(0x3407) +#define IMX290_OPB_SIZE_V IMX290_REG_8BIT(0x3414) +#define IMX290_Y_OUT_SIZE IMX290_REG_16BIT(0x3418) +#define IMX290_CSI_DT_FMT IMX290_REG_16BIT(0x3441) +#define IMX290_CSI_DT_FMT_RAW10 0x0a0a +#define IMX290_CSI_DT_FMT_RAW12 0x0c0c +#define IMX290_CSI_LANE_MODE IMX290_REG_8BIT(0x3443) +#define IMX290_EXTCK_FREQ IMX290_REG_16BIT(0x3444) +#define IMX290_TCLKPOST IMX290_REG_16BIT(0x3446) +#define IMX290_THSZERO IMX290_REG_16BIT(0x3448) +#define IMX290_THSPREPARE IMX290_REG_16BIT(0x344a) +#define IMX290_TCLKTRAIL IMX290_REG_16BIT(0x344c) +#define IMX290_THSTRAIL IMX290_REG_16BIT(0x344e) +#define IMX290_TCLKZERO IMX290_REG_16BIT(0x3450) +#define IMX290_TCLKPREPARE IMX290_REG_16BIT(0x3452) +#define IMX290_TLPX IMX290_REG_16BIT(0x3454) +#define IMX290_X_OUT_SIZE IMX290_REG_16BIT(0x3472) + +#define IMX290_PGCTRL_REGEN BIT(0) +#define IMX290_PGCTRL_THRU BIT(1) +#define IMX290_PGCTRL_MODE(n) ((n) << 4) + +#define IMX290_VMAX_DEFAULT 1125 + + +/* + * The IMX290 pixel array is organized as follows: + * + * +------------------------------------+ + * | Optical Black | } Vertical effective optical black (10) + * +---+------------------------------------+---+ + * | | | | } Effective top margin (8) + * | | +----------------------------+ | | \ + * | | | | | | | + * | | | | | | | + * | | | | | | | + * | | | Recording Pixel Area | | | | Recommended height (1080) + * | | | | | | | + * | | | | | | | + * | | | | | | | + * | | +----------------------------+ | | / + * | | | | } Effective bottom margin (9) + * +---+------------------------------------+---+ + * <-> <-> <--------------------------> <-> <-> + * \---- Ignored right margin (4) + * \-------- Effective right margin (9) + * \------------------------- Recommended width (1920) + * \----------------------------------------- Effective left margin (8) + * \--------------------------------------------- Ignored left margin (4) + * + * The optical black lines are output over CSI-2 with a separate data type. + * + * The pixel array is meant to have 1920x1080 usable pixels after image + * processing in an ISP. It has 8 (9) extra active pixels usable for color + * processing in the ISP on the top and left (bottom and right) sides of the + * image. In addition, 4 additional pixels are present on the left and right + * sides of the image, documented as "ignored area". + * + * As far as is understood, all pixels of the pixel array (ignored area, color + * processing margins and recording area) can be output by the sensor. + */ + +#define IMX290_PIXEL_ARRAY_WIDTH 1945 +#define IMX290_PIXEL_ARRAY_HEIGHT 1097 +#define IMX920_PIXEL_ARRAY_MARGIN_LEFT 12 +#define IMX920_PIXEL_ARRAY_MARGIN_RIGHT 13 +#define IMX920_PIXEL_ARRAY_MARGIN_TOP 8 +#define IMX920_PIXEL_ARRAY_MARGIN_BOTTOM 9 +#define IMX290_PIXEL_ARRAY_RECORDING_WIDTH 1920 +#define IMX290_PIXEL_ARRAY_RECORDING_HEIGHT 1080 static const char * const imx290_supply_name[] = { "vdda", @@ -48,8 +161,8 @@ static const char * const imx290_supply_name[] = { #define IMX290_NUM_SUPPLIES ARRAY_SIZE(imx290_supply_name) struct imx290_regval { - u16 reg; - u8 val; + u32 reg; + u32 val; }; struct imx290_mode { @@ -80,6 +193,8 @@ struct imx290 { struct v4l2_ctrl_handler ctrls; struct v4l2_ctrl *link_freq; struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; struct mutex lock; }; @@ -97,7 +212,6 @@ static const struct imx290_pixfmt imx290_formats[] = { static const struct regmap_config imx290_regmap_config = { .reg_bits = 16, .val_bits = 8, - .cache_type = REGCACHE_RBTREE, }; static const char * const imx290_test_pattern_menu[] = { @@ -112,163 +226,129 @@ static const char * const imx290_test_pattern_menu[] = { }; static const struct imx290_regval imx290_global_init_settings[] = { - { 0x3007, 0x00 }, - { 0x3018, 0x65 }, - { 0x3019, 0x04 }, - { 0x301a, 0x00 }, - { 0x3444, 0x20 }, - { 0x3445, 0x25 }, - { 0x303a, 0x0c }, - { 0x3040, 0x00 }, - { 0x3041, 0x00 }, - { 0x303c, 0x00 }, - { 0x303d, 0x00 }, - { 0x3042, 0x9c }, - { 0x3043, 0x07 }, - { 0x303e, 0x49 }, - { 0x303f, 0x04 }, - { 0x304b, 0x0a }, - { 0x300f, 0x00 }, - { 0x3010, 0x21 }, - { 0x3012, 0x64 }, - { 0x3016, 0x09 }, - { 0x3070, 0x02 }, - { 0x3071, 0x11 }, - { 0x309b, 0x10 }, - { 0x309c, 0x22 }, - { 0x30a2, 0x02 }, - { 0x30a6, 0x20 }, - { 0x30a8, 0x20 }, - { 0x30aa, 0x20 }, - { 0x30ac, 0x20 }, - { 0x30b0, 0x43 }, - { 0x3119, 0x9e }, - { 0x311c, 0x1e }, - { 0x311e, 0x08 }, - { 0x3128, 0x05 }, - { 0x313d, 0x83 }, - { 0x3150, 0x03 }, - { 0x317e, 0x00 }, - { 0x32b8, 0x50 }, - { 0x32b9, 0x10 }, - { 0x32ba, 0x00 }, - { 0x32bb, 0x04 }, - { 0x32c8, 0x50 }, - { 0x32c9, 0x10 }, - { 0x32ca, 0x00 }, - { 0x32cb, 0x04 }, - { 0x332c, 0xd3 }, - { 0x332d, 0x10 }, - { 0x332e, 0x0d }, - { 0x3358, 0x06 }, - { 0x3359, 0xe1 }, - { 0x335a, 0x11 }, - { 0x3360, 0x1e }, - { 0x3361, 0x61 }, - { 0x3362, 0x10 }, - { 0x33b0, 0x50 }, - { 0x33b2, 0x1a }, - { 0x33b3, 0x04 }, + { IMX290_CTRL_07, IMX290_WINMODE_1080P }, + { IMX290_VMAX, IMX290_VMAX_DEFAULT }, + { IMX290_EXTCK_FREQ, 0x2520 }, + { IMX290_WINWV_OB, 12 }, + { IMX290_WINPH, 0 }, + { IMX290_WINPV, 0 }, + { IMX290_WINWH, 1948 }, + { IMX290_WINWV, 1097 }, + { IMX290_XSOUTSEL, IMX290_XSOUTSEL_XVSOUTSEL_VSYNC | + IMX290_XSOUTSEL_XHSOUTSEL_HSYNC }, + { IMX290_REG_8BIT(0x300f), 0x00 }, + { IMX290_REG_8BIT(0x3010), 0x21 }, + { IMX290_REG_8BIT(0x3012), 0x64 }, + { IMX290_REG_8BIT(0x3013), 0x00 }, + { IMX290_REG_8BIT(0x3016), 0x09 }, + { IMX290_REG_8BIT(0x3070), 0x02 }, + { IMX290_REG_8BIT(0x3071), 0x11 }, + { IMX290_REG_8BIT(0x309b), 0x10 }, + { IMX290_REG_8BIT(0x309c), 0x22 }, + { IMX290_REG_8BIT(0x30a2), 0x02 }, + { IMX290_REG_8BIT(0x30a6), 0x20 }, + { IMX290_REG_8BIT(0x30a8), 0x20 }, + { IMX290_REG_8BIT(0x30aa), 0x20 }, + { IMX290_REG_8BIT(0x30ac), 0x20 }, + { IMX290_REG_8BIT(0x30b0), 0x43 }, + { IMX290_REG_8BIT(0x3119), 0x9e }, + { IMX290_REG_8BIT(0x311c), 0x1e }, + { IMX290_REG_8BIT(0x311e), 0x08 }, + { IMX290_REG_8BIT(0x3128), 0x05 }, + { IMX290_REG_8BIT(0x313d), 0x83 }, + { IMX290_REG_8BIT(0x3150), 0x03 }, + { IMX290_REG_8BIT(0x317e), 0x00 }, + { IMX290_REG_8BIT(0x32b8), 0x50 }, + { IMX290_REG_8BIT(0x32b9), 0x10 }, + { IMX290_REG_8BIT(0x32ba), 0x00 }, + { IMX290_REG_8BIT(0x32bb), 0x04 }, + { IMX290_REG_8BIT(0x32c8), 0x50 }, + { IMX290_REG_8BIT(0x32c9), 0x10 }, + { IMX290_REG_8BIT(0x32ca), 0x00 }, + { IMX290_REG_8BIT(0x32cb), 0x04 }, + { IMX290_REG_8BIT(0x332c), 0xd3 }, + { IMX290_REG_8BIT(0x332d), 0x10 }, + { IMX290_REG_8BIT(0x332e), 0x0d }, + { IMX290_REG_8BIT(0x3358), 0x06 }, + { IMX290_REG_8BIT(0x3359), 0xe1 }, + { IMX290_REG_8BIT(0x335a), 0x11 }, + { IMX290_REG_8BIT(0x3360), 0x1e }, + { IMX290_REG_8BIT(0x3361), 0x61 }, + { IMX290_REG_8BIT(0x3362), 0x10 }, + { IMX290_REG_8BIT(0x33b0), 0x50 }, + { IMX290_REG_8BIT(0x33b2), 0x1a }, + { IMX290_REG_8BIT(0x33b3), 0x04 }, + { IMX290_REG_8BIT(0x3480), 0x49 }, }; static const struct imx290_regval imx290_1080p_settings[] = { /* mode settings */ - { 0x3007, 0x00 }, - { 0x303a, 0x0c }, - { 0x3414, 0x0a }, - { 0x3472, 0x80 }, - { 0x3473, 0x07 }, - { 0x3418, 0x38 }, - { 0x3419, 0x04 }, - { 0x3012, 0x64 }, - { 0x3013, 0x00 }, - { 0x305c, 0x18 }, - { 0x305d, 0x03 }, - { 0x305e, 0x20 }, - { 0x305f, 0x01 }, - { 0x315e, 0x1a }, - { 0x3164, 0x1a }, - { 0x3480, 0x49 }, + { IMX290_CTRL_07, IMX290_WINMODE_1080P }, + { IMX290_WINWV_OB, 12 }, + { IMX290_OPB_SIZE_V, 10 }, + { IMX290_X_OUT_SIZE, 1920 }, + { IMX290_Y_OUT_SIZE, 1080 }, + { IMX290_INCKSEL1, 0x18 }, + { IMX290_INCKSEL2, 0x03 }, + { IMX290_INCKSEL3, 0x20 }, + { IMX290_INCKSEL4, 0x01 }, + { IMX290_INCKSEL5, 0x1a }, + { IMX290_INCKSEL6, 0x1a }, /* data rate settings */ - { 0x3405, 0x10 }, - { 0x3446, 0x57 }, - { 0x3447, 0x00 }, - { 0x3448, 0x37 }, - { 0x3449, 0x00 }, - { 0x344a, 0x1f }, - { 0x344b, 0x00 }, - { 0x344c, 0x1f }, - { 0x344d, 0x00 }, - { 0x344e, 0x1f }, - { 0x344f, 0x00 }, - { 0x3450, 0x77 }, - { 0x3451, 0x00 }, - { 0x3452, 0x1f }, - { 0x3453, 0x00 }, - { 0x3454, 0x17 }, - { 0x3455, 0x00 }, + { IMX290_REPETITION, 0x10 }, + { IMX290_TCLKPOST, 87 }, + { IMX290_THSZERO, 55 }, + { IMX290_THSPREPARE, 31 }, + { IMX290_TCLKTRAIL, 31 }, + { IMX290_THSTRAIL, 31 }, + { IMX290_TCLKZERO, 119 }, + { IMX290_TCLKPREPARE, 31 }, + { IMX290_TLPX, 23 }, }; static const struct imx290_regval imx290_720p_settings[] = { /* mode settings */ - { 0x3007, 0x10 }, - { 0x303a, 0x06 }, - { 0x3414, 0x04 }, - { 0x3472, 0x00 }, - { 0x3473, 0x05 }, - { 0x3418, 0xd0 }, - { 0x3419, 0x02 }, - { 0x3012, 0x64 }, - { 0x3013, 0x00 }, - { 0x305c, 0x20 }, - { 0x305d, 0x00 }, - { 0x305e, 0x20 }, - { 0x305f, 0x01 }, - { 0x315e, 0x1a }, - { 0x3164, 0x1a }, - { 0x3480, 0x49 }, + { IMX290_CTRL_07, IMX290_WINMODE_720P }, + { IMX290_WINWV_OB, 6 }, + { IMX290_OPB_SIZE_V, 4 }, + { IMX290_X_OUT_SIZE, 1280 }, + { IMX290_Y_OUT_SIZE, 720 }, + { IMX290_INCKSEL1, 0x20 }, + { IMX290_INCKSEL2, 0x00 }, + { IMX290_INCKSEL3, 0x20 }, + { IMX290_INCKSEL4, 0x01 }, + { IMX290_INCKSEL5, 0x1a }, + { IMX290_INCKSEL6, 0x1a }, /* data rate settings */ - { 0x3405, 0x10 }, - { 0x3446, 0x4f }, - { 0x3447, 0x00 }, - { 0x3448, 0x2f }, - { 0x3449, 0x00 }, - { 0x344a, 0x17 }, - { 0x344b, 0x00 }, - { 0x344c, 0x17 }, - { 0x344d, 0x00 }, - { 0x344e, 0x17 }, - { 0x344f, 0x00 }, - { 0x3450, 0x57 }, - { 0x3451, 0x00 }, - { 0x3452, 0x17 }, - { 0x3453, 0x00 }, - { 0x3454, 0x17 }, - { 0x3455, 0x00 }, + { IMX290_REPETITION, 0x10 }, + { IMX290_TCLKPOST, 79 }, + { IMX290_THSZERO, 47 }, + { IMX290_THSPREPARE, 23 }, + { IMX290_TCLKTRAIL, 23 }, + { IMX290_THSTRAIL, 23 }, + { IMX290_TCLKZERO, 87 }, + { IMX290_TCLKPREPARE, 23 }, + { IMX290_TLPX, 23 }, }; static const struct imx290_regval imx290_10bit_settings[] = { - { 0x3005, 0x00}, - { 0x3046, 0x00}, - { 0x3129, 0x1d}, - { 0x317c, 0x12}, - { 0x31ec, 0x37}, - { 0x3441, 0x0a}, - { 0x3442, 0x0a}, - { 0x300a, 0x3c}, - { 0x300b, 0x00}, + { IMX290_ADBIT, IMX290_ADBIT_10BIT }, + { IMX290_OUT_CTRL, IMX290_ODBIT_10BIT }, + { IMX290_ADBIT1, IMX290_ADBIT1_10BIT }, + { IMX290_ADBIT2, IMX290_ADBIT2_10BIT }, + { IMX290_ADBIT3, IMX290_ADBIT3_10BIT }, + { IMX290_CSI_DT_FMT, IMX290_CSI_DT_FMT_RAW10 }, + { IMX290_BLKLEVEL, 60 }, }; static const struct imx290_regval imx290_12bit_settings[] = { - { 0x3005, 0x01 }, - { 0x3046, 0x01 }, - { 0x3129, 0x00 }, - { 0x317c, 0x00 }, - { 0x31ec, 0x0e }, - { 0x3441, 0x0c }, - { 0x3442, 0x0c }, - { 0x300a, 0xf0 }, - { 0x300b, 0x00 }, + { IMX290_ADBIT, IMX290_ADBIT_12BIT }, + { IMX290_OUT_CTRL, IMX290_ODBIT_12BIT }, + { IMX290_ADBIT1, IMX290_ADBIT1_12BIT }, + { IMX290_ADBIT2, IMX290_ADBIT2_12BIT }, + { IMX290_ADBIT3, IMX290_ADBIT3_12BIT }, + { IMX290_CSI_DT_FMT, IMX290_CSI_DT_FMT_RAW12 }, + { IMX290_BLKLEVEL, 240 }, }; /* supported link frequencies */ @@ -308,7 +388,7 @@ static const struct imx290_mode imx290_modes_2lanes[] = { { .width = 1920, .height = 1080, - .hmax = 0x1130, + .hmax = 4400, .link_freq_index = FREQ_INDEX_1080P, .data = imx290_1080p_settings, .data_size = ARRAY_SIZE(imx290_1080p_settings), @@ -316,7 +396,7 @@ static const struct imx290_mode imx290_modes_2lanes[] = { { .width = 1280, .height = 720, - .hmax = 0x19c8, + .hmax = 6600, .link_freq_index = FREQ_INDEX_720P, .data = imx290_720p_settings, .data_size = ARRAY_SIZE(imx290_720p_settings), @@ -327,7 +407,7 @@ static const struct imx290_mode imx290_modes_4lanes[] = { { .width = 1920, .height = 1080, - .hmax = 0x0898, + .hmax = 2200, .link_freq_index = FREQ_INDEX_1080P, .data = imx290_1080p_settings, .data_size = ARRAY_SIZE(imx290_1080p_settings), @@ -335,7 +415,7 @@ static const struct imx290_mode imx290_modes_4lanes[] = { { .width = 1280, .height = 720, - .hmax = 0x0ce4, + .hmax = 3300, .link_freq_index = FREQ_INDEX_720P, .data = imx290_720p_settings, .data_size = ARRAY_SIZE(imx290_720p_settings), @@ -363,30 +443,40 @@ static inline struct imx290 *to_imx290(struct v4l2_subdev *_sd) return container_of(_sd, struct imx290, sd); } -static inline int __always_unused imx290_read_reg(struct imx290 *imx290, u16 addr, u8 *value) +static int __always_unused imx290_read(struct imx290 *imx290, u32 addr, u32 *value) { - unsigned int regval; + u8 data[3] = { 0, 0, 0 }; int ret; - ret = regmap_read(imx290->regmap, addr, ®val); - if (ret) { - dev_err(imx290->dev, "I2C read failed for addr: %x\n", addr); + ret = regmap_raw_read(imx290->regmap, addr & IMX290_REG_ADDR_MASK, + data, (addr >> IMX290_REG_SIZE_SHIFT) & 3); + if (ret < 0) { + dev_err(imx290->dev, "%u-bit read from 0x%04x failed: %d\n", + ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8, + addr & IMX290_REG_ADDR_MASK, ret); return ret; } - *value = regval & 0xff; - + *value = (data[2] << 16) | (data[1] << 8) | data[0]; return 0; } -static int imx290_write_reg(struct imx290 *imx290, u16 addr, u8 value) +static int imx290_write(struct imx290 *imx290, u32 addr, u32 value, int *err) { + u8 data[3] = { value & 0xff, (value >> 8) & 0xff, value >> 16 }; int ret; - ret = regmap_write(imx290->regmap, addr, value); - if (ret) { - dev_err(imx290->dev, "I2C write failed for addr: %x\n", addr); - return ret; + if (err && *err) + return *err; + + ret = regmap_raw_write(imx290->regmap, addr & IMX290_REG_ADDR_MASK, + data, (addr >> IMX290_REG_SIZE_SHIFT) & 3); + if (ret < 0) { + dev_err(imx290->dev, "%u-bit write to 0x%04x failed: %d\n", + ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8, + addr & IMX290_REG_ADDR_MASK, ret); + if (err) + *err = ret; } return ret; @@ -400,7 +490,7 @@ static int imx290_set_register_array(struct imx290 *imx290, int ret; for (i = 0; i < num_settings; ++i, ++settings) { - ret = imx290_write_reg(imx290, settings->reg, settings->val); + ret = imx290_write(imx290, settings->reg, settings->val, NULL); if (ret < 0) return ret; } @@ -411,59 +501,16 @@ static int imx290_set_register_array(struct imx290 *imx290, return 0; } -static int imx290_write_buffered_reg(struct imx290 *imx290, u16 address_low, - u8 nr_regs, u32 value) -{ - unsigned int i; - int ret; - - ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x01); - if (ret) { - dev_err(imx290->dev, "Error setting hold register\n"); - return ret; - } - - for (i = 0; i < nr_regs; i++) { - ret = imx290_write_reg(imx290, address_low + i, - (u8)(value >> (i * 8))); - if (ret) { - dev_err(imx290->dev, "Error writing buffered registers\n"); - return ret; - } - } - - ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x00); - if (ret) { - dev_err(imx290->dev, "Error setting hold register\n"); - return ret; - } - - return ret; -} - -static int imx290_set_gain(struct imx290 *imx290, u32 value) -{ - int ret; - - ret = imx290_write_buffered_reg(imx290, IMX290_GAIN, 1, value); - if (ret) - dev_err(imx290->dev, "Unable to write gain\n"); - - return ret; -} - /* Stop streaming */ static int imx290_stop_streaming(struct imx290 *imx290) { - int ret; + int ret = 0; - ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x01); - if (ret < 0) - return ret; + imx290_write(imx290, IMX290_STANDBY, 0x01, &ret); msleep(30); - return imx290_write_reg(imx290, IMX290_XMSTA, 0x01); + return imx290_write(imx290, IMX290_XMSTA, 0x01, &ret); } static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) @@ -477,28 +524,32 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) return 0; switch (ctrl->id) { - case V4L2_CID_GAIN: - ret = imx290_set_gain(imx290, ctrl->val); + case V4L2_CID_ANALOGUE_GAIN: + ret = imx290_write(imx290, IMX290_GAIN, ctrl->val, NULL); + break; + + case V4L2_CID_EXPOSURE: + ret = imx290_write(imx290, IMX290_SHS1, + IMX290_VMAX_DEFAULT - ctrl->val - 1, NULL); break; + case V4L2_CID_TEST_PATTERN: if (ctrl->val) { - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, 0x00); - imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00); + imx290_write(imx290, IMX290_BLKLEVEL, 0, &ret); usleep_range(10000, 11000); - imx290_write_reg(imx290, IMX290_PGCTRL, - (u8)(IMX290_PGCTRL_REGEN | - IMX290_PGCTRL_THRU | - IMX290_PGCTRL_MODE(ctrl->val))); + imx290_write(imx290, IMX290_PGCTRL, + (u8)(IMX290_PGCTRL_REGEN | + IMX290_PGCTRL_THRU | + IMX290_PGCTRL_MODE(ctrl->val)), &ret); } else { - imx290_write_reg(imx290, IMX290_PGCTRL, 0x00); + imx290_write(imx290, IMX290_PGCTRL, 0x00, &ret); usleep_range(10000, 11000); if (imx290->bpp == 10) - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, - 0x3c); + imx290_write(imx290, IMX290_BLKLEVEL, 0x3c, + &ret); else /* 12 bits per pixel */ - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, - 0xf0); - imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00); + imx290_write(imx290, IMX290_BLKLEVEL, 0xf0, + &ret); } break; default: @@ -515,6 +566,16 @@ static const struct v4l2_ctrl_ops imx290_ctrl_ops = { .s_ctrl = imx290_set_ctrl, }; +static struct v4l2_mbus_framefmt * +imx290_get_pad_format(struct imx290 *imx290, struct v4l2_subdev_state *state, + u32 which) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + return &imx290->current_format; + else + return v4l2_subdev_get_try_format(&imx290->sd, state, 0); +} + static int imx290_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -558,12 +619,7 @@ static int imx290_get_fmt(struct v4l2_subdev *sd, mutex_lock(&imx290->lock); - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) - framefmt = v4l2_subdev_get_try_format(&imx290->sd, sd_state, - fmt->pad); - else - framefmt = &imx290->current_format; - + framefmt = imx290_get_pad_format(imx290, sd_state, fmt->which); fmt->format = *framefmt; mutex_unlock(&imx290->lock); @@ -623,10 +679,9 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, fmt->format.code = imx290_formats[i].code; fmt->format.field = V4L2_FIELD_NONE; - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - format = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); - } else { - format = &imx290->current_format; + format = imx290_get_pad_format(imx290, sd_state, fmt->which); + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { imx290->current_mode = mode; imx290->bpp = imx290_formats[i].bpp; @@ -636,6 +691,20 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, if (imx290->pixel_rate) __v4l2_ctrl_s_ctrl_int64(imx290->pixel_rate, imx290_calc_pixel_rate(imx290)); + + if (imx290->hblank) { + unsigned int hblank = mode->hmax - mode->width; + + __v4l2_ctrl_modify_range(imx290->hblank, hblank, hblank, + 1, hblank); + } + + if (imx290->vblank) { + unsigned int vblank = IMX290_VMAX_DEFAULT - mode->height; + + __v4l2_ctrl_modify_range(imx290->vblank, vblank, vblank, + 1, vblank); + } } *format = fmt->format; @@ -645,6 +714,52 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, return 0; } +static int imx290_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct imx290 *imx290 = to_imx290(sd); + struct v4l2_mbus_framefmt *format; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + format = imx290_get_pad_format(imx290, sd_state, sel->which); + + mutex_lock(&imx290->lock); + + sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP + + (IMX290_PIXEL_ARRAY_RECORDING_HEIGHT - format->height) / 2; + sel->r.left = IMX920_PIXEL_ARRAY_MARGIN_LEFT + + (IMX290_PIXEL_ARRAY_RECORDING_WIDTH - format->width) / 2; + sel->r.width = format->width; + sel->r.height = format->height; + + mutex_unlock(&imx290->lock); + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = IMX290_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX290_PIXEL_ARRAY_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP; + sel->r.left = IMX920_PIXEL_ARRAY_MARGIN_LEFT; + sel->r.width = IMX290_PIXEL_ARRAY_RECORDING_WIDTH; + sel->r.height = IMX290_PIXEL_ARRAY_RECORDING_HEIGHT; + + return 0; + + default: + return -EINVAL; + } +} + static int imx290_entity_init_cfg(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state) { @@ -690,25 +805,6 @@ static int imx290_write_current_format(struct imx290 *imx290) return 0; } -static int imx290_set_hmax(struct imx290 *imx290, u32 val) -{ - int ret; - - ret = imx290_write_reg(imx290, IMX290_HMAX_LOW, (val & 0xff)); - if (ret) { - dev_err(imx290->dev, "Error setting HMAX register\n"); - return ret; - } - - ret = imx290_write_reg(imx290, IMX290_HMAX_HIGH, ((val >> 8) & 0xff)); - if (ret) { - dev_err(imx290->dev, "Error setting HMAX register\n"); - return ret; - } - - return 0; -} - /* Start streaming */ static int imx290_start_streaming(struct imx290 *imx290) { @@ -737,8 +833,10 @@ static int imx290_start_streaming(struct imx290 *imx290) dev_err(imx290->dev, "Could not set current mode\n"); return ret; } - ret = imx290_set_hmax(imx290, imx290->current_mode->hmax); - if (ret < 0) + + ret = imx290_write(imx290, IMX290_HMAX, imx290->current_mode->hmax, + NULL); + if (ret) return ret; /* Apply customized values from user */ @@ -748,14 +846,12 @@ static int imx290_start_streaming(struct imx290 *imx290) return ret; } - ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x00); - if (ret < 0) - return ret; + imx290_write(imx290, IMX290_STANDBY, 0x00, &ret); msleep(30); /* Start streaming */ - return imx290_write_reg(imx290, IMX290_XMSTA, 0x00); + return imx290_write(imx290, IMX290_XMSTA, 0x00, &ret); } static int imx290_set_stream(struct v4l2_subdev *sd, int enable) @@ -788,10 +884,10 @@ static int imx290_get_regulators(struct device *dev, struct imx290 *imx290) { unsigned int i; - for (i = 0; i < IMX290_NUM_SUPPLIES; i++) + for (i = 0; i < ARRAY_SIZE(imx290->supplies); i++) imx290->supplies[i].supply = imx290_supply_name[i]; - return devm_regulator_bulk_get(dev, IMX290_NUM_SUPPLIES, + return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx290->supplies), imx290->supplies); } @@ -814,27 +910,13 @@ static int imx290_set_data_lanes(struct imx290 *imx290) * validated in probe itself */ dev_err(imx290->dev, "Lane configuration not supported\n"); - ret = -EINVAL; - goto exit; - } - - ret = imx290_write_reg(imx290, IMX290_PHY_LANE_NUM, laneval); - if (ret) { - dev_err(imx290->dev, "Error setting Physical Lane number register\n"); - goto exit; - } - - ret = imx290_write_reg(imx290, IMX290_CSI_LANE_MODE, laneval); - if (ret) { - dev_err(imx290->dev, "Error setting CSI Lane mode register\n"); - goto exit; + return -EINVAL; } - ret = imx290_write_reg(imx290, IMX290_FR_FDG_SEL, frsel); - if (ret) - dev_err(imx290->dev, "Error setting FR/FDG SEL register\n"); + imx290_write(imx290, IMX290_PHY_LANE_NUM, laneval, &ret); + imx290_write(imx290, IMX290_CSI_LANE_MODE, laneval, &ret); + imx290_write(imx290, IMX290_FR_FDG_SEL, frsel, &ret); -exit: return ret; } @@ -850,7 +932,8 @@ static int imx290_power_on(struct device *dev) return ret; } - ret = regulator_bulk_enable(IMX290_NUM_SUPPLIES, imx290->supplies); + ret = regulator_bulk_enable(ARRAY_SIZE(imx290->supplies), + imx290->supplies); if (ret) { dev_err(dev, "Failed to enable regulators\n"); clk_disable_unprepare(imx290->xclk); @@ -874,7 +957,7 @@ static int imx290_power_off(struct device *dev) clk_disable_unprepare(imx290->xclk); gpiod_set_value_cansleep(imx290->rst_gpio, 1); - regulator_bulk_disable(IMX290_NUM_SUPPLIES, imx290->supplies); + regulator_bulk_disable(ARRAY_SIZE(imx290->supplies), imx290->supplies); return 0; } @@ -893,6 +976,7 @@ static const struct v4l2_subdev_pad_ops imx290_pad_ops = { .enum_frame_size = imx290_enum_frame_size, .get_fmt = imx290_get_fmt, .set_fmt = imx290_set_fmt, + .get_selection = imx290_get_selection, }; static const struct v4l2_subdev_ops imx290_subdev_ops = { @@ -904,6 +988,85 @@ static const struct media_entity_operations imx290_subdev_entity_ops = { .link_validate = v4l2_subdev_link_validate, }; +static int imx290_ctrl_init(struct imx290 *imx290) +{ + struct v4l2_fwnode_device_properties props; + unsigned int blank; + int ret; + + ret = v4l2_fwnode_device_parse(imx290->dev, &props); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(&imx290->ctrls, 9); + imx290->ctrls.lock = &imx290->lock; + + /* + * The sensor has an analog gain and a digital gain, both controlled + * through a single gain value, expressed in 0.3dB increments. Values + * from 0.0dB (0) to 30.0dB (100) apply analog gain only, higher values + * up to 72.0dB (240) add further digital gain. Limit the range to + * analog gain only, support for digital gain can be added separately + * if needed. + * + * The IMX327 and IMX462 are largely compatible with the IMX290, but + * have an analog gain range of 0.0dB to 29.4dB and 42dB of digital + * gain. When support for those sensors gets added to the driver, the + * gain control should be adjusted accordingly. + */ + v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, 0, 100, 1, 0); + + v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_EXPOSURE, 1, IMX290_VMAX_DEFAULT - 2, 1, + IMX290_VMAX_DEFAULT - 2); + + imx290->link_freq = + v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_LINK_FREQ, + imx290_link_freqs_num(imx290) - 1, 0, + imx290_link_freqs_ptr(imx290)); + if (imx290->link_freq) + imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, INT_MAX, 1, + imx290_calc_pixel_rate(imx290)); + + v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx290_test_pattern_menu) - 1, + 0, 0, imx290_test_pattern_menu); + + blank = imx290->current_mode->hmax - imx290->current_mode->width; + imx290->hblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_HBLANK, blank, blank, 1, + blank); + if (imx290->hblank) + imx290->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + blank = IMX290_VMAX_DEFAULT - imx290->current_mode->height; + imx290->vblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_VBLANK, blank, blank, 1, + blank); + if (imx290->vblank) + imx290->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_fwnode_properties(&imx290->ctrls, &imx290_ctrl_ops, + &props); + + imx290->sd.ctrl_handler = &imx290->ctrls; + + if (imx290->ctrls.error) { + ret = imx290->ctrls.error; + v4l2_ctrl_handler_free(&imx290->ctrls); + return ret; + } + + return 0; +} + /* * Returns 0 if all link frequencies used by the driver for the given number * of MIPI data lanes are mentioned in the device tree, or the value of the @@ -1042,36 +1205,10 @@ static int imx290_probe(struct i2c_client *client) */ imx290_entity_init_cfg(&imx290->sd, NULL); - v4l2_ctrl_handler_init(&imx290->ctrls, 4); - - v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_GAIN, 0, 72, 1, 0); - - imx290->link_freq = - v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_LINK_FREQ, - imx290_link_freqs_num(imx290) - 1, 0, - imx290_link_freqs_ptr(imx290)); - if (imx290->link_freq) - imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_PIXEL_RATE, - 1, INT_MAX, 1, - imx290_calc_pixel_rate(imx290)); - - v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(imx290_test_pattern_menu) - 1, - 0, 0, imx290_test_pattern_menu); - - imx290->sd.ctrl_handler = &imx290->ctrls; - - if (imx290->ctrls.error) { - dev_err(dev, "Control initialization error %d\n", - imx290->ctrls.error); - ret = imx290->ctrls.error; - goto free_ctrl; + ret = imx290_ctrl_init(imx290); + if (ret < 0) { + dev_err(dev, "Control initialization error %d\n", ret); + goto free_mutex; } v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops); @@ -1112,6 +1249,7 @@ free_entity: media_entity_cleanup(&imx290->sd.entity); free_ctrl: v4l2_ctrl_handler_free(&imx290->ctrls); +free_mutex: mutex_destroy(&imx290->lock); free_err: v4l2_fwnode_endpoint_free(&ep); diff --git a/drivers/media/i2c/imx319.c b/drivers/media/i2c/imx319.c index 245a18fb40ad..45b1b61b2880 100644 --- a/drivers/media/i2c/imx319.c +++ b/drivers/media/i2c/imx319.c @@ -2328,8 +2328,12 @@ static int imx319_init_controls(struct imx319 *imx319) imx319->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx319->hflip) + imx319->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; imx319->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx319->vflip) + imx319->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_new_std(ctrl_hdlr, &imx319_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, IMX319_ANA_GAIN_MIN, IMX319_ANA_GAIN_MAX, diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c index b46178681c05..25d4dbb6041e 100644 --- a/drivers/media/i2c/imx355.c +++ b/drivers/media/i2c/imx355.c @@ -1617,8 +1617,12 @@ static int imx355_init_controls(struct imx355 *imx355) imx355->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx355->hflip) + imx355->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; imx355->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx355->vflip) + imx355->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, IMX355_ANA_GAIN_MIN, IMX355_ANA_GAIN_MAX, diff --git a/drivers/media/i2c/imx412.c b/drivers/media/i2c/imx412.c index 7f6d29e0e7c4..e1e986dc8856 100644 --- a/drivers/media/i2c/imx412.c +++ b/drivers/media/i2c/imx412.c @@ -1172,6 +1172,7 @@ static int imx412_init_controls(struct imx412 *imx412) static int imx412_probe(struct i2c_client *client) { struct imx412 *imx412; + const char *name; int ret; imx412 = devm_kzalloc(&client->dev, sizeof(*imx412), GFP_KERNEL); @@ -1179,6 +1180,9 @@ static int imx412_probe(struct i2c_client *client) return -ENOMEM; imx412->dev = &client->dev; + name = device_get_match_data(&client->dev); + if (!name) + return -ENODEV; /* Initialize subdev */ v4l2_i2c_subdev_init(&imx412->sd, client, &imx412_subdev_ops); @@ -1218,6 +1222,8 @@ static int imx412_probe(struct i2c_client *client) imx412->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; imx412->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + v4l2_i2c_subdev_set_name(&imx412->sd, client, name, NULL); + /* Initialize source pad */ imx412->pad.flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&imx412->sd.entity, 1, &imx412->pad); @@ -1279,7 +1285,8 @@ static const struct dev_pm_ops imx412_pm_ops = { }; static const struct of_device_id imx412_of_match[] = { - { .compatible = "sony,imx412" }, + { .compatible = "sony,imx412", .data = "imx412" }, + { .compatible = "sony,imx577", .data = "imx577" }, { } }; diff --git a/drivers/media/i2c/isl7998x.c b/drivers/media/i2c/isl7998x.c index 20f548a8a054..ae7af2cc94f5 100644 --- a/drivers/media/i2c/isl7998x.c +++ b/drivers/media/i2c/isl7998x.c @@ -665,7 +665,7 @@ static int isl7998x_set_standard(struct isl7998x *isl7998x, v4l2_std_id norm) static int isl7998x_init(struct isl7998x *isl7998x) { const unsigned int lanes = isl7998x->nr_mipi_lanes; - const u32 isl7998x_video_in_chan_map[] = { 0x00, 0x11, 0x02, 0x02 }; + static const u32 isl7998x_video_in_chan_map[] = { 0x00, 0x11, 0x02, 0x02 }; const struct reg_sequence isl7998x_init_seq_custom[] = { { ISL7998X_REG_P0_VIDEO_IN_CHAN_CTL, isl7998x_video_in_chan_map[isl7998x->nr_inputs - 1] }, diff --git a/drivers/media/i2c/ks0127.c b/drivers/media/i2c/ks0127.c index 215d9a43b0b9..0d86f2db7ad2 100644 --- a/drivers/media/i2c/ks0127.c +++ b/drivers/media/i2c/ks0127.c @@ -650,7 +650,7 @@ static const struct v4l2_subdev_ops ks0127_ops = { /* ----------------------------------------------------------------------- */ -static int ks0127_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int ks0127_probe(struct i2c_client *client) { struct ks0127 *ks; struct v4l2_subdev *sd; @@ -696,7 +696,7 @@ static struct i2c_driver ks0127_driver = { .driver = { .name = "ks0127", }, - .probe = ks0127_probe, + .probe_new = ks0127_probe, .remove = ks0127_remove, .id_table = ks0127_id, }; diff --git a/drivers/media/i2c/lm3560.c b/drivers/media/i2c/lm3560.c index edad3138cb07..5ef613604be7 100644 --- a/drivers/media/i2c/lm3560.c +++ b/drivers/media/i2c/lm3560.c @@ -391,8 +391,7 @@ static int lm3560_init_device(struct lm3560_flash *flash) return rval; } -static int lm3560_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int lm3560_probe(struct i2c_client *client) { struct lm3560_flash *flash; struct lm3560_platform_data *pdata = dev_get_platdata(&client->dev); @@ -468,7 +467,7 @@ static struct i2c_driver lm3560_i2c_driver = { .name = LM3560_NAME, .pm = NULL, }, - .probe = lm3560_probe, + .probe_new = lm3560_probe, .remove = lm3560_remove, .id_table = lm3560_id_table, }; diff --git a/drivers/media/i2c/lm3646.c b/drivers/media/i2c/lm3646.c index 0aaa963917d8..2a0cf74d2bed 100644 --- a/drivers/media/i2c/lm3646.c +++ b/drivers/media/i2c/lm3646.c @@ -334,8 +334,7 @@ static int lm3646_init_device(struct lm3646_flash *flash) return regmap_read(flash->regmap, REG_FLAG, ®_val); } -static int lm3646_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int lm3646_probe(struct i2c_client *client) { struct lm3646_flash *flash; struct lm3646_platform_data *pdata = dev_get_platdata(&client->dev); @@ -397,7 +396,7 @@ static struct i2c_driver lm3646_i2c_driver = { .driver = { .name = LM3646_NAME, }, - .probe = lm3646_probe, + .probe_new = lm3646_probe, .remove = lm3646_remove, .id_table = lm3646_id_table, }; diff --git a/drivers/media/i2c/m52790.c b/drivers/media/i2c/m52790.c index 2ab91b993c33..0e6507ab7e08 100644 --- a/drivers/media/i2c/m52790.c +++ b/drivers/media/i2c/m52790.c @@ -129,8 +129,7 @@ static const struct v4l2_subdev_ops m52790_ops = { /* i2c implementation */ -static int m52790_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int m52790_probe(struct i2c_client *client) { struct m52790_state *state; struct v4l2_subdev *sd; @@ -173,7 +172,7 @@ static struct i2c_driver m52790_driver = { .driver = { .name = "m52790", }, - .probe = m52790_probe, + .probe_new = m52790_probe, .remove = m52790_remove, .id_table = m52790_id, }; diff --git a/drivers/media/i2c/m5mols/m5mols_core.c b/drivers/media/i2c/m5mols/m5mols_core.c index 2201d2a26353..2b01873ba0db 100644 --- a/drivers/media/i2c/m5mols/m5mols_core.c +++ b/drivers/media/i2c/m5mols/m5mols_core.c @@ -939,8 +939,7 @@ static irqreturn_t m5mols_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static int m5mols_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int m5mols_probe(struct i2c_client *client) { const struct m5mols_platform_data *pdata = client->dev.platform_data; struct m5mols_info *info; @@ -1039,7 +1038,7 @@ static struct i2c_driver m5mols_i2c_driver = { .driver = { .name = MODULE_NAME, }, - .probe = m5mols_probe, + .probe_new = m5mols_probe, .remove = m5mols_remove, .id_table = m5mols_id, }; diff --git a/drivers/media/i2c/ml86v7667.c b/drivers/media/i2c/ml86v7667.c index 49ec59b0ca43..dbd2f0bd3651 100644 --- a/drivers/media/i2c/ml86v7667.c +++ b/drivers/media/i2c/ml86v7667.c @@ -359,8 +359,7 @@ static int ml86v7667_init(struct ml86v7667_priv *priv) return ret; } -static int ml86v7667_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int ml86v7667_probe(struct i2c_client *client) { struct ml86v7667_priv *priv; int ret; @@ -434,7 +433,7 @@ static struct i2c_driver ml86v7667_i2c_driver = { .driver = { .name = DRV_NAME, }, - .probe = ml86v7667_probe, + .probe_new = ml86v7667_probe, .remove = ml86v7667_remove, .id_table = ml86v7667_id, }; diff --git a/drivers/media/i2c/mt9m032.c b/drivers/media/i2c/mt9m032.c index 76b8c9c08c82..958cfdd73d57 100644 --- a/drivers/media/i2c/mt9m032.c +++ b/drivers/media/i2c/mt9m032.c @@ -701,8 +701,7 @@ static const struct v4l2_subdev_ops mt9m032_ops = { * Driver initialization and probing */ -static int mt9m032_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int mt9m032_probe(struct i2c_client *client) { struct mt9m032_platform_data *pdata = client->dev.platform_data; struct i2c_adapter *adapter = client->adapter; @@ -880,7 +879,7 @@ static struct i2c_driver mt9m032_i2c_driver = { .driver = { .name = MT9M032_NAME, }, - .probe = mt9m032_probe, + .probe_new = mt9m032_probe, .remove = mt9m032_remove, .id_table = mt9m032_id_table, }; diff --git a/drivers/media/i2c/mt9p031.c b/drivers/media/i2c/mt9p031.c index 45f7b5e52bc3..4ffc2f6e7db4 100644 --- a/drivers/media/i2c/mt9p031.c +++ b/drivers/media/i2c/mt9p031.c @@ -307,6 +307,7 @@ static inline int mt9p031_pll_disable(struct mt9p031 *mt9p031) static int mt9p031_power_on(struct mt9p031 *mt9p031) { + unsigned long rate, delay; int ret; /* Ensure RESET_BAR is active */ @@ -334,7 +335,12 @@ static int mt9p031_power_on(struct mt9p031 *mt9p031) /* Now RESET_BAR must be high */ if (mt9p031->reset) { gpiod_set_value(mt9p031->reset, 0); - usleep_range(1000, 2000); + /* Wait 850000 EXTCLK cycles before de-asserting reset. */ + rate = clk_get_rate(mt9p031->clk); + if (!rate) + rate = 6000000; /* Slowest supported clock, 6 MHz */ + delay = DIV_ROUND_UP(850000 * 1000, rate); + msleep(delay); } return 0; @@ -702,7 +708,6 @@ static int mt9p031_init_cfg(struct v4l2_subdev *subdev, V4L2_SUBDEV_FORMAT_TRY; crop = __mt9p031_get_pad_crop(mt9p031, sd_state, 0, which); - v4l2_subdev_get_try_crop(subdev, sd_state, 0); crop->left = MT9P031_COLUMN_START_DEF; crop->top = MT9P031_ROW_START_DEF; crop->width = MT9P031_WINDOW_WIDTH_DEF; diff --git a/drivers/media/i2c/mt9t001.c b/drivers/media/i2c/mt9t001.c index d5abe4a7ef07..c635ed11388a 100644 --- a/drivers/media/i2c/mt9t001.c +++ b/drivers/media/i2c/mt9t001.c @@ -856,8 +856,7 @@ static const struct v4l2_subdev_internal_ops mt9t001_subdev_internal_ops = { .close = mt9t001_close, }; -static int mt9t001_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int mt9t001_probe(struct i2c_client *client) { struct mt9t001_platform_data *pdata = client->dev.platform_data; struct mt9t001 *mt9t001; @@ -981,7 +980,7 @@ static struct i2c_driver mt9t001_driver = { .driver = { .name = "mt9t001", }, - .probe = mt9t001_probe, + .probe_new = mt9t001_probe, .remove = mt9t001_remove, .id_table = mt9t001_id, }; diff --git a/drivers/media/i2c/mt9t112.c b/drivers/media/i2c/mt9t112.c index ad564095d0cf..a82f056787b8 100644 --- a/drivers/media/i2c/mt9t112.c +++ b/drivers/media/i2c/mt9t112.c @@ -1060,8 +1060,7 @@ done: return ret; } -static int mt9t112_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int mt9t112_probe(struct i2c_client *client) { struct mt9t112_priv *priv; int ret; @@ -1120,7 +1119,7 @@ static struct i2c_driver mt9t112_i2c_driver = { .driver = { .name = "mt9t112", }, - .probe = mt9t112_probe, + .probe_new = mt9t112_probe, .remove = mt9t112_remove, .id_table = mt9t112_id, }; diff --git a/drivers/media/i2c/mt9v011.c b/drivers/media/i2c/mt9v011.c index 9952ce06ebb2..c54c7fbf0963 100644 --- a/drivers/media/i2c/mt9v011.c +++ b/drivers/media/i2c/mt9v011.c @@ -478,8 +478,7 @@ static const struct v4l2_subdev_ops mt9v011_ops = { I2C Client & Driver ****************************************************************************/ -static int mt9v011_probe(struct i2c_client *c, - const struct i2c_device_id *id) +static int mt9v011_probe(struct i2c_client *c) { u16 version; struct mt9v011 *core; @@ -586,7 +585,7 @@ static struct i2c_driver mt9v011_driver = { .driver = { .name = "mt9v011", }, - .probe = mt9v011_probe, + .probe_new = mt9v011_probe, .remove = mt9v011_remove, .id_table = mt9v011_id, }; diff --git a/drivers/media/i2c/noon010pc30.c b/drivers/media/i2c/noon010pc30.c index ecaf5e9057f1..144bef2835f7 100644 --- a/drivers/media/i2c/noon010pc30.c +++ b/drivers/media/i2c/noon010pc30.c @@ -702,8 +702,7 @@ static int noon010_detect(struct i2c_client *client, struct noon010_info *info) return ret == NOON010PC30_ID ? 0 : -ENODEV; } -static int noon010_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int noon010_probe(struct i2c_client *client) { struct noon010_info *info; struct v4l2_subdev *sd; @@ -810,7 +809,7 @@ static struct i2c_driver noon010_i2c_driver = { .driver = { .name = MODULE_NAME }, - .probe = noon010_probe, + .probe_new = noon010_probe, .remove = noon010_remove, .id_table = noon010_id, }; diff --git a/drivers/media/i2c/ov08d10.c b/drivers/media/i2c/ov08d10.c index c1703596c3dc..a39e086a51c5 100644 --- a/drivers/media/i2c/ov08d10.c +++ b/drivers/media/i2c/ov08d10.c @@ -990,8 +990,13 @@ static int ov08d10_init_controls(struct ov08d10 *ov08d10) ov08d10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov08d10_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + if (ov08d10->hflip) + ov08d10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ov08d10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov08d10_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + if (ov08d10->vflip) + ov08d10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + if (ctrl_hdlr->error) return ctrl_hdlr->error; diff --git a/drivers/media/i2c/ov08x40.c b/drivers/media/i2c/ov08x40.c new file mode 100644 index 000000000000..72ae7fba94eb --- /dev/null +++ b/drivers/media/i2c/ov08x40.c @@ -0,0 +1,3325 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022 Intel Corporation. + +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define OV08X40_REG_VALUE_08BIT 1 +#define OV08X40_REG_VALUE_16BIT 2 +#define OV08X40_REG_VALUE_24BIT 3 + +#define OV08X40_REG_MODE_SELECT 0x0100 +#define OV08X40_MODE_STANDBY 0x00 +#define OV08X40_MODE_STREAMING 0x01 + +#define OV08X40_REG_AO_STANDBY 0x1000 +#define OV08X40_AO_STREAMING 0x04 + +#define OV08X40_REG_MS_SELECT 0x1001 +#define OV08X40_MS_STANDBY 0x00 +#define OV08X40_MS_STREAMING 0x04 + +#define OV08X40_REG_SOFTWARE_RST 0x0103 +#define OV08X40_SOFTWARE_RST 0x01 + +/* Chip ID */ +#define OV08X40_REG_CHIP_ID 0x300a +#define OV08X40_CHIP_ID 0x560858 + +/* V_TIMING internal */ +#define OV08X40_REG_VTS 0x380e +#define OV08X40_VTS_30FPS 0x1388 +#define OV08X40_VTS_BIN_30FPS 0x115c +#define OV08X40_VTS_MAX 0x7fff + +/* H TIMING internal */ +#define OV08X40_REG_HTS 0x380c +#define OV08X40_HTS_30FPS 0x0280 + +/* Exposure control */ +#define OV08X40_REG_EXPOSURE 0x3500 +#define OV08X40_EXPOSURE_MAX_MARGIN 31 +#define OV08X40_EXPOSURE_MIN 1 +#define OV08X40_EXPOSURE_STEP 1 +#define OV08X40_EXPOSURE_DEFAULT 0x40 + +/* Short Exposure control */ +#define OV08X40_REG_SHORT_EXPOSURE 0x3540 + +/* Analog gain control */ +#define OV08X40_REG_ANALOG_GAIN 0x3508 +#define OV08X40_ANA_GAIN_MIN 0x80 +#define OV08X40_ANA_GAIN_MAX 0x07c0 +#define OV08X40_ANA_GAIN_STEP 1 +#define OV08X40_ANA_GAIN_DEFAULT 0x80 + +/* Digital gain control */ +#define OV08X40_REG_DGTL_GAIN_H 0x350a +#define OV08X40_REG_DGTL_GAIN_M 0x350b +#define OV08X40_REG_DGTL_GAIN_L 0x350c + +#define OV08X40_DGTL_GAIN_MIN 1024 /* Min = 1 X */ +#define OV08X40_DGTL_GAIN_MAX (4096 - 1) /* Max = 4 X */ +#define OV08X40_DGTL_GAIN_DEFAULT 2560 /* Default gain = 2.5 X */ +#define OV08X40_DGTL_GAIN_STEP 1 /* Each step = 1/1024 */ + +#define OV08X40_DGTL_GAIN_L_SHIFT 6 +#define OV08X40_DGTL_GAIN_L_MASK 0x3 +#define OV08X40_DGTL_GAIN_M_SHIFT 2 +#define OV08X40_DGTL_GAIN_M_MASK 0xff +#define OV08X40_DGTL_GAIN_H_SHIFT 10 +#define OV08X40_DGTL_GAIN_H_MASK 0x1F + +/* Test Pattern Control */ +#define OV08X40_REG_TEST_PATTERN 0x50C1 +#define OV08X40_REG_ISP 0x5000 +#define OV08X40_REG_SHORT_TEST_PATTERN 0x53C1 +#define OV08X40_TEST_PATTERN_ENABLE BIT(0) +#define OV08X40_TEST_PATTERN_MASK 0xcf +#define OV08X40_TEST_PATTERN_BAR_SHIFT 4 + +/* Flip Control */ +#define OV08X40_REG_VFLIP 0x3820 +#define OV08X40_REG_MIRROR 0x3821 + +/* Horizontal Window Offset */ +#define OV08X40_REG_H_WIN_OFFSET 0x3811 + +/* Vertical Window Offset */ +#define OV08X40_REG_V_WIN_OFFSET 0x3813 + +enum { + OV08X40_LINK_FREQ_400MHZ_INDEX, +}; + +struct ov08x40_reg { + u16 address; + u8 val; +}; + +struct ov08x40_reg_list { + u32 num_of_regs; + const struct ov08x40_reg *regs; +}; + +/* Link frequency config */ +struct ov08x40_link_freq_config { + u32 pixels_per_line; + + /* registers for this link frequency */ + struct ov08x40_reg_list reg_list; +}; + +/* Mode : resolution and related config&values */ +struct ov08x40_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + u32 lanes; + /* V-timing */ + u32 vts_def; + u32 vts_min; + + /* Index of Link frequency config to be used */ + u32 link_freq_index; + /* Default register values */ + struct ov08x40_reg_list reg_list; +}; + +static const struct ov08x40_reg mipi_data_rate_800mbps[] = { + {0x0103, 0x01}, + {0x1000, 0x00}, + {0x1601, 0xd0}, + {0x1001, 0x04}, + {0x5004, 0x53}, + {0x5110, 0x00}, + {0x5111, 0x14}, + {0x5112, 0x01}, + {0x5113, 0x7b}, + {0x5114, 0x00}, + {0x5152, 0xa3}, + {0x5a52, 0x1f}, + {0x5a1a, 0x0e}, + {0x5a1b, 0x10}, + {0x5a1f, 0x0e}, + {0x5a27, 0x0e}, + {0x6002, 0x2e}, +}; + +static const struct ov08x40_reg mode_3856x2416_regs[] = { + {0x5000, 0x5d}, + {0x5001, 0x20}, + {0x5008, 0xb0}, + {0x50c1, 0x00}, + {0x53c1, 0x00}, + {0x5f40, 0x00}, + {0x5f41, 0x40}, + {0x0300, 0x3a}, + {0x0301, 0xc8}, + {0x0302, 0x31}, + {0x0303, 0x03}, + {0x0304, 0x01}, + {0x0305, 0xa1}, + {0x0306, 0x04}, + {0x0307, 0x01}, + {0x0308, 0x03}, + {0x0309, 0x03}, + {0x0310, 0x0a}, + {0x0311, 0x02}, + {0x0312, 0x01}, + {0x0313, 0x08}, + {0x0314, 0x66}, + {0x0315, 0x00}, + {0x0316, 0x34}, + {0x0320, 0x02}, + {0x0321, 0x03}, + {0x0323, 0x05}, + {0x0324, 0x01}, + {0x0325, 0xb8}, + {0x0326, 0x4a}, + {0x0327, 0x04}, + {0x0329, 0x00}, + {0x032a, 0x05}, + {0x032b, 0x00}, + {0x032c, 0x00}, + {0x032d, 0x00}, + {0x032e, 0x02}, + {0x032f, 0xa0}, + {0x0350, 0x00}, + {0x0360, 0x01}, + {0x1216, 0x60}, + {0x1217, 0x5b}, + {0x1218, 0x00}, + {0x1220, 0x24}, + {0x198a, 0x00}, + {0x198b, 0x01}, + {0x198e, 0x00}, + {0x198f, 0x01}, + {0x3009, 0x04}, + {0x3012, 0x41}, + {0x3015, 0x00}, + {0x3016, 0xb0}, + {0x3017, 0xf0}, + {0x3018, 0xf0}, + {0x3019, 0xd2}, + {0x301a, 0xb0}, + {0x301c, 0x81}, + {0x301d, 0x02}, + {0x301e, 0x80}, + {0x3022, 0xf0}, + {0x3025, 0x89}, + {0x3030, 0x03}, + {0x3044, 0xc2}, + {0x3050, 0x35}, + {0x3051, 0x60}, + {0x3052, 0x25}, + {0x3053, 0x00}, + {0x3054, 0x00}, + {0x3055, 0x02}, + {0x3056, 0x80}, + {0x3057, 0x80}, + {0x3058, 0x80}, + {0x3059, 0x00}, + {0x3107, 0x86}, + {0x3400, 0x1c}, + {0x3401, 0x80}, + {0x3402, 0x8c}, + {0x3419, 0x13}, + {0x341a, 0x89}, + {0x341b, 0x30}, + {0x3420, 0x00}, + {0x3421, 0x00}, + {0x3422, 0x00}, + {0x3423, 0x00}, + {0x3424, 0x00}, + {0x3425, 0x00}, + {0x3426, 0x00}, + {0x3427, 0x00}, + {0x3428, 0x0f}, + {0x3429, 0x00}, + {0x342a, 0x00}, + {0x342b, 0x00}, + {0x342c, 0x00}, + {0x342d, 0x00}, + {0x342e, 0x00}, + {0x342f, 0x11}, + {0x3430, 0x11}, + {0x3431, 0x10}, + {0x3432, 0x00}, + {0x3433, 0x00}, + {0x3434, 0x00}, + {0x3435, 0x00}, + {0x3436, 0x00}, + {0x3437, 0x00}, + {0x3442, 0x02}, + {0x3443, 0x02}, + {0x3444, 0x07}, + {0x3450, 0x00}, + {0x3451, 0x00}, + {0x3452, 0x18}, + {0x3453, 0x18}, + {0x3454, 0x00}, + {0x3455, 0x80}, + {0x3456, 0x08}, + {0x3500, 0x00}, + {0x3501, 0x02}, + {0x3502, 0x00}, + {0x3504, 0x4c}, + {0x3506, 0x30}, + {0x3507, 0x00}, + {0x3508, 0x01}, + {0x3509, 0x00}, + {0x350a, 0x01}, + {0x350b, 0x00}, + {0x350c, 0x00}, + {0x3540, 0x00}, + {0x3541, 0x01}, + {0x3542, 0x00}, + {0x3544, 0x4c}, + {0x3546, 0x30}, + {0x3547, 0x00}, + {0x3548, 0x01}, + {0x3549, 0x00}, + {0x354a, 0x01}, + {0x354b, 0x00}, + {0x354c, 0x00}, + {0x3688, 0x02}, + {0x368a, 0x2e}, + {0x368e, 0x71}, + {0x3696, 0xd1}, + {0x3699, 0x00}, + {0x369a, 0x00}, + {0x36a4, 0x00}, + {0x36a6, 0x00}, + {0x3711, 0x00}, + {0x3712, 0x51}, + {0x3713, 0x00}, + {0x3714, 0x24}, + {0x3716, 0x00}, + {0x3718, 0x07}, + {0x371a, 0x1c}, + {0x371b, 0x00}, + {0x3720, 0x08}, + {0x3725, 0x32}, + {0x3727, 0x05}, + {0x3760, 0x02}, + {0x3761, 0x17}, + {0x3762, 0x02}, + {0x3763, 0x02}, + {0x3764, 0x02}, + {0x3765, 0x2c}, + {0x3766, 0x04}, + {0x3767, 0x2c}, + {0x3768, 0x02}, + {0x3769, 0x00}, + {0x376b, 0x20}, + {0x376e, 0x03}, + {0x37b0, 0x00}, + {0x37b1, 0xab}, + {0x37b2, 0x01}, + {0x37b3, 0x82}, + {0x37b4, 0x00}, + {0x37b5, 0xe4}, + {0x37b6, 0x01}, + {0x37b7, 0xee}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0f}, + {0x3805, 0x1f}, + {0x3806, 0x09}, + {0x3807, 0x7f}, + {0x3808, 0x0f}, + {0x3809, 0x10}, + {0x380a, 0x09}, + {0x380b, 0x70}, + {0x380c, 0x02}, + {0x380d, 0x80}, + {0x380e, 0x13}, + {0x380f, 0x88}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x07}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3820, 0x00}, + {0x3821, 0x04}, + {0x3822, 0x00}, + {0x3823, 0x04}, + {0x3828, 0x0f}, + {0x382a, 0x80}, + {0x382e, 0x41}, + {0x3837, 0x08}, + {0x383a, 0x81}, + {0x383b, 0x81}, + {0x383c, 0x11}, + {0x383d, 0x11}, + {0x383e, 0x00}, + {0x383f, 0x38}, + {0x3840, 0x00}, + {0x3847, 0x00}, + {0x384a, 0x00}, + {0x384c, 0x02}, + {0x384d, 0x80}, + {0x3856, 0x50}, + {0x3857, 0x30}, + {0x3858, 0x80}, + {0x3859, 0x40}, + {0x3860, 0x00}, + {0x3888, 0x00}, + {0x3889, 0x00}, + {0x388a, 0x00}, + {0x388b, 0x00}, + {0x388c, 0x00}, + {0x388d, 0x00}, + {0x388e, 0x00}, + {0x388f, 0x00}, + {0x3894, 0x00}, + {0x3895, 0x00}, + {0x3c84, 0x00}, + {0x3d85, 0x8b}, + {0x3daa, 0x80}, + {0x3dab, 0x14}, + {0x3dac, 0x80}, + {0x3dad, 0xc8}, + {0x3dae, 0x81}, + {0x3daf, 0x7b}, + {0x3f00, 0x10}, + {0x3f01, 0x11}, + {0x3f06, 0x0d}, + {0x3f07, 0x0b}, + {0x3f08, 0x0d}, + {0x3f09, 0x0b}, + {0x3f0a, 0x01}, + {0x3f0b, 0x11}, + {0x3f0c, 0x33}, + {0x4001, 0x07}, + {0x4007, 0x20}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x400a, 0x00}, + {0x400b, 0x08}, + {0x400c, 0x00}, + {0x400d, 0x08}, + {0x400e, 0x14}, + {0x4010, 0xf4}, + {0x4011, 0x03}, + {0x4012, 0x55}, + {0x4015, 0x00}, + {0x4016, 0x2d}, + {0x4017, 0x00}, + {0x4018, 0x0f}, + {0x401b, 0x08}, + {0x401c, 0x00}, + {0x401d, 0x10}, + {0x401e, 0x02}, + {0x401f, 0x00}, + {0x4050, 0x06}, + {0x4051, 0xff}, + {0x4052, 0xff}, + {0x4053, 0xff}, + {0x4054, 0xff}, + {0x4055, 0xff}, + {0x4056, 0xff}, + {0x4057, 0x7f}, + {0x4058, 0x00}, + {0x4059, 0x00}, + {0x405a, 0x00}, + {0x405b, 0x00}, + {0x405c, 0x07}, + {0x405d, 0xff}, + {0x405e, 0x07}, + {0x405f, 0xff}, + {0x4080, 0x78}, + {0x4081, 0x78}, + {0x4082, 0x78}, + {0x4083, 0x78}, + {0x4019, 0x00}, + {0x401a, 0x40}, + {0x4020, 0x04}, + {0x4021, 0x00}, + {0x4022, 0x04}, + {0x4023, 0x00}, + {0x4024, 0x04}, + {0x4025, 0x00}, + {0x4026, 0x04}, + {0x4027, 0x00}, + {0x4030, 0x00}, + {0x4031, 0x00}, + {0x4032, 0x00}, + {0x4033, 0x00}, + {0x4034, 0x00}, + {0x4035, 0x00}, + {0x4036, 0x00}, + {0x4037, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x80}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4060, 0x00}, + {0x4061, 0x00}, + {0x4062, 0x00}, + {0x4063, 0x00}, + {0x4064, 0x00}, + {0x4065, 0x00}, + {0x4066, 0x00}, + {0x4067, 0x00}, + {0x4068, 0x00}, + {0x4069, 0x00}, + {0x406a, 0x00}, + {0x406b, 0x00}, + {0x406c, 0x00}, + {0x406d, 0x00}, + {0x406e, 0x00}, + {0x406f, 0x00}, + {0x4070, 0x00}, + {0x4071, 0x00}, + {0x4072, 0x00}, + {0x4073, 0x00}, + {0x4074, 0x00}, + {0x4075, 0x00}, + {0x4076, 0x00}, + {0x4077, 0x00}, + {0x4078, 0x00}, + {0x4079, 0x00}, + {0x407a, 0x00}, + {0x407b, 0x00}, + {0x407c, 0x00}, + {0x407d, 0x00}, + {0x407e, 0x00}, + {0x407f, 0x00}, + {0x40e0, 0x00}, + {0x40e1, 0x00}, + {0x40e2, 0x00}, + {0x40e3, 0x00}, + {0x40e4, 0x00}, + {0x40e5, 0x00}, + {0x40e6, 0x00}, + {0x40e7, 0x00}, + {0x40e8, 0x00}, + {0x40e9, 0x80}, + {0x40ea, 0x00}, + {0x40eb, 0x80}, + {0x40ec, 0x00}, + {0x40ed, 0x80}, + {0x40ee, 0x00}, + {0x40ef, 0x80}, + {0x40f0, 0x02}, + {0x40f1, 0x04}, + {0x4300, 0x00}, + {0x4301, 0x00}, + {0x4302, 0x00}, + {0x4303, 0x00}, + {0x4304, 0x00}, + {0x4305, 0x00}, + {0x4306, 0x00}, + {0x4307, 0x00}, + {0x4308, 0x00}, + {0x4309, 0x00}, + {0x430a, 0x00}, + {0x430b, 0xff}, + {0x430c, 0xff}, + {0x430d, 0x00}, + {0x430e, 0x00}, + {0x4315, 0x00}, + {0x4316, 0x00}, + {0x4317, 0x00}, + {0x4318, 0x00}, + {0x4319, 0x00}, + {0x431a, 0x00}, + {0x431b, 0x00}, + {0x431c, 0x00}, + {0x4500, 0x07}, + {0x4501, 0x00}, + {0x4502, 0x00}, + {0x4503, 0x0f}, + {0x4504, 0x80}, + {0x4506, 0x01}, + {0x4509, 0x05}, + {0x450c, 0x00}, + {0x450d, 0x20}, + {0x450e, 0x00}, + {0x450f, 0x00}, + {0x4510, 0x00}, + {0x4523, 0x00}, + {0x4526, 0x00}, + {0x4542, 0x00}, + {0x4543, 0x00}, + {0x4544, 0x00}, + {0x4545, 0x00}, + {0x4546, 0x00}, + {0x4547, 0x10}, + {0x4602, 0x00}, + {0x4603, 0x15}, + {0x460b, 0x07}, + {0x4680, 0x11}, + {0x4686, 0x00}, + {0x4687, 0x00}, + {0x4700, 0x00}, + {0x4800, 0x64}, + {0x4806, 0x40}, + {0x480b, 0x10}, + {0x480c, 0x80}, + {0x480f, 0x32}, + {0x4813, 0xe4}, + {0x4837, 0x14}, + {0x4850, 0x42}, + {0x4884, 0x04}, + {0x4c00, 0xf8}, + {0x4c01, 0x44}, + {0x4c03, 0x00}, + {0x4d00, 0x00}, + {0x4d01, 0x16}, + {0x4d04, 0x10}, + {0x4d05, 0x00}, + {0x4d06, 0x0c}, + {0x4d07, 0x00}, + {0x3d84, 0x04}, + {0x3680, 0xa4}, + {0x3682, 0x80}, + {0x3601, 0x40}, + {0x3602, 0x90}, + {0x3608, 0x0a}, + {0x3938, 0x09}, + {0x3a74, 0x84}, + {0x3a99, 0x84}, + {0x3ab9, 0xa6}, + {0x3aba, 0xba}, + {0x3b12, 0x84}, + {0x3b14, 0xbb}, + {0x3b15, 0xbf}, + {0x3a29, 0x26}, + {0x3a1f, 0x8a}, + {0x3a22, 0x91}, + {0x3a25, 0x96}, + {0x3a28, 0xb4}, + {0x3a2b, 0xba}, + {0x3a2e, 0xbf}, + {0x3a31, 0xc1}, + {0x3a20, 0x00}, + {0x3939, 0x9d}, + {0x3902, 0x0e}, + {0x3903, 0x0e}, + {0x3904, 0x0e}, + {0x3905, 0x0e}, + {0x3906, 0x07}, + {0x3907, 0x0d}, + {0x3908, 0x11}, + {0x3909, 0x12}, + {0x360f, 0x99}, + {0x390c, 0x33}, + {0x390d, 0x66}, + {0x390e, 0xaa}, + {0x3911, 0x90}, + {0x3913, 0x90}, + {0x3915, 0x90}, + {0x3917, 0x90}, + {0x3b3f, 0x9d}, + {0x3b45, 0x9d}, + {0x3b1b, 0xc9}, + {0x3b21, 0xc9}, + {0x3440, 0xa4}, + {0x3a23, 0x15}, + {0x3a26, 0x1d}, + {0x3a2c, 0x4a}, + {0x3a2f, 0x18}, + {0x3a32, 0x55}, + {0x3b0a, 0x01}, + {0x3b0b, 0x00}, + {0x3b0e, 0x01}, + {0x3b0f, 0x00}, + {0x392c, 0x02}, + {0x392d, 0x02}, + {0x392e, 0x04}, + {0x392f, 0x03}, + {0x3930, 0x08}, + {0x3931, 0x07}, + {0x3932, 0x10}, + {0x3933, 0x0c}, + {0x3609, 0x08}, + {0x3921, 0x0f}, + {0x3928, 0x15}, + {0x3929, 0x2a}, + {0x392a, 0x54}, + {0x392b, 0xa8}, + {0x3426, 0x10}, + {0x3407, 0x01}, + {0x3404, 0x01}, + {0x3500, 0x00}, + {0x3501, 0x10}, + {0x3502, 0x10}, + {0x3508, 0x0f}, + {0x3509, 0x80}, + {0x5a80, 0x75}, + {0x5a81, 0x75}, + {0x5a82, 0x75}, + {0x5a83, 0x75}, + {0x5a84, 0x75}, + {0x5a85, 0x75}, + {0x5a86, 0x75}, + {0x5a87, 0x75}, + {0x5a88, 0x75}, + {0x5a89, 0x75}, + {0x5a8a, 0x75}, + {0x5a8b, 0x75}, + {0x5a8c, 0x75}, + {0x5a8d, 0x75}, + {0x5a8e, 0x75}, + {0x5a8f, 0x75}, + {0x5a90, 0x75}, + {0x5a91, 0x75}, + {0x5a92, 0x75}, + {0x5a93, 0x75}, + {0x5a94, 0x75}, + {0x5a95, 0x75}, + {0x5a96, 0x75}, + {0x5a97, 0x75}, + {0x5a98, 0x75}, + {0x5a99, 0x75}, + {0x5a9a, 0x75}, + {0x5a9b, 0x75}, + {0x5a9c, 0x75}, + {0x5a9d, 0x75}, + {0x5a9e, 0x75}, + {0x5a9f, 0x75}, + {0x5aa0, 0x75}, + {0x5aa1, 0x75}, + {0x5aa2, 0x75}, + {0x5aa3, 0x75}, + {0x5aa4, 0x75}, + {0x5aa5, 0x75}, + {0x5aa6, 0x75}, + {0x5aa7, 0x75}, + {0x5aa8, 0x75}, + {0x5aa9, 0x75}, + {0x5aaa, 0x75}, + {0x5aab, 0x75}, + {0x5aac, 0x75}, + {0x5aad, 0x75}, + {0x5aae, 0x75}, + {0x5aaf, 0x75}, + {0x5ab0, 0x75}, + {0x5ab1, 0x75}, + {0x5ab2, 0x75}, + {0x5ab3, 0x75}, + {0x5ab4, 0x75}, + {0x5ab5, 0x75}, + {0x5ab6, 0x75}, + {0x5ab7, 0x75}, + {0x5ab8, 0x75}, + {0x5ab9, 0x75}, + {0x5aba, 0x75}, + {0x5abb, 0x75}, + {0x5abc, 0x75}, + {0x5abd, 0x75}, + {0x5abe, 0x75}, + {0x5abf, 0x75}, + {0x5ac0, 0x75}, + {0x5ac1, 0x75}, + {0x5ac2, 0x75}, + {0x5ac3, 0x75}, + {0x5ac4, 0x75}, + {0x5ac5, 0x75}, + {0x5ac6, 0x75}, + {0x5ac7, 0x75}, + {0x5ac8, 0x75}, + {0x5ac9, 0x75}, + {0x5aca, 0x75}, + {0x5acb, 0x75}, + {0x5acc, 0x75}, + {0x5acd, 0x75}, + {0x5ace, 0x75}, + {0x5acf, 0x75}, + {0x5ad0, 0x75}, + {0x5ad1, 0x75}, + {0x5ad2, 0x75}, + {0x5ad3, 0x75}, + {0x5ad4, 0x75}, + {0x5ad5, 0x75}, + {0x5ad6, 0x75}, + {0x5ad7, 0x75}, + {0x5ad8, 0x75}, + {0x5ad9, 0x75}, + {0x5ada, 0x75}, + {0x5adb, 0x75}, + {0x5adc, 0x75}, + {0x5add, 0x75}, + {0x5ade, 0x75}, + {0x5adf, 0x75}, + {0x5ae0, 0x75}, + {0x5ae1, 0x75}, + {0x5ae2, 0x75}, + {0x5ae3, 0x75}, + {0x5ae4, 0x75}, + {0x5ae5, 0x75}, + {0x5ae6, 0x75}, + {0x5ae7, 0x75}, + {0x5ae8, 0x75}, + {0x5ae9, 0x75}, + {0x5aea, 0x75}, + {0x5aeb, 0x75}, + {0x5aec, 0x75}, + {0x5aed, 0x75}, + {0x5aee, 0x75}, + {0x5aef, 0x75}, + {0x5af0, 0x75}, + {0x5af1, 0x75}, + {0x5af2, 0x75}, + {0x5af3, 0x75}, + {0x5af4, 0x75}, + {0x5af5, 0x75}, + {0x5af6, 0x75}, + {0x5af7, 0x75}, + {0x5af8, 0x75}, + {0x5af9, 0x75}, + {0x5afa, 0x75}, + {0x5afb, 0x75}, + {0x5afc, 0x75}, + {0x5afd, 0x75}, + {0x5afe, 0x75}, + {0x5aff, 0x75}, + {0x5b00, 0x75}, + {0x5b01, 0x75}, + {0x5b02, 0x75}, + {0x5b03, 0x75}, + {0x5b04, 0x75}, + {0x5b05, 0x75}, + {0x5b06, 0x75}, + {0x5b07, 0x75}, + {0x5b08, 0x75}, + {0x5b09, 0x75}, + {0x5b0a, 0x75}, + {0x5b0b, 0x75}, + {0x5b0c, 0x75}, + {0x5b0d, 0x75}, + {0x5b0e, 0x75}, + {0x5b0f, 0x75}, + {0x5b10, 0x75}, + {0x5b11, 0x75}, + {0x5b12, 0x75}, + {0x5b13, 0x75}, + {0x5b14, 0x75}, + {0x5b15, 0x75}, + {0x5b16, 0x75}, + {0x5b17, 0x75}, + {0x5b18, 0x75}, + {0x5b19, 0x75}, + {0x5b1a, 0x75}, + {0x5b1b, 0x75}, + {0x5b1c, 0x75}, + {0x5b1d, 0x75}, + {0x5b1e, 0x75}, + {0x5b1f, 0x75}, + {0x5b20, 0x75}, + {0x5b21, 0x75}, + {0x5b22, 0x75}, + {0x5b23, 0x75}, + {0x5b24, 0x75}, + {0x5b25, 0x75}, + {0x5b26, 0x75}, + {0x5b27, 0x75}, + {0x5b28, 0x75}, + {0x5b29, 0x75}, + {0x5b2a, 0x75}, + {0x5b2b, 0x75}, + {0x5b2c, 0x75}, + {0x5b2d, 0x75}, + {0x5b2e, 0x75}, + {0x5b2f, 0x75}, + {0x5b30, 0x75}, + {0x5b31, 0x75}, + {0x5b32, 0x75}, + {0x5b33, 0x75}, + {0x5b34, 0x75}, + {0x5b35, 0x75}, + {0x5b36, 0x75}, + {0x5b37, 0x75}, + {0x5b38, 0x75}, + {0x5b39, 0x75}, + {0x5b3a, 0x75}, + {0x5b3b, 0x75}, + {0x5b3c, 0x75}, + {0x5b3d, 0x75}, + {0x5b3e, 0x75}, + {0x5b3f, 0x75}, + {0x5b40, 0x75}, + {0x5b41, 0x75}, + {0x5b42, 0x75}, + {0x5b43, 0x75}, + {0x5b44, 0x75}, + {0x5b45, 0x75}, + {0x5b46, 0x75}, + {0x5b47, 0x75}, + {0x5b48, 0x75}, + {0x5b49, 0x75}, + {0x5b4a, 0x75}, + {0x5b4b, 0x75}, + {0x5b4c, 0x75}, + {0x5b4d, 0x75}, + {0x5b4e, 0x75}, + {0x5b4f, 0x75}, + {0x5b50, 0x75}, + {0x5b51, 0x75}, + {0x5b52, 0x75}, + {0x5b53, 0x75}, + {0x5b54, 0x75}, + {0x5b55, 0x75}, + {0x5b56, 0x75}, + {0x5b57, 0x75}, + {0x5b58, 0x75}, + {0x5b59, 0x75}, + {0x5b5a, 0x75}, + {0x5b5b, 0x75}, + {0x5b5c, 0x75}, + {0x5b5d, 0x75}, + {0x5b5e, 0x75}, + {0x5b5f, 0x75}, + {0x5b60, 0x75}, + {0x5b61, 0x75}, + {0x5b62, 0x75}, + {0x5b63, 0x75}, + {0x5b64, 0x75}, + {0x5b65, 0x75}, + {0x5b66, 0x75}, + {0x5b67, 0x75}, + {0x5b68, 0x75}, + {0x5b69, 0x75}, + {0x5b6a, 0x75}, + {0x5b6b, 0x75}, + {0x5b6c, 0x75}, + {0x5b6d, 0x75}, + {0x5b6e, 0x75}, + {0x5b6f, 0x75}, + {0x5b70, 0x75}, + {0x5b71, 0x75}, + {0x5b72, 0x75}, + {0x5b73, 0x75}, + {0x5b74, 0x75}, + {0x5b75, 0x75}, + {0x5b76, 0x75}, + {0x5b77, 0x75}, + {0x5b78, 0x75}, + {0x5b79, 0x75}, + {0x5b7a, 0x75}, + {0x5b7b, 0x75}, + {0x5b7c, 0x75}, + {0x5b7d, 0x75}, + {0x5b7e, 0x75}, + {0x5b7f, 0x75}, + {0x5b80, 0x75}, + {0x5b81, 0x75}, + {0x5b82, 0x75}, + {0x5b83, 0x75}, + {0x5b84, 0x75}, + {0x5b85, 0x75}, + {0x5b86, 0x75}, + {0x5b87, 0x75}, + {0x5b88, 0x75}, + {0x5b89, 0x75}, + {0x5b8a, 0x75}, + {0x5b8b, 0x75}, + {0x5b8c, 0x75}, + {0x5b8d, 0x75}, + {0x5b8e, 0x75}, + {0x5b8f, 0x75}, + {0x5b90, 0x75}, + {0x5b91, 0x75}, + {0x5b92, 0x75}, + {0x5b93, 0x75}, + {0x5b94, 0x75}, + {0x5b95, 0x75}, + {0x5b96, 0x75}, + {0x5b97, 0x75}, + {0x5b98, 0x75}, + {0x5b99, 0x75}, + {0x5b9a, 0x75}, + {0x5b9b, 0x75}, + {0x5b9c, 0x75}, + {0x5b9d, 0x75}, + {0x5b9e, 0x75}, + {0x5b9f, 0x75}, + {0x5bc0, 0x75}, + {0x5bc1, 0x75}, + {0x5bc2, 0x75}, + {0x5bc3, 0x75}, + {0x5bc4, 0x75}, + {0x5bc5, 0x75}, + {0x5bc6, 0x75}, + {0x5bc7, 0x75}, + {0x5bc8, 0x75}, + {0x5bc9, 0x75}, + {0x5bca, 0x75}, + {0x5bcb, 0x75}, + {0x5bcc, 0x75}, + {0x5bcd, 0x75}, + {0x5bce, 0x75}, + {0x5bcf, 0x75}, + {0x5bd0, 0x75}, + {0x5bd1, 0x75}, + {0x5bd2, 0x75}, + {0x5bd3, 0x75}, + {0x5bd4, 0x75}, + {0x5bd5, 0x75}, + {0x5bd6, 0x75}, + {0x5bd7, 0x75}, + {0x5bd8, 0x75}, + {0x5bd9, 0x75}, + {0x5bda, 0x75}, + {0x5bdb, 0x75}, + {0x5bdc, 0x75}, + {0x5bdd, 0x75}, + {0x5bde, 0x75}, + {0x5bdf, 0x75}, + {0x5be0, 0x75}, + {0x5be1, 0x75}, + {0x5be2, 0x75}, + {0x5be3, 0x75}, + {0x5be4, 0x75}, + {0x5be5, 0x75}, + {0x5be6, 0x75}, + {0x5be7, 0x75}, + {0x5be8, 0x75}, + {0x5be9, 0x75}, + {0x5bea, 0x75}, + {0x5beb, 0x75}, + {0x5bec, 0x75}, + {0x5bed, 0x75}, + {0x5bee, 0x75}, + {0x5bef, 0x75}, + {0x5bf0, 0x75}, + {0x5bf1, 0x75}, + {0x5bf2, 0x75}, + {0x5bf3, 0x75}, + {0x5bf4, 0x75}, + {0x5bf5, 0x75}, + {0x5bf6, 0x75}, + {0x5bf7, 0x75}, + {0x5bf8, 0x75}, + {0x5bf9, 0x75}, + {0x5bfa, 0x75}, + {0x5bfb, 0x75}, + {0x5bfc, 0x75}, + {0x5bfd, 0x75}, + {0x5bfe, 0x75}, + {0x5bff, 0x75}, + {0x5c00, 0x75}, + {0x5c01, 0x75}, + {0x5c02, 0x75}, + {0x5c03, 0x75}, + {0x5c04, 0x75}, + {0x5c05, 0x75}, + {0x5c06, 0x75}, + {0x5c07, 0x75}, + {0x5c08, 0x75}, + {0x5c09, 0x75}, + {0x5c0a, 0x75}, + {0x5c0b, 0x75}, + {0x5c0c, 0x75}, + {0x5c0d, 0x75}, + {0x5c0e, 0x75}, + {0x5c0f, 0x75}, + {0x5c10, 0x75}, + {0x5c11, 0x75}, + {0x5c12, 0x75}, + {0x5c13, 0x75}, + {0x5c14, 0x75}, + {0x5c15, 0x75}, + {0x5c16, 0x75}, + {0x5c17, 0x75}, + {0x5c18, 0x75}, + {0x5c19, 0x75}, + {0x5c1a, 0x75}, + {0x5c1b, 0x75}, + {0x5c1c, 0x75}, + {0x5c1d, 0x75}, + {0x5c1e, 0x75}, + {0x5c1f, 0x75}, + {0x5c20, 0x75}, + {0x5c21, 0x75}, + {0x5c22, 0x75}, + {0x5c23, 0x75}, + {0x5c24, 0x75}, + {0x5c25, 0x75}, + {0x5c26, 0x75}, + {0x5c27, 0x75}, + {0x5c28, 0x75}, + {0x5c29, 0x75}, + {0x5c2a, 0x75}, + {0x5c2b, 0x75}, + {0x5c2c, 0x75}, + {0x5c2d, 0x75}, + {0x5c2e, 0x75}, + {0x5c2f, 0x75}, + {0x5c30, 0x75}, + {0x5c31, 0x75}, + {0x5c32, 0x75}, + {0x5c33, 0x75}, + {0x5c34, 0x75}, + {0x5c35, 0x75}, + {0x5c36, 0x75}, + {0x5c37, 0x75}, + {0x5c38, 0x75}, + {0x5c39, 0x75}, + {0x5c3a, 0x75}, + {0x5c3b, 0x75}, + {0x5c3c, 0x75}, + {0x5c3d, 0x75}, + {0x5c3e, 0x75}, + {0x5c3f, 0x75}, + {0x5c40, 0x75}, + {0x5c41, 0x75}, + {0x5c42, 0x75}, + {0x5c43, 0x75}, + {0x5c44, 0x75}, + {0x5c45, 0x75}, + {0x5c46, 0x75}, + {0x5c47, 0x75}, + {0x5c48, 0x75}, + {0x5c49, 0x75}, + {0x5c4a, 0x75}, + {0x5c4b, 0x75}, + {0x5c4c, 0x75}, + {0x5c4d, 0x75}, + {0x5c4e, 0x75}, + {0x5c4f, 0x75}, + {0x5c50, 0x75}, + {0x5c51, 0x75}, + {0x5c52, 0x75}, + {0x5c53, 0x75}, + {0x5c54, 0x75}, + {0x5c55, 0x75}, + {0x5c56, 0x75}, + {0x5c57, 0x75}, + {0x5c58, 0x75}, + {0x5c59, 0x75}, + {0x5c5a, 0x75}, + {0x5c5b, 0x75}, + {0x5c5c, 0x75}, + {0x5c5d, 0x75}, + {0x5c5e, 0x75}, + {0x5c5f, 0x75}, + {0x5c60, 0x75}, + {0x5c61, 0x75}, + {0x5c62, 0x75}, + {0x5c63, 0x75}, + {0x5c64, 0x75}, + {0x5c65, 0x75}, + {0x5c66, 0x75}, + {0x5c67, 0x75}, + {0x5c68, 0x75}, + {0x5c69, 0x75}, + {0x5c6a, 0x75}, + {0x5c6b, 0x75}, + {0x5c6c, 0x75}, + {0x5c6d, 0x75}, + {0x5c6e, 0x75}, + {0x5c6f, 0x75}, + {0x5c70, 0x75}, + {0x5c71, 0x75}, + {0x5c72, 0x75}, + {0x5c73, 0x75}, + {0x5c74, 0x75}, + {0x5c75, 0x75}, + {0x5c76, 0x75}, + {0x5c77, 0x75}, + {0x5c78, 0x75}, + {0x5c79, 0x75}, + {0x5c7a, 0x75}, + {0x5c7b, 0x75}, + {0x5c7c, 0x75}, + {0x5c7d, 0x75}, + {0x5c7e, 0x75}, + {0x5c7f, 0x75}, + {0x5c80, 0x75}, + {0x5c81, 0x75}, + {0x5c82, 0x75}, + {0x5c83, 0x75}, + {0x5c84, 0x75}, + {0x5c85, 0x75}, + {0x5c86, 0x75}, + {0x5c87, 0x75}, + {0x5c88, 0x75}, + {0x5c89, 0x75}, + {0x5c8a, 0x75}, + {0x5c8b, 0x75}, + {0x5c8c, 0x75}, + {0x5c8d, 0x75}, + {0x5c8e, 0x75}, + {0x5c8f, 0x75}, + {0x5c90, 0x75}, + {0x5c91, 0x75}, + {0x5c92, 0x75}, + {0x5c93, 0x75}, + {0x5c94, 0x75}, + {0x5c95, 0x75}, + {0x5c96, 0x75}, + {0x5c97, 0x75}, + {0x5c98, 0x75}, + {0x5c99, 0x75}, + {0x5c9a, 0x75}, + {0x5c9b, 0x75}, + {0x5c9c, 0x75}, + {0x5c9d, 0x75}, + {0x5c9e, 0x75}, + {0x5c9f, 0x75}, + {0x5ca0, 0x75}, + {0x5ca1, 0x75}, + {0x5ca2, 0x75}, + {0x5ca3, 0x75}, + {0x5ca4, 0x75}, + {0x5ca5, 0x75}, + {0x5ca6, 0x75}, + {0x5ca7, 0x75}, + {0x5ca8, 0x75}, + {0x5ca9, 0x75}, + {0x5caa, 0x75}, + {0x5cab, 0x75}, + {0x5cac, 0x75}, + {0x5cad, 0x75}, + {0x5cae, 0x75}, + {0x5caf, 0x75}, + {0x5cb0, 0x75}, + {0x5cb1, 0x75}, + {0x5cb2, 0x75}, + {0x5cb3, 0x75}, + {0x5cb4, 0x75}, + {0x5cb5, 0x75}, + {0x5cb6, 0x75}, + {0x5cb7, 0x75}, + {0x5cb8, 0x75}, + {0x5cb9, 0x75}, + {0x5cba, 0x75}, + {0x5cbb, 0x75}, + {0x5cbc, 0x75}, + {0x5cbd, 0x75}, + {0x5cbe, 0x75}, + {0x5cbf, 0x75}, + {0x5cc0, 0x75}, + {0x5cc1, 0x75}, + {0x5cc2, 0x75}, + {0x5cc3, 0x75}, + {0x5cc4, 0x75}, + {0x5cc5, 0x75}, + {0x5cc6, 0x75}, + {0x5cc7, 0x75}, + {0x5cc8, 0x75}, + {0x5cc9, 0x75}, + {0x5cca, 0x75}, + {0x5ccb, 0x75}, + {0x5ccc, 0x75}, + {0x5ccd, 0x75}, + {0x5cce, 0x75}, + {0x5ccf, 0x75}, + {0x5cd0, 0x75}, + {0x5cd1, 0x75}, + {0x5cd2, 0x75}, + {0x5cd3, 0x75}, + {0x5cd4, 0x75}, + {0x5cd5, 0x75}, + {0x5cd6, 0x75}, + {0x5cd7, 0x75}, + {0x5cd8, 0x75}, + {0x5cd9, 0x75}, + {0x5cda, 0x75}, + {0x5cdb, 0x75}, + {0x5cdc, 0x75}, + {0x5cdd, 0x75}, + {0x5cde, 0x75}, + {0x5cdf, 0x75}, + {0x5ce0, 0x75}, + {0x5ce1, 0x75}, + {0x5ce2, 0x75}, + {0x5ce3, 0x75}, + {0x5ce4, 0x75}, + {0x5ce5, 0x75}, + {0x5ce6, 0x75}, + {0x5ce7, 0x75}, + {0x5ce8, 0x75}, + {0x5ce9, 0x75}, + {0x5cea, 0x75}, + {0x5ceb, 0x75}, + {0x5cec, 0x75}, + {0x5ced, 0x75}, + {0x5cee, 0x75}, + {0x5cef, 0x75}, + {0x5cf0, 0x75}, + {0x5cf1, 0x75}, + {0x5cf2, 0x75}, + {0x5cf3, 0x75}, + {0x5cf4, 0x75}, + {0x5cf5, 0x75}, + {0x5cf6, 0x75}, + {0x5cf7, 0x75}, + {0x5cf8, 0x75}, + {0x5cf9, 0x75}, + {0x5cfa, 0x75}, + {0x5cfb, 0x75}, + {0x5cfc, 0x75}, + {0x5cfd, 0x75}, + {0x5cfe, 0x75}, + {0x5cff, 0x75}, + {0x5d00, 0x75}, + {0x5d01, 0x75}, + {0x5d02, 0x75}, + {0x5d03, 0x75}, + {0x5d04, 0x75}, + {0x5d05, 0x75}, + {0x5d06, 0x75}, + {0x5d07, 0x75}, + {0x5d08, 0x75}, + {0x5d09, 0x75}, + {0x5d0a, 0x75}, + {0x5d0b, 0x75}, + {0x5d0c, 0x75}, + {0x5d0d, 0x75}, + {0x5d0e, 0x75}, + {0x5d0f, 0x75}, + {0x5d10, 0x75}, + {0x5d11, 0x75}, + {0x5d12, 0x75}, + {0x5d13, 0x75}, + {0x5d14, 0x75}, + {0x5d15, 0x75}, + {0x5d16, 0x75}, + {0x5d17, 0x75}, + {0x5d18, 0x75}, + {0x5d19, 0x75}, + {0x5d1a, 0x75}, + {0x5d1b, 0x75}, + {0x5d1c, 0x75}, + {0x5d1d, 0x75}, + {0x5d1e, 0x75}, + {0x5d1f, 0x75}, + {0x5d20, 0x75}, + {0x5d21, 0x75}, + {0x5d22, 0x75}, + {0x5d23, 0x75}, + {0x5d24, 0x75}, + {0x5d25, 0x75}, + {0x5d26, 0x75}, + {0x5d27, 0x75}, + {0x5d28, 0x75}, + {0x5d29, 0x75}, + {0x5d2a, 0x75}, + {0x5d2b, 0x75}, + {0x5d2c, 0x75}, + {0x5d2d, 0x75}, + {0x5d2e, 0x75}, + {0x5d2f, 0x75}, + {0x5d30, 0x75}, + {0x5d31, 0x75}, + {0x5d32, 0x75}, + {0x5d33, 0x75}, + {0x5d34, 0x75}, + {0x5d35, 0x75}, + {0x5d36, 0x75}, + {0x5d37, 0x75}, + {0x5d38, 0x75}, + {0x5d39, 0x75}, + {0x5d3a, 0x75}, + {0x5d3b, 0x75}, + {0x5d3c, 0x75}, + {0x5d3d, 0x75}, + {0x5d3e, 0x75}, + {0x5d3f, 0x75}, + {0x5d40, 0x75}, + {0x5d41, 0x75}, + {0x5d42, 0x75}, + {0x5d43, 0x75}, + {0x5d44, 0x75}, + {0x5d45, 0x75}, + {0x5d46, 0x75}, + {0x5d47, 0x75}, + {0x5d48, 0x75}, + {0x5d49, 0x75}, + {0x5d4a, 0x75}, + {0x5d4b, 0x75}, + {0x5d4c, 0x75}, + {0x5d4d, 0x75}, + {0x5d4e, 0x75}, + {0x5d4f, 0x75}, + {0x5d50, 0x75}, + {0x5d51, 0x75}, + {0x5d52, 0x75}, + {0x5d53, 0x75}, + {0x5d54, 0x75}, + {0x5d55, 0x75}, + {0x5d56, 0x75}, + {0x5d57, 0x75}, + {0x5d58, 0x75}, + {0x5d59, 0x75}, + {0x5d5a, 0x75}, + {0x5d5b, 0x75}, + {0x5d5c, 0x75}, + {0x5d5d, 0x75}, + {0x5d5e, 0x75}, + {0x5d5f, 0x75}, + {0x5d60, 0x75}, + {0x5d61, 0x75}, + {0x5d62, 0x75}, + {0x5d63, 0x75}, + {0x5d64, 0x75}, + {0x5d65, 0x75}, + {0x5d66, 0x75}, + {0x5d67, 0x75}, + {0x5d68, 0x75}, + {0x5d69, 0x75}, + {0x5d6a, 0x75}, + {0x5d6b, 0x75}, + {0x5d6c, 0x75}, + {0x5d6d, 0x75}, + {0x5d6e, 0x75}, + {0x5d6f, 0x75}, + {0x5d70, 0x75}, + {0x5d71, 0x75}, + {0x5d72, 0x75}, + {0x5d73, 0x75}, + {0x5d74, 0x75}, + {0x5d75, 0x75}, + {0x5d76, 0x75}, + {0x5d77, 0x75}, + {0x5d78, 0x75}, + {0x5d79, 0x75}, + {0x5d7a, 0x75}, + {0x5d7b, 0x75}, + {0x5d7c, 0x75}, + {0x5d7d, 0x75}, + {0x5d7e, 0x75}, + {0x5d7f, 0x75}, + {0x5d80, 0x75}, + {0x5d81, 0x75}, + {0x5d82, 0x75}, + {0x5d83, 0x75}, + {0x5d84, 0x75}, + {0x5d85, 0x75}, + {0x5d86, 0x75}, + {0x5d87, 0x75}, + {0x5d88, 0x75}, + {0x5d89, 0x75}, + {0x5d8a, 0x75}, + {0x5d8b, 0x75}, + {0x5d8c, 0x75}, + {0x5d8d, 0x75}, + {0x5d8e, 0x75}, + {0x5d8f, 0x75}, + {0x5d90, 0x75}, + {0x5d91, 0x75}, + {0x5d92, 0x75}, + {0x5d93, 0x75}, + {0x5d94, 0x75}, + {0x5d95, 0x75}, + {0x5d96, 0x75}, + {0x5d97, 0x75}, + {0x5d98, 0x75}, + {0x5d99, 0x75}, + {0x5d9a, 0x75}, + {0x5d9b, 0x75}, + {0x5d9c, 0x75}, + {0x5d9d, 0x75}, + {0x5d9e, 0x75}, + {0x5d9f, 0x75}, + {0x5da0, 0x75}, + {0x5da1, 0x75}, + {0x5da2, 0x75}, + {0x5da3, 0x75}, + {0x5da4, 0x75}, + {0x5da5, 0x75}, + {0x5da6, 0x75}, + {0x5da7, 0x75}, + {0x5da8, 0x75}, + {0x5da9, 0x75}, + {0x5daa, 0x75}, + {0x5dab, 0x75}, + {0x5dac, 0x75}, + {0x5dad, 0x75}, + {0x5dae, 0x75}, + {0x5daf, 0x75}, + {0x5db0, 0x75}, + {0x5db1, 0x75}, + {0x5db2, 0x75}, + {0x5db3, 0x75}, + {0x5db4, 0x75}, + {0x5db5, 0x75}, + {0x5db6, 0x75}, + {0x5db7, 0x75}, + {0x5db8, 0x75}, + {0x5db9, 0x75}, + {0x5dba, 0x75}, + {0x5dbb, 0x75}, + {0x5dbc, 0x75}, + {0x5dbd, 0x75}, + {0x5dbe, 0x75}, + {0x5dbf, 0x75}, + {0x5dc0, 0x75}, + {0x5dc1, 0x75}, + {0x5dc2, 0x75}, + {0x5dc3, 0x75}, + {0x5dc4, 0x75}, + {0x5dc5, 0x75}, + {0x5dc6, 0x75}, + {0x5dc7, 0x75}, + {0x5dc8, 0x75}, + {0x5dc9, 0x75}, + {0x5dca, 0x75}, + {0x5dcb, 0x75}, + {0x5dcc, 0x75}, + {0x5dcd, 0x75}, + {0x5dce, 0x75}, + {0x5dcf, 0x75}, + {0x5dd0, 0x75}, + {0x5dd1, 0x75}, + {0x5dd2, 0x75}, + {0x5dd3, 0x75}, + {0x5dd4, 0x75}, + {0x5dd5, 0x75}, + {0x5dd6, 0x75}, + {0x5dd7, 0x75}, + {0x5dd8, 0x75}, + {0x5dd9, 0x75}, + {0x5dda, 0x75}, + {0x5ddb, 0x75}, + {0x5ddc, 0x75}, + {0x5ddd, 0x75}, + {0x5dde, 0x75}, + {0x5ddf, 0x75}, + {0x5de0, 0x75}, + {0x5de1, 0x75}, + {0x5de2, 0x75}, + {0x5de3, 0x75}, + {0x5de4, 0x75}, + {0x5de5, 0x75}, + {0x5de6, 0x75}, + {0x5de7, 0x75}, + {0x5de8, 0x75}, + {0x5de9, 0x75}, + {0x5dea, 0x75}, + {0x5deb, 0x75}, + {0x5dec, 0x75}, + {0x5ded, 0x75}, + {0x5dee, 0x75}, + {0x5def, 0x75}, + {0x5df0, 0x75}, + {0x5df1, 0x75}, + {0x5df2, 0x75}, + {0x5df3, 0x75}, + {0x5df4, 0x75}, + {0x5df5, 0x75}, + {0x5df6, 0x75}, + {0x5df7, 0x75}, + {0x5df8, 0x75}, + {0x5df9, 0x75}, + {0x5dfa, 0x75}, + {0x5dfb, 0x75}, + {0x5dfc, 0x75}, + {0x5dfd, 0x75}, + {0x5dfe, 0x75}, + {0x5dff, 0x75}, + {0x5e00, 0x75}, + {0x5e01, 0x75}, + {0x5e02, 0x75}, + {0x5e03, 0x75}, + {0x5e04, 0x75}, + {0x5e05, 0x75}, + {0x5e06, 0x75}, + {0x5e07, 0x75}, + {0x5e08, 0x75}, + {0x5e09, 0x75}, + {0x5e0a, 0x75}, + {0x5e0b, 0x75}, + {0x5e0c, 0x75}, + {0x5e0d, 0x75}, + {0x5e0e, 0x75}, + {0x5e0f, 0x75}, + {0x5e10, 0x75}, + {0x5e11, 0x75}, + {0x5e12, 0x75}, + {0x5e13, 0x75}, + {0x5e14, 0x75}, + {0x5e15, 0x75}, + {0x5e16, 0x75}, + {0x5e17, 0x75}, + {0x5e18, 0x75}, + {0x5e19, 0x75}, + {0x5e1a, 0x75}, + {0x5e1b, 0x75}, + {0x5e1c, 0x75}, + {0x5e1d, 0x75}, + {0x5e1e, 0x75}, + {0x5e1f, 0x75}, + {0x5e20, 0x75}, + {0x5e21, 0x75}, + {0x5e22, 0x75}, + {0x5e23, 0x75}, + {0x5e24, 0x75}, + {0x5e25, 0x75}, + {0x5e26, 0x75}, + {0x5e27, 0x75}, + {0x5e28, 0x75}, + {0x5e29, 0x75}, + {0x5e2a, 0x75}, + {0x5e2b, 0x75}, + {0x5e2c, 0x75}, + {0x5e2d, 0x75}, + {0x5e2e, 0x75}, + {0x5e2f, 0x75}, + {0x5e30, 0x75}, + {0x5e31, 0x75}, + {0x5e32, 0x75}, + {0x5e33, 0x75}, + {0x5e34, 0x75}, + {0x5e35, 0x75}, + {0x5e36, 0x75}, + {0x5e37, 0x75}, + {0x5e38, 0x75}, + {0x5e39, 0x75}, + {0x5e3a, 0x75}, + {0x5e3b, 0x75}, + {0x5e3c, 0x75}, + {0x5e3d, 0x75}, + {0x5e3e, 0x75}, + {0x5e3f, 0x75}, + {0x5e40, 0x75}, + {0x5e41, 0x75}, + {0x5e42, 0x75}, + {0x5e43, 0x75}, + {0x5e44, 0x75}, + {0x5e45, 0x75}, + {0x5e46, 0x75}, + {0x5e47, 0x75}, + {0x5e48, 0x75}, + {0x5e49, 0x75}, + {0x5e4a, 0x75}, + {0x5e4b, 0x75}, + {0x5e4c, 0x75}, + {0x5e4d, 0x75}, + {0x5e4e, 0x75}, + {0x5e4f, 0x75}, + {0x5e50, 0x75}, + {0x5e51, 0x75}, + {0x5e52, 0x75}, + {0x5e53, 0x75}, + {0x5e54, 0x75}, + {0x5e55, 0x75}, + {0x5e56, 0x75}, + {0x5e57, 0x75}, + {0x5e58, 0x75}, + {0x5e59, 0x75}, + {0x5e5a, 0x75}, + {0x5e5b, 0x75}, + {0x5e5c, 0x75}, + {0x5e5d, 0x75}, + {0x5e5e, 0x75}, + {0x5e5f, 0x75}, + {0x5e60, 0x75}, + {0x5e61, 0x75}, + {0x5e62, 0x75}, + {0x5e63, 0x75}, + {0x5e64, 0x75}, + {0x5e65, 0x75}, + {0x5e66, 0x75}, + {0x5e67, 0x75}, + {0x5e68, 0x75}, + {0x5e69, 0x75}, + {0x5e6a, 0x75}, + {0x5e6b, 0x75}, + {0x5e6c, 0x75}, + {0x5e6d, 0x75}, + {0x5e6e, 0x75}, + {0x5e6f, 0x75}, + {0x5e70, 0x75}, + {0x5e71, 0x75}, + {0x5e72, 0x75}, + {0x5e73, 0x75}, + {0x5e74, 0x75}, + {0x5e75, 0x75}, + {0x5e76, 0x75}, + {0x5e77, 0x75}, + {0x5e78, 0x75}, + {0x5e79, 0x75}, + {0x5e7a, 0x75}, + {0x5e7b, 0x75}, + {0x5e7c, 0x75}, + {0x5e7d, 0x75}, + {0x5e7e, 0x75}, + {0x5e7f, 0x75}, + {0x5e80, 0x75}, + {0x5e81, 0x75}, + {0x5e82, 0x75}, + {0x5e83, 0x75}, + {0x5e84, 0x75}, + {0x5e85, 0x75}, + {0x5e86, 0x75}, + {0x5e87, 0x75}, + {0x5e88, 0x75}, + {0x5e89, 0x75}, + {0x5e8a, 0x75}, + {0x5e8b, 0x75}, + {0x5e8c, 0x75}, + {0x5e8d, 0x75}, + {0x5e8e, 0x75}, + {0x5e8f, 0x75}, + {0x5e90, 0x75}, + {0x5e91, 0x75}, + {0x5e92, 0x75}, + {0x5e93, 0x75}, + {0x5e94, 0x75}, + {0x5e95, 0x75}, + {0x5e96, 0x75}, + {0x5e97, 0x75}, + {0x5e98, 0x75}, + {0x5e99, 0x75}, + {0x5e9a, 0x75}, + {0x5e9b, 0x75}, + {0x5e9c, 0x75}, + {0x5e9d, 0x75}, + {0x5e9e, 0x75}, + {0x5e9f, 0x75}, + {0x5ea0, 0x75}, + {0x5ea1, 0x75}, + {0x5ea2, 0x75}, + {0x5ea3, 0x75}, + {0x5ea4, 0x75}, + {0x5ea5, 0x75}, + {0x5ea6, 0x75}, + {0x5ea7, 0x75}, + {0x5ea8, 0x75}, + {0x5ea9, 0x75}, + {0x5eaa, 0x75}, + {0x5eab, 0x75}, + {0x5eac, 0x75}, + {0x5ead, 0x75}, + {0x5eae, 0x75}, + {0x5eaf, 0x75}, + {0x5eb0, 0x75}, + {0x5eb1, 0x75}, + {0x5eb2, 0x75}, + {0x5eb3, 0x75}, + {0x5eb4, 0x75}, + {0x5eb5, 0x75}, + {0x5eb6, 0x75}, + {0x5eb7, 0x75}, + {0x5eb8, 0x75}, + {0x5eb9, 0x75}, + {0x5eba, 0x75}, + {0x5ebb, 0x75}, + {0x5ebc, 0x75}, + {0x5ebd, 0x75}, + {0x5ebe, 0x75}, + {0x5ebf, 0x75}, + {0x5ec0, 0x75}, + {0x5ec1, 0x75}, + {0x5ec2, 0x75}, + {0x5ec3, 0x75}, + {0x5ec4, 0x75}, + {0x5ec5, 0x75}, + {0x5ec6, 0x75}, + {0x5ec7, 0x75}, + {0x5ec8, 0x75}, + {0x5ec9, 0x75}, + {0x5eca, 0x75}, + {0x5ecb, 0x75}, + {0x5ecc, 0x75}, + {0x5ecd, 0x75}, + {0x5ece, 0x75}, + {0x5ecf, 0x75}, + {0x5ed0, 0x75}, + {0x5ed1, 0x75}, + {0x5ed2, 0x75}, + {0x5ed3, 0x75}, + {0x5ed4, 0x75}, + {0x5ed5, 0x75}, + {0x5ed6, 0x75}, + {0x5ed7, 0x75}, + {0x5ed8, 0x75}, + {0x5ed9, 0x75}, + {0x5eda, 0x75}, + {0x5edb, 0x75}, + {0x5edc, 0x75}, + {0x5edd, 0x75}, + {0x5ede, 0x75}, + {0x5edf, 0x75}, + {0x5ee0, 0x75}, + {0x5ee1, 0x75}, + {0x5ee2, 0x75}, + {0x5ee3, 0x75}, + {0x5ee4, 0x75}, + {0x5ee5, 0x75}, + {0x5ee6, 0x75}, + {0x5ee7, 0x75}, + {0x5ee8, 0x75}, + {0x5ee9, 0x75}, + {0x5eea, 0x75}, + {0x5eeb, 0x75}, + {0x5eec, 0x75}, + {0x5eed, 0x75}, + {0x5eee, 0x75}, + {0x5eef, 0x75}, + {0x5ef0, 0x75}, + {0x5ef1, 0x75}, + {0x5ef2, 0x75}, + {0x5ef3, 0x75}, + {0x5ef4, 0x75}, + {0x5ef5, 0x75}, + {0x5ef6, 0x75}, + {0x5ef7, 0x75}, + {0x5ef8, 0x75}, + {0x5ef9, 0x75}, + {0x5efa, 0x75}, + {0x5efb, 0x75}, + {0x5efc, 0x75}, + {0x5efd, 0x75}, + {0x5efe, 0x75}, + {0x5eff, 0x75}, + {0x5f00, 0x75}, + {0x5f01, 0x75}, + {0x5f02, 0x75}, + {0x5f03, 0x75}, + {0x5f04, 0x75}, + {0x5f05, 0x75}, + {0x5f06, 0x75}, + {0x5f07, 0x75}, + {0x5f08, 0x75}, + {0x5f09, 0x75}, + {0x5f0a, 0x75}, + {0x5f0b, 0x75}, + {0x5f0c, 0x75}, + {0x5f0d, 0x75}, + {0x5f0e, 0x75}, + {0x5f0f, 0x75}, + {0x5f10, 0x75}, + {0x5f11, 0x75}, + {0x5f12, 0x75}, + {0x5f13, 0x75}, + {0x5f14, 0x75}, + {0x5f15, 0x75}, + {0x5f16, 0x75}, + {0x5f17, 0x75}, + {0x5f18, 0x75}, + {0x5f19, 0x75}, + {0x5f1a, 0x75}, + {0x5f1b, 0x75}, + {0x5f1c, 0x75}, + {0x5f1d, 0x75}, + {0x5f1e, 0x75}, + {0x5f1f, 0x75}, +}; + +static const struct ov08x40_reg mode_1928x1208_regs[] = { + {0x5000, 0x55}, + {0x5001, 0x00}, + {0x5008, 0xb0}, + {0x50c1, 0x00}, + {0x53c1, 0x00}, + {0x5f40, 0x00}, + {0x5f41, 0x40}, + {0x0300, 0x3a}, + {0x0301, 0xc8}, + {0x0302, 0x31}, + {0x0303, 0x03}, + {0x0304, 0x01}, + {0x0305, 0xa1}, + {0x0306, 0x04}, + {0x0307, 0x01}, + {0x0308, 0x03}, + {0x0309, 0x03}, + {0x0310, 0x0a}, + {0x0311, 0x02}, + {0x0312, 0x01}, + {0x0313, 0x08}, + {0x0314, 0x66}, + {0x0315, 0x00}, + {0x0316, 0x34}, + {0x0320, 0x02}, + {0x0321, 0x03}, + {0x0323, 0x05}, + {0x0324, 0x01}, + {0x0325, 0xb8}, + {0x0326, 0x4a}, + {0x0327, 0x04}, + {0x0329, 0x00}, + {0x032a, 0x05}, + {0x032b, 0x00}, + {0x032c, 0x00}, + {0x032d, 0x00}, + {0x032e, 0x02}, + {0x032f, 0xa0}, + {0x0350, 0x00}, + {0x0360, 0x01}, + {0x1216, 0x60}, + {0x1217, 0x5b}, + {0x1218, 0x00}, + {0x1220, 0x24}, + {0x198a, 0x00}, + {0x198b, 0x01}, + {0x198e, 0x00}, + {0x198f, 0x01}, + {0x3009, 0x04}, + {0x3012, 0x41}, + {0x3015, 0x00}, + {0x3016, 0xb0}, + {0x3017, 0xf0}, + {0x3018, 0xf0}, + {0x3019, 0xd2}, + {0x301a, 0xb0}, + {0x301c, 0x81}, + {0x301d, 0x02}, + {0x301e, 0x80}, + {0x3022, 0xf0}, + {0x3025, 0x89}, + {0x3030, 0x03}, + {0x3044, 0xc2}, + {0x3050, 0x35}, + {0x3051, 0x60}, + {0x3052, 0x25}, + {0x3053, 0x00}, + {0x3054, 0x00}, + {0x3055, 0x02}, + {0x3056, 0x80}, + {0x3057, 0x80}, + {0x3058, 0x80}, + {0x3059, 0x00}, + {0x3107, 0x86}, + {0x3400, 0x1c}, + {0x3401, 0x80}, + {0x3402, 0x8c}, + {0x3419, 0x08}, + {0x341a, 0xaf}, + {0x341b, 0x30}, + {0x3420, 0x00}, + {0x3421, 0x00}, + {0x3422, 0x00}, + {0x3423, 0x00}, + {0x3424, 0x00}, + {0x3425, 0x00}, + {0x3426, 0x00}, + {0x3427, 0x00}, + {0x3428, 0x0f}, + {0x3429, 0x00}, + {0x342a, 0x00}, + {0x342b, 0x00}, + {0x342c, 0x00}, + {0x342d, 0x00}, + {0x342e, 0x00}, + {0x342f, 0x11}, + {0x3430, 0x11}, + {0x3431, 0x10}, + {0x3432, 0x00}, + {0x3433, 0x00}, + {0x3434, 0x00}, + {0x3435, 0x00}, + {0x3436, 0x00}, + {0x3437, 0x00}, + {0x3442, 0x02}, + {0x3443, 0x02}, + {0x3444, 0x07}, + {0x3450, 0x00}, + {0x3451, 0x00}, + {0x3452, 0x18}, + {0x3453, 0x18}, + {0x3454, 0x00}, + {0x3455, 0x80}, + {0x3456, 0x08}, + {0x3500, 0x00}, + {0x3501, 0x02}, + {0x3502, 0x00}, + {0x3504, 0x4c}, + {0x3506, 0x30}, + {0x3507, 0x00}, + {0x3508, 0x01}, + {0x3509, 0x00}, + {0x350a, 0x01}, + {0x350b, 0x00}, + {0x350c, 0x00}, + {0x3540, 0x00}, + {0x3541, 0x01}, + {0x3542, 0x00}, + {0x3544, 0x4c}, + {0x3546, 0x30}, + {0x3547, 0x00}, + {0x3548, 0x01}, + {0x3549, 0x00}, + {0x354a, 0x01}, + {0x354b, 0x00}, + {0x354c, 0x00}, + {0x3688, 0x02}, + {0x368a, 0x2e}, + {0x368e, 0x71}, + {0x3696, 0xd1}, + {0x3699, 0x00}, + {0x369a, 0x00}, + {0x36a4, 0x00}, + {0x36a6, 0x00}, + {0x3711, 0x00}, + {0x3712, 0x50}, + {0x3713, 0x00}, + {0x3714, 0x21}, + {0x3716, 0x00}, + {0x3718, 0x07}, + {0x371a, 0x1c}, + {0x371b, 0x00}, + {0x3720, 0x08}, + {0x3725, 0x32}, + {0x3727, 0x05}, + {0x3760, 0x02}, + {0x3761, 0x28}, + {0x3762, 0x02}, + {0x3763, 0x02}, + {0x3764, 0x02}, + {0x3765, 0x2c}, + {0x3766, 0x04}, + {0x3767, 0x2c}, + {0x3768, 0x02}, + {0x3769, 0x00}, + {0x376b, 0x20}, + {0x376e, 0x07}, + {0x37b0, 0x01}, + {0x37b1, 0x0f}, + {0x37b2, 0x01}, + {0x37b3, 0xd6}, + {0x37b4, 0x01}, + {0x37b5, 0x48}, + {0x37b6, 0x02}, + {0x37b7, 0x40}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0f}, + {0x3805, 0x1f}, + {0x3806, 0x09}, + {0x3807, 0x7f}, + {0x3808, 0x07}, + {0x3809, 0x88}, + {0x380a, 0x04}, + {0x380b, 0xb8}, + {0x380c, 0x02}, + {0x380d, 0xd0}, + {0x380e, 0x11}, + {0x380f, 0x5c}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3812, 0x00}, + {0x3813, 0x03}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3820, 0x02}, + {0x3821, 0x14}, + {0x3822, 0x00}, + {0x3823, 0x04}, + {0x3828, 0x0f}, + {0x382a, 0x80}, + {0x382e, 0x41}, + {0x3837, 0x08}, + {0x383a, 0x81}, + {0x383b, 0x81}, + {0x383c, 0x11}, + {0x383d, 0x11}, + {0x383e, 0x00}, + {0x383f, 0x38}, + {0x3840, 0x00}, + {0x3847, 0x00}, + {0x384a, 0x00}, + {0x384c, 0x02}, + {0x384d, 0xd0}, + {0x3856, 0x50}, + {0x3857, 0x30}, + {0x3858, 0x80}, + {0x3859, 0x40}, + {0x3860, 0x00}, + {0x3888, 0x00}, + {0x3889, 0x00}, + {0x388a, 0x00}, + {0x388b, 0x00}, + {0x388c, 0x00}, + {0x388d, 0x00}, + {0x388e, 0x00}, + {0x388f, 0x00}, + {0x3894, 0x00}, + {0x3895, 0x00}, + {0x3c84, 0x00}, + {0x3d85, 0x8b}, + {0x3daa, 0x80}, + {0x3dab, 0x14}, + {0x3dac, 0x80}, + {0x3dad, 0xc8}, + {0x3dae, 0x81}, + {0x3daf, 0x7b}, + {0x3f00, 0x10}, + {0x3f01, 0x11}, + {0x3f06, 0x0d}, + {0x3f07, 0x0b}, + {0x3f08, 0x0d}, + {0x3f09, 0x0b}, + {0x3f0a, 0x01}, + {0x3f0b, 0x11}, + {0x3f0c, 0x33}, + {0x4001, 0x07}, + {0x4007, 0x20}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x400a, 0x00}, + {0x400b, 0x04}, + {0x400c, 0x00}, + {0x400d, 0x04}, + {0x400e, 0x14}, + {0x4010, 0xf4}, + {0x4011, 0x03}, + {0x4012, 0x55}, + {0x4015, 0x00}, + {0x4016, 0x27}, + {0x4017, 0x00}, + {0x4018, 0x0f}, + {0x401b, 0x08}, + {0x401c, 0x00}, + {0x401d, 0x10}, + {0x401e, 0x02}, + {0x401f, 0x00}, + {0x4050, 0x06}, + {0x4051, 0xff}, + {0x4052, 0xff}, + {0x4053, 0xff}, + {0x4054, 0xff}, + {0x4055, 0xff}, + {0x4056, 0xff}, + {0x4057, 0x7f}, + {0x4058, 0x00}, + {0x4059, 0x00}, + {0x405a, 0x00}, + {0x405b, 0x00}, + {0x405c, 0x07}, + {0x405d, 0xff}, + {0x405e, 0x07}, + {0x405f, 0xff}, + {0x4080, 0x78}, + {0x4081, 0x78}, + {0x4082, 0x78}, + {0x4083, 0x78}, + {0x4019, 0x00}, + {0x401a, 0x40}, + {0x4020, 0x04}, + {0x4021, 0x00}, + {0x4022, 0x04}, + {0x4023, 0x00}, + {0x4024, 0x04}, + {0x4025, 0x00}, + {0x4026, 0x04}, + {0x4027, 0x00}, + {0x4030, 0x00}, + {0x4031, 0x00}, + {0x4032, 0x00}, + {0x4033, 0x00}, + {0x4034, 0x00}, + {0x4035, 0x00}, + {0x4036, 0x00}, + {0x4037, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x80}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4060, 0x00}, + {0x4061, 0x00}, + {0x4062, 0x00}, + {0x4063, 0x00}, + {0x4064, 0x00}, + {0x4065, 0x00}, + {0x4066, 0x00}, + {0x4067, 0x00}, + {0x4068, 0x00}, + {0x4069, 0x00}, + {0x406a, 0x00}, + {0x406b, 0x00}, + {0x406c, 0x00}, + {0x406d, 0x00}, + {0x406e, 0x00}, + {0x406f, 0x00}, + {0x4070, 0x00}, + {0x4071, 0x00}, + {0x4072, 0x00}, + {0x4073, 0x00}, + {0x4074, 0x00}, + {0x4075, 0x00}, + {0x4076, 0x00}, + {0x4077, 0x00}, + {0x4078, 0x00}, + {0x4079, 0x00}, + {0x407a, 0x00}, + {0x407b, 0x00}, + {0x407c, 0x00}, + {0x407d, 0x00}, + {0x407e, 0x00}, + {0x407f, 0x00}, + {0x40e0, 0x00}, + {0x40e1, 0x00}, + {0x40e2, 0x00}, + {0x40e3, 0x00}, + {0x40e4, 0x00}, + {0x40e5, 0x00}, + {0x40e6, 0x00}, + {0x40e7, 0x00}, + {0x40e8, 0x00}, + {0x40e9, 0x80}, + {0x40ea, 0x00}, + {0x40eb, 0x80}, + {0x40ec, 0x00}, + {0x40ed, 0x80}, + {0x40ee, 0x00}, + {0x40ef, 0x80}, + {0x40f0, 0x02}, + {0x40f1, 0x04}, + {0x4300, 0x00}, + {0x4301, 0x00}, + {0x4302, 0x00}, + {0x4303, 0x00}, + {0x4304, 0x00}, + {0x4305, 0x00}, + {0x4306, 0x00}, + {0x4307, 0x00}, + {0x4308, 0x00}, + {0x4309, 0x00}, + {0x430a, 0x00}, + {0x430b, 0xff}, + {0x430c, 0xff}, + {0x430d, 0x00}, + {0x430e, 0x00}, + {0x4315, 0x00}, + {0x4316, 0x00}, + {0x4317, 0x00}, + {0x4318, 0x00}, + {0x4319, 0x00}, + {0x431a, 0x00}, + {0x431b, 0x00}, + {0x431c, 0x00}, + {0x4500, 0x07}, + {0x4501, 0x10}, + {0x4502, 0x00}, + {0x4503, 0x0f}, + {0x4504, 0x80}, + {0x4506, 0x01}, + {0x4509, 0x05}, + {0x450c, 0x00}, + {0x450d, 0x20}, + {0x450e, 0x00}, + {0x450f, 0x00}, + {0x4510, 0x00}, + {0x4523, 0x00}, + {0x4526, 0x00}, + {0x4542, 0x00}, + {0x4543, 0x00}, + {0x4544, 0x00}, + {0x4545, 0x00}, + {0x4546, 0x00}, + {0x4547, 0x10}, + {0x4602, 0x00}, + {0x4603, 0x15}, + {0x460b, 0x07}, + {0x4680, 0x11}, + {0x4686, 0x00}, + {0x4687, 0x00}, + {0x4700, 0x00}, + {0x4800, 0x64}, + {0x4806, 0x40}, + {0x480b, 0x10}, + {0x480c, 0x80}, + {0x480f, 0x32}, + {0x4813, 0xe4}, + {0x4837, 0x14}, + {0x4850, 0x42}, + {0x4884, 0x04}, + {0x4c00, 0xf8}, + {0x4c01, 0x44}, + {0x4c03, 0x00}, + {0x4d00, 0x00}, + {0x4d01, 0x16}, + {0x4d04, 0x10}, + {0x4d05, 0x00}, + {0x4d06, 0x0c}, + {0x4d07, 0x00}, + {0x3d84, 0x04}, + {0x3680, 0xa4}, + {0x3682, 0x80}, + {0x3601, 0x40}, + {0x3602, 0x90}, + {0x3608, 0x0a}, + {0x3938, 0x09}, + {0x3a74, 0x84}, + {0x3a99, 0x84}, + {0x3ab9, 0xa6}, + {0x3aba, 0xba}, + {0x3b12, 0x84}, + {0x3b14, 0xbb}, + {0x3b15, 0xbf}, + {0x3a29, 0x26}, + {0x3a1f, 0x8a}, + {0x3a22, 0x91}, + {0x3a25, 0x96}, + {0x3a28, 0xb4}, + {0x3a2b, 0xba}, + {0x3a2e, 0xbf}, + {0x3a31, 0xc1}, + {0x3a20, 0x05}, + {0x3939, 0x6b}, + {0x3902, 0x10}, + {0x3903, 0x10}, + {0x3904, 0x10}, + {0x3905, 0x10}, + {0x3906, 0x01}, + {0x3907, 0x0b}, + {0x3908, 0x10}, + {0x3909, 0x13}, + {0x360f, 0x99}, + {0x390b, 0x11}, + {0x390c, 0x21}, + {0x390d, 0x32}, + {0x390e, 0x76}, + {0x3911, 0x90}, + {0x3913, 0x90}, + {0x3b3f, 0x9d}, + {0x3b45, 0x9d}, + {0x3b1b, 0xc9}, + {0x3b21, 0xc9}, + {0x3a1a, 0x1c}, + {0x3a23, 0x15}, + {0x3a26, 0x17}, + {0x3a2c, 0x50}, + {0x3a2f, 0x18}, + {0x3a32, 0x4f}, + {0x3ace, 0x01}, + {0x3ad2, 0x01}, + {0x3ad6, 0x01}, + {0x3ada, 0x01}, + {0x3ade, 0x01}, + {0x3ae2, 0x01}, + {0x3aee, 0x01}, + {0x3af2, 0x01}, + {0x3af6, 0x01}, + {0x3afa, 0x01}, + {0x3afe, 0x01}, + {0x3b02, 0x01}, + {0x3b06, 0x01}, + {0x3b0a, 0x01}, + {0x3b0b, 0x00}, + {0x3b0e, 0x01}, + {0x3b0f, 0x00}, + {0x392c, 0x02}, + {0x392d, 0x01}, + {0x392e, 0x04}, + {0x392f, 0x03}, + {0x3930, 0x09}, + {0x3931, 0x07}, + {0x3932, 0x10}, + {0x3933, 0x0d}, + {0x3609, 0x08}, + {0x3921, 0x0f}, + {0x3928, 0x15}, + {0x3929, 0x2a}, + {0x392a, 0x52}, + {0x392b, 0xa3}, + {0x340b, 0x1b}, + {0x3426, 0x10}, + {0x3407, 0x01}, + {0x3404, 0x01}, + {0x3500, 0x00}, + {0x3501, 0x08}, + {0x3502, 0x10}, + {0x3508, 0x04}, + {0x3509, 0x00}, +}; + +static const char * const ov08x40_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Configurations for supported link frequencies */ +#define OV08X40_LINK_FREQ_400MHZ 400000000ULL + +#define OV08X40_EXT_CLK 19200000 +#define OV08X40_DATA_LANES 4 + +/* + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample + * data rate => double data rate; number of lanes => 4; bits per pixel => 10 + */ +static u64 link_freq_to_pixel_rate(u64 f) +{ + f *= 2 * OV08X40_DATA_LANES; + do_div(f, 10); + + return f; +} + +/* Menu items for LINK_FREQ V4L2 control */ +static const s64 link_freq_menu_items[] = { + OV08X40_LINK_FREQ_400MHZ, +}; + +/* Link frequency configs */ +static const struct ov08x40_link_freq_config link_freq_configs[] = { + [OV08X40_LINK_FREQ_400MHZ_INDEX] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_800mbps), + .regs = mipi_data_rate_800mbps, + } + }, +}; + +/* Mode configs */ +static const struct ov08x40_mode supported_modes[] = { + { + .width = 3856, + .height = 2416, + .vts_def = OV08X40_VTS_30FPS, + .vts_min = OV08X40_VTS_30FPS, + .lanes = 4, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3856x2416_regs), + .regs = mode_3856x2416_regs, + }, + .link_freq_index = OV08X40_LINK_FREQ_400MHZ_INDEX, + }, + { + .width = 1928, + .height = 1208, + .vts_def = OV08X40_VTS_BIN_30FPS, + .vts_min = OV08X40_VTS_BIN_30FPS, + .lanes = 4, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1928x1208_regs), + .regs = mode_1928x1208_regs, + }, + .link_freq_index = OV08X40_LINK_FREQ_400MHZ_INDEX, + }, +}; + +struct ov08x40 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov08x40_mode *cur_mode; + + /* Mutex for serialized access */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +#define to_ov08x40(_sd) container_of(_sd, struct ov08x40, sd) + +/* Read registers up to 4 at a time */ +static int ov08x40_read_reg(struct ov08x40 *ov08x, + u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + struct i2c_msg msgs[2]; + u8 *data_be_p; + int ret; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + + if (len > 4) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int ov08x40_write_reg(struct ov08x40 *ov08x, + u16 reg, u32 len, u32 __val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int buf_i, val_i; + u8 buf[6], *val_p; + __be32 val; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val = cpu_to_be32(__val); + val_p = (u8 *)&val; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int ov08x40_write_regs(struct ov08x40 *ov08x, + const struct ov08x40_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int ret; + u32 i; + + for (i = 0; i < len; i++) { + ret = ov08x40_write_reg(ov08x, regs[i].address, 1, + regs[i].val); + + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +static int ov08x40_write_reg_list(struct ov08x40 *ov08x, + const struct ov08x40_reg_list *r_list) +{ + return ov08x40_write_regs(ov08x, r_list->regs, r_list->num_of_regs); +} + +static int ov08x40_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + const struct ov08x40_mode *default_mode = &supported_modes[0]; + struct ov08x40 *ov08x = to_ov08x40(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->state, 0); + + mutex_lock(&ov08x->mutex); + + /* Initialize try_fmt */ + try_fmt->width = default_mode->width; + try_fmt->height = default_mode->height; + try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + /* No crop or compose */ + mutex_unlock(&ov08x->mutex); + + return 0; +} + +static int ov08x40_update_digital_gain(struct ov08x40 *ov08x, u32 d_gain) +{ + int ret; + u32 val; + + /* + * 0x350C[1:0], 0x350B[7:0], 0x350A[4:0] + */ + + val = (d_gain & OV08X40_DGTL_GAIN_L_MASK) << OV08X40_DGTL_GAIN_L_SHIFT; + ret = ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_L, + OV08X40_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = (d_gain >> OV08X40_DGTL_GAIN_M_SHIFT) & OV08X40_DGTL_GAIN_M_MASK; + ret = ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_M, + OV08X40_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = (d_gain >> OV08X40_DGTL_GAIN_H_SHIFT) & OV08X40_DGTL_GAIN_H_MASK; + + return ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_H, + OV08X40_REG_VALUE_08BIT, val); +} + +static int ov08x40_enable_test_pattern(struct ov08x40 *ov08x, u32 pattern) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + if (pattern) { + ret = ov08x40_read_reg(ov08x, OV08X40_REG_ISP, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov08x40_write_reg(ov08x, OV08X40_REG_ISP, + OV08X40_REG_VALUE_08BIT, + val | BIT(1)); + if (ret) + return ret; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_SHORT_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov08x40_write_reg(ov08x, OV08X40_REG_SHORT_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, + val | BIT(0)); + if (ret) + return ret; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + val &= OV08X40_TEST_PATTERN_MASK; + val |= ((pattern - 1) << OV08X40_TEST_PATTERN_BAR_SHIFT) | + OV08X40_TEST_PATTERN_ENABLE; + } else { + val &= ~OV08X40_TEST_PATTERN_ENABLE; + } + + return ov08x40_write_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, val); +} + +static int ov08x40_set_ctrl_hflip(struct ov08x40 *ov08x, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_MIRROR, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_MIRROR, + OV08X40_REG_VALUE_08BIT, + ctrl_val ? val | BIT(2) : val & ~BIT(2)); +} + +static int ov08x40_set_ctrl_vflip(struct ov08x40 *ov08x, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_VFLIP, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_VFLIP, + OV08X40_REG_VALUE_08BIT, + ctrl_val ? val | BIT(2) : val & ~BIT(2)); +} + +static int ov08x40_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov08x40 *ov08x = container_of(ctrl->handler, + struct ov08x40, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + s64 max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov08x->cur_mode->height + ctrl->val - OV08X40_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(ov08x->exposure, + ov08x->exposure->minimum, + max, ov08x->exposure->step, max); + break; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_ANALOG_GAIN, + OV08X40_REG_VALUE_16BIT, + ctrl->val << 1); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = ov08x40_update_digital_gain(ov08x, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_EXPOSURE, + OV08X40_REG_VALUE_24BIT, + ctrl->val); + break; + case V4L2_CID_VBLANK: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_VTS, + OV08X40_REG_VALUE_16BIT, + ov08x->cur_mode->height + + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov08x40_enable_test_pattern(ov08x, ctrl->val); + break; + case V4L2_CID_HFLIP: + ov08x40_set_ctrl_hflip(ov08x, ctrl->val); + break; + case V4L2_CID_VFLIP: + ov08x40_set_ctrl_vflip(ov08x, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov08x40_ctrl_ops = { + .s_ctrl = ov08x40_set_ctrl, +}; + +static int ov08x40_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order(GRBG) is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov08x40_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void ov08x40_update_pad_format(const struct ov08x40_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int ov08x40_do_get_pad_format(struct ov08x40 *ov08x, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_subdev *sd = &ov08x->sd; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + fmt->format = *framefmt; + } else { + ov08x40_update_pad_format(ov08x->cur_mode, fmt); + } + + return 0; +} + +static int ov08x40_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + int ret; + + mutex_lock(&ov08x->mutex); + ret = ov08x40_do_get_pad_format(ov08x, sd_state, fmt); + mutex_unlock(&ov08x->mutex); + + return ret; +} + +static int +ov08x40_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + const struct ov08x40_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + s64 pixel_rate; + s64 link_freq; + + mutex_lock(&ov08x->mutex); + + /* Only one raw bayer(GRBG) order is supported */ + if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10) + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + ov08x40_update_pad_format(mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + *framefmt = fmt->format; + } else { + ov08x->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov08x->link_freq, mode->link_freq_index); + link_freq = link_freq_menu_items[mode->link_freq_index]; + pixel_rate = link_freq_to_pixel_rate(link_freq); + __v4l2_ctrl_s_ctrl_int64(ov08x->pixel_rate, pixel_rate); + + /* Update limits and set FPS to default */ + vblank_def = ov08x->cur_mode->vts_def - + ov08x->cur_mode->height; + vblank_min = ov08x->cur_mode->vts_min - + ov08x->cur_mode->height; + __v4l2_ctrl_modify_range(ov08x->vblank, vblank_min, + OV08X40_VTS_MAX + - ov08x->cur_mode->height, + 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov08x->vblank, vblank_def); + h_blank = + link_freq_configs[mode->link_freq_index].pixels_per_line + - ov08x->cur_mode->width; + __v4l2_ctrl_modify_range(ov08x->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&ov08x->mutex); + + return 0; +} + +static int ov08x40_start_streaming(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + const struct ov08x40_reg_list *reg_list; + int ret, link_freq_index; + + /* Get out of from software reset */ + ret = ov08x40_write_reg(ov08x, OV08X40_REG_SOFTWARE_RST, + OV08X40_REG_VALUE_08BIT, OV08X40_SOFTWARE_RST); + if (ret) { + dev_err(&client->dev, "%s failed to set powerup registers\n", + __func__); + return ret; + } + + link_freq_index = ov08x->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + + ret = ov08x40_write_reg_list(ov08x, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &ov08x->cur_mode->reg_list; + ret = ov08x40_write_reg_list(ov08x, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(ov08x->sd.ctrl_handler); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_MODE_SELECT, + OV08X40_REG_VALUE_08BIT, + OV08X40_MODE_STREAMING); +} + +/* Stop streaming */ +static int ov08x40_stop_streaming(struct ov08x40 *ov08x) +{ + return ov08x40_write_reg(ov08x, OV08X40_REG_MODE_SELECT, + OV08X40_REG_VALUE_08BIT, OV08X40_MODE_STANDBY); +} + +static int ov08x40_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&ov08x->mutex); + if (ov08x->streaming == enable) { + mutex_unlock(&ov08x->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto err_unlock; + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = ov08x40_start_streaming(ov08x); + if (ret) + goto err_rpm_put; + } else { + ov08x40_stop_streaming(ov08x); + pm_runtime_put(&client->dev); + } + + ov08x->streaming = enable; + mutex_unlock(&ov08x->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&ov08x->mutex); + + return ret; +} + +static int __maybe_unused ov08x40_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov08x40 *ov08x = to_ov08x40(sd); + + if (ov08x->streaming) + ov08x40_stop_streaming(ov08x); + + return 0; +} + +static int __maybe_unused ov08x40_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov08x40 *ov08x = to_ov08x40(sd); + int ret; + + if (ov08x->streaming) { + ret = ov08x40_start_streaming(ov08x); + if (ret) + goto error; + } + + return 0; + +error: + ov08x40_stop_streaming(ov08x); + ov08x->streaming = false; + return ret; +} + +/* Verify chip ID */ +static int ov08x40_identify_module(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_CHIP_ID, + OV08X40_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV08X40_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + OV08X40_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops ov08x40_video_ops = { + .s_stream = ov08x40_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov08x40_pad_ops = { + .enum_mbus_code = ov08x40_enum_mbus_code, + .get_fmt = ov08x40_get_pad_format, + .set_fmt = ov08x40_set_pad_format, + .enum_frame_size = ov08x40_enum_frame_size, +}; + +static const struct v4l2_subdev_ops ov08x40_subdev_ops = { + .video = &ov08x40_video_ops, + .pad = &ov08x40_pad_ops, +}; + +static const struct media_entity_operations ov08x40_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov08x40_internal_ops = { + .open = ov08x40_open, +}; + +static int ov08x40_init_controls(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max; + s64 vblank_def; + s64 vblank_min; + s64 hblank; + s64 pixel_rate_min; + s64 pixel_rate_max; + const struct ov08x40_mode *mode; + u32 max; + int ret; + + ctrl_hdlr = &ov08x->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + mutex_init(&ov08x->mutex); + ctrl_hdlr->lock = &ov08x->mutex; + max = ARRAY_SIZE(link_freq_menu_items) - 1; + ov08x->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &ov08x40_ctrl_ops, + V4L2_CID_LINK_FREQ, + max, + 0, + link_freq_menu_items); + if (ov08x->link_freq) + ov08x->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]); + pixel_rate_min = 0; + /* By default, PIXEL_RATE is read only */ + ov08x->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate_min, pixel_rate_max, + 1, pixel_rate_max); + + mode = ov08x->cur_mode; + vblank_def = mode->vts_def - mode->height; + vblank_min = mode->vts_min - mode->height; + ov08x->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_VBLANK, + vblank_min, + OV08X40_VTS_MAX - mode->height, 1, + vblank_def); + + hblank = link_freq_configs[mode->link_freq_index].pixels_per_line - + mode->width; + ov08x->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_HBLANK, + hblank, hblank, 1, hblank); + if (ov08x->hblank) + ov08x->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + exposure_max = mode->vts_def - OV08X40_EXPOSURE_MAX_MARGIN; + ov08x->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_EXPOSURE, + OV08X40_EXPOSURE_MIN, + exposure_max, OV08X40_EXPOSURE_STEP, + exposure_max); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV08X40_ANA_GAIN_MIN, OV08X40_ANA_GAIN_MAX, + OV08X40_ANA_GAIN_STEP, OV08X40_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV08X40_DGTL_GAIN_MIN, OV08X40_DGTL_GAIN_MAX, + OV08X40_DGTL_GAIN_STEP, OV08X40_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov08x40_test_pattern_menu) - 1, + 0, 0, ov08x40_test_pattern_menu); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov08x40_ctrl_ops, + &props); + if (ret) + goto error; + + ov08x->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&ov08x->mutex); + + return ret; +} + +static void ov08x40_free_controls(struct ov08x40 *ov08x) +{ + v4l2_ctrl_handler_free(ov08x->sd.ctrl_handler); + mutex_destroy(&ov08x->mutex); +} + +static int ov08x40_check_hwcfg(struct device *dev) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + unsigned int i, j; + int ret; + u32 ext_clk; + + if (!fwnode) + return -ENXIO; + + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &ext_clk); + if (ret) { + dev_err(dev, "can't get clock frequency"); + return ret; + } + + if (ext_clk != OV08X40_EXT_CLK) { + dev_err(dev, "external clock %d is not supported", + ext_clk); + return -EINVAL; + } + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV08X40_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto out_err; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto out_err; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto out_err; + } + } + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov08x40_probe(struct i2c_client *client) +{ + struct ov08x40 *ov08x; + int ret; + + /* Check HW config */ + ret = ov08x40_check_hwcfg(&client->dev); + if (ret) { + dev_err(&client->dev, "failed to check hwcfg: %d", ret); + return ret; + } + + ov08x = devm_kzalloc(&client->dev, sizeof(*ov08x), GFP_KERNEL); + if (!ov08x) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov08x->sd, client, &ov08x40_subdev_ops); + + /* Check module identity */ + ret = ov08x40_identify_module(ov08x); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + return ret; + } + + /* Set default mode to max resolution */ + ov08x->cur_mode = &supported_modes[0]; + + ret = ov08x40_init_controls(ov08x); + if (ret) + return ret; + + /* Initialize subdev */ + ov08x->sd.internal_ops = &ov08x40_internal_ops; + ov08x->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov08x->sd.entity.ops = &ov08x40_subdev_entity_ops; + ov08x->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + ov08x->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov08x->sd.entity, 1, &ov08x->pad); + if (ret) { + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&ov08x->sd); + if (ret < 0) + goto error_media_entity; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&ov08x->sd.entity); + +error_handler_free: + ov08x40_free_controls(ov08x); + + return ret; +} + +static void ov08x40_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov08x40 *ov08x = to_ov08x40(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + ov08x40_free_controls(ov08x); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct dev_pm_ops ov08x40_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov08x40_suspend, ov08x40_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov08x40_acpi_ids[] = { + {"OVTI08F4"}, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, ov08x40_acpi_ids); +#endif + +static struct i2c_driver ov08x40_i2c_driver = { + .driver = { + .name = "ov08x40", + .pm = &ov08x40_pm_ops, + .acpi_match_table = ACPI_PTR(ov08x40_acpi_ids), + }, + .probe_new = ov08x40_probe, + .remove = ov08x40_remove, +}; + +module_i2c_driver(ov08x40_i2c_driver); + +MODULE_AUTHOR("Jason Chen <jason.z.chen@intel.com>"); +MODULE_AUTHOR("Shawn Tu <shawnx.tu@intel.com>"); +MODULE_DESCRIPTION("OmniVision OV08X40 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c index e618b613e078..69a7a2c590db 100644 --- a/drivers/media/i2c/ov13858.c +++ b/drivers/media/i2c/ov13858.c @@ -1698,8 +1698,7 @@ static void ov13858_free_controls(struct ov13858 *ov13858) mutex_destroy(&ov13858->mutex); } -static int ov13858_probe(struct i2c_client *client, - const struct i2c_device_id *devid) +static int ov13858_probe(struct i2c_client *client) { struct ov13858 *ov13858; int ret; @@ -1807,7 +1806,7 @@ static struct i2c_driver ov13858_i2c_driver = { .pm = &ov13858_pm_ops, .acpi_match_table = ACPI_PTR(ov13858_acpi_ids), }, - .probe = ov13858_probe, + .probe_new = ov13858_probe, .remove = ov13858_remove, .id_table = ov13858_id_table, }; diff --git a/drivers/media/i2c/ov2640.c b/drivers/media/i2c/ov2640.c index 29ed0ef8c033..39d56838a4ef 100644 --- a/drivers/media/i2c/ov2640.c +++ b/drivers/media/i2c/ov2640.c @@ -16,9 +16,7 @@ #include <linux/clk.h> #include <linux/slab.h> #include <linux/delay.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> -#include <linux/of_gpio.h> #include <linux/v4l2-mediabus.h> #include <linux/videodev2.h> diff --git a/drivers/media/i2c/ov2680.c b/drivers/media/i2c/ov2680.c index de66d3395a4d..54153bf66bdd 100644 --- a/drivers/media/i2c/ov2680.c +++ b/drivers/media/i2c/ov2680.c @@ -967,6 +967,8 @@ static int ov2680_v4l2_register(struct ov2680_dev *sensor) ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrls->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + ctrls->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c index 5d74ad479214..f3731f932a94 100644 --- a/drivers/media/i2c/ov2740.c +++ b/drivers/media/i2c/ov2740.c @@ -77,7 +77,6 @@ #define OV2740_REG_OTP_CUSTOMER 0x7010 struct nvm_data { - struct i2c_client *client; struct nvmem_device *nvmem; struct regmap *regmap; char *nvm_buffer; @@ -379,7 +378,7 @@ static int ov2740_read_reg(struct ov2740 *ov2740, u16 reg, u16 len, u32 *val) struct i2c_msg msgs[2]; u8 addr_buf[2]; u8 data_buf[4] = {0}; - int ret = 0; + int ret; if (len > sizeof(data_buf)) return -EINVAL; @@ -407,7 +406,7 @@ static int ov2740_write_reg(struct ov2740 *ov2740, u16 reg, u16 len, u32 val) { struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); u8 buf[6]; - int ret = 0; + int ret; if (len > 4) return -EINVAL; @@ -427,14 +426,14 @@ static int ov2740_write_reg_list(struct ov2740 *ov2740, { struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); unsigned int i; - int ret = 0; + int ret; for (i = 0; i < r_list->num_of_regs; i++) { ret = ov2740_write_reg(ov2740, r_list->regs[i].address, 1, r_list->regs[i].val); if (ret) { dev_err_ratelimited(&client->dev, - "write reg 0x%4.4x return err = %d", + "write reg 0x%4.4x return err = %d\n", r_list->regs[i].address, ret); return ret; } @@ -457,7 +456,7 @@ static int ov2740_identify_module(struct ov2740 *ov2740) return ret; if (val != OV2740_CHIP_ID) { - dev_err(&client->dev, "chip id mismatch: %x!=%x", + dev_err(&client->dev, "chip id mismatch: %x != %x\n", OV2740_CHIP_ID, val); return -ENXIO; } @@ -469,7 +468,7 @@ static int ov2740_identify_module(struct ov2740 *ov2740) static int ov2740_update_digital_gain(struct ov2740 *ov2740, u32 d_gain) { - int ret = 0; + int ret; ret = ov2740_write_reg(ov2740, OV2740_REG_GROUP_ACCESS, 1, OV2740_GROUP_HOLD_START); @@ -513,7 +512,7 @@ static int ov2740_set_ctrl(struct v4l2_ctrl *ctrl) struct ov2740, ctrl_handler); struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); s64 exposure_max; - int ret = 0; + int ret; /* Propagate change of current control to all related controls */ if (ctrl->id == V4L2_CID_VBLANK) { @@ -576,7 +575,7 @@ static int ov2740_init_controls(struct ov2740 *ov2740) s64 exposure_max, h_blank, pixel_rate; u32 vblank_min, vblank_max, vblank_default; int size; - int ret = 0; + int ret; ctrl_hdlr = &ov2740->ctrl_handler; ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); @@ -649,34 +648,28 @@ static void ov2740_update_pad_format(const struct ov2740_mode *mode, static int ov2740_load_otp_data(struct nvm_data *nvm) { - struct i2c_client *client; - struct ov2740 *ov2740; + struct device *dev = regmap_get_device(nvm->regmap); + struct ov2740 *ov2740 = to_ov2740(dev_get_drvdata(dev)); u32 isp_ctrl00 = 0; u32 isp_ctrl01 = 0; int ret; - if (!nvm) - return -EINVAL; - if (nvm->nvm_buffer) return 0; - client = nvm->client; - ov2740 = to_ov2740(i2c_get_clientdata(client)); - nvm->nvm_buffer = kzalloc(CUSTOMER_USE_OTP_SIZE, GFP_KERNEL); if (!nvm->nvm_buffer) return -ENOMEM; ret = ov2740_read_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, &isp_ctrl00); if (ret) { - dev_err(&client->dev, "failed to read ISP CTRL00\n"); + dev_err(dev, "failed to read ISP CTRL00\n"); goto err; } ret = ov2740_read_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, &isp_ctrl01); if (ret) { - dev_err(&client->dev, "failed to read ISP CTRL01\n"); + dev_err(dev, "failed to read ISP CTRL01\n"); goto err; } @@ -684,7 +677,7 @@ static int ov2740_load_otp_data(struct nvm_data *nvm) ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, isp_ctrl00 & ~BIT(5)); if (ret) { - dev_err(&client->dev, "failed to set ISP CTRL00\n"); + dev_err(dev, "failed to set ISP CTRL00\n"); goto err; } @@ -692,14 +685,14 @@ static int ov2740_load_otp_data(struct nvm_data *nvm) ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, isp_ctrl01 & ~BIT(7)); if (ret) { - dev_err(&client->dev, "failed to set ISP CTRL01\n"); + dev_err(dev, "failed to set ISP CTRL01\n"); goto err; } ret = ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, OV2740_MODE_STREAMING); if (ret) { - dev_err(&client->dev, "failed to set streaming mode\n"); + dev_err(dev, "failed to set streaming mode\n"); goto err; } @@ -712,26 +705,26 @@ static int ov2740_load_otp_data(struct nvm_data *nvm) ret = regmap_bulk_read(nvm->regmap, OV2740_REG_OTP_CUSTOMER, nvm->nvm_buffer, CUSTOMER_USE_OTP_SIZE); if (ret) { - dev_err(&client->dev, "failed to read OTP data, ret %d\n", ret); + dev_err(dev, "failed to read OTP data, ret %d\n", ret); goto err; } ret = ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, OV2740_MODE_STANDBY); if (ret) { - dev_err(&client->dev, "failed to set streaming mode\n"); + dev_err(dev, "failed to set streaming mode\n"); goto err; } ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL01, 1, isp_ctrl01); if (ret) { - dev_err(&client->dev, "failed to set ISP CTRL01\n"); + dev_err(dev, "failed to set ISP CTRL01\n"); goto err; } ret = ov2740_write_reg(ov2740, OV2740_REG_ISP_CTRL00, 1, isp_ctrl00); if (ret) { - dev_err(&client->dev, "failed to set ISP CTRL00\n"); + dev_err(dev, "failed to set ISP CTRL00\n"); goto err; } @@ -746,29 +739,29 @@ err: static int ov2740_start_streaming(struct ov2740 *ov2740) { struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); - struct nvm_data *nvm = ov2740->nvm; const struct ov2740_reg_list *reg_list; int link_freq_index; - int ret = 0; + int ret; ret = ov2740_identify_module(ov2740); if (ret) return ret; - ov2740_load_otp_data(nvm); + if (ov2740->nvm) + ov2740_load_otp_data(ov2740->nvm); link_freq_index = ov2740->cur_mode->link_freq_index; reg_list = &link_freq_configs[link_freq_index].reg_list; ret = ov2740_write_reg_list(ov2740, reg_list); if (ret) { - dev_err(&client->dev, "failed to set plls"); + dev_err(&client->dev, "failed to set plls\n"); return ret; } reg_list = &ov2740->cur_mode->reg_list; ret = ov2740_write_reg_list(ov2740, reg_list); if (ret) { - dev_err(&client->dev, "failed to set mode"); + dev_err(&client->dev, "failed to set mode\n"); return ret; } @@ -779,7 +772,7 @@ static int ov2740_start_streaming(struct ov2740 *ov2740) ret = ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, OV2740_MODE_STREAMING); if (ret) - dev_err(&client->dev, "failed to start streaming"); + dev_err(&client->dev, "failed to start streaming\n"); return ret; } @@ -790,7 +783,7 @@ static void ov2740_stop_streaming(struct ov2740 *ov2740) if (ov2740_write_reg(ov2740, OV2740_REG_MODE_SELECT, 1, OV2740_MODE_STANDBY)) - dev_err(&client->dev, "failed to stop streaming"); + dev_err(&client->dev, "failed to stop streaming\n"); } static int ov2740_set_stream(struct v4l2_subdev *sd, int enable) @@ -827,7 +820,7 @@ static int ov2740_set_stream(struct v4l2_subdev *sd, int enable) return ret; } -static int __maybe_unused ov2740_suspend(struct device *dev) +static int ov2740_suspend(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct ov2740 *ov2740 = to_ov2740(sd); @@ -841,7 +834,7 @@ static int __maybe_unused ov2740_suspend(struct device *dev) return 0; } -static int __maybe_unused ov2740_resume(struct device *dev) +static int ov2740_resume(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct ov2740 *ov2740 = to_ov2740(sd); @@ -998,17 +991,14 @@ static int ov2740_check_hwcfg(struct device *dev) int ret; unsigned int i, j; - if (!fwnode) - return -ENXIO; - ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); if (ret) return ret; - if (mclk != OV2740_MCLK) { - dev_err(dev, "external clock %d is not supported", mclk); - return -EINVAL; - } + if (mclk != OV2740_MCLK) + return dev_err_probe(dev, -EINVAL, + "external clock %d is not supported\n", + mclk); ep = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!ep) @@ -1020,15 +1010,14 @@ static int ov2740_check_hwcfg(struct device *dev) return ret; if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV2740_DATA_LANES) { - dev_err(dev, "number of CSI2 data lanes %d is not supported", - bus_cfg.bus.mipi_csi2.num_data_lanes); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, + "number of CSI2 data lanes %d is not supported\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); goto check_hwcfg_error; } if (!bus_cfg.nr_of_link_frequencies) { - dev_err(dev, "no link frequencies defined"); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, "no link frequencies defined\n"); goto check_hwcfg_error; } @@ -1040,9 +1029,9 @@ static int ov2740_check_hwcfg(struct device *dev) } if (j == bus_cfg.nr_of_link_frequencies) { - dev_err(dev, "no link frequency %lld supported", - link_freq_menu_items[i]); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, + "no link frequency %lld supported\n", + link_freq_menu_items[i]); goto check_hwcfg_error; } } @@ -1069,9 +1058,8 @@ static int ov2740_nvmem_read(void *priv, unsigned int off, void *val, size_t count) { struct nvm_data *nvm = priv; - struct v4l2_subdev *sd = i2c_get_clientdata(nvm->client); - struct device *dev = &nvm->client->dev; - struct ov2740 *ov2740 = to_ov2740(sd); + struct device *dev = regmap_get_device(nvm->regmap); + struct ov2740 *ov2740 = to_ov2740(dev_get_drvdata(dev)); int ret = 0; mutex_lock(&ov2740->mutex); @@ -1104,7 +1092,6 @@ static int ov2740_register_nvmem(struct i2c_client *client, struct nvmem_config nvmem_config = { }; struct regmap *regmap; struct device *dev = &client->dev; - int ret; nvm = devm_kzalloc(dev, sizeof(*nvm), GFP_KERNEL); if (!nvm) @@ -1118,7 +1105,6 @@ static int ov2740_register_nvmem(struct i2c_client *client, return PTR_ERR(regmap); nvm->regmap = regmap; - nvm->client = client; nvmem_config.name = dev_name(dev); nvmem_config.dev = dev; @@ -1135,26 +1121,23 @@ static int ov2740_register_nvmem(struct i2c_client *client, nvmem_config.size = CUSTOMER_USE_OTP_SIZE; nvm->nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvm->nvmem)) + return PTR_ERR(nvm->nvmem); - ret = PTR_ERR_OR_ZERO(nvm->nvmem); - if (!ret) - ov2740->nvm = nvm; - - return ret; + ov2740->nvm = nvm; + return 0; } static int ov2740_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct ov2740 *ov2740; - int ret = 0; bool full_power; + int ret; ret = ov2740_check_hwcfg(&client->dev); - if (ret) { - dev_err(&client->dev, "failed to check HW configuration: %d", - ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to check HW configuration\n"); ov2740 = devm_kzalloc(&client->dev, sizeof(*ov2740), GFP_KERNEL); if (!ov2740) @@ -1164,17 +1147,15 @@ static int ov2740_probe(struct i2c_client *client) full_power = acpi_dev_state_d0(&client->dev); if (full_power) { ret = ov2740_identify_module(ov2740); - if (ret) { - dev_err(&client->dev, "failed to find sensor: %d", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to find sensor\n"); } mutex_init(&ov2740->mutex); ov2740->cur_mode = &supported_modes[0]; ret = ov2740_init_controls(ov2740); if (ret) { - dev_err(&client->dev, "failed to init controls: %d", ret); + dev_err_probe(dev, ret, "failed to init controls\n"); goto probe_error_v4l2_ctrl_handler_free; } @@ -1185,14 +1166,13 @@ static int ov2740_probe(struct i2c_client *client) ov2740->pad.flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&ov2740->sd.entity, 1, &ov2740->pad); if (ret) { - dev_err(&client->dev, "failed to init entity pads: %d", ret); + dev_err_probe(dev, ret, "failed to init entity pads\n"); goto probe_error_v4l2_ctrl_handler_free; } ret = v4l2_async_register_subdev_sensor(&ov2740->sd); if (ret < 0) { - dev_err(&client->dev, "failed to register V4L2 subdev: %d", - ret); + dev_err_probe(dev, ret, "failed to register V4L2 subdev\n"); goto probe_error_media_entity_cleanup; } @@ -1218,9 +1198,7 @@ probe_error_v4l2_ctrl_handler_free: return ret; } -static const struct dev_pm_ops ov2740_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(ov2740_suspend, ov2740_resume) -}; +static DEFINE_SIMPLE_DEV_PM_OPS(ov2740_pm_ops, ov2740_suspend, ov2740_resume); static const struct acpi_device_id ov2740_acpi_ids[] = { {"INT3474"}, @@ -1232,7 +1210,7 @@ MODULE_DEVICE_TABLE(acpi, ov2740_acpi_ids); static struct i2c_driver ov2740_i2c_driver = { .driver = { .name = "ov2740", - .pm = &ov2740_pm_ops, + .pm = pm_sleep_ptr(&ov2740_pm_ops), .acpi_match_table = ov2740_acpi_ids, }, .probe_new = ov2740_probe, diff --git a/drivers/media/i2c/ov4689.c b/drivers/media/i2c/ov4689.c new file mode 100644 index 000000000000..c602e507d42b --- /dev/null +++ b/drivers/media/i2c/ov4689.c @@ -0,0 +1,1018 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ov4689 driver + * + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + * Copyright (C) 2022 Mikhail Rudenko + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-fwnode.h> + +#define CHIP_ID 0x004688 +#define OV4689_REG_CHIP_ID 0x300a + +#define OV4689_XVCLK_FREQ 24000000 + +#define OV4689_REG_CTRL_MODE 0x0100 +#define OV4689_MODE_SW_STANDBY 0x0 +#define OV4689_MODE_STREAMING BIT(0) + +#define OV4689_REG_EXPOSURE 0x3500 +#define OV4689_EXPOSURE_MIN 4 +#define OV4689_EXPOSURE_STEP 1 +#define OV4689_VTS_MAX 0x7fff + +#define OV4689_REG_GAIN_H 0x3508 +#define OV4689_REG_GAIN_L 0x3509 +#define OV4689_GAIN_H_MASK 0x07 +#define OV4689_GAIN_H_SHIFT 8 +#define OV4689_GAIN_L_MASK 0xff +#define OV4689_GAIN_STEP 1 +#define OV4689_GAIN_DEFAULT 0x80 + +#define OV4689_REG_TEST_PATTERN 0x5040 +#define OV4689_TEST_PATTERN_ENABLE 0x80 +#define OV4689_TEST_PATTERN_DISABLE 0x0 + +#define OV4689_REG_VTS 0x380e + +#define REG_NULL 0xFFFF + +#define OV4689_REG_VALUE_08BIT 1 +#define OV4689_REG_VALUE_16BIT 2 +#define OV4689_REG_VALUE_24BIT 3 + +#define OV4689_LANES 4 + +static const char *const ov4689_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +struct regval { + u16 addr; + u8 val; +}; + +enum ov4689_mode_id { + OV4689_MODE_2688_1520 = 0, + OV4689_NUM_MODES, +}; + +struct ov4689_mode { + enum ov4689_mode_id id; + u32 width; + u32 height; + u32 max_fps; + u32 hts_def; + u32 vts_def; + u32 exp_def; + u32 pixel_rate; + u32 sensor_width; + u32 sensor_height; + u32 crop_top; + u32 crop_left; + const struct regval *reg_list; +}; + +struct ov4689 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(ov4689_supply_names)]; + + struct v4l2_subdev subdev; + struct media_pad pad; + + u32 clock_rate; + + struct mutex mutex; /* lock to protect streaming, ctrls and cur_mode */ + bool streaming; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + + const struct ov4689_mode *cur_mode; +}; + +#define to_ov4689(sd) container_of(sd, struct ov4689, subdev) + +struct ov4689_gain_range { + u32 logical_min; + u32 logical_max; + u32 offset; + u32 divider; + u32 physical_min; + u32 physical_max; +}; + +/* + * Xclk 24Mhz + * max_framerate 30fps + * mipi_datarate per lane 1008Mbps + */ +static const struct regval ov4689_2688x1520_regs[] = { + {0x0103, 0x01}, {0x3638, 0x00}, {0x0300, 0x00}, + {0x0302, 0x2a}, {0x0303, 0x00}, {0x0304, 0x03}, + {0x030b, 0x00}, {0x030d, 0x1e}, {0x030e, 0x04}, + {0x030f, 0x01}, {0x0312, 0x01}, {0x031e, 0x00}, + {0x3000, 0x20}, {0x3002, 0x00}, {0x3018, 0x72}, + {0x3020, 0x93}, {0x3021, 0x03}, {0x3022, 0x01}, + {0x3031, 0x0a}, {0x303f, 0x0c}, {0x3305, 0xf1}, + {0x3307, 0x04}, {0x3309, 0x29}, {0x3500, 0x00}, + {0x3501, 0x60}, {0x3502, 0x00}, {0x3503, 0x04}, + {0x3504, 0x00}, {0x3505, 0x00}, {0x3506, 0x00}, + {0x3507, 0x00}, {0x3508, 0x00}, {0x3509, 0x80}, + {0x350a, 0x00}, {0x350b, 0x00}, {0x350c, 0x00}, + {0x350d, 0x00}, {0x350e, 0x00}, {0x350f, 0x80}, + {0x3510, 0x00}, {0x3511, 0x00}, {0x3512, 0x00}, + {0x3513, 0x00}, {0x3514, 0x00}, {0x3515, 0x80}, + {0x3516, 0x00}, {0x3517, 0x00}, {0x3518, 0x00}, + {0x3519, 0x00}, {0x351a, 0x00}, {0x351b, 0x80}, + {0x351c, 0x00}, {0x351d, 0x00}, {0x351e, 0x00}, + {0x351f, 0x00}, {0x3520, 0x00}, {0x3521, 0x80}, + {0x3522, 0x08}, {0x3524, 0x08}, {0x3526, 0x08}, + {0x3528, 0x08}, {0x352a, 0x08}, {0x3602, 0x00}, + {0x3603, 0x40}, {0x3604, 0x02}, {0x3605, 0x00}, + {0x3606, 0x00}, {0x3607, 0x00}, {0x3609, 0x12}, + {0x360a, 0x40}, {0x360c, 0x08}, {0x360f, 0xe5}, + {0x3608, 0x8f}, {0x3611, 0x00}, {0x3613, 0xf7}, + {0x3616, 0x58}, {0x3619, 0x99}, {0x361b, 0x60}, + {0x361c, 0x7a}, {0x361e, 0x79}, {0x361f, 0x02}, + {0x3632, 0x00}, {0x3633, 0x10}, {0x3634, 0x10}, + {0x3635, 0x10}, {0x3636, 0x15}, {0x3646, 0x86}, + {0x364a, 0x0b}, {0x3700, 0x17}, {0x3701, 0x22}, + {0x3703, 0x10}, {0x370a, 0x37}, {0x3705, 0x00}, + {0x3706, 0x63}, {0x3709, 0x3c}, {0x370b, 0x01}, + {0x370c, 0x30}, {0x3710, 0x24}, {0x3711, 0x0c}, + {0x3716, 0x00}, {0x3720, 0x28}, {0x3729, 0x7b}, + {0x372a, 0x84}, {0x372b, 0xbd}, {0x372c, 0xbc}, + {0x372e, 0x52}, {0x373c, 0x0e}, {0x373e, 0x33}, + {0x3743, 0x10}, {0x3744, 0x88}, {0x3745, 0xc0}, + {0x374a, 0x43}, {0x374c, 0x00}, {0x374e, 0x23}, + {0x3751, 0x7b}, {0x3752, 0x84}, {0x3753, 0xbd}, + {0x3754, 0xbc}, {0x3756, 0x52}, {0x375c, 0x00}, + {0x3760, 0x00}, {0x3761, 0x00}, {0x3762, 0x00}, + {0x3763, 0x00}, {0x3764, 0x00}, {0x3767, 0x04}, + {0x3768, 0x04}, {0x3769, 0x08}, {0x376a, 0x08}, + {0x376b, 0x20}, {0x376c, 0x00}, {0x376d, 0x00}, + {0x376e, 0x00}, {0x3773, 0x00}, {0x3774, 0x51}, + {0x3776, 0xbd}, {0x3777, 0xbd}, {0x3781, 0x18}, + {0x3783, 0x25}, {0x3798, 0x1b}, {0x3800, 0x00}, + {0x3801, 0x08}, {0x3802, 0x00}, {0x3803, 0x04}, + {0x3804, 0x0a}, {0x3805, 0x97}, {0x3806, 0x05}, + {0x3807, 0xfb}, {0x3808, 0x0a}, {0x3809, 0x80}, + {0x380a, 0x05}, {0x380b, 0xf0}, {0x380c, 0x0a}, + {0x380d, 0x0e}, {0x380e, 0x06}, {0x380f, 0x12}, + {0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00}, + {0x3813, 0x04}, {0x3814, 0x01}, {0x3815, 0x01}, + {0x3819, 0x01}, {0x3820, 0x00}, {0x3821, 0x06}, + {0x3829, 0x00}, {0x382a, 0x01}, {0x382b, 0x01}, + {0x382d, 0x7f}, {0x3830, 0x04}, {0x3836, 0x01}, + {0x3837, 0x00}, {0x3841, 0x02}, {0x3846, 0x08}, + {0x3847, 0x07}, {0x3d85, 0x36}, {0x3d8c, 0x71}, + {0x3d8d, 0xcb}, {0x3f0a, 0x00}, {0x4000, 0xf1}, + {0x4001, 0x40}, {0x4002, 0x04}, {0x4003, 0x14}, + {0x400e, 0x00}, {0x4011, 0x00}, {0x401a, 0x00}, + {0x401b, 0x00}, {0x401c, 0x00}, {0x401d, 0x00}, + {0x401f, 0x00}, {0x4020, 0x00}, {0x4021, 0x10}, + {0x4022, 0x07}, {0x4023, 0xcf}, {0x4024, 0x09}, + {0x4025, 0x60}, {0x4026, 0x09}, {0x4027, 0x6f}, + {0x4028, 0x00}, {0x4029, 0x02}, {0x402a, 0x06}, + {0x402b, 0x04}, {0x402c, 0x02}, {0x402d, 0x02}, + {0x402e, 0x0e}, {0x402f, 0x04}, {0x4302, 0xff}, + {0x4303, 0xff}, {0x4304, 0x00}, {0x4305, 0x00}, + {0x4306, 0x00}, {0x4308, 0x02}, {0x4500, 0x6c}, + {0x4501, 0xc4}, {0x4502, 0x40}, {0x4503, 0x01}, + {0x4601, 0xa7}, {0x4800, 0x04}, {0x4813, 0x08}, + {0x481f, 0x40}, {0x4829, 0x78}, {0x4837, 0x10}, + {0x4b00, 0x2a}, {0x4b0d, 0x00}, {0x4d00, 0x04}, + {0x4d01, 0x42}, {0x4d02, 0xd1}, {0x4d03, 0x93}, + {0x4d04, 0xf5}, {0x4d05, 0xc1}, {0x5000, 0xf3}, + {0x5001, 0x11}, {0x5004, 0x00}, {0x500a, 0x00}, + {0x500b, 0x00}, {0x5032, 0x00}, {0x5040, 0x00}, + {0x5050, 0x0c}, {0x5500, 0x00}, {0x5501, 0x10}, + {0x5502, 0x01}, {0x5503, 0x0f}, {0x8000, 0x00}, + {0x8001, 0x00}, {0x8002, 0x00}, {0x8003, 0x00}, + {0x8004, 0x00}, {0x8005, 0x00}, {0x8006, 0x00}, + {0x8007, 0x00}, {0x8008, 0x00}, {0x3638, 0x00}, + {REG_NULL, 0x00}, +}; + +static const struct ov4689_mode supported_modes[] = { + { + .id = OV4689_MODE_2688_1520, + .width = 2688, + .height = 1520, + .sensor_width = 2720, + .sensor_height = 1536, + .crop_top = 8, + .crop_left = 16, + .max_fps = 30, + .exp_def = 1536, + .hts_def = 4 * 2574, + .vts_def = 1554, + .pixel_rate = 480000000, + .reg_list = ov4689_2688x1520_regs, + }, +}; + +static const u64 link_freq_menu_items[] = { 504000000 }; + +static const char *const ov4689_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* + * These coefficients are based on those used in Rockchip's camera + * engine, with minor tweaks for continuity. + */ +static const struct ov4689_gain_range ov4689_gain_ranges[] = { + { + .logical_min = 0, + .logical_max = 255, + .offset = 0, + .divider = 1, + .physical_min = 0, + .physical_max = 255, + }, + { + .logical_min = 256, + .logical_max = 511, + .offset = 252, + .divider = 2, + .physical_min = 376, + .physical_max = 504, + }, + { + .logical_min = 512, + .logical_max = 1023, + .offset = 758, + .divider = 4, + .physical_min = 884, + .physical_max = 1012, + }, + { + .logical_min = 1024, + .logical_max = 2047, + .offset = 1788, + .divider = 8, + .physical_min = 1912, + .physical_max = 2047, + }, +}; + +/* Write registers up to 4 at a time */ +static int ov4689_write_reg(struct i2c_client *client, u16 reg, u32 len, + u32 val) +{ + u32 buf_i, val_i; + __be32 val_be; + u8 *val_p; + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int ov4689_write_array(struct i2c_client *client, + const struct regval *regs) +{ + int ret = 0; + u32 i; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = ov4689_write_reg(client, regs[i].addr, + OV4689_REG_VALUE_08BIT, regs[i].val); + + return ret; +} + +/* Read registers up to 4 at a time */ +static int ov4689_read_reg(struct i2c_client *client, u16 reg, unsigned int len, + u32 *val) +{ + __be16 reg_addr_be = cpu_to_be16(reg); + struct i2c_msg msgs[2]; + __be32 data_be = 0; + u8 *data_be_p; + int ret; + + if (len > 4 || !len) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +static void ov4689_fill_fmt(const struct ov4689_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; +} + +static int ov4689_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format; + struct ov4689 *ov4689 = to_ov4689(sd); + + /* only one mode supported for now */ + ov4689_fill_fmt(ov4689->cur_mode, mbus_fmt); + + return 0; +} + +static int ov4689_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format; + struct ov4689 *ov4689 = to_ov4689(sd); + + /* only one mode supported for now */ + ov4689_fill_fmt(ov4689->cur_mode, mbus_fmt); + + return 0; +} + +static int ov4689_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + code->code = MEDIA_BUS_FMT_SBGGR10_1X10; + + return 0; +} + +static int ov4689_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->max_height = supported_modes[fse->index].height; + fse->min_height = supported_modes[fse->index].height; + + return 0; +} + +static int ov4689_enable_test_pattern(struct ov4689 *ov4689, u32 pattern) +{ + u32 val; + + if (pattern) + val = (pattern - 1) | OV4689_TEST_PATTERN_ENABLE; + else + val = OV4689_TEST_PATTERN_DISABLE; + + return ov4689_write_reg(ov4689->client, OV4689_REG_TEST_PATTERN, + OV4689_REG_VALUE_08BIT, val); +} + +static int ov4689_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + const struct ov4689_mode *mode = to_ov4689(sd)->cur_mode; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = mode->sensor_width; + sel->r.height = mode->sensor_height; + return 0; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = mode->crop_top; + sel->r.left = mode->crop_left; + sel->r.width = mode->width; + sel->r.height = mode->height; + return 0; + } + + return -EINVAL; +} + +static int ov4689_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ov4689 *ov4689 = to_ov4689(sd); + struct i2c_client *client = ov4689->client; + int ret = 0; + + mutex_lock(&ov4689->mutex); + + on = !!on; + if (on == ov4689->streaming) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto unlock_and_return; + + ret = ov4689_write_array(ov4689->client, + ov4689->cur_mode->reg_list); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + + ret = __v4l2_ctrl_handler_setup(&ov4689->ctrl_handler); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + + ret = ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE, + OV4689_REG_VALUE_08BIT, + OV4689_MODE_STREAMING); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + } else { + ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE, + OV4689_REG_VALUE_08BIT, + OV4689_MODE_SW_STANDBY); + pm_runtime_put(&client->dev); + } + + ov4689->streaming = on; + +unlock_and_return: + mutex_unlock(&ov4689->mutex); + + return ret; +} + +/* Calculate the delay in us by clock rate and clock cycles */ +static inline u32 ov4689_cal_delay(struct ov4689 *ov4689, u32 cycles) +{ + return DIV_ROUND_UP(cycles * 1000, + DIV_ROUND_UP(ov4689->clock_rate, 1000)); +} + +static int __maybe_unused ov4689_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov4689 *ov4689 = to_ov4689(sd); + u32 delay_us; + int ret; + + ret = clk_prepare_enable(ov4689->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + gpiod_set_value_cansleep(ov4689->reset_gpio, 1); + + ret = regulator_bulk_enable(ARRAY_SIZE(ov4689_supply_names), + ov4689->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + gpiod_set_value_cansleep(ov4689->reset_gpio, 0); + usleep_range(500, 1000); + gpiod_set_value_cansleep(ov4689->pwdn_gpio, 0); + + /* 8192 cycles prior to first SCCB transaction */ + delay_us = ov4689_cal_delay(ov4689, 8192); + usleep_range(delay_us, delay_us * 2); + + return 0; + +disable_clk: + clk_disable_unprepare(ov4689->xvclk); + + return ret; +} + +static int __maybe_unused ov4689_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov4689 *ov4689 = to_ov4689(sd); + + gpiod_set_value_cansleep(ov4689->pwdn_gpio, 1); + clk_disable_unprepare(ov4689->xvclk); + gpiod_set_value_cansleep(ov4689->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ov4689_supply_names), + ov4689->supplies); + return 0; +} + +static int ov4689_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov4689 *ov4689 = to_ov4689(sd); + struct v4l2_mbus_framefmt *try_fmt; + + mutex_lock(&ov4689->mutex); + + try_fmt = v4l2_subdev_get_try_format(sd, fh->state, 0); + /* Initialize try_fmt */ + ov4689_fill_fmt(&supported_modes[OV4689_MODE_2688_1520], try_fmt); + + mutex_unlock(&ov4689->mutex); + + return 0; +} + +static const struct dev_pm_ops ov4689_pm_ops = { + SET_RUNTIME_PM_OPS(ov4689_power_off, ov4689_power_on, NULL) +}; + +static const struct v4l2_subdev_internal_ops ov4689_internal_ops = { + .open = ov4689_open, +}; + +static const struct v4l2_subdev_video_ops ov4689_video_ops = { + .s_stream = ov4689_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov4689_pad_ops = { + .enum_mbus_code = ov4689_enum_mbus_code, + .enum_frame_size = ov4689_enum_frame_sizes, + .get_fmt = ov4689_get_fmt, + .set_fmt = ov4689_set_fmt, + .get_selection = ov4689_get_selection, +}; + +static const struct v4l2_subdev_ops ov4689_subdev_ops = { + .video = &ov4689_video_ops, + .pad = &ov4689_pad_ops, +}; + +/* + * Map userspace (logical) gain to sensor (physical) gain using + * ov4689_gain_ranges table. + */ +static int ov4689_map_gain(struct ov4689 *ov4689, int logical_gain, int *result) +{ + const struct device *dev = &ov4689->client->dev; + const struct ov4689_gain_range *range; + unsigned int n; + + for (n = 0; n < ARRAY_SIZE(ov4689_gain_ranges); n++) { + if (logical_gain >= ov4689_gain_ranges[n].logical_min && + logical_gain <= ov4689_gain_ranges[n].logical_max) + break; + } + + if (n == ARRAY_SIZE(ov4689_gain_ranges)) { + dev_warn_ratelimited(dev, "no mapping found for gain %d\n", + logical_gain); + return -EINVAL; + } + + range = &ov4689_gain_ranges[n]; + + *result = clamp(range->offset + (logical_gain) / range->divider, + range->physical_min, range->physical_max); + return 0; +} + +static int ov4689_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov4689 *ov4689 = + container_of(ctrl->handler, struct ov4689, ctrl_handler); + struct i2c_client *client = ov4689->client; + int sensor_gain; + s64 max_expo; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max_expo = ov4689->cur_mode->height + ctrl->val - 4; + __v4l2_ctrl_modify_range(ov4689->exposure, + ov4689->exposure->minimum, max_expo, + ov4689->exposure->step, + ov4689->exposure->default_value); + break; + } + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part */ + ret = ov4689_write_reg(ov4689->client, OV4689_REG_EXPOSURE, + OV4689_REG_VALUE_24BIT, ctrl->val << 4); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ov4689_map_gain(ov4689, ctrl->val, &sensor_gain); + + ret = ret ?: + ov4689_write_reg(ov4689->client, OV4689_REG_GAIN_H, + OV4689_REG_VALUE_08BIT, + (sensor_gain >> OV4689_GAIN_H_SHIFT) & + OV4689_GAIN_H_MASK); + ret = ret ?: + ov4689_write_reg(ov4689->client, OV4689_REG_GAIN_L, + OV4689_REG_VALUE_08BIT, + sensor_gain & OV4689_GAIN_L_MASK); + break; + case V4L2_CID_VBLANK: + ret = ov4689_write_reg(ov4689->client, OV4689_REG_VTS, + OV4689_REG_VALUE_16BIT, + ctrl->val + ov4689->cur_mode->height); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov4689_enable_test_pattern(ov4689, ctrl->val); + break; + default: + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov4689_ctrl_ops = { + .s_ctrl = ov4689_set_ctrl, +}; + +static int ov4689_initialize_controls(struct ov4689 *ov4689) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov4689->subdev); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *handler; + const struct ov4689_mode *mode; + s64 exposure_max, vblank_def; + struct v4l2_ctrl *ctrl; + s64 h_blank_def; + int ret; + + handler = &ov4689->ctrl_handler; + mode = ov4689->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 10); + if (ret) + return ret; + handler->lock = &ov4689->mutex; + + ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, 0, 0, + link_freq_menu_items); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 0, + mode->pixel_rate, 1, mode->pixel_rate); + + h_blank_def = mode->hts_def - mode->width; + ctrl = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, h_blank_def, + h_blank_def, 1, h_blank_def); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts_def - mode->height; + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_VBLANK, + vblank_def, OV4689_VTS_MAX - mode->height, 1, + vblank_def); + + exposure_max = mode->vts_def - 4; + ov4689->exposure = + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_EXPOSURE, + OV4689_EXPOSURE_MIN, exposure_max, + OV4689_EXPOSURE_STEP, mode->exp_def); + + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + ov4689_gain_ranges[0].logical_min, + ov4689_gain_ranges[ARRAY_SIZE(ov4689_gain_ranges) - 1] + .logical_max, + OV4689_GAIN_STEP, OV4689_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(handler, &ov4689_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov4689_test_pattern_menu) - 1, + 0, 0, ov4689_test_pattern_menu); + + if (handler->error) { + ret = handler->error; + dev_err(&ov4689->client->dev, "Failed to init controls(%d)\n", + ret); + goto err_free_handler; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto err_free_handler; + + ret = v4l2_ctrl_new_fwnode_properties(handler, &ov4689_ctrl_ops, + &props); + if (ret) + goto err_free_handler; + + ov4689->subdev.ctrl_handler = handler; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int ov4689_check_sensor_id(struct ov4689 *ov4689, + struct i2c_client *client) +{ + struct device *dev = &ov4689->client->dev; + u32 id = 0; + int ret; + + ret = ov4689_read_reg(client, OV4689_REG_CHIP_ID, + OV4689_REG_VALUE_16BIT, &id); + if (ret) { + dev_err(dev, "Cannot read sensor ID\n"); + return ret; + } + + if (id != CHIP_ID) { + dev_err(dev, "Unexpected sensor ID %06x, expected %06x\n", + id, CHIP_ID); + return -ENODEV; + } + + dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID); + + return 0; +} + +static int ov4689_configure_regulators(struct ov4689 *ov4689) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ov4689_supply_names); i++) + ov4689->supplies[i].supply = ov4689_supply_names[i]; + + return devm_regulator_bulk_get(&ov4689->client->dev, + ARRAY_SIZE(ov4689_supply_names), + ov4689->supplies); +} + +static u64 ov4689_check_link_frequency(struct v4l2_fwnode_endpoint *ep) +{ + const u64 *freqs = link_freq_menu_items; + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < ep->nr_of_link_frequencies; j++) + if (freqs[i] == ep->link_frequencies[j]) + return freqs[i]; + } + + return 0; +} + +static int ov4689_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *endpoint; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!endpoint) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + fwnode_handle_put(endpoint); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV4689_LANES) { + dev_err(dev, "Only a 4-lane CSI2 config is supported"); + ret = -EINVAL; + goto out_free_bus_cfg; + } + + if (!ov4689_check_link_frequency(&bus_cfg)) { + dev_err(dev, "No supported link frequency found\n"); + ret = -EINVAL; + } + +out_free_bus_cfg: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov4689_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct v4l2_subdev *sd; + struct ov4689 *ov4689; + int ret; + + ret = ov4689_check_hwcfg(dev); + if (ret) + return ret; + + ov4689 = devm_kzalloc(dev, sizeof(*ov4689), GFP_KERNEL); + if (!ov4689) + return -ENOMEM; + + ov4689->client = client; + ov4689->cur_mode = &supported_modes[OV4689_MODE_2688_1520]; + + ov4689->xvclk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(ov4689->xvclk)) + return dev_err_probe(dev, PTR_ERR(ov4689->xvclk), + "Failed to get external clock\n"); + + if (!ov4689->xvclk) { + dev_dbg(dev, + "No clock provided, using clock-frequency property\n"); + device_property_read_u32(dev, "clock-frequency", + &ov4689->clock_rate); + } else { + ov4689->clock_rate = clk_get_rate(ov4689->xvclk); + } + + if (ov4689->clock_rate != OV4689_XVCLK_FREQ) { + dev_err(dev, + "External clock rate mismatch: got %d Hz, expected %d Hz\n", + ov4689->clock_rate, OV4689_XVCLK_FREQ); + return -EINVAL; + } + + ov4689->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ov4689->reset_gpio)) { + dev_err(dev, "Failed to get reset-gpios\n"); + return PTR_ERR(ov4689->reset_gpio); + } + + ov4689->pwdn_gpio = devm_gpiod_get_optional(dev, "pwdn", GPIOD_OUT_LOW); + if (IS_ERR(ov4689->pwdn_gpio)) { + dev_err(dev, "Failed to get pwdn-gpios\n"); + return PTR_ERR(ov4689->pwdn_gpio); + } + + ret = ov4689_configure_regulators(ov4689); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get power regulators\n"); + + mutex_init(&ov4689->mutex); + + sd = &ov4689->subdev; + v4l2_i2c_subdev_init(sd, client, &ov4689_subdev_ops); + ret = ov4689_initialize_controls(ov4689); + if (ret) + goto err_destroy_mutex; + + ret = ov4689_power_on(dev); + if (ret) + goto err_free_handler; + + ret = ov4689_check_sensor_id(ov4689, client); + if (ret) + goto err_power_off; + + sd->internal_ops = &ov4689_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ov4689->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov4689->pad); + if (ret < 0) + goto err_power_off; + + ret = v4l2_async_register_subdev_sensor(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: + media_entity_cleanup(&sd->entity); +err_power_off: + ov4689_power_off(dev); +err_free_handler: + v4l2_ctrl_handler_free(&ov4689->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&ov4689->mutex); + + return ret; +} + +static void ov4689_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov4689 *ov4689 = to_ov4689(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + v4l2_ctrl_handler_free(&ov4689->ctrl_handler); + mutex_destroy(&ov4689->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + ov4689_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id ov4689_of_match[] = { + { .compatible = "ovti,ov4689" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ov4689_of_match); + +static struct i2c_driver ov4689_i2c_driver = { + .driver = { + .name = "ov4689", + .pm = &ov4689_pm_ops, + .of_match_table = ov4689_of_match, + }, + .probe_new = ov4689_probe, + .remove = ov4689_remove, +}; + +module_i2c_driver(ov4689_i2c_driver); + +MODULE_DESCRIPTION("OmniVision ov4689 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c index 2d740397a5d4..e0f908af581b 100644 --- a/drivers/media/i2c/ov5640.c +++ b/drivers/media/i2c/ov5640.c @@ -2715,20 +2715,20 @@ static int ov5640_sensor_resume(struct device *dev) static int ov5640_try_frame_interval(struct ov5640_dev *sensor, struct v4l2_fract *fi, - u32 width, u32 height) + const struct ov5640_mode_info *mode_info) { - const struct ov5640_mode_info *mode; + const struct ov5640_mode_info *mode = mode_info; enum ov5640_frame_rate rate = OV5640_15_FPS; int minfps, maxfps, best_fps, fps; int i; minfps = ov5640_framerates[OV5640_15_FPS]; - maxfps = ov5640_framerates[OV5640_60_FPS]; + maxfps = ov5640_framerates[mode->max_fps]; if (fi->numerator == 0) { fi->denominator = maxfps; fi->numerator = 1; - rate = OV5640_60_FPS; + rate = mode->max_fps; goto find_mode; } @@ -2749,7 +2749,7 @@ static int ov5640_try_frame_interval(struct ov5640_dev *sensor, fi->denominator = best_fps; find_mode: - mode = ov5640_find_mode(sensor, width, height, false); + mode = ov5640_find_mode(sensor, mode->width, mode->height, false); return mode ? rate : -EINVAL; } @@ -3554,6 +3554,7 @@ static int ov5640_enum_frame_interval( struct v4l2_subdev_frame_interval_enum *fie) { struct ov5640_dev *sensor = to_ov5640_dev(sd); + const struct ov5640_mode_info *mode; struct v4l2_fract tpf; int ret; @@ -3562,11 +3563,14 @@ static int ov5640_enum_frame_interval( if (fie->index >= OV5640_NUM_FRAMERATES) return -EINVAL; + mode = ov5640_find_mode(sensor, fie->width, fie->height, false); + if (!mode) + return -EINVAL; + tpf.numerator = 1; tpf.denominator = ov5640_framerates[fie->index]; - ret = ov5640_try_frame_interval(sensor, &tpf, - fie->width, fie->height); + ret = ov5640_try_frame_interval(sensor, &tpf, mode); if (ret < 0) return -EINVAL; @@ -3605,9 +3609,7 @@ static int ov5640_s_frame_interval(struct v4l2_subdev *sd, mode = sensor->current_mode; - frame_rate = ov5640_try_frame_interval(sensor, &fi->interval, - mode->width, - mode->height); + frame_rate = ov5640_try_frame_interval(sensor, &fi->interval, mode); if (frame_rate < 0) { /* Always return a valid frame interval value */ fi->interval = sensor->frame_interval; @@ -3817,7 +3819,8 @@ static int ov5640_probe(struct i2c_client *client) sensor->current_mode = &ov5640_mode_data[OV5640_MODE_VGA_640_480]; sensor->last_mode = sensor->current_mode; - sensor->current_link_freq = OV5640_DEFAULT_LINK_FREQ; + sensor->current_link_freq = + ov5640_csi2_link_freqs[OV5640_DEFAULT_LINK_FREQ]; sensor->ae_target = 52; diff --git a/drivers/media/i2c/ov5645.c b/drivers/media/i2c/ov5645.c index 81e4e87e1821..c8999fc4f26f 100644 --- a/drivers/media/i2c/ov5645.c +++ b/drivers/media/i2c/ov5645.c @@ -14,9 +14,6 @@ * https://www.mail-archive.com/linux-media%40vger.kernel.org/msg92671.html */ -/* - */ - #include <linux/bitops.h> #include <linux/clk.h> #include <linux/delay.h> @@ -27,6 +24,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_graph.h> +#include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/types.h> @@ -108,7 +106,6 @@ struct ov5645 { u8 timing_tc_reg21; struct mutex power_lock; /* lock to protect power state */ - int power_count; struct gpio_desc *enable_gpio; struct gpio_desc *rst_gpio; @@ -635,8 +632,24 @@ static int ov5645_set_register_array(struct ov5645 *ov5645, return 0; } -static int ov5645_set_power_on(struct ov5645 *ov5645) +static int ov5645_set_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov5645 *ov5645 = to_ov5645(sd); + + ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x58); + gpiod_set_value_cansleep(ov5645->rst_gpio, 1); + gpiod_set_value_cansleep(ov5645->enable_gpio, 0); + clk_disable_unprepare(ov5645->xclk); + regulator_bulk_disable(OV5645_NUM_SUPPLIES, ov5645->supplies); + + return 0; +} + +static int ov5645_set_power_on(struct device *dev) { + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov5645 *ov5645 = to_ov5645(sd); int ret; ret = regulator_bulk_enable(OV5645_NUM_SUPPLIES, ov5645->supplies); @@ -658,57 +671,19 @@ static int ov5645_set_power_on(struct ov5645 *ov5645) msleep(20); - return 0; -} - -static void ov5645_set_power_off(struct ov5645 *ov5645) -{ - gpiod_set_value_cansleep(ov5645->rst_gpio, 1); - gpiod_set_value_cansleep(ov5645->enable_gpio, 0); - clk_disable_unprepare(ov5645->xclk); - regulator_bulk_disable(OV5645_NUM_SUPPLIES, ov5645->supplies); -} - -static int ov5645_s_power(struct v4l2_subdev *sd, int on) -{ - struct ov5645 *ov5645 = to_ov5645(sd); - int ret = 0; - - mutex_lock(&ov5645->power_lock); - - /* If the power count is modified from 0 to != 0 or from != 0 to 0, - * update the power state. - */ - if (ov5645->power_count == !on) { - if (on) { - ret = ov5645_set_power_on(ov5645); - if (ret < 0) - goto exit; - - ret = ov5645_set_register_array(ov5645, - ov5645_global_init_setting, + ret = ov5645_set_register_array(ov5645, ov5645_global_init_setting, ARRAY_SIZE(ov5645_global_init_setting)); - if (ret < 0) { - dev_err(ov5645->dev, - "could not set init registers\n"); - ov5645_set_power_off(ov5645); - goto exit; - } - - usleep_range(500, 1000); - } else { - ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x58); - ov5645_set_power_off(ov5645); - } + if (ret < 0) { + dev_err(ov5645->dev, "could not set init registers\n"); + goto exit; } - /* Update the power count. */ - ov5645->power_count += on ? 1 : -1; - WARN_ON(ov5645->power_count < 0); + usleep_range(500, 1000); -exit: - mutex_unlock(&ov5645->power_lock); + return 0; +exit: + ov5645_set_power_off(dev); return ret; } @@ -795,7 +770,7 @@ static int ov5645_s_ctrl(struct v4l2_ctrl *ctrl) int ret; mutex_lock(&ov5645->power_lock); - if (!ov5645->power_count) { + if (!pm_runtime_get_if_in_use(ov5645->dev)) { mutex_unlock(&ov5645->power_lock); return 0; } @@ -827,6 +802,8 @@ static int ov5645_s_ctrl(struct v4l2_ctrl *ctrl) break; } + pm_runtime_mark_last_busy(ov5645->dev); + pm_runtime_put_autosuspend(ov5645->dev); mutex_unlock(&ov5645->power_lock); return ret; @@ -991,6 +968,10 @@ static int ov5645_s_stream(struct v4l2_subdev *subdev, int enable) int ret; if (enable) { + ret = pm_runtime_resume_and_get(ov5645->dev); + if (ret < 0) + return ret; + ret = ov5645_set_register_array(ov5645, ov5645->current_mode->data, ov5645->current_mode->data_size); @@ -998,39 +979,44 @@ static int ov5645_s_stream(struct v4l2_subdev *subdev, int enable) dev_err(ov5645->dev, "could not set mode %dx%d\n", ov5645->current_mode->width, ov5645->current_mode->height); - return ret; + goto err_rpm_put; } ret = v4l2_ctrl_handler_setup(&ov5645->ctrls); if (ret < 0) { dev_err(ov5645->dev, "could not sync v4l2 controls\n"); - return ret; + goto err_rpm_put; } ret = ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x45); if (ret < 0) - return ret; + goto err_rpm_put; ret = ov5645_write_reg(ov5645, OV5645_SYSTEM_CTRL0, OV5645_SYSTEM_CTRL0_START); if (ret < 0) - return ret; + goto err_rpm_put; } else { ret = ov5645_write_reg(ov5645, OV5645_IO_MIPI_CTRL00, 0x40); if (ret < 0) - return ret; + goto stream_off_rpm_put; ret = ov5645_write_reg(ov5645, OV5645_SYSTEM_CTRL0, OV5645_SYSTEM_CTRL0_STOP); - if (ret < 0) - return ret; + + goto stream_off_rpm_put; } return 0; -} -static const struct v4l2_subdev_core_ops ov5645_core_ops = { - .s_power = ov5645_s_power, -}; +err_rpm_put: + pm_runtime_put_sync(ov5645->dev); + return ret; + +stream_off_rpm_put: + pm_runtime_mark_last_busy(ov5645->dev); + pm_runtime_put_autosuspend(ov5645->dev); + return ret; +} static const struct v4l2_subdev_video_ops ov5645_video_ops = { .s_stream = ov5645_s_stream, @@ -1046,7 +1032,6 @@ static const struct v4l2_subdev_pad_ops ov5645_subdev_pad_ops = { }; static const struct v4l2_subdev_ops ov5645_subdev_ops = { - .core = &ov5645_core_ops, .video = &ov5645_video_ops, .pad = &ov5645_subdev_pad_ops, }; @@ -1090,7 +1075,7 @@ static int ov5645_probe(struct i2c_client *client) } /* get system clock (xclk) */ - ov5645->xclk = devm_clk_get(dev, "xclk"); + ov5645->xclk = devm_clk_get(dev, NULL); if (IS_ERR(ov5645->xclk)) { dev_err(dev, "could not get xclk"); return PTR_ERR(ov5645->xclk); @@ -1188,11 +1173,9 @@ static int ov5645_probe(struct i2c_client *client) goto free_ctrl; } - ret = ov5645_s_power(&ov5645->sd, true); - if (ret < 0) { - dev_err(dev, "could not power up OV5645\n"); + ret = ov5645_set_power_on(dev); + if (ret) goto free_entity; - } ret = ov5645_read_reg(ov5645, OV5645_CHIP_ID_HIGH, &chip_id_high); if (ret < 0 || chip_id_high != OV5645_CHIP_ID_HIGH_BYTE) { @@ -1233,20 +1216,30 @@ static int ov5645_probe(struct i2c_client *client) goto power_down; } - ov5645_s_power(&ov5645->sd, false); + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + + ov5645_entity_init_cfg(&ov5645->sd, NULL); ret = v4l2_async_register_subdev(&ov5645->sd); if (ret < 0) { dev_err(dev, "could not register v4l2 device\n"); - goto free_entity; + goto err_pm_runtime; } - ov5645_entity_init_cfg(&ov5645->sd, NULL); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); return 0; +err_pm_runtime: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); power_down: - ov5645_s_power(&ov5645->sd, false); + ov5645_set_power_off(dev); free_entity: media_entity_cleanup(&ov5645->sd.entity); free_ctrl: @@ -1264,6 +1257,10 @@ static void ov5645_remove(struct i2c_client *client) v4l2_async_unregister_subdev(&ov5645->sd); media_entity_cleanup(&ov5645->sd.entity); v4l2_ctrl_handler_free(&ov5645->ctrls); + pm_runtime_disable(ov5645->dev); + if (!pm_runtime_status_suspended(ov5645->dev)) + ov5645_set_power_off(ov5645->dev); + pm_runtime_set_suspended(ov5645->dev); mutex_destroy(&ov5645->power_lock); } @@ -1279,10 +1276,15 @@ static const struct of_device_id ov5645_of_match[] = { }; MODULE_DEVICE_TABLE(of, ov5645_of_match); +static const struct dev_pm_ops ov5645_pm_ops = { + SET_RUNTIME_PM_OPS(ov5645_set_power_off, ov5645_set_power_on, NULL) +}; + static struct i2c_driver ov5645_i2c_driver = { .driver = { .of_match_table = ov5645_of_match, .name = "ov5645", + .pm = &ov5645_pm_ops, }, .probe_new = ov5645_probe, .remove = ov5645_remove, diff --git a/drivers/media/i2c/ov5648.c b/drivers/media/i2c/ov5648.c index 84604ea7bdf9..17465fcf28e3 100644 --- a/drivers/media/i2c/ov5648.c +++ b/drivers/media/i2c/ov5648.c @@ -2597,6 +2597,7 @@ static void ov5648_remove(struct i2c_client *client) v4l2_ctrl_handler_free(&sensor->ctrls.handler); mutex_destroy(&sensor->mutex); media_entity_cleanup(&subdev->entity); + v4l2_fwnode_endpoint_free(&sensor->endpoint); } static const struct dev_pm_ops ov5648_pm_ops = { diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index a97ec132ba3a..e3c3bed69ad6 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -156,6 +156,7 @@ struct ov5693_device { struct gpio_desc *reset; struct gpio_desc *powerdown; + struct gpio_desc *privacy_led; struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; struct clk *xvclk; @@ -789,6 +790,7 @@ static int ov5693_sensor_init(struct ov5693_device *ov5693) static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) { + gpiod_set_value_cansleep(ov5693->privacy_led, 0); gpiod_set_value_cansleep(ov5693->reset, 1); gpiod_set_value_cansleep(ov5693->powerdown, 1); @@ -818,6 +820,7 @@ static int ov5693_sensor_powerup(struct ov5693_device *ov5693) gpiod_set_value_cansleep(ov5693->powerdown, 0); gpiod_set_value_cansleep(ov5693->reset, 0); + gpiod_set_value_cansleep(ov5693->privacy_led, 1); usleep_range(5000, 7500); @@ -1325,6 +1328,13 @@ static int ov5693_configure_gpios(struct ov5693_device *ov5693) return PTR_ERR(ov5693->powerdown); } + ov5693->privacy_led = devm_gpiod_get_optional(ov5693->dev, "privacy-led", + GPIOD_OUT_LOW); + if (IS_ERR(ov5693->privacy_led)) { + dev_err(ov5693->dev, "Error fetching privacy-led GPIO\n"); + return PTR_ERR(ov5693->privacy_led); + } + return 0; } diff --git a/drivers/media/i2c/ov6650.c b/drivers/media/i2c/ov6650.c index 18f041e985b7..4c0ea2ae671b 100644 --- a/drivers/media/i2c/ov6650.c +++ b/drivers/media/i2c/ov6650.c @@ -1025,8 +1025,7 @@ static const struct v4l2_subdev_internal_ops ov6650_internal_ops = { /* * i2c_driver function */ -static int ov6650_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int ov6650_probe(struct i2c_client *client) { struct ov6650 *priv; int ret; @@ -1114,7 +1113,7 @@ static struct i2c_driver ov6650_i2c_driver = { .driver = { .name = "ov6650", }, - .probe = ov6650_probe, + .probe_new = ov6650_probe, .remove = ov6650_remove, .id_table = ov6650_id, }; diff --git a/drivers/media/i2c/ov7640.c b/drivers/media/i2c/ov7640.c index 5e2d67f0f9f2..e6751d5cc64b 100644 --- a/drivers/media/i2c/ov7640.c +++ b/drivers/media/i2c/ov7640.c @@ -42,8 +42,7 @@ static int write_regs(struct i2c_client *client, static const struct v4l2_subdev_ops ov7640_ops; -static int ov7640_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ov7640_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct v4l2_subdev *sd; @@ -87,7 +86,7 @@ static struct i2c_driver ov7640_driver = { .driver = { .name = "ov7640", }, - .probe = ov7640_probe, + .probe_new = ov7640_probe, .remove = ov7640_remove, .id_table = ov7640_id, }; diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c index 4b9b156b53c7..11d3bef65d43 100644 --- a/drivers/media/i2c/ov7670.c +++ b/drivers/media/i2c/ov7670.c @@ -15,7 +15,6 @@ #include <linux/i2c.h> #include <linux/delay.h> #include <linux/videodev2.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c index efa18d026ac3..cf8384e09413 100644 --- a/drivers/media/i2c/ov8856.c +++ b/drivers/media/i2c/ov8856.c @@ -2110,17 +2110,18 @@ static int ov8856_set_stream(struct v4l2_subdev *sd, int enable) return ret; } -static int __ov8856_power_on(struct ov8856 *ov8856) +static int ov8856_power_on(struct device *dev) { - struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov8856 *ov8856 = to_ov8856(sd); int ret; - if (is_acpi_node(dev_fwnode(&client->dev))) + if (is_acpi_node(dev_fwnode(dev))) return 0; ret = clk_prepare_enable(ov8856->xvclk); if (ret < 0) { - dev_err(&client->dev, "failed to enable xvclk\n"); + dev_err(dev, "failed to enable xvclk\n"); return ret; } @@ -2132,7 +2133,7 @@ static int __ov8856_power_on(struct ov8856 *ov8856) ret = regulator_bulk_enable(ARRAY_SIZE(ov8856_supply_names), ov8856->supplies); if (ret < 0) { - dev_err(&client->dev, "failed to enable regulators\n"); + dev_err(dev, "failed to enable regulators\n"); goto disable_clk; } @@ -2148,17 +2149,20 @@ disable_clk: return ret; } -static void __ov8856_power_off(struct ov8856 *ov8856) +static int ov8856_power_off(struct device *dev) { - struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov8856 *ov8856 = to_ov8856(sd); - if (is_acpi_node(dev_fwnode(&client->dev))) - return; + if (is_acpi_node(dev_fwnode(dev))) + return 0; gpiod_set_value_cansleep(ov8856->reset_gpio, 1); regulator_bulk_disable(ARRAY_SIZE(ov8856_supply_names), ov8856->supplies); clk_disable_unprepare(ov8856->xvclk); + + return 0; } static int __maybe_unused ov8856_suspend(struct device *dev) @@ -2170,7 +2174,7 @@ static int __maybe_unused ov8856_suspend(struct device *dev) if (ov8856->streaming) ov8856_stop_streaming(ov8856); - __ov8856_power_off(ov8856); + ov8856_power_off(dev); mutex_unlock(&ov8856->mutex); return 0; @@ -2184,7 +2188,7 @@ static int __maybe_unused ov8856_resume(struct device *dev) mutex_lock(&ov8856->mutex); - __ov8856_power_on(ov8856); + ov8856_power_on(dev); if (ov8856->streaming) { ret = ov8856_start_streaming(ov8856); if (ret) { @@ -2451,7 +2455,7 @@ static void ov8856_remove(struct i2c_client *client) pm_runtime_disable(&client->dev); mutex_destroy(&ov8856->mutex); - __ov8856_power_off(ov8856); + ov8856_power_off(&client->dev); } static int ov8856_probe(struct i2c_client *client) @@ -2475,7 +2479,7 @@ static int ov8856_probe(struct i2c_client *client) full_power = acpi_dev_state_d0(&client->dev); if (full_power) { - ret = __ov8856_power_on(ov8856); + ret = ov8856_power_on(&client->dev); if (ret) { dev_err(&client->dev, "failed to power on\n"); return ret; @@ -2531,13 +2535,14 @@ probe_error_v4l2_ctrl_handler_free: mutex_destroy(&ov8856->mutex); probe_power_off: - __ov8856_power_off(ov8856); + ov8856_power_off(&client->dev); return ret; } static const struct dev_pm_ops ov8856_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ov8856_suspend, ov8856_resume) + SET_RUNTIME_PM_OPS(ov8856_power_off, ov8856_power_on, NULL) }; #ifdef CONFIG_ACPI diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index df144a2f6eda..37a55d53af56 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -11,8 +11,10 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> #include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> #include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> @@ -21,6 +23,13 @@ #define OV9282_MODE_STANDBY 0x00 #define OV9282_MODE_STREAMING 0x01 +#define OV9282_REG_PLL_CTRL_0D 0x030d +#define OV9282_PLL_CTRL_0D_RAW8 0x60 +#define OV9282_PLL_CTRL_0D_RAW10 0x50 + +#define OV9282_REG_TIMING_HTS 0x380c +#define OV9282_TIMING_HTS_MAX 0x7fff + /* Lines per frame */ #define OV9282_REG_LPFR 0x380e @@ -45,6 +54,17 @@ /* Group hold register */ #define OV9282_REG_HOLD 0x3308 +#define OV9282_REG_ANA_CORE_2 0x3662 +#define OV9282_ANA_CORE2_RAW8 0x07 +#define OV9282_ANA_CORE2_RAW10 0x05 + +#define OV9282_REG_TIMING_FORMAT_1 0x3820 +#define OV9282_REG_TIMING_FORMAT_2 0x3821 +#define OV9282_FLIP_BIT BIT(2) + +#define OV9282_REG_MIPI_CTRL00 0x4800 +#define OV9282_GATED_CLOCK BIT(5) + /* Input clock rate */ #define OV9282_INCLK_RATE 24000000 @@ -52,9 +72,34 @@ #define OV9282_LINK_FREQ 400000000 #define OV9282_NUM_DATA_LANES 2 +/* Pixel rate */ +#define OV9282_PIXEL_RATE_10BIT (OV9282_LINK_FREQ * 2 * \ + OV9282_NUM_DATA_LANES / 10) +#define OV9282_PIXEL_RATE_8BIT (OV9282_LINK_FREQ * 2 * \ + OV9282_NUM_DATA_LANES / 8) + +/* + * OV9282 native and active pixel array size. + * 8 dummy rows/columns on each edge of a 1280x800 active array + */ +#define OV9282_NATIVE_WIDTH 1296U +#define OV9282_NATIVE_HEIGHT 816U +#define OV9282_PIXEL_ARRAY_LEFT 8U +#define OV9282_PIXEL_ARRAY_TOP 8U +#define OV9282_PIXEL_ARRAY_WIDTH 1280U +#define OV9282_PIXEL_ARRAY_HEIGHT 800U + #define OV9282_REG_MIN 0x00 #define OV9282_REG_MAX 0xfffff +static const char * const ov9282_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OV9282_NUM_SUPPLIES ARRAY_SIZE(ov9282_supply_names) + /** * struct ov9282_reg - ov9282 sensor register * @address: Register address @@ -79,25 +124,24 @@ struct ov9282_reg_list { * struct ov9282_mode - ov9282 sensor mode structure * @width: Frame width * @height: Frame height - * @code: Format code - * @hblank: Horizontal blanking in lines + * @hblank_min: Minimum horizontal blanking in lines for non-continuous[0] and + * continuous[1] clock modes * @vblank: Vertical blanking in lines * @vblank_min: Minimum vertical blanking in lines * @vblank_max: Maximum vertical blanking in lines - * @pclk: Sensor pixel clock * @link_freq_idx: Link frequency index + * @crop: on-sensor cropping for this mode * @reg_list: Register list for sensor mode */ struct ov9282_mode { u32 width; u32 height; - u32 code; - u32 hblank; + u32 hblank_min[2]; u32 vblank; u32 vblank_min; u32 vblank_max; - u64 pclk; u32 link_freq_idx; + struct v4l2_rect crop; struct ov9282_reg_list reg_list; }; @@ -109,15 +153,18 @@ struct ov9282_mode { * @pad: Media pad. Only one pad supported * @reset_gpio: Sensor reset gpio * @inclk: Sensor input clock + * @supplies: Regulator supplies for the sensor * @ctrl_handler: V4L2 control handler * @link_freq_ctrl: Pointer to link frequency control - * @pclk_ctrl: Pointer to pixel clock control * @hblank_ctrl: Pointer to horizontal blanking control * @vblank_ctrl: Pointer to vertical blanking control * @exp_ctrl: Pointer to exposure control * @again_ctrl: Pointer to analog gain control + * @pixel_rate: Pointer to pixel rate control * @vblank: Vertical blanking in lines + * @noncontinuous_clock: Selection of CSI2 noncontinuous clock mode * @cur_mode: Pointer to current selected sensor mode + * @code: Mbus code currently selected * @mutex: Mutex for serializing sensor controls * @streaming: Flag indicating streaming state */ @@ -128,17 +175,20 @@ struct ov9282 { struct media_pad pad; struct gpio_desc *reset_gpio; struct clk *inclk; + struct regulator_bulk_data supplies[OV9282_NUM_SUPPLIES]; struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl *link_freq_ctrl; - struct v4l2_ctrl *pclk_ctrl; struct v4l2_ctrl *hblank_ctrl; struct v4l2_ctrl *vblank_ctrl; struct { struct v4l2_ctrl *exp_ctrl; struct v4l2_ctrl *again_ctrl; }; + struct v4l2_ctrl *pixel_rate; u32 vblank; + bool noncontinuous_clock; const struct ov9282_mode *cur_mode; + u32 code; struct mutex mutex; bool streaming; }; @@ -147,10 +197,15 @@ static const s64 link_freq[] = { OV9282_LINK_FREQ, }; -/* Sensor mode registers */ -static const struct ov9282_reg mode_1280x720_regs[] = { +/* + * Common registers + * + * Note: Do NOT include a software reset (0x0103, 0x01) in any of these + * register arrays as some settings are written as part of ov9282_power_on, + * and the reset will clear them. + */ +static const struct ov9282_reg common_regs[] = { {0x0302, 0x32}, - {0x030d, 0x50}, {0x030e, 0x02}, {0x3001, 0x00}, {0x3004, 0x00}, @@ -163,14 +218,10 @@ static const struct ov9282_reg mode_1280x720_regs[] = { {0x3030, 0x10}, {0x3039, 0x32}, {0x303a, 0x00}, - {0x3500, 0x00}, - {0x3501, 0x5f}, - {0x3502, 0x1e}, {0x3503, 0x08}, {0x3505, 0x8c}, {0x3507, 0x03}, {0x3508, 0x00}, - {0x3509, 0x10}, {0x3610, 0x80}, {0x3611, 0xa0}, {0x3620, 0x6e}, @@ -183,13 +234,85 @@ static const struct ov9282_reg mode_1280x720_regs[] = { {0x372d, 0x22}, {0x3731, 0x80}, {0x3732, 0x30}, - {0x3778, 0x00}, {0x377d, 0x22}, {0x3788, 0x02}, {0x3789, 0xa4}, {0x378a, 0x00}, {0x378b, 0x4a}, {0x3799, 0x20}, + {0x3881, 0x42}, + {0x38a8, 0x02}, + {0x38a9, 0x80}, + {0x38b1, 0x00}, + {0x38c4, 0x00}, + {0x38c5, 0xc0}, + {0x38c6, 0x04}, + {0x38c7, 0x80}, + {0x3920, 0xff}, + {0x4010, 0x40}, + {0x4043, 0x40}, + {0x4307, 0x30}, + {0x4317, 0x00}, + {0x4501, 0x00}, + {0x450a, 0x08}, + {0x4601, 0x04}, + {0x470f, 0x00}, + {0x4f07, 0x00}, + {0x5000, 0x9f}, + {0x5001, 0x00}, + {0x5e00, 0x00}, + {0x5d00, 0x07}, + {0x5d01, 0x00}, + {0x0101, 0x01}, + {0x1000, 0x03}, + {0x5a08, 0x84}, +}; + +static struct ov9282_reg_list common_regs_list = { + .num_of_regs = ARRAY_SIZE(common_regs), + .regs = common_regs, +}; + +#define MODE_1280_800 0 +#define MODE_1280_720 1 +#define MODE_640_400 2 + +#define DEFAULT_MODE MODE_1280_720 + +/* Sensor mode registers */ +static const struct ov9282_reg mode_1280x800_regs[] = { + {0x3778, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x05}, + {0x3805, 0x0f}, + {0x3806, 0x03}, + {0x3807, 0x2f}, + {0x3808, 0x05}, + {0x3809, 0x00}, + {0x380a, 0x03}, + {0x380b, 0x20}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x08}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3820, 0x40}, + {0x3821, 0x00}, + {0x4003, 0x40}, + {0x4008, 0x04}, + {0x4009, 0x0b}, + {0x400c, 0x00}, + {0x400d, 0x07}, + {0x4507, 0x00}, + {0x4509, 0x00}, +}; + +static const struct ov9282_reg mode_1280x720_regs[] = { + {0x3778, 0x00}, {0x3800, 0x00}, {0x3801, 0x00}, {0x3802, 0x00}, @@ -202,10 +325,6 @@ static const struct ov9282_reg mode_1280x720_regs[] = { {0x3809, 0x00}, {0x380a, 0x02}, {0x380b, 0xd0}, - {0x380c, 0x05}, - {0x380d, 0xfa}, - {0x380e, 0x06}, - {0x380f, 0xce}, {0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00}, @@ -214,56 +333,107 @@ static const struct ov9282_reg mode_1280x720_regs[] = { {0x3815, 0x11}, {0x3820, 0x3c}, {0x3821, 0x84}, - {0x3881, 0x42}, - {0x38a8, 0x02}, - {0x38a9, 0x80}, - {0x38b1, 0x00}, - {0x38c4, 0x00}, - {0x38c5, 0xc0}, - {0x38c6, 0x04}, - {0x38c7, 0x80}, - {0x3920, 0xff}, {0x4003, 0x40}, {0x4008, 0x02}, {0x4009, 0x05}, {0x400c, 0x00}, {0x400d, 0x03}, - {0x4010, 0x40}, - {0x4043, 0x40}, - {0x4307, 0x30}, - {0x4317, 0x00}, - {0x4501, 0x00}, {0x4507, 0x00}, {0x4509, 0x80}, - {0x450a, 0x08}, - {0x4601, 0x04}, - {0x470f, 0x00}, - {0x4f07, 0x00}, - {0x4800, 0x20}, - {0x5000, 0x9f}, - {0x5001, 0x00}, - {0x5e00, 0x00}, - {0x5d00, 0x07}, - {0x5d01, 0x00}, - {0x0101, 0x01}, - {0x1000, 0x03}, - {0x5a08, 0x84}, +}; + +static const struct ov9282_reg mode_640x400_regs[] = { + {0x3778, 0x10}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x05}, + {0x3805, 0x0f}, + {0x3806, 0x03}, + {0x3807, 0x2f}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0x90}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3812, 0x00}, + {0x3813, 0x04}, + {0x3814, 0x31}, + {0x3815, 0x22}, + {0x3820, 0x60}, + {0x3821, 0x01}, + {0x4008, 0x02}, + {0x4009, 0x05}, + {0x400c, 0x00}, + {0x400d, 0x03}, + {0x4507, 0x03}, + {0x4509, 0x80}, }; /* Supported sensor mode configurations */ -static const struct ov9282_mode supported_mode = { - .width = 1280, - .height = 720, - .hblank = 250, - .vblank = 1022, - .vblank_min = 151, - .vblank_max = 51540, - .pclk = 160000000, - .link_freq_idx = 0, - .code = MEDIA_BUS_FMT_Y10_1X10, - .reg_list = { - .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), - .regs = mode_1280x720_regs, +static const struct ov9282_mode supported_modes[] = { + [MODE_1280_800] = { + .width = 1280, + .height = 800, + .hblank_min = { 250, 176 }, + .vblank = 1022, + .vblank_min = 110, + .vblank_max = 51540, + .link_freq_idx = 0, + .crop = { + .left = OV9282_PIXEL_ARRAY_LEFT, + .top = OV9282_PIXEL_ARRAY_TOP, + .width = 1280, + .height = 800 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x800_regs), + .regs = mode_1280x800_regs, + }, + }, + [MODE_1280_720] = { + .width = 1280, + .height = 720, + .hblank_min = { 250, 176 }, + .vblank = 1022, + .vblank_min = 41, + .vblank_max = 51540, + .link_freq_idx = 0, + .crop = { + /* + * Note that this mode takes the top 720 lines from the + * 800 of the sensor. It does not take a middle crop. + */ + .left = OV9282_PIXEL_ARRAY_LEFT, + .top = OV9282_PIXEL_ARRAY_TOP, + .width = 1280, + .height = 720 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, + }, + }, + [MODE_640_400] = { + .width = 640, + .height = 400, + .hblank_min = { 890, 816 }, + .vblank = 1022, + .vblank_min = 22, + .vblank_max = 51540, + .link_freq_idx = 0, + .crop = { + .left = OV9282_PIXEL_ARRAY_LEFT, + .top = OV9282_PIXEL_ARRAY_TOP, + .width = 1280, + .height = 800 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_640x400_regs), + .regs = mode_640x400_regs, + }, }, }; @@ -373,19 +543,33 @@ static int ov9282_write_regs(struct ov9282 *ov9282, * ov9282_update_controls() - Update control ranges based on streaming mode * @ov9282: pointer to ov9282 device * @mode: pointer to ov9282_mode sensor mode + * @fmt: pointer to the requested mode * * Return: 0 if successful, error code otherwise. */ static int ov9282_update_controls(struct ov9282 *ov9282, - const struct ov9282_mode *mode) + const struct ov9282_mode *mode, + const struct v4l2_subdev_format *fmt) { + u32 hblank_min; + s64 pixel_rate; int ret; ret = __v4l2_ctrl_s_ctrl(ov9282->link_freq_ctrl, mode->link_freq_idx); if (ret) return ret; - ret = __v4l2_ctrl_s_ctrl(ov9282->hblank_ctrl, mode->hblank); + pixel_rate = (fmt->format.code == MEDIA_BUS_FMT_Y10_1X10) ? + OV9282_PIXEL_RATE_10BIT : OV9282_PIXEL_RATE_8BIT; + ret = __v4l2_ctrl_modify_range(ov9282->pixel_rate, pixel_rate, + pixel_rate, 1, pixel_rate); + if (ret) + return ret; + + hblank_min = mode->hblank_min[ov9282->noncontinuous_clock ? 0 : 1]; + ret = __v4l2_ctrl_modify_range(ov9282->hblank_ctrl, hblank_min, + OV9282_TIMING_HTS_MAX - mode->width, 1, + hblank_min); if (ret) return ret; @@ -403,22 +587,15 @@ static int ov9282_update_controls(struct ov9282 *ov9282, */ static int ov9282_update_exp_gain(struct ov9282 *ov9282, u32 exposure, u32 gain) { - u32 lpfr; int ret; - lpfr = ov9282->vblank + ov9282->cur_mode->height; - - dev_dbg(ov9282->dev, "Set exp %u, analog gain %u, lpfr %u", - exposure, gain, lpfr); + dev_dbg(ov9282->dev, "Set exp %u, analog gain %u", + exposure, gain); ret = ov9282_write_reg(ov9282, OV9282_REG_HOLD, 1, 1); if (ret) return ret; - ret = ov9282_write_reg(ov9282, OV9282_REG_LPFR, 2, lpfr); - if (ret) - goto error_release_group_hold; - ret = ov9282_write_reg(ov9282, OV9282_REG_EXPOSURE, 3, exposure << 4); if (ret) goto error_release_group_hold; @@ -431,6 +608,40 @@ error_release_group_hold: return ret; } +static int ov9282_set_ctrl_hflip(struct ov9282 *ov9282, int value) +{ + u32 current_val; + int ret = ov9282_read_reg(ov9282, OV9282_REG_TIMING_FORMAT_2, 1, + ¤t_val); + if (ret) + return ret; + + if (value) + current_val |= OV9282_FLIP_BIT; + else + current_val &= ~OV9282_FLIP_BIT; + + return ov9282_write_reg(ov9282, OV9282_REG_TIMING_FORMAT_2, 1, + current_val); +} + +static int ov9282_set_ctrl_vflip(struct ov9282 *ov9282, int value) +{ + u32 current_val; + int ret = ov9282_read_reg(ov9282, OV9282_REG_TIMING_FORMAT_1, 1, + ¤t_val); + if (ret) + return ret; + + if (value) + current_val |= OV9282_FLIP_BIT; + else + current_val &= ~OV9282_FLIP_BIT; + + return ov9282_write_reg(ov9282, OV9282_REG_TIMING_FORMAT_1, 1, + current_val); +} + /** * ov9282_set_ctrl() - Set subdevice control * @ctrl: pointer to v4l2_ctrl structure @@ -449,6 +660,7 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) container_of(ctrl->handler, struct ov9282, ctrl_handler); u32 analog_gain; u32 exposure; + u32 lpfr; int ret; switch (ctrl->id) { @@ -466,11 +678,14 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) OV9282_EXPOSURE_OFFSET, 1, OV9282_EXPOSURE_DEFAULT); break; - case V4L2_CID_EXPOSURE: - /* Set controls only if sensor is in power on state */ - if (!pm_runtime_get_if_in_use(ov9282->dev)) - return 0; + } + + /* Set controls only if sensor is in power on state */ + if (!pm_runtime_get_if_in_use(ov9282->dev)) + return 0; + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: exposure = ctrl->val; analog_gain = ov9282->again_ctrl->val; @@ -478,15 +693,28 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) exposure, analog_gain); ret = ov9282_update_exp_gain(ov9282, exposure, analog_gain); - - pm_runtime_put(ov9282->dev); - + break; + case V4L2_CID_VBLANK: + lpfr = ov9282->vblank + ov9282->cur_mode->height; + ret = ov9282_write_reg(ov9282, OV9282_REG_LPFR, 2, lpfr); + break; + case V4L2_CID_HFLIP: + ret = ov9282_set_ctrl_hflip(ov9282, ctrl->val); + break; + case V4L2_CID_VFLIP: + ret = ov9282_set_ctrl_vflip(ov9282, ctrl->val); + break; + case V4L2_CID_HBLANK: + ret = ov9282_write_reg(ov9282, OV9282_REG_TIMING_HTS, 2, + (ctrl->val + ov9282->cur_mode->width) >> 1); break; default: dev_err(ov9282->dev, "Invalid control %d", ctrl->id); ret = -EINVAL; } + pm_runtime_put(ov9282->dev); + return ret; } @@ -507,10 +735,16 @@ static int ov9282_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) { - if (code->index > 0) + switch (code->index) { + case 0: + code->code = MEDIA_BUS_FMT_Y10_1X10; + break; + case 1: + code->code = MEDIA_BUS_FMT_Y8_1X8; + break; + default: return -EINVAL; - - code->code = supported_mode.code; + } return 0; } @@ -527,15 +761,16 @@ static int ov9282_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_size_enum *fsize) { - if (fsize->index > 0) + if (fsize->index >= ARRAY_SIZE(supported_modes)) return -EINVAL; - if (fsize->code != supported_mode.code) + if (fsize->code != MEDIA_BUS_FMT_Y10_1X10 && + fsize->code != MEDIA_BUS_FMT_Y8_1X8) return -EINVAL; - fsize->min_width = supported_mode.width; + fsize->min_width = supported_modes[fsize->index].width; fsize->max_width = fsize->min_width; - fsize->min_height = supported_mode.height; + fsize->min_height = supported_modes[fsize->index].height; fsize->max_height = fsize->min_height; return 0; @@ -546,15 +781,17 @@ static int ov9282_enum_frame_size(struct v4l2_subdev *sd, * from selected sensor mode * @ov9282: pointer to ov9282 device * @mode: pointer to ov9282_mode sensor mode + * @code: mbus code to be stored * @fmt: V4L2 sub-device format need to be filled */ static void ov9282_fill_pad_format(struct ov9282 *ov9282, const struct ov9282_mode *mode, + u32 code, struct v4l2_subdev_format *fmt) { fmt->format.width = mode->width; fmt->format.height = mode->height; - fmt->format.code = mode->code; + fmt->format.code = code; fmt->format.field = V4L2_FIELD_NONE; fmt->format.colorspace = V4L2_COLORSPACE_RAW; fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; @@ -584,7 +821,8 @@ static int ov9282_get_pad_format(struct v4l2_subdev *sd, framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); fmt->format = *framefmt; } else { - ov9282_fill_pad_format(ov9282, ov9282->cur_mode, fmt); + ov9282_fill_pad_format(ov9282, ov9282->cur_mode, ov9282->code, + fmt); } mutex_unlock(&ov9282->mutex); @@ -606,12 +844,22 @@ static int ov9282_set_pad_format(struct v4l2_subdev *sd, { struct ov9282 *ov9282 = to_ov9282(sd); const struct ov9282_mode *mode; + u32 code; int ret = 0; mutex_lock(&ov9282->mutex); - mode = &supported_mode; - ov9282_fill_pad_format(ov9282, mode, fmt); + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, + fmt->format.height); + if (fmt->format.code == MEDIA_BUS_FMT_Y8_1X8) + code = MEDIA_BUS_FMT_Y8_1X8; + else + code = MEDIA_BUS_FMT_Y10_1X10; + + ov9282_fill_pad_format(ov9282, mode, code, fmt); if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { struct v4l2_mbus_framefmt *framefmt; @@ -619,9 +867,11 @@ static int ov9282_set_pad_format(struct v4l2_subdev *sd, framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); *framefmt = fmt->format; } else { - ret = ov9282_update_controls(ov9282, mode); - if (!ret) + ret = ov9282_update_controls(ov9282, mode, fmt); + if (!ret) { ov9282->cur_mode = mode; + ov9282->code = code; + } } mutex_unlock(&ov9282->mutex); @@ -643,11 +893,64 @@ static int ov9282_init_pad_cfg(struct v4l2_subdev *sd, struct v4l2_subdev_format fmt = { 0 }; fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; - ov9282_fill_pad_format(ov9282, &supported_mode, &fmt); + ov9282_fill_pad_format(ov9282, &supported_modes[DEFAULT_MODE], + ov9282->code, &fmt); return ov9282_set_pad_format(sd, sd_state, &fmt); } +static const struct v4l2_rect * +__ov9282_get_pad_crop(struct ov9282 *ov9282, + struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&ov9282->sd, sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &ov9282->cur_mode->crop; + } + + return NULL; +} + +static int ov9282_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct ov9282 *ov9282 = to_ov9282(sd); + + mutex_lock(&ov9282->mutex); + sel->r = *__ov9282_get_pad_crop(ov9282, sd_state, sel->pad, + sel->which); + mutex_unlock(&ov9282->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = OV9282_NATIVE_WIDTH; + sel->r.height = OV9282_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = OV9282_PIXEL_ARRAY_TOP; + sel->r.left = OV9282_PIXEL_ARRAY_LEFT; + sel->r.width = OV9282_PIXEL_ARRAY_WIDTH; + sel->r.height = OV9282_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + /** * ov9282_start_streaming() - Start sensor stream * @ov9282: pointer to ov9282 device @@ -656,9 +959,34 @@ static int ov9282_init_pad_cfg(struct v4l2_subdev *sd, */ static int ov9282_start_streaming(struct ov9282 *ov9282) { + const struct ov9282_reg bitdepth_regs[2][2] = { + { + {OV9282_REG_PLL_CTRL_0D, OV9282_PLL_CTRL_0D_RAW10}, + {OV9282_REG_ANA_CORE_2, OV9282_ANA_CORE2_RAW10}, + }, { + {OV9282_REG_PLL_CTRL_0D, OV9282_PLL_CTRL_0D_RAW8}, + {OV9282_REG_ANA_CORE_2, OV9282_ANA_CORE2_RAW8}, + } + }; const struct ov9282_reg_list *reg_list; + int bitdepth_index; int ret; + /* Write common registers */ + ret = ov9282_write_regs(ov9282, common_regs_list.regs, + common_regs_list.num_of_regs); + if (ret) { + dev_err(ov9282->dev, "fail to write common registers"); + return ret; + } + + bitdepth_index = ov9282->code == MEDIA_BUS_FMT_Y10_1X10 ? 0 : 1; + ret = ov9282_write_regs(ov9282, bitdepth_regs[bitdepth_index], 2); + if (ret) { + dev_err(ov9282->dev, "fail to write bitdepth regs"); + return ret; + } + /* Write sensor mode registers */ reg_list = &ov9282->cur_mode->reg_list; ret = ov9282_write_regs(ov9282, reg_list->regs, reg_list->num_of_regs); @@ -767,6 +1095,18 @@ static int ov9282_detect(struct ov9282 *ov9282) return 0; } +static int ov9282_configure_regulators(struct ov9282 *ov9282) +{ + unsigned int i; + + for (i = 0; i < OV9282_NUM_SUPPLIES; i++) + ov9282->supplies[i].supply = ov9282_supply_names[i]; + + return devm_regulator_bulk_get(ov9282->dev, + OV9282_NUM_SUPPLIES, + ov9282->supplies); +} + /** * ov9282_parse_hw_config() - Parse HW configuration and check if supported * @ov9282: pointer to ov9282 device @@ -803,6 +1143,12 @@ static int ov9282_parse_hw_config(struct ov9282 *ov9282) return PTR_ERR(ov9282->inclk); } + ret = ov9282_configure_regulators(ov9282); + if (ret) { + dev_err(ov9282->dev, "Failed to get power regulators\n"); + return ret; + } + rate = clk_get_rate(ov9282->inclk); if (rate != OV9282_INCLK_RATE) { dev_err(ov9282->dev, "inclk frequency mismatch"); @@ -818,6 +1164,9 @@ static int ov9282_parse_hw_config(struct ov9282 *ov9282) if (ret) return ret; + ov9282->noncontinuous_clock = + bus_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV9282_NUM_DATA_LANES) { dev_err(ov9282->dev, "number of CSI2 data lanes %d is not supported", @@ -845,6 +1194,11 @@ done_endpoint_free: } /* V4l2 subdevice ops */ +static const struct v4l2_subdev_core_ops ov9282_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + static const struct v4l2_subdev_video_ops ov9282_video_ops = { .s_stream = ov9282_set_stream, }; @@ -855,9 +1209,11 @@ static const struct v4l2_subdev_pad_ops ov9282_pad_ops = { .enum_frame_size = ov9282_enum_frame_size, .get_fmt = ov9282_get_pad_format, .set_fmt = ov9282_set_pad_format, + .get_selection = ov9282_get_selection, }; static const struct v4l2_subdev_ops ov9282_subdev_ops = { + .core = &ov9282_core_ops, .video = &ov9282_video_ops, .pad = &ov9282_pad_ops, }; @@ -874,6 +1230,12 @@ static int ov9282_power_on(struct device *dev) struct ov9282 *ov9282 = to_ov9282(sd); int ret; + ret = regulator_bulk_enable(OV9282_NUM_SUPPLIES, ov9282->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + return ret; + } + usleep_range(400, 600); gpiod_set_value_cansleep(ov9282->reset_gpio, 1); @@ -886,11 +1248,23 @@ static int ov9282_power_on(struct device *dev) usleep_range(400, 600); + ret = ov9282_write_reg(ov9282, OV9282_REG_MIPI_CTRL00, 1, + ov9282->noncontinuous_clock ? + OV9282_GATED_CLOCK : 0); + if (ret) { + dev_err(ov9282->dev, "fail to write MIPI_CTRL00"); + goto error_clk; + } + return 0; +error_clk: + clk_disable_unprepare(ov9282->inclk); error_reset: gpiod_set_value_cansleep(ov9282->reset_gpio, 0); + regulator_bulk_disable(OV9282_NUM_SUPPLIES, ov9282->supplies); + return ret; } @@ -909,6 +1283,8 @@ static int ov9282_power_off(struct device *dev) clk_disable_unprepare(ov9282->inclk); + regulator_bulk_disable(OV9282_NUM_SUPPLIES, ov9282->supplies); + return 0; } @@ -922,10 +1298,12 @@ static int ov9282_init_controls(struct ov9282 *ov9282) { struct v4l2_ctrl_handler *ctrl_hdlr = &ov9282->ctrl_handler; const struct ov9282_mode *mode = ov9282->cur_mode; + struct v4l2_fwnode_device_properties props; + u32 hblank_min; u32 lpfr; int ret; - ret = v4l2_ctrl_handler_init(ctrl_hdlr, 6); + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); if (ret) return ret; @@ -959,12 +1337,18 @@ static int ov9282_init_controls(struct ov9282 *ov9282) mode->vblank_max, 1, mode->vblank); + v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_VFLIP, + 0, 1, 1, 1); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_HFLIP, + 0, 1, 1, 1); + /* Read only controls */ - ov9282->pclk_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, - &ov9282_ctrl_ops, - V4L2_CID_PIXEL_RATE, - mode->pclk, mode->pclk, - 1, mode->pclk); + ov9282->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, + V4L2_CID_PIXEL_RATE, + OV9282_PIXEL_RATE_10BIT, + OV9282_PIXEL_RATE_10BIT, 1, + OV9282_PIXEL_RATE_10BIT); ov9282->link_freq_ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov9282_ctrl_ops, @@ -976,16 +1360,22 @@ static int ov9282_init_controls(struct ov9282 *ov9282) if (ov9282->link_freq_ctrl) ov9282->link_freq_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + hblank_min = mode->hblank_min[ov9282->noncontinuous_clock ? 0 : 1]; ov9282->hblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_HBLANK, - OV9282_REG_MIN, - OV9282_REG_MAX, - 1, mode->hblank); - if (ov9282->hblank_ctrl) - ov9282->hblank_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + hblank_min, + OV9282_TIMING_HTS_MAX - mode->width, + 1, hblank_min); + + ret = v4l2_fwnode_device_parse(ov9282->dev, &props); + if (!ret) { + /* Failure sets ctrl_hdlr->error, which we check afterwards anyway */ + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov9282_ctrl_ops, + &props); + } - if (ctrl_hdlr->error) { + if (ctrl_hdlr->error || ret) { dev_err(ov9282->dev, "control init failed: %d", ctrl_hdlr->error); v4l2_ctrl_handler_free(ctrl_hdlr); @@ -1016,6 +1406,8 @@ static int ov9282_probe(struct i2c_client *client) /* Initialize subdev */ v4l2_i2c_subdev_init(&ov9282->sd, client, &ov9282_subdev_ops); + v4l2_i2c_subdev_set_name(&ov9282->sd, client, + device_get_match_data(ov9282->dev), NULL); ret = ov9282_parse_hw_config(ov9282); if (ret) { @@ -1038,8 +1430,9 @@ static int ov9282_probe(struct i2c_client *client) goto error_power_off; } - /* Set default mode to max resolution */ - ov9282->cur_mode = &supported_mode; + /* Set default mode to first mode */ + ov9282->cur_mode = &supported_modes[DEFAULT_MODE]; + ov9282->code = MEDIA_BUS_FMT_Y10_1X10; ov9282->vblank = ov9282->cur_mode->vblank; ret = ov9282_init_controls(ov9282); @@ -1049,7 +1442,8 @@ static int ov9282_probe(struct i2c_client *client) } /* Initialize subdev */ - ov9282->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov9282->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; ov9282->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; /* Initialize source pad */ @@ -1113,7 +1507,8 @@ static const struct dev_pm_ops ov9282_pm_ops = { }; static const struct of_device_id ov9282_of_match[] = { - { .compatible = "ovti,ov9282" }, + { .compatible = "ovti,ov9281", .data = "ov9281" }, + { .compatible = "ovti,ov9282", .data = "ov9282" }, { } }; diff --git a/drivers/media/i2c/ov9640.c b/drivers/media/i2c/ov9640.c index 8b80be33c5f4..a80fa59bf2ae 100644 --- a/drivers/media/i2c/ov9640.c +++ b/drivers/media/i2c/ov9640.c @@ -682,8 +682,7 @@ static const struct v4l2_subdev_ops ov9640_subdev_ops = { /* * i2c_driver function */ -static int ov9640_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int ov9640_probe(struct i2c_client *client) { struct ov9640_priv *priv; int ret; @@ -763,7 +762,7 @@ static struct i2c_driver ov9640_i2c_driver = { .driver = { .name = "ov9640", }, - .probe = ov9640_probe, + .probe_new = ov9640_probe, .remove = ov9640_remove, .id_table = ov9640_id, }; diff --git a/drivers/media/i2c/ov9650.c b/drivers/media/i2c/ov9650.c index 4d458993e6d6..7e7cb1e4520e 100644 --- a/drivers/media/i2c/ov9650.c +++ b/drivers/media/i2c/ov9650.c @@ -10,7 +10,6 @@ */ #include <linux/clk.h> #include <linux/delay.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/kernel.h> @@ -30,7 +29,6 @@ #include <media/v4l2-image-sizes.h> #include <media/v4l2-subdev.h> #include <media/v4l2-mediabus.h> -#include <media/i2c/ov9650.h> static int debug; module_param(debug, int, 0644); @@ -1402,38 +1400,6 @@ static const struct v4l2_subdev_ops ov965x_subdev_ops = { .video = &ov965x_video_ops, }; -/* - * Reset and power down GPIOs configuration - */ -static int ov965x_configure_gpios_pdata(struct ov965x *ov965x, - const struct ov9650_platform_data *pdata) -{ - int ret, i; - int gpios[NUM_GPIOS]; - struct device *dev = regmap_get_device(ov965x->regmap); - - gpios[GPIO_PWDN] = pdata->gpio_pwdn; - gpios[GPIO_RST] = pdata->gpio_reset; - - for (i = 0; i < ARRAY_SIZE(ov965x->gpios); i++) { - int gpio = gpios[i]; - - if (!gpio_is_valid(gpio)) - continue; - ret = devm_gpio_request_one(dev, gpio, - GPIOF_OUT_INIT_HIGH, "OV965X"); - if (ret < 0) - return ret; - v4l2_dbg(1, debug, &ov965x->sd, "set gpio %d to 1\n", gpio); - - gpio_set_value_cansleep(gpio, 1); - gpio_export(gpio, 0); - ov965x->gpios[i] = gpio_to_desc(gpio); - } - - return 0; -} - static int ov965x_configure_gpios(struct ov965x *ov965x) { struct device *dev = regmap_get_device(ov965x->regmap); @@ -1493,7 +1459,6 @@ out: static int ov965x_probe(struct i2c_client *client) { - const struct ov9650_platform_data *pdata = client->dev.platform_data; struct v4l2_subdev *sd; struct ov965x *ov965x; int ret; @@ -1513,17 +1478,7 @@ static int ov965x_probe(struct i2c_client *client) return PTR_ERR(ov965x->regmap); } - if (pdata) { - if (pdata->mclk_frequency == 0) { - dev_err(&client->dev, "MCLK frequency not specified\n"); - return -EINVAL; - } - ov965x->mclk_frequency = pdata->mclk_frequency; - - ret = ov965x_configure_gpios_pdata(ov965x, pdata); - if (ret < 0) - return ret; - } else if (dev_fwnode(&client->dev)) { + if (dev_fwnode(&client->dev)) { ov965x->clk = devm_clk_get(&client->dev, NULL); if (IS_ERR(ov965x->clk)) return PTR_ERR(ov965x->clk); @@ -1534,7 +1489,7 @@ static int ov965x_probe(struct i2c_client *client) return ret; } else { dev_err(&client->dev, - "Neither platform data nor device property specified\n"); + "No device properties specified\n"); return -EINVAL; } diff --git a/drivers/media/i2c/rj54n1cb0c.c b/drivers/media/i2c/rj54n1cb0c.c index 1c3502f34cd3..9db5473daba0 100644 --- a/drivers/media/i2c/rj54n1cb0c.c +++ b/drivers/media/i2c/rj54n1cb0c.c @@ -1297,8 +1297,7 @@ done: return ret; } -static int rj54n1_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int rj54n1_probe(struct i2c_client *client) { struct rj54n1 *rj54n1; struct i2c_adapter *adapter = client->adapter; @@ -1422,7 +1421,7 @@ static struct i2c_driver rj54n1_i2c_driver = { .driver = { .name = "rj54n1cb0c", }, - .probe = rj54n1_probe, + .probe_new = rj54n1_probe, .remove = rj54n1_remove, .id_table = rj54n1_id, }; diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-core.c b/drivers/media/i2c/s5c73m3/s5c73m3-core.c index d96ba58ce1e5..59b03b0860d5 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-core.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-core.c @@ -10,12 +10,11 @@ #include <linux/clk.h> #include <linux/delay.h> #include <linux/firmware.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/init.h> #include <linux/media.h> #include <linux/module.h> -#include <linux/of_gpio.h> #include <linux/of_graph.h> #include <linux/regulator/consumer.h> #include <linux/sizes.h> @@ -1347,24 +1346,6 @@ static int s5c73m3_oif_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) return 0; } -static int s5c73m3_gpio_set_value(struct s5c73m3 *priv, int id, u32 val) -{ - if (!gpio_is_valid(priv->gpio[id].gpio)) - return 0; - gpio_set_value(priv->gpio[id].gpio, !!val); - return 1; -} - -static int s5c73m3_gpio_assert(struct s5c73m3 *priv, int id) -{ - return s5c73m3_gpio_set_value(priv, id, priv->gpio[id].level); -} - -static int s5c73m3_gpio_deassert(struct s5c73m3 *priv, int id) -{ - return s5c73m3_gpio_set_value(priv, id, !priv->gpio[id].level); -} - static int __s5c73m3_power_on(struct s5c73m3 *state) { int i, ret; @@ -1386,10 +1367,9 @@ static int __s5c73m3_power_on(struct s5c73m3 *state) v4l2_dbg(1, s5c73m3_dbg, &state->oif_sd, "clock frequency: %ld\n", clk_get_rate(state->clock)); - s5c73m3_gpio_deassert(state, STBY); + gpiod_set_value(state->stby, 0); usleep_range(100, 200); - - s5c73m3_gpio_deassert(state, RSET); + gpiod_set_value(state->reset, 0); usleep_range(50, 100); return 0; @@ -1404,11 +1384,10 @@ static int __s5c73m3_power_off(struct s5c73m3 *state) { int i, ret; - if (s5c73m3_gpio_assert(state, RSET)) - usleep_range(10, 50); - - if (s5c73m3_gpio_assert(state, STBY)) - usleep_range(100, 200); + gpiod_set_value(state->reset, 1); + usleep_range(10, 50); + gpiod_set_value(state->stby, 1); + usleep_range(100, 200); clk_disable_unprepare(state->clock); @@ -1543,58 +1522,10 @@ static const struct v4l2_subdev_ops oif_subdev_ops = { .video = &s5c73m3_oif_video_ops, }; -static int s5c73m3_configure_gpios(struct s5c73m3 *state) -{ - static const char * const gpio_names[] = { - "S5C73M3_STBY", "S5C73M3_RST" - }; - struct i2c_client *c = state->i2c_client; - struct s5c73m3_gpio *g = state->gpio; - int ret, i; - - for (i = 0; i < GPIO_NUM; ++i) { - unsigned int flags = GPIOF_DIR_OUT; - if (g[i].level) - flags |= GPIOF_INIT_HIGH; - ret = devm_gpio_request_one(&c->dev, g[i].gpio, flags, - gpio_names[i]); - if (ret) { - v4l2_err(c, "failed to request gpio %s\n", - gpio_names[i]); - return ret; - } - } - return 0; -} - -static int s5c73m3_parse_gpios(struct s5c73m3 *state) -{ - static const char * const prop_names[] = { - "standby-gpios", "xshutdown-gpios", - }; - struct device *dev = &state->i2c_client->dev; - struct device_node *node = dev->of_node; - int ret, i; - - for (i = 0; i < GPIO_NUM; ++i) { - enum of_gpio_flags of_flags; - - ret = of_get_named_gpio_flags(node, prop_names[i], - 0, &of_flags); - if (ret < 0) { - dev_err(dev, "failed to parse %s DT property\n", - prop_names[i]); - return -EINVAL; - } - state->gpio[i].gpio = ret; - state->gpio[i].level = !(of_flags & OF_GPIO_ACTIVE_LOW); - } - return 0; -} - static int s5c73m3_get_platform_data(struct s5c73m3 *state) { - struct device *dev = &state->i2c_client->dev; + struct i2c_client *c = state->i2c_client; + struct device *dev = &c->dev; const struct s5c73m3_platform_data *pdata = dev->platform_data; struct device_node *node = dev->of_node; struct device_node *node_ep; @@ -1608,8 +1539,6 @@ static int s5c73m3_get_platform_data(struct s5c73m3 *state) } state->mclk_frequency = pdata->mclk_frequency; - state->gpio[STBY] = pdata->gpio_stby; - state->gpio[RSET] = pdata->gpio_reset; return 0; } @@ -1624,9 +1553,17 @@ static int s5c73m3_get_platform_data(struct s5c73m3 *state) state->mclk_frequency); } - ret = s5c73m3_parse_gpios(state); - if (ret < 0) - return -EINVAL; + /* Request GPIO lines asserted */ + state->stby = devm_gpiod_get(dev, "standby", GPIOD_OUT_HIGH); + if (IS_ERR(state->stby)) + return dev_err_probe(dev, PTR_ERR(state->stby), + "failed to request gpio S5C73M3_STBY\n"); + gpiod_set_consumer_name(state->stby, "S5C73M3_STBY"); + state->reset = devm_gpiod_get(dev, "xshutdown", GPIOD_OUT_HIGH); + if (IS_ERR(state->reset)) + return dev_err_probe(dev, PTR_ERR(state->reset), + "failed to request gpio S5C73M3_RST\n"); + gpiod_set_consumer_name(state->reset, "S5C73M3_RST"); node_ep = of_graph_get_next_endpoint(node, NULL); if (!node_ep) { @@ -1708,10 +1645,6 @@ static int s5c73m3_probe(struct i2c_client *client) if (ret < 0) return ret; - ret = s5c73m3_configure_gpios(state); - if (ret) - goto out_err; - for (i = 0; i < S5C73M3_MAX_SUPPLIES; i++) state->supplies[i].supply = s5c73m3_supply_names[i]; diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c b/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c index 141ad0ba7f5a..e3543ae384ed 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c @@ -10,7 +10,6 @@ #include <linux/sizes.h> #include <linux/delay.h> #include <linux/firmware.h> -#include <linux/gpio.h> #include <linux/i2c.h> #include <linux/init.h> #include <linux/media.h> diff --git a/drivers/media/i2c/s5c73m3/s5c73m3.h b/drivers/media/i2c/s5c73m3/s5c73m3.h index c3fcfdd3ea66..1fc7df41c5ee 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3.h +++ b/drivers/media/i2c/s5c73m3/s5c73m3.h @@ -12,6 +12,7 @@ #include <linux/clk.h> #include <linux/kernel.h> #include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-subdev.h> @@ -351,12 +352,6 @@ struct s5c73m3_ctrls { struct v4l2_ctrl *scene_mode; }; -enum s5c73m3_gpio_id { - STBY, - RSET, - GPIO_NUM, -}; - enum s5c73m3_resolution_types { RES_ISP, RES_JPEG, @@ -383,7 +378,8 @@ struct s5c73m3 { u32 i2c_read_address; struct regulator_bulk_data supplies[S5C73M3_MAX_SUPPLIES]; - struct s5c73m3_gpio gpio[GPIO_NUM]; + struct gpio_desc *stby; + struct gpio_desc *reset; struct clk *clock; diff --git a/drivers/media/i2c/s5k4ecgx.c b/drivers/media/i2c/s5k4ecgx.c deleted file mode 100644 index 3dddcd9dd351..000000000000 --- a/drivers/media/i2c/s5k4ecgx.c +++ /dev/null @@ -1,1032 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Driver for Samsung S5K4ECGX 1/4" 5Mp CMOS Image Sensor SoC - * with an Embedded Image Signal Processor. - * - * Copyright (C) 2012, Linaro, Sangwook Lee <sangwook.lee@linaro.org> - * Copyright (C) 2012, Insignal Co,. Ltd, Homin Lee <suapapa@insignal.co.kr> - * - * Based on s5k6aa and noon010pc30 driver - * Copyright (C) 2011, Samsung Electronics Co., Ltd. - */ - -#include <linux/clk.h> -#include <linux/crc32.h> -#include <linux/ctype.h> -#include <linux/delay.h> -#include <linux/firmware.h> -#include <linux/gpio.h> -#include <linux/i2c.h> -#include <linux/module.h> -#include <linux/regulator/consumer.h> -#include <linux/slab.h> -#include <asm/unaligned.h> - -#include <media/media-entity.h> -#include <media/i2c/s5k4ecgx.h> -#include <media/v4l2-ctrls.h> -#include <media/v4l2-device.h> -#include <media/v4l2-mediabus.h> -#include <media/v4l2-subdev.h> - -static int debug; -module_param(debug, int, 0644); - -#define S5K4ECGX_DRIVER_NAME "s5k4ecgx" -#define S5K4ECGX_FIRMWARE "s5k4ecgx.bin" - -/* Firmware revision information */ -#define REG_FW_REVISION 0x700001a6 -#define REG_FW_VERSION 0x700001a4 -#define S5K4ECGX_REVISION_1_1 0x11 -#define S5K4ECGX_FW_VERSION 0x4ec0 - -/* General purpose parameters */ -#define REG_USER_BRIGHTNESS 0x7000022c -#define REG_USER_CONTRAST 0x7000022e -#define REG_USER_SATURATION 0x70000230 - -#define REG_G_ENABLE_PREV 0x7000023e -#define REG_G_ENABLE_PREV_CHG 0x70000240 -#define REG_G_NEW_CFG_SYNC 0x7000024a -#define REG_G_PREV_IN_WIDTH 0x70000250 -#define REG_G_PREV_IN_HEIGHT 0x70000252 -#define REG_G_PREV_IN_XOFFS 0x70000254 -#define REG_G_PREV_IN_YOFFS 0x70000256 -#define REG_G_CAP_IN_WIDTH 0x70000258 -#define REG_G_CAP_IN_HEIGHT 0x7000025a -#define REG_G_CAP_IN_XOFFS 0x7000025c -#define REG_G_CAP_IN_YOFFS 0x7000025e -#define REG_G_INPUTS_CHANGE_REQ 0x70000262 -#define REG_G_ACTIVE_PREV_CFG 0x70000266 -#define REG_G_PREV_CFG_CHG 0x70000268 -#define REG_G_PREV_OPEN_AFTER_CH 0x7000026a - -/* Preview context register sets. n = 0...4. */ -#define PREG(n, x) ((n) * 0x30 + (x)) -#define REG_P_OUT_WIDTH(n) PREG(n, 0x700002a6) -#define REG_P_OUT_HEIGHT(n) PREG(n, 0x700002a8) -#define REG_P_FMT(n) PREG(n, 0x700002aa) -#define REG_P_PVI_MASK(n) PREG(n, 0x700002b4) -#define REG_P_FR_TIME_TYPE(n) PREG(n, 0x700002be) -#define FR_TIME_DYNAMIC 0 -#define FR_TIME_FIXED 1 -#define FR_TIME_FIXED_ACCURATE 2 -#define REG_P_FR_TIME_Q_TYPE(n) PREG(n, 0x700002c0) -#define FR_TIME_Q_DYNAMIC 0 -#define FR_TIME_Q_BEST_FRRATE 1 -#define FR_TIME_Q_BEST_QUALITY 2 - -/* Frame period in 0.1 ms units */ -#define REG_P_MAX_FR_TIME(n) PREG(n, 0x700002c2) -#define REG_P_MIN_FR_TIME(n) PREG(n, 0x700002c4) -#define US_TO_FR_TIME(__t) ((__t) / 100) -#define REG_P_PREV_MIRROR(n) PREG(n, 0x700002d0) -#define REG_P_CAP_MIRROR(n) PREG(n, 0x700002d2) - -#define REG_G_PREVZOOM_IN_WIDTH 0x70000494 -#define REG_G_PREVZOOM_IN_HEIGHT 0x70000496 -#define REG_G_PREVZOOM_IN_XOFFS 0x70000498 -#define REG_G_PREVZOOM_IN_YOFFS 0x7000049a -#define REG_G_CAPZOOM_IN_WIDTH 0x7000049c -#define REG_G_CAPZOOM_IN_HEIGHT 0x7000049e -#define REG_G_CAPZOOM_IN_XOFFS 0x700004a0 -#define REG_G_CAPZOOM_IN_YOFFS 0x700004a2 - -/* n = 0...4 */ -#define REG_USER_SHARPNESS(n) (0x70000a28 + (n) * 0xb6) - -/* Reduce sharpness range for user space API */ -#define SHARPNESS_DIV 8208 -#define TOK_TERM 0xffffffff - -/* - * FIXME: This is copied from s5k6aa, because of no information - * in the S5K4ECGX datasheet. - * H/W register Interface (0xd0000000 - 0xd0000fff) - */ -#define AHB_MSB_ADDR_PTR 0xfcfc -#define GEN_REG_OFFSH 0xd000 -#define REG_CMDWR_ADDRH 0x0028 -#define REG_CMDWR_ADDRL 0x002a -#define REG_CMDRD_ADDRH 0x002c -#define REG_CMDRD_ADDRL 0x002e -#define REG_CMDBUF0_ADDR 0x0f12 - -struct s5k4ecgx_frmsize { - struct v4l2_frmsize_discrete size; - /* Fixed sensor matrix crop rectangle */ - struct v4l2_rect input_window; -}; - -struct regval_list { - u32 addr; - u16 val; -}; - -/* - * TODO: currently only preview is supported and snapshot (capture) - * is not implemented yet - */ -static const struct s5k4ecgx_frmsize s5k4ecgx_prev_sizes[] = { - { - .size = { 176, 144 }, - .input_window = { 0x00, 0x00, 0x928, 0x780 }, - }, { - .size = { 352, 288 }, - .input_window = { 0x00, 0x00, 0x928, 0x780 }, - }, { - .size = { 640, 480 }, - .input_window = { 0x00, 0x00, 0xa00, 0x780 }, - }, { - .size = { 720, 480 }, - .input_window = { 0x00, 0x00, 0xa00, 0x6a8 }, - } -}; - -#define S5K4ECGX_NUM_PREV ARRAY_SIZE(s5k4ecgx_prev_sizes) - -struct s5k4ecgx_pixfmt { - u32 code; - u32 colorspace; - /* REG_TC_PCFG_Format register value */ - u16 reg_p_format; -}; - -/* By default value, output from sensor will be YUV422 0-255 */ -static const struct s5k4ecgx_pixfmt s5k4ecgx_formats[] = { - { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG, 5 }, -}; - -static const char * const s5k4ecgx_supply_names[] = { - /* - * Usually 2.8V is used for analog power (vdda) - * and digital IO (vddio, vdddcore) - */ - "vdda", - "vddio", - "vddcore", - "vddreg", /* The internal s5k4ecgx regulator's supply (1.8V) */ -}; - -#define S5K4ECGX_NUM_SUPPLIES ARRAY_SIZE(s5k4ecgx_supply_names) - -enum s5k4ecgx_gpio_id { - STBY, - RSET, - GPIO_NUM, -}; - -struct s5k4ecgx { - struct v4l2_subdev sd; - struct media_pad pad; - struct v4l2_ctrl_handler handler; - - struct s5k4ecgx_platform_data *pdata; - const struct s5k4ecgx_pixfmt *curr_pixfmt; - const struct s5k4ecgx_frmsize *curr_frmsize; - struct mutex lock; - u8 streaming; - u8 set_params; - - struct regulator_bulk_data supplies[S5K4ECGX_NUM_SUPPLIES]; - struct s5k4ecgx_gpio gpio[GPIO_NUM]; -}; - -static inline struct s5k4ecgx *to_s5k4ecgx(struct v4l2_subdev *sd) -{ - return container_of(sd, struct s5k4ecgx, sd); -} - -static int s5k4ecgx_i2c_read(struct i2c_client *client, u16 addr, u16 *val) -{ - u8 wbuf[2] = { addr >> 8, addr & 0xff }; - struct i2c_msg msg[2]; - u8 rbuf[2]; - int ret; - - msg[0].addr = client->addr; - msg[0].flags = 0; - msg[0].len = 2; - msg[0].buf = wbuf; - - msg[1].addr = client->addr; - msg[1].flags = I2C_M_RD; - msg[1].len = 2; - msg[1].buf = rbuf; - - ret = i2c_transfer(client->adapter, msg, 2); - *val = be16_to_cpu(*((__be16 *)rbuf)); - - v4l2_dbg(4, debug, client, "i2c_read: 0x%04X : 0x%04x\n", addr, *val); - - return ret == 2 ? 0 : ret; -} - -static int s5k4ecgx_i2c_write(struct i2c_client *client, u16 addr, u16 val) -{ - u8 buf[4] = { addr >> 8, addr & 0xff, val >> 8, val & 0xff }; - - int ret = i2c_master_send(client, buf, 4); - v4l2_dbg(4, debug, client, "i2c_write: 0x%04x : 0x%04x\n", addr, val); - - return ret == 4 ? 0 : ret; -} - -static int s5k4ecgx_write(struct i2c_client *client, u32 addr, u16 val) -{ - u16 high = addr >> 16, low = addr & 0xffff; - int ret; - - v4l2_dbg(3, debug, client, "write: 0x%08x : 0x%04x\n", addr, val); - - ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRH, high); - if (!ret) - ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRL, low); - if (!ret) - ret = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); - - return ret; -} - -static int s5k4ecgx_read(struct i2c_client *client, u32 addr, u16 *val) -{ - u16 high = addr >> 16, low = addr & 0xffff; - int ret; - - ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRH, high); - if (!ret) - ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRL, low); - if (!ret) - ret = s5k4ecgx_i2c_read(client, REG_CMDBUF0_ADDR, val); - - return ret; -} - -static int s5k4ecgx_read_fw_ver(struct v4l2_subdev *sd) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - u16 hw_rev, fw_ver = 0; - int ret; - - ret = s5k4ecgx_read(client, REG_FW_VERSION, &fw_ver); - if (ret < 0 || fw_ver != S5K4ECGX_FW_VERSION) { - v4l2_err(sd, "FW version check failed!\n"); - return -ENODEV; - } - - ret = s5k4ecgx_read(client, REG_FW_REVISION, &hw_rev); - if (ret < 0) - return ret; - - v4l2_info(sd, "chip found FW ver: 0x%x, HW rev: 0x%x\n", - fw_ver, hw_rev); - return 0; -} - -static int s5k4ecgx_set_ahb_address(struct v4l2_subdev *sd) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret; - - /* Set APB peripherals start address */ - ret = s5k4ecgx_i2c_write(client, AHB_MSB_ADDR_PTR, GEN_REG_OFFSH); - if (ret < 0) - return ret; - /* - * FIXME: This is copied from s5k6aa, because of no information - * in s5k4ecgx's datasheet. - * sw_reset is activated to put device into idle status - */ - ret = s5k4ecgx_i2c_write(client, 0x0010, 0x0001); - if (ret < 0) - return ret; - - ret = s5k4ecgx_i2c_write(client, 0x1030, 0x0000); - if (ret < 0) - return ret; - /* Halt ARM CPU */ - return s5k4ecgx_i2c_write(client, 0x0014, 0x0001); -} - -#define FW_CRC_SIZE 4 -/* Register address, value are 4, 2 bytes */ -#define FW_RECORD_SIZE 6 -/* - * The firmware has following format: - * < total number of records (4 bytes + 2 bytes padding) N >, - * < record 0 >, ..., < record N - 1 >, < CRC32-CCITT (4-bytes) >, - * where "record" is a 4-byte register address followed by 2-byte - * register value (little endian). - * The firmware generator can be found in following git repository: - * git://git.linaro.org/people/sangwook/fimc-v4l2-app.git - */ -static int s5k4ecgx_load_firmware(struct v4l2_subdev *sd) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - const struct firmware *fw; - const u8 *ptr; - int err, i, regs_num; - u32 addr, crc, crc_file, addr_inc = 0; - u16 val; - - err = request_firmware(&fw, S5K4ECGX_FIRMWARE, sd->v4l2_dev->dev); - if (err) { - v4l2_err(sd, "Failed to read firmware %s\n", S5K4ECGX_FIRMWARE); - return err; - } - regs_num = get_unaligned_le32(fw->data); - - v4l2_dbg(3, debug, sd, "FW: %s size %zu register sets %d\n", - S5K4ECGX_FIRMWARE, fw->size, regs_num); - - regs_num++; /* Add header */ - if (fw->size != regs_num * FW_RECORD_SIZE + FW_CRC_SIZE) { - err = -EINVAL; - goto fw_out; - } - crc_file = get_unaligned_le32(fw->data + regs_num * FW_RECORD_SIZE); - crc = crc32_le(~0, fw->data, regs_num * FW_RECORD_SIZE); - if (crc != crc_file) { - v4l2_err(sd, "FW: invalid crc (%#x:%#x)\n", crc, crc_file); - err = -EINVAL; - goto fw_out; - } - ptr = fw->data + FW_RECORD_SIZE; - for (i = 1; i < regs_num; i++) { - addr = get_unaligned_le32(ptr); - ptr += sizeof(u32); - val = get_unaligned_le16(ptr); - ptr += sizeof(u16); - if (addr - addr_inc != 2) - err = s5k4ecgx_write(client, addr, val); - else - err = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); - if (err) - break; - addr_inc = addr; - } -fw_out: - release_firmware(fw); - return err; -} - -/* Set preview and capture input window */ -static int s5k4ecgx_set_input_window(struct i2c_client *c, - const struct v4l2_rect *r) -{ - int ret; - - ret = s5k4ecgx_write(c, REG_G_PREV_IN_WIDTH, r->width); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREV_IN_HEIGHT, r->height); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREV_IN_XOFFS, r->left); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREV_IN_YOFFS, r->top); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAP_IN_WIDTH, r->width); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAP_IN_HEIGHT, r->height); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAP_IN_XOFFS, r->left); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAP_IN_YOFFS, r->top); - - return ret; -} - -/* Set preview and capture zoom input window */ -static int s5k4ecgx_set_zoom_window(struct i2c_client *c, - const struct v4l2_rect *r) -{ - int ret; - - ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_WIDTH, r->width); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_HEIGHT, r->height); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_XOFFS, r->left); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_YOFFS, r->top); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_WIDTH, r->width); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_HEIGHT, r->height); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_XOFFS, r->left); - if (!ret) - ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_YOFFS, r->top); - - return ret; -} - -static int s5k4ecgx_set_output_framefmt(struct s5k4ecgx *priv) -{ - struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); - int ret; - - ret = s5k4ecgx_write(client, REG_P_OUT_WIDTH(0), - priv->curr_frmsize->size.width); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_OUT_HEIGHT(0), - priv->curr_frmsize->size.height); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_FMT(0), - priv->curr_pixfmt->reg_p_format); - return ret; -} - -static int s5k4ecgx_init_sensor(struct v4l2_subdev *sd) -{ - int ret; - - ret = s5k4ecgx_set_ahb_address(sd); - - /* The delay is from manufacturer's settings */ - msleep(100); - - if (!ret) - ret = s5k4ecgx_load_firmware(sd); - if (ret) - v4l2_err(sd, "Failed to write initial settings\n"); - - return ret; -} - -static int s5k4ecgx_gpio_set_value(struct s5k4ecgx *priv, int id, u32 val) -{ - if (!gpio_is_valid(priv->gpio[id].gpio)) - return 0; - gpio_set_value(priv->gpio[id].gpio, val); - - return 1; -} - -static int __s5k4ecgx_power_on(struct s5k4ecgx *priv) -{ - int ret; - - ret = regulator_bulk_enable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); - if (ret) - return ret; - usleep_range(30, 50); - - /* The polarity of STBY is controlled by TSP */ - if (s5k4ecgx_gpio_set_value(priv, STBY, priv->gpio[STBY].level)) - usleep_range(30, 50); - - if (s5k4ecgx_gpio_set_value(priv, RSET, priv->gpio[RSET].level)) - usleep_range(30, 50); - - return 0; -} - -static int __s5k4ecgx_power_off(struct s5k4ecgx *priv) -{ - if (s5k4ecgx_gpio_set_value(priv, RSET, !priv->gpio[RSET].level)) - usleep_range(30, 50); - - if (s5k4ecgx_gpio_set_value(priv, STBY, !priv->gpio[STBY].level)) - usleep_range(30, 50); - - priv->streaming = 0; - - return regulator_bulk_disable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); -} - -/* Find nearest matching image pixel size. */ -static int s5k4ecgx_try_frame_size(struct v4l2_mbus_framefmt *mf, - const struct s5k4ecgx_frmsize **size) -{ - unsigned int min_err = ~0; - int i = ARRAY_SIZE(s5k4ecgx_prev_sizes); - const struct s5k4ecgx_frmsize *fsize = &s5k4ecgx_prev_sizes[0], - *match = NULL; - - while (i--) { - int err = abs(fsize->size.width - mf->width) - + abs(fsize->size.height - mf->height); - if (err < min_err) { - min_err = err; - match = fsize; - } - fsize++; - } - if (match) { - mf->width = match->size.width; - mf->height = match->size.height; - if (size) - *size = match; - return 0; - } - - return -EINVAL; -} - -static int s5k4ecgx_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_mbus_code_enum *code) -{ - if (code->index >= ARRAY_SIZE(s5k4ecgx_formats)) - return -EINVAL; - code->code = s5k4ecgx_formats[code->index].code; - - return 0; -} - -static int s5k4ecgx_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - struct v4l2_mbus_framefmt *mf; - - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - if (sd_state) { - mf = v4l2_subdev_get_try_format(sd, sd_state, 0); - fmt->format = *mf; - } - return 0; - } - - mf = &fmt->format; - - mutex_lock(&priv->lock); - mf->width = priv->curr_frmsize->size.width; - mf->height = priv->curr_frmsize->size.height; - mf->code = priv->curr_pixfmt->code; - mf->colorspace = priv->curr_pixfmt->colorspace; - mf->field = V4L2_FIELD_NONE; - mutex_unlock(&priv->lock); - - return 0; -} - -static const struct s5k4ecgx_pixfmt *s5k4ecgx_try_fmt(struct v4l2_subdev *sd, - struct v4l2_mbus_framefmt *mf) -{ - int i = ARRAY_SIZE(s5k4ecgx_formats); - - while (--i) - if (mf->code == s5k4ecgx_formats[i].code) - break; - mf->code = s5k4ecgx_formats[i].code; - - return &s5k4ecgx_formats[i]; -} - -static int s5k4ecgx_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - const struct s5k4ecgx_frmsize *fsize = NULL; - const struct s5k4ecgx_pixfmt *pf; - struct v4l2_mbus_framefmt *mf; - int ret = 0; - - pf = s5k4ecgx_try_fmt(sd, &fmt->format); - s5k4ecgx_try_frame_size(&fmt->format, &fsize); - fmt->format.colorspace = V4L2_COLORSPACE_JPEG; - fmt->format.field = V4L2_FIELD_NONE; - - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - if (sd_state) { - mf = v4l2_subdev_get_try_format(sd, sd_state, 0); - *mf = fmt->format; - } - return 0; - } - - mutex_lock(&priv->lock); - if (!priv->streaming) { - priv->curr_frmsize = fsize; - priv->curr_pixfmt = pf; - priv->set_params = 1; - } else { - ret = -EBUSY; - } - mutex_unlock(&priv->lock); - - return ret; -} - -static const struct v4l2_subdev_pad_ops s5k4ecgx_pad_ops = { - .enum_mbus_code = s5k4ecgx_enum_mbus_code, - .get_fmt = s5k4ecgx_get_fmt, - .set_fmt = s5k4ecgx_set_fmt, -}; - -/* - * V4L2 subdev controls - */ -static int s5k4ecgx_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct v4l2_subdev *sd = &container_of(ctrl->handler, struct s5k4ecgx, - handler)->sd; - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - unsigned int i; - int err = 0; - - v4l2_dbg(1, debug, sd, "ctrl: 0x%x, value: %d\n", ctrl->id, ctrl->val); - - mutex_lock(&priv->lock); - switch (ctrl->id) { - case V4L2_CID_CONTRAST: - err = s5k4ecgx_write(client, REG_USER_CONTRAST, ctrl->val); - break; - - case V4L2_CID_SATURATION: - err = s5k4ecgx_write(client, REG_USER_SATURATION, ctrl->val); - break; - - case V4L2_CID_SHARPNESS: - /* TODO: Revisit, is this setting for all presets ? */ - for (i = 0; i < 4 && !err; i++) - err = s5k4ecgx_write(client, REG_USER_SHARPNESS(i), - ctrl->val * SHARPNESS_DIV); - break; - - case V4L2_CID_BRIGHTNESS: - err = s5k4ecgx_write(client, REG_USER_BRIGHTNESS, ctrl->val); - break; - } - mutex_unlock(&priv->lock); - if (err < 0) - v4l2_err(sd, "Failed to write s_ctrl err %d\n", err); - - return err; -} - -static const struct v4l2_ctrl_ops s5k4ecgx_ctrl_ops = { - .s_ctrl = s5k4ecgx_s_ctrl, -}; - -/* - * Reading s5k4ecgx version information - */ -static int s5k4ecgx_registered(struct v4l2_subdev *sd) -{ - int ret; - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - - mutex_lock(&priv->lock); - ret = __s5k4ecgx_power_on(priv); - if (!ret) { - ret = s5k4ecgx_read_fw_ver(sd); - __s5k4ecgx_power_off(priv); - } - mutex_unlock(&priv->lock); - - return ret; -} - -/* - * V4L2 subdev internal operations - */ -static int s5k4ecgx_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct v4l2_mbus_framefmt *mf = v4l2_subdev_get_try_format(sd, - fh->state, - 0); - - mf->width = s5k4ecgx_prev_sizes[0].size.width; - mf->height = s5k4ecgx_prev_sizes[0].size.height; - mf->code = s5k4ecgx_formats[0].code; - mf->colorspace = V4L2_COLORSPACE_JPEG; - mf->field = V4L2_FIELD_NONE; - - return 0; -} - -static const struct v4l2_subdev_internal_ops s5k4ecgx_subdev_internal_ops = { - .registered = s5k4ecgx_registered, - .open = s5k4ecgx_open, -}; - -static int s5k4ecgx_s_power(struct v4l2_subdev *sd, int on) -{ - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - int ret; - - v4l2_dbg(1, debug, sd, "Switching %s\n", on ? "on" : "off"); - - if (on) { - ret = __s5k4ecgx_power_on(priv); - if (ret < 0) - return ret; - /* Time to stabilize sensor */ - msleep(100); - ret = s5k4ecgx_init_sensor(sd); - if (ret < 0) - __s5k4ecgx_power_off(priv); - else - priv->set_params = 1; - } else { - ret = __s5k4ecgx_power_off(priv); - } - - return ret; -} - -static int s5k4ecgx_log_status(struct v4l2_subdev *sd) -{ - v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name); - - return 0; -} - -static const struct v4l2_subdev_core_ops s5k4ecgx_core_ops = { - .s_power = s5k4ecgx_s_power, - .log_status = s5k4ecgx_log_status, -}; - -static int __s5k4ecgx_s_params(struct s5k4ecgx *priv) -{ - struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); - const struct v4l2_rect *crop_rect = &priv->curr_frmsize->input_window; - int ret; - - ret = s5k4ecgx_set_input_window(client, crop_rect); - if (!ret) - ret = s5k4ecgx_set_zoom_window(client, crop_rect); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_INPUTS_CHANGE_REQ, 1); - if (!ret) - ret = s5k4ecgx_write(client, 0x70000a1e, 0x28); - if (!ret) - ret = s5k4ecgx_write(client, 0x70000ad4, 0x3c); - if (!ret) - ret = s5k4ecgx_set_output_framefmt(priv); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_PVI_MASK(0), 0x52); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_FR_TIME_TYPE(0), - FR_TIME_DYNAMIC); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_FR_TIME_Q_TYPE(0), - FR_TIME_Q_BEST_FRRATE); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_MIN_FR_TIME(0), - US_TO_FR_TIME(33300)); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_MAX_FR_TIME(0), - US_TO_FR_TIME(66600)); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_PREV_MIRROR(0), 0); - if (!ret) - ret = s5k4ecgx_write(client, REG_P_CAP_MIRROR(0), 0); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_ACTIVE_PREV_CFG, 0); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_PREV_OPEN_AFTER_CH, 1); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_NEW_CFG_SYNC, 1); - if (!ret) - ret = s5k4ecgx_write(client, REG_G_PREV_CFG_CHG, 1); - - return ret; -} - -static int __s5k4ecgx_s_stream(struct s5k4ecgx *priv, int on) -{ - struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); - int ret; - - if (on && priv->set_params) { - ret = __s5k4ecgx_s_params(priv); - if (ret < 0) - return ret; - priv->set_params = 0; - } - /* - * This enables/disables preview stream only. Capture requests - * are not supported yet. - */ - ret = s5k4ecgx_write(client, REG_G_ENABLE_PREV, on); - if (ret < 0) - return ret; - return s5k4ecgx_write(client, REG_G_ENABLE_PREV_CHG, 1); -} - -static int s5k4ecgx_s_stream(struct v4l2_subdev *sd, int on) -{ - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - int ret = 0; - - v4l2_dbg(1, debug, sd, "Turn streaming %s\n", on ? "on" : "off"); - - mutex_lock(&priv->lock); - - if (priv->streaming == !on) { - ret = __s5k4ecgx_s_stream(priv, on); - if (!ret) - priv->streaming = on & 1; - } - - mutex_unlock(&priv->lock); - return ret; -} - -static const struct v4l2_subdev_video_ops s5k4ecgx_video_ops = { - .s_stream = s5k4ecgx_s_stream, -}; - -static const struct v4l2_subdev_ops s5k4ecgx_ops = { - .core = &s5k4ecgx_core_ops, - .pad = &s5k4ecgx_pad_ops, - .video = &s5k4ecgx_video_ops, -}; - -/* - * GPIO setup - */ -static int s5k4ecgx_config_gpio(int nr, int val, const char *name) -{ - unsigned long flags = val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; - int ret; - - if (!gpio_is_valid(nr)) - return 0; - ret = gpio_request_one(nr, flags, name); - if (!ret) - gpio_export(nr, 0); - - return ret; -} - -static void s5k4ecgx_free_gpios(struct s5k4ecgx *priv) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(priv->gpio); i++) { - if (!gpio_is_valid(priv->gpio[i].gpio)) - continue; - gpio_free(priv->gpio[i].gpio); - priv->gpio[i].gpio = -EINVAL; - } -} - -static int s5k4ecgx_config_gpios(struct s5k4ecgx *priv, - const struct s5k4ecgx_platform_data *pdata) -{ - const struct s5k4ecgx_gpio *gpio = &pdata->gpio_stby; - int ret; - - priv->gpio[STBY].gpio = -EINVAL; - priv->gpio[RSET].gpio = -EINVAL; - - ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_STBY"); - - if (ret) { - s5k4ecgx_free_gpios(priv); - return ret; - } - priv->gpio[STBY] = *gpio; - if (gpio_is_valid(gpio->gpio)) - gpio_set_value(gpio->gpio, 0); - - gpio = &pdata->gpio_reset; - - ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_RST"); - if (ret) { - s5k4ecgx_free_gpios(priv); - return ret; - } - priv->gpio[RSET] = *gpio; - if (gpio_is_valid(gpio->gpio)) - gpio_set_value(gpio->gpio, 0); - - return 0; -} - -static int s5k4ecgx_init_v4l2_ctrls(struct s5k4ecgx *priv) -{ - const struct v4l2_ctrl_ops *ops = &s5k4ecgx_ctrl_ops; - struct v4l2_ctrl_handler *hdl = &priv->handler; - int ret; - - ret = v4l2_ctrl_handler_init(hdl, 4); - if (ret) - return ret; - - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0); - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); - - /* Sharpness default is 24612, and then (24612/SHARPNESS_DIV) = 2 */ - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -32704/SHARPNESS_DIV, - 24612/SHARPNESS_DIV, 1, 2); - if (hdl->error) { - ret = hdl->error; - v4l2_ctrl_handler_free(hdl); - return ret; - } - priv->sd.ctrl_handler = hdl; - - return 0; -}; - -static int s5k4ecgx_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; - struct v4l2_subdev *sd; - struct s5k4ecgx *priv; - int ret, i; - - if (pdata == NULL) { - dev_err(&client->dev, "platform data is missing!\n"); - return -EINVAL; - } - - priv = devm_kzalloc(&client->dev, sizeof(struct s5k4ecgx), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - mutex_init(&priv->lock); - priv->streaming = 0; - - sd = &priv->sd; - /* Registering subdev */ - v4l2_i2c_subdev_init(sd, client, &s5k4ecgx_ops); - /* Static name; NEVER use in new drivers! */ - strscpy(sd->name, S5K4ECGX_DRIVER_NAME, sizeof(sd->name)); - - sd->internal_ops = &s5k4ecgx_subdev_internal_ops; - /* Support v4l2 sub-device user space API */ - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - - priv->pad.flags = MEDIA_PAD_FL_SOURCE; - sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; - ret = media_entity_pads_init(&sd->entity, 1, &priv->pad); - if (ret) - return ret; - - ret = s5k4ecgx_config_gpios(priv, pdata); - if (ret) { - dev_err(&client->dev, "Failed to set gpios\n"); - goto out_err1; - } - for (i = 0; i < S5K4ECGX_NUM_SUPPLIES; i++) - priv->supplies[i].supply = s5k4ecgx_supply_names[i]; - - ret = devm_regulator_bulk_get(&client->dev, S5K4ECGX_NUM_SUPPLIES, - priv->supplies); - if (ret) { - dev_err(&client->dev, "Failed to get regulators\n"); - goto out_err2; - } - ret = s5k4ecgx_init_v4l2_ctrls(priv); - if (ret) - goto out_err2; - - priv->curr_pixfmt = &s5k4ecgx_formats[0]; - priv->curr_frmsize = &s5k4ecgx_prev_sizes[0]; - - return 0; - -out_err2: - s5k4ecgx_free_gpios(priv); -out_err1: - media_entity_cleanup(&priv->sd.entity); - - return ret; -} - -static void s5k4ecgx_remove(struct i2c_client *client) -{ - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct s5k4ecgx *priv = to_s5k4ecgx(sd); - - mutex_destroy(&priv->lock); - s5k4ecgx_free_gpios(priv); - v4l2_device_unregister_subdev(sd); - v4l2_ctrl_handler_free(&priv->handler); - media_entity_cleanup(&sd->entity); -} - -static const struct i2c_device_id s5k4ecgx_id[] = { - { S5K4ECGX_DRIVER_NAME, 0 }, - {} -}; -MODULE_DEVICE_TABLE(i2c, s5k4ecgx_id); - -static struct i2c_driver v4l2_i2c_driver = { - .driver = { - .name = S5K4ECGX_DRIVER_NAME, - }, - .probe = s5k4ecgx_probe, - .remove = s5k4ecgx_remove, - .id_table = s5k4ecgx_id, -}; - -module_i2c_driver(v4l2_i2c_driver); - -MODULE_DESCRIPTION("Samsung S5K4ECGX 5MP SOC camera"); -MODULE_AUTHOR("Sangwook Lee <sangwook.lee@linaro.org>"); -MODULE_AUTHOR("Seok-Young Jang <quartz.jang@samsung.com>"); -MODULE_LICENSE("GPL"); -MODULE_FIRMWARE(S5K4ECGX_FIRMWARE); diff --git a/drivers/media/i2c/s5k5baf.c b/drivers/media/i2c/s5k5baf.c index 5c2253ab3b6f..960fbf6428ea 100644 --- a/drivers/media/i2c/s5k5baf.c +++ b/drivers/media/i2c/s5k5baf.c @@ -13,11 +13,10 @@ #include <linux/clk.h> #include <linux/delay.h> #include <linux/firmware.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/media.h> #include <linux/module.h> -#include <linux/of_gpio.h> #include <linux/of_graph.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -228,11 +227,6 @@ static const char * const s5k5baf_supply_names[] = { }; #define S5K5BAF_NUM_SUPPLIES ARRAY_SIZE(s5k5baf_supply_names) -struct s5k5baf_gpio { - int gpio; - int level; -}; - enum s5k5baf_gpio_id { STBY, RSET, @@ -284,7 +278,7 @@ struct s5k5baf_fw { }; struct s5k5baf { - struct s5k5baf_gpio gpios[NUM_GPIOS]; + struct gpio_desc *gpios[NUM_GPIOS]; enum v4l2_mbus_type bus_type; u8 nlanes; struct regulator_bulk_data supplies[S5K5BAF_NUM_SUPPLIES]; @@ -936,16 +930,12 @@ static void s5k5baf_hw_set_test_pattern(struct s5k5baf *state, int id) static void s5k5baf_gpio_assert(struct s5k5baf *state, int id) { - struct s5k5baf_gpio *gpio = &state->gpios[id]; - - gpio_set_value(gpio->gpio, gpio->level); + gpiod_set_value_cansleep(state->gpios[id], 1); } static void s5k5baf_gpio_deassert(struct s5k5baf *state, int id) { - struct s5k5baf_gpio *gpio = &state->gpios[id]; - - gpio_set_value(gpio->gpio, !gpio->level); + gpiod_set_value_cansleep(state->gpios[id], 0); } static int s5k5baf_power_on(struct s5k5baf *state) @@ -1799,44 +1789,30 @@ static const struct v4l2_subdev_ops s5k5baf_subdev_ops = { static int s5k5baf_configure_gpios(struct s5k5baf *state) { - static const char * const name[] = { "S5K5BAF_STBY", "S5K5BAF_RST" }; + static const char * const name[] = { "stbyn", "rstn" }; + static const char * const label[] = { "S5K5BAF_STBY", "S5K5BAF_RST" }; struct i2c_client *c = v4l2_get_subdevdata(&state->sd); - struct s5k5baf_gpio *g = state->gpios; + struct gpio_desc *gpio; int ret, i; for (i = 0; i < NUM_GPIOS; ++i) { - int flags = GPIOF_DIR_OUT; - if (g[i].level) - flags |= GPIOF_INIT_HIGH; - ret = devm_gpio_request_one(&c->dev, g[i].gpio, flags, name[i]); - if (ret < 0) { - v4l2_err(c, "failed to request gpio %s\n", name[i]); + gpio = devm_gpiod_get(&c->dev, name[i], GPIOD_OUT_HIGH); + ret = PTR_ERR_OR_ZERO(gpio); + if (ret) { + v4l2_err(c, "failed to request gpio %s: %d\n", + name[i], ret); return ret; } - } - return 0; -} - -static int s5k5baf_parse_gpios(struct s5k5baf_gpio *gpios, struct device *dev) -{ - static const char * const names[] = { - "stbyn-gpios", - "rstn-gpios", - }; - struct device_node *node = dev->of_node; - enum of_gpio_flags flags; - int ret, i; - for (i = 0; i < NUM_GPIOS; ++i) { - ret = of_get_named_gpio_flags(node, names[i], 0, &flags); - if (ret < 0) { - dev_err(dev, "no %s GPIO pin provided\n", names[i]); + ret = gpiod_set_consumer_name(gpio, label[i]); + if (ret) { + v4l2_err(c, "failed to set up name for gpio %s: %d\n", + name[i], ret); return ret; } - gpios[i].gpio = ret; - gpios[i].level = !(flags & OF_GPIO_ACTIVE_LOW); - } + state->gpios[i] = gpio; + } return 0; } @@ -1860,10 +1836,6 @@ static int s5k5baf_parse_device_node(struct s5k5baf *state, struct device *dev) state->mclk_frequency); } - ret = s5k5baf_parse_gpios(state->gpios, dev); - if (ret < 0) - return ret; - node_ep = of_graph_get_next_endpoint(node, NULL); if (!node_ep) { dev_err(dev, "no endpoint defined at node %pOF\n", node); diff --git a/drivers/media/i2c/s5k6a3.c b/drivers/media/i2c/s5k6a3.c index a4efd6d10b43..ef6673b10580 100644 --- a/drivers/media/i2c/s5k6a3.c +++ b/drivers/media/i2c/s5k6a3.c @@ -9,12 +9,12 @@ #include <linux/clk.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/err.h> #include <linux/errno.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_gpio.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -59,7 +59,7 @@ struct s5k6a3 { struct v4l2_subdev subdev; struct media_pad pad; struct regulator_bulk_data supplies[S5K6A3_NUM_SUPPLIES]; - int gpio_reset; + struct gpio_desc *gpio_reset; struct mutex lock; struct v4l2_mbus_framefmt format; struct clk *clock; @@ -216,11 +216,11 @@ static int __s5k6a3_power_on(struct s5k6a3 *sensor) goto error_clk; } - gpio_set_value(sensor->gpio_reset, 1); + gpiod_set_value_cansleep(sensor->gpio_reset, 0); usleep_range(600, 800); - gpio_set_value(sensor->gpio_reset, 0); + gpiod_set_value_cansleep(sensor->gpio_reset, 1); usleep_range(600, 800); - gpio_set_value(sensor->gpio_reset, 1); + gpiod_set_value_cansleep(sensor->gpio_reset, 0); /* Delay needed for the sensor initialization */ msleep(20); @@ -240,7 +240,7 @@ static int __s5k6a3_power_off(struct s5k6a3 *sensor) { int i; - gpio_set_value(sensor->gpio_reset, 0); + gpiod_set_value_cansleep(sensor->gpio_reset, 1); for (i = S5K6A3_NUM_SUPPLIES - 1; i >= 0; i--) regulator_disable(sensor->supplies[i].consumer); @@ -285,32 +285,24 @@ static int s5k6a3_probe(struct i2c_client *client) struct device *dev = &client->dev; struct s5k6a3 *sensor; struct v4l2_subdev *sd; - int gpio, i, ret; + int i, ret; sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); if (!sensor) return -ENOMEM; mutex_init(&sensor->lock); - sensor->gpio_reset = -EINVAL; - sensor->clock = ERR_PTR(-EINVAL); sensor->dev = dev; sensor->clock = devm_clk_get(sensor->dev, S5K6A3_CLK_NAME); if (IS_ERR(sensor->clock)) return PTR_ERR(sensor->clock); - gpio = of_get_gpio_flags(dev->of_node, 0, NULL); - if (!gpio_is_valid(gpio)) - return gpio; - - ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_LOW, - S5K6A3_DRV_NAME); - if (ret < 0) + sensor->gpio_reset = devm_gpiod_get(dev, NULL, GPIOD_OUT_HIGH); + ret = PTR_ERR_OR_ZERO(sensor->gpio_reset); + if (ret) return ret; - sensor->gpio_reset = gpio; - if (of_property_read_u32(dev->of_node, "clock-frequency", &sensor->clock_frequency)) { sensor->clock_frequency = S5K6A3_DEFAULT_CLK_FREQ; diff --git a/drivers/media/i2c/s5k6aa.c b/drivers/media/i2c/s5k6aa.c index 059211788a65..5996153371fc 100644 --- a/drivers/media/i2c/s5k6aa.c +++ b/drivers/media/i2c/s5k6aa.c @@ -1544,8 +1544,7 @@ static int s5k6aa_configure_gpios(struct s5k6aa *s5k6aa, return 0; } -static int s5k6aa_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int s5k6aa_probe(struct i2c_client *client) { const struct s5k6aa_platform_data *pdata = client->dev.platform_data; struct v4l2_subdev *sd; @@ -1641,7 +1640,7 @@ static struct i2c_driver s5k6aa_i2c_driver = { .driver = { .name = DRIVER_NAME }, - .probe = s5k6aa_probe, + .probe_new = s5k6aa_probe, .remove = s5k6aa_remove, .id_table = s5k6aa_id, }; diff --git a/drivers/media/i2c/saa6588.c b/drivers/media/i2c/saa6588.c index d6a51beabd02..8752f7cff611 100644 --- a/drivers/media/i2c/saa6588.c +++ b/drivers/media/i2c/saa6588.c @@ -448,8 +448,7 @@ static const struct v4l2_subdev_ops saa6588_ops = { /* ---------------------------------------------------------------------- */ -static int saa6588_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa6588_probe(struct i2c_client *client) { struct saa6588 *s; struct v4l2_subdev *sd; @@ -506,7 +505,7 @@ static struct i2c_driver saa6588_driver = { .driver = { .name = "saa6588", }, - .probe = saa6588_probe, + .probe_new = saa6588_probe, .remove = saa6588_remove, .id_table = saa6588_id, }; diff --git a/drivers/media/i2c/saa6752hs.c b/drivers/media/i2c/saa6752hs.c index 5928cc6f4595..892d64fe6e81 100644 --- a/drivers/media/i2c/saa6752hs.c +++ b/drivers/media/i2c/saa6752hs.c @@ -659,8 +659,7 @@ static const struct v4l2_subdev_ops saa6752hs_ops = { .pad = &saa6752hs_pad_ops, }; -static int saa6752hs_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa6752hs_probe(struct i2c_client *client) { struct saa6752hs_state *h; struct v4l2_subdev *sd; @@ -782,7 +781,7 @@ static struct i2c_driver saa6752hs_driver = { .driver = { .name = "saa6752hs", }, - .probe = saa6752hs_probe, + .probe_new = saa6752hs_probe, .remove = saa6752hs_remove, .id_table = saa6752hs_id, }; diff --git a/drivers/media/i2c/saa7110.c b/drivers/media/i2c/saa7110.c index 5067525d8b11..b58e71517376 100644 --- a/drivers/media/i2c/saa7110.c +++ b/drivers/media/i2c/saa7110.c @@ -358,8 +358,7 @@ static const struct v4l2_subdev_ops saa7110_ops = { /* ----------------------------------------------------------------------- */ -static int saa7110_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa7110_probe(struct i2c_client *client) { struct saa7110 *decoder; struct v4l2_subdev *sd; @@ -449,7 +448,7 @@ static struct i2c_driver saa7110_driver = { .driver = { .name = "saa7110", }, - .probe = saa7110_probe, + .probe_new = saa7110_probe, .remove = saa7110_remove, .id_table = saa7110_id, }; diff --git a/drivers/media/i2c/saa717x.c b/drivers/media/i2c/saa717x.c index 4f3d1b432a4e..df01059076fa 100644 --- a/drivers/media/i2c/saa717x.c +++ b/drivers/media/i2c/saa717x.c @@ -1228,8 +1228,7 @@ static const struct v4l2_subdev_ops saa717x_ops = { /* i2c implementation */ /* ----------------------------------------------------------------------- */ -static int saa717x_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int saa717x_probe(struct i2c_client *client) { struct saa717x_state *decoder; struct v4l2_ctrl_handler *hdl; @@ -1344,7 +1343,7 @@ static struct i2c_driver saa717x_driver = { .driver = { .name = "saa717x", }, - .probe = saa717x_probe, + .probe_new = saa717x_probe, .remove = saa717x_remove, .id_table = saa717x_id, }; diff --git a/drivers/media/i2c/saa7185.c b/drivers/media/i2c/saa7185.c index 266462325d30..c78f2e95ba37 100644 --- a/drivers/media/i2c/saa7185.c +++ b/drivers/media/i2c/saa7185.c @@ -290,8 +290,7 @@ static const struct v4l2_subdev_ops saa7185_ops = { /* ----------------------------------------------------------------------- */ -static int saa7185_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa7185_probe(struct i2c_client *client) { int i; struct saa7185 *encoder; @@ -344,7 +343,7 @@ static struct i2c_driver saa7185_driver = { .driver = { .name = "saa7185", }, - .probe = saa7185_probe, + .probe_new = saa7185_probe, .remove = saa7185_remove, .id_table = saa7185_id, }; diff --git a/drivers/media/i2c/sony-btf-mpx.c b/drivers/media/i2c/sony-btf-mpx.c index 927a9ec41463..eef6c8a7c9c9 100644 --- a/drivers/media/i2c/sony-btf-mpx.c +++ b/drivers/media/i2c/sony-btf-mpx.c @@ -331,8 +331,7 @@ static const struct v4l2_subdev_ops sony_btf_mpx_ops = { /* --------------------------------------------------------------------------*/ -static int sony_btf_mpx_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int sony_btf_mpx_probe(struct i2c_client *client) { struct sony_btf_mpx *t; struct v4l2_subdev *sd; @@ -376,7 +375,7 @@ static struct i2c_driver sony_btf_mpx_driver = { .driver = { .name = "sony-btf-mpx", }, - .probe = sony_btf_mpx_probe, + .probe_new = sony_btf_mpx_probe, .remove = sony_btf_mpx_remove, .id_table = sony_btf_mpx_id, }; diff --git a/drivers/media/i2c/sr030pc30.c b/drivers/media/i2c/sr030pc30.c index ff18693beb5c..a83c8bf1c5dd 100644 --- a/drivers/media/i2c/sr030pc30.c +++ b/drivers/media/i2c/sr030pc30.c @@ -675,8 +675,7 @@ static int sr030pc30_detect(struct i2c_client *client) } -static int sr030pc30_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int sr030pc30_probe(struct i2c_client *client) { struct sr030pc30_info *info; struct v4l2_subdev *sd; @@ -751,7 +750,7 @@ static struct i2c_driver sr030pc30_i2c_driver = { .driver = { .name = MODULE_NAME }, - .probe = sr030pc30_probe, + .probe_new = sr030pc30_probe, .remove = sr030pc30_remove, .id_table = sr030pc30_id, }; diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c new file mode 100644 index 000000000000..826baf4e064d --- /dev/null +++ b/drivers/media/i2c/st-vgxy61.c @@ -0,0 +1,1963 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for VGXY61 global shutter sensor family driver + * + * Copyright (C) 2022 STMicroelectronics SA + */ + +#include <asm-generic/unaligned.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/units.h> +#include <media/mipi-csi2.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define VGXY61_REG_8BIT(n) ((1 << 16) | (n)) +#define VGXY61_REG_16BIT(n) ((2 << 16) | (n)) +#define VGXY61_REG_32BIT(n) ((4 << 16) | (n)) +#define VGXY61_REG_SIZE_SHIFT 16 +#define VGXY61_REG_ADDR_MASK 0xffff + +#define VGXY61_REG_MODEL_ID VGXY61_REG_16BIT(0x0000) +#define VG5661_MODEL_ID 0x5661 +#define VG5761_MODEL_ID 0x5761 +#define VGXY61_REG_REVISION VGXY61_REG_16BIT(0x0002) +#define VGXY61_REG_FWPATCH_REVISION VGXY61_REG_16BIT(0x0014) +#define VGXY61_REG_FWPATCH_START_ADDR VGXY61_REG_8BIT(0x2000) +#define VGXY61_REG_SYSTEM_FSM VGXY61_REG_8BIT(0x0020) +#define VGXY61_SYSTEM_FSM_SW_STBY 0x03 +#define VGXY61_SYSTEM_FSM_STREAMING 0x04 +#define VGXY61_REG_NVM VGXY61_REG_8BIT(0x0023) +#define VGXY61_NVM_OK 0x04 +#define VGXY61_REG_STBY VGXY61_REG_8BIT(0x0201) +#define VGXY61_STBY_NO_REQ 0 +#define VGXY61_STBY_REQ_TMP_READ BIT(2) +#define VGXY61_REG_STREAMING VGXY61_REG_8BIT(0x0202) +#define VGXY61_STREAMING_NO_REQ 0 +#define VGXY61_STREAMING_REQ_STOP BIT(0) +#define VGXY61_STREAMING_REQ_START BIT(1) +#define VGXY61_REG_EXT_CLOCK VGXY61_REG_32BIT(0x0220) +#define VGXY61_REG_CLK_PLL_PREDIV VGXY61_REG_8BIT(0x0224) +#define VGXY61_REG_CLK_SYS_PLL_MULT VGXY61_REG_8BIT(0x0225) +#define VGXY61_REG_GPIO_0_CTRL VGXY61_REG_8BIT(0x0236) +#define VGXY61_REG_GPIO_1_CTRL VGXY61_REG_8BIT(0x0237) +#define VGXY61_REG_GPIO_2_CTRL VGXY61_REG_8BIT(0x0238) +#define VGXY61_REG_GPIO_3_CTRL VGXY61_REG_8BIT(0x0239) +#define VGXY61_REG_SIGNALS_POLARITY_CTRL VGXY61_REG_8BIT(0x023b) +#define VGXY61_REG_LINE_LENGTH VGXY61_REG_16BIT(0x0300) +#define VGXY61_REG_ORIENTATION VGXY61_REG_8BIT(0x0302) +#define VGXY61_REG_VT_CTRL VGXY61_REG_8BIT(0x0304) +#define VGXY61_REG_FORMAT_CTRL VGXY61_REG_8BIT(0x0305) +#define VGXY61_REG_OIF_CTRL VGXY61_REG_16BIT(0x0306) +#define VGXY61_REG_OIF_ROI0_CTRL VGXY61_REG_8BIT(0x030a) +#define VGXY61_REG_ROI0_START_H VGXY61_REG_16BIT(0x0400) +#define VGXY61_REG_ROI0_START_V VGXY61_REG_16BIT(0x0402) +#define VGXY61_REG_ROI0_END_H VGXY61_REG_16BIT(0x0404) +#define VGXY61_REG_ROI0_END_V VGXY61_REG_16BIT(0x0406) +#define VGXY61_REG_PATGEN_CTRL VGXY61_REG_32BIT(0x0440) +#define VGXY61_PATGEN_LONG_ENABLE BIT(16) +#define VGXY61_PATGEN_SHORT_ENABLE BIT(0) +#define VGXY61_PATGEN_LONG_TYPE_SHIFT 18 +#define VGXY61_PATGEN_SHORT_TYPE_SHIFT 4 +#define VGXY61_REG_FRAME_CONTENT_CTRL VGXY61_REG_8BIT(0x0478) +#define VGXY61_REG_COARSE_EXPOSURE_LONG VGXY61_REG_16BIT(0x0500) +#define VGXY61_REG_COARSE_EXPOSURE_SHORT VGXY61_REG_16BIT(0x0504) +#define VGXY61_REG_ANALOG_GAIN VGXY61_REG_8BIT(0x0508) +#define VGXY61_REG_DIGITAL_GAIN_LONG VGXY61_REG_16BIT(0x050a) +#define VGXY61_REG_DIGITAL_GAIN_SHORT VGXY61_REG_16BIT(0x0512) +#define VGXY61_REG_FRAME_LENGTH VGXY61_REG_16BIT(0x051a) +#define VGXY61_REG_SIGNALS_CTRL VGXY61_REG_16BIT(0x0522) +#define VGXY61_SIGNALS_GPIO_ID_SHIFT 4 +#define VGXY61_REG_READOUT_CTRL VGXY61_REG_8BIT(0x0530) +#define VGXY61_REG_HDR_CTRL VGXY61_REG_8BIT(0x0532) +#define VGXY61_REG_PATGEN_LONG_DATA_GR VGXY61_REG_16BIT(0x092c) +#define VGXY61_REG_PATGEN_LONG_DATA_R VGXY61_REG_16BIT(0x092e) +#define VGXY61_REG_PATGEN_LONG_DATA_B VGXY61_REG_16BIT(0x0930) +#define VGXY61_REG_PATGEN_LONG_DATA_GB VGXY61_REG_16BIT(0x0932) +#define VGXY61_REG_PATGEN_SHORT_DATA_GR VGXY61_REG_16BIT(0x0950) +#define VGXY61_REG_PATGEN_SHORT_DATA_R VGXY61_REG_16BIT(0x0952) +#define VGXY61_REG_PATGEN_SHORT_DATA_B VGXY61_REG_16BIT(0x0954) +#define VGXY61_REG_PATGEN_SHORT_DATA_GB VGXY61_REG_16BIT(0x0956) +#define VGXY61_REG_BYPASS_CTRL VGXY61_REG_8BIT(0x0a60) + +#define VGX661_WIDTH 1464 +#define VGX661_HEIGHT 1104 +#define VGX761_WIDTH 1944 +#define VGX761_HEIGHT 1204 +#define VGX661_DEFAULT_MODE 1 +#define VGX761_DEFAULT_MODE 1 +#define VGX661_SHORT_ROT_TERM 93 +#define VGX761_SHORT_ROT_TERM 90 +#define VGXY61_EXPOS_ROT_TERM 66 +#define VGXY61_WRITE_MULTIPLE_CHUNK_MAX 16 +#define VGXY61_NB_GPIOS 4 +#define VGXY61_NB_POLARITIES 5 +#define VGXY61_FRAME_LENGTH_DEF 1313 +#define VGXY61_MIN_FRAME_LENGTH 1288 +#define VGXY61_MIN_EXPOSURE 10 +#define VGXY61_HDR_LINEAR_RATIO 10 +#define VGXY61_TIMEOUT_MS 500 +#define VGXY61_MEDIA_BUS_FMT_DEF MEDIA_BUS_FMT_Y8_1X8 + +#define VGXY61_FWPATCH_REVISION_MAJOR 2 +#define VGXY61_FWPATCH_REVISION_MINOR 0 +#define VGXY61_FWPATCH_REVISION_MICRO 5 + +static const u8 patch_array[] = { + 0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45, + 0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c, + 0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0, + 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1, + 0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40, + 0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0, + 0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41, + 0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80, + 0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80, + 0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0, + 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c, + 0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03, + 0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, + 0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0, + 0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, + 0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c, + 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d, + 0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84, + 0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d, + 0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02, + 0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c, + 0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1, + 0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, + 0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, + 0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40, + 0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0, + 0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1, + 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40, + 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80, + 0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80, + 0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02, + 0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00, + 0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1, + 0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1, + 0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef, + 0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53, + 0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8, + 0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55, + 0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef, + 0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c, + 0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64, + 0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0, + 0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63, + 0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c, + 0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0, + 0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c, + 0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44, + 0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04, + 0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40, + 0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54, + 0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89, + 0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00, + 0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85, + 0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83, + 0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83, + 0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42, + 0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54, + 0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54, + 0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02, + 0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0, + 0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82, + 0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70, + 0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90, + 0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00, + 0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0, + 0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00, + 0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52, + 0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00, +}; + +static const char * const vgxy61_test_pattern_menu[] = { + "Disabled", + "Solid", + "Colorbar", + "Gradbar", + "Hgrey", + "Vgrey", + "Dgrey", + "PN28", +}; + +static const char * const vgxy61_hdr_mode_menu[] = { + "HDR linearize", + "HDR substraction", + "No HDR", +}; + +static const char * const vgxy61_supply_name[] = { + "VCORE", + "VDDIO", + "VANA", +}; + +static const s64 link_freq[] = { + /* + * MIPI output freq is 804Mhz / 2, as it uses both rising edge and + * falling edges to send data + */ + 402000000ULL +}; + +enum vgxy61_bin_mode { + VGXY61_BIN_MODE_NORMAL, + VGXY61_BIN_MODE_DIGITAL_X2, + VGXY61_BIN_MODE_DIGITAL_X4, +}; + +enum vgxy61_hdr_mode { + VGXY61_HDR_LINEAR, + VGXY61_HDR_SUB, + VGXY61_NO_HDR, +}; + +enum vgxy61_strobe_mode { + VGXY61_STROBE_DISABLED, + VGXY61_STROBE_LONG, + VGXY61_STROBE_ENABLED, +}; + +struct vgxy61_mode_info { + u32 width; + u32 height; + enum vgxy61_bin_mode bin_mode; + struct v4l2_rect crop; +}; + +struct vgxy61_fmt_desc { + u32 code; + u8 bpp; + u8 data_type; +}; + +static const struct vgxy61_fmt_desc vgxy61_supported_codes[] = { + { + .code = MEDIA_BUS_FMT_Y8_1X8, + .bpp = 8, + .data_type = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_Y10_1X10, + .bpp = 10, + .data_type = MIPI_CSI2_DT_RAW10, + }, + { + .code = MEDIA_BUS_FMT_Y12_1X12, + .bpp = 12, + .data_type = MIPI_CSI2_DT_RAW12, + }, + { + .code = MEDIA_BUS_FMT_Y14_1X14, + .bpp = 14, + .data_type = MIPI_CSI2_DT_RAW14, + }, + { + .code = MEDIA_BUS_FMT_Y16_1X16, + .bpp = 16, + .data_type = MIPI_CSI2_DT_RAW16, + }, +}; + +static const struct vgxy61_mode_info vgx661_mode_data[] = { + { + .width = VGX661_WIDTH, + .height = VGX661_HEIGHT, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 0, + .top = 0, + .width = VGX661_WIDTH, + .height = VGX661_HEIGHT, + }, + }, + { + .width = 1280, + .height = 720, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 92, + .top = 192, + .width = 1280, + .height = 720, + }, + }, + { + .width = 640, + .height = 480, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2, + .crop = { + .left = 92, + .top = 72, + .width = 1280, + .height = 960, + }, + }, + { + .width = 320, + .height = 240, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X4, + .crop = { + .left = 92, + .top = 72, + .width = 1280, + .height = 960, + }, + }, +}; + +static const struct vgxy61_mode_info vgx761_mode_data[] = { + { + .width = VGX761_WIDTH, + .height = VGX761_HEIGHT, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 0, + .top = 0, + .width = VGX761_WIDTH, + .height = VGX761_HEIGHT, + }, + }, + { + .width = 1920, + .height = 1080, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 12, + .top = 62, + .width = 1920, + .height = 1080, + }, + }, + { + .width = 1280, + .height = 720, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 332, + .top = 242, + .width = 1280, + .height = 720, + }, + }, + { + .width = 640, + .height = 480, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2, + .crop = { + .left = 332, + .top = 122, + .width = 1280, + .height = 960, + }, + }, + { + .width = 320, + .height = 240, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X4, + .crop = { + .left = 332, + .top = 122, + .width = 1280, + .height = 960, + }, + }, +}; + +struct vgxy61_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + struct media_pad pad; + struct regulator_bulk_data supplies[ARRAY_SIZE(vgxy61_supply_name)]; + struct gpio_desc *reset_gpio; + struct clk *xclk; + u32 clk_freq; + u16 id; + u16 sensor_width; + u16 sensor_height; + u16 oif_ctrl; + unsigned int nb_of_lane; + u32 data_rate_in_mbps; + u32 pclk; + u16 line_length; + u16 rot_term; + bool gpios_polarity; + /* Lock to protect all members below */ + struct mutex lock; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *pixel_rate_ctrl; + struct v4l2_ctrl *expo_ctrl; + struct v4l2_ctrl *vblank_ctrl; + struct v4l2_ctrl *vflip_ctrl; + struct v4l2_ctrl *hflip_ctrl; + bool streaming; + struct v4l2_mbus_framefmt fmt; + const struct vgxy61_mode_info *sensor_modes; + unsigned int sensor_modes_nb; + const struct vgxy61_mode_info *default_mode; + const struct vgxy61_mode_info *current_mode; + bool hflip; + bool vflip; + enum vgxy61_hdr_mode hdr; + u16 expo_long; + u16 expo_short; + u16 expo_max; + u16 expo_min; + u16 vblank; + u16 vblank_min; + u16 frame_length; + u16 digital_gain; + u8 analog_gain; + enum vgxy61_strobe_mode strobe_mode; + u32 pattern; +}; + +static u8 get_bpp_by_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) { + if (vgxy61_supported_codes[i].code == code) + return vgxy61_supported_codes[i].bpp; + } + /* Should never happen */ + WARN(1, "Unsupported code %d. default to 8 bpp", code); + return 8; +} + +static u8 get_data_type_by_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) { + if (vgxy61_supported_codes[i].code == code) + return vgxy61_supported_codes[i].data_type; + } + /* Should never happen */ + WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", + code); + return MIPI_CSI2_DT_RAW8; +} + +static void compute_pll_parameters_by_freq(u32 freq, u8 *prediv, u8 *mult) +{ + const unsigned int predivs[] = {1, 2, 4}; + unsigned int i; + + /* + * Freq range is [6Mhz-27Mhz] already checked. + * Output of divider should be in [6Mhz-12Mhz[. + */ + for (i = 0; i < ARRAY_SIZE(predivs); i++) { + *prediv = predivs[i]; + if (freq / *prediv < 12 * HZ_PER_MHZ) + break; + } + WARN_ON(i == ARRAY_SIZE(predivs)); + + /* + * Target freq is 804Mhz. Don't change this as it will impact image + * quality. + */ + *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq; +} + +static s32 get_pixel_rate(struct vgxy61_dev *sensor) +{ + return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane, + get_bpp_by_code(sensor->fmt.code)); +} + +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct vgxy61_dev, sd); +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct vgxy61_dev, + ctrl_handler)->sd; +} + +static unsigned int get_chunk_size(struct vgxy61_dev *sensor) +{ + struct i2c_adapter *adapter = sensor->i2c_client->adapter; + int max_write_len = VGXY61_WRITE_MULTIPLE_CHUNK_MAX; + + if (adapter->quirks && adapter->quirks->max_write_len) + max_write_len = adapter->quirks->max_write_len - 2; + + max_write_len = min(max_write_len, VGXY61_WRITE_MULTIPLE_CHUNK_MAX); + + return max(max_write_len, 1); +} + +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u32 reg, + unsigned int len) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg[2]; + u8 buf[2]; + u8 val[sizeof(u32)] = {0}; + int ret; + + if (len > sizeof(u32)) + return -EINVAL; + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = val; + msg[1].len = len; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", + __func__, client->addr, reg, ret); + return ret; + } + + return get_unaligned_le32(val); +} + +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u32 reg) +{ + return vgxy61_read_multiple(sensor, reg & VGXY61_REG_ADDR_MASK, + (reg >> VGXY61_REG_SIZE_SHIFT) & 7); +} + +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u32 reg, + const u8 *data, unsigned int len, int *err) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + u8 buf[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2]; + unsigned int i; + int ret; + + if (err && *err) + return *err; + + if (len > VGXY61_WRITE_MULTIPLE_CHUNK_MAX) + return -EINVAL; + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + for (i = 0; i < len; i++) + buf[i + 2] = data[i]; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = len + 2; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", + __func__, reg, ret); + if (err) + *err = ret; + return ret; + } + + return 0; +} + +static int vgxy61_write_array(struct vgxy61_dev *sensor, u32 reg, + unsigned int nb, const u8 *array) +{ + const unsigned int chunk_size = get_chunk_size(sensor); + int ret; + unsigned int sz; + + while (nb) { + sz = min(nb, chunk_size); + ret = vgxy61_write_multiple(sensor, reg, array, sz, NULL); + if (ret < 0) + return ret; + nb -= sz; + reg += sz; + array += sz; + } + + return 0; +} + +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u32 reg, u32 val, + int *err) +{ + return vgxy61_write_multiple(sensor, reg & VGXY61_REG_ADDR_MASK, + (u8 *)&val, + (reg >> VGXY61_REG_SIZE_SHIFT) & 7, err); +} + +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u32 reg, u8 poll_val, + unsigned int timeout_ms) +{ + const unsigned int loop_delay_ms = 10; + int ret; + + return read_poll_timeout(vgxy61_read_reg, ret, + ((ret < 0) || (ret == poll_val)), + loop_delay_ms * 1000, timeout_ms * 1000, + false, sensor, reg); +} + +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state, + unsigned int timeout_ms) +{ + return vgxy61_poll_reg(sensor, VGXY61_REG_SYSTEM_FSM, state, + timeout_ms); +} + +static int vgxy61_check_bw(struct vgxy61_dev *sensor) +{ + /* + * Simplification of time needed to send short packets and for the MIPI + * to add transition times (EoT, LPS, and SoT packet delimiters) needed + * by the protocol to go in low power between 2 packets of data. This + * is a mipi IP constant for the sensor. + */ + const unsigned int mipi_margin = 1056; + unsigned int binning_scale = sensor->current_mode->crop.height / + sensor->current_mode->height; + u8 bpp = get_bpp_by_code(sensor->fmt.code); + unsigned int max_bit_per_line; + unsigned int bit_per_line; + u64 line_rate; + + line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * + sensor->line_length; + max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin; + bit_per_line = (bpp * sensor->current_mode->width) / binning_scale; + + return bit_per_line > max_bit_per_line ? -EINVAL : 0; +} + +static int vgxy61_apply_exposure(struct vgxy61_dev *sensor) +{ + int ret = 0; + + /* We first set expo to zero to avoid forbidden parameters couple */ + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_SHORT, 0, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_LONG, + sensor->expo_long, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_SHORT, + sensor->expo_short, &ret); + + return ret; +} + +static int vgxy61_get_regulators(struct vgxy61_dev *sensor) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supply_name); i++) + sensor->supplies[i].supply = vgxy61_supply_name[i]; + + return devm_regulator_bulk_get(&sensor->i2c_client->dev, + ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); +} + +static int vgxy61_apply_reset(struct vgxy61_dev *sensor) +{ + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(sensor->reset_gpio, 1); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(40000, 100000); + return vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); +} + +static void vgxy61_fill_framefmt(struct vgxy61_dev *sensor, + const struct vgxy61_mode_info *mode, + struct v4l2_mbus_framefmt *fmt, u32 code) +{ + fmt->code = code; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->field = V4L2_FIELD_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt, + const struct vgxy61_mode_info **new_mode) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *mode = sensor->sensor_modes; + unsigned int index; + + for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) { + if (vgxy61_supported_codes[index].code == fmt->code) + break; + } + if (index == ARRAY_SIZE(vgxy61_supported_codes)) + index = 0; + + mode = v4l2_find_nearest_size(sensor->sensor_modes, + sensor->sensor_modes_nb, width, height, + fmt->width, fmt->height); + if (new_mode) + *new_mode = mode; + + vgxy61_fill_framefmt(sensor, mode, fmt, + vgxy61_supported_codes[index].code); + + return 0; +} + +static int vgxy61_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = sensor->current_mode->crop; + return 0; + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = sensor->sensor_width; + sel->r.height = sensor->sensor_height; + return 0; + } + + return -EINVAL; +} + +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(vgxy61_supported_codes)) + return -EINVAL; + + code->code = vgxy61_supported_codes[code->index].code; + + return 0; +} + +static int vgxy61_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, + format->pad); + else + fmt = &sensor->fmt; + + format->format = *fmt; + + mutex_unlock(&sensor->lock); + + return 0; +} + +static u16 vgxy61_get_vblank_min(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode hdr) +{ + u16 min_vblank = VGXY61_MIN_FRAME_LENGTH - + sensor->current_mode->crop.height; + /* Ensure the first rule of thumb can't be negative */ + u16 min_vblank_hdr = VGXY61_MIN_EXPOSURE + sensor->rot_term + 1; + + if (hdr != VGXY61_NO_HDR) + return max(min_vblank, min_vblank_hdr); + return min_vblank; +} + +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + if (fse->index >= sensor->sensor_modes_nb) + return -EINVAL; + + fse->min_width = sensor->sensor_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = sensor->sensor_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target) +{ + sensor->analog_gain = target; + + if (sensor->streaming) + return vgxy61_write_reg(sensor, VGXY61_REG_ANALOG_GAIN, target, + NULL); + return 0; +} + +static int vgxy61_apply_digital_gain(struct vgxy61_dev *sensor, + u32 digital_gain) +{ + int ret = 0; + + /* + * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and + * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all + * four sub pixels. + */ + vgxy61_write_reg(sensor, VGXY61_REG_DIGITAL_GAIN_LONG, digital_gain, + &ret); + vgxy61_write_reg(sensor, VGXY61_REG_DIGITAL_GAIN_SHORT, digital_gain, + &ret); + + return ret; +} + +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target) +{ + sensor->digital_gain = target; + + if (sensor->streaming) + return vgxy61_apply_digital_gain(sensor, sensor->digital_gain); + return 0; +} + +static int vgxy61_apply_patgen(struct vgxy61_dev *sensor, u32 index) +{ + static const u8 index2val[] = { + 0x0, 0x1, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13 + }; + u32 pattern = index2val[index]; + u32 reg = (pattern << VGXY61_PATGEN_LONG_TYPE_SHIFT) | + (pattern << VGXY61_PATGEN_SHORT_TYPE_SHIFT); + + if (pattern) + reg |= VGXY61_PATGEN_LONG_ENABLE | VGXY61_PATGEN_SHORT_ENABLE; + return vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_CTRL, reg, NULL); +} + +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 pattern) +{ + sensor->pattern = pattern; + + if (sensor->streaming) + return vgxy61_apply_patgen(sensor, sensor->pattern); + return 0; +} + +static int vgxy61_apply_gpiox_strobe_mode(struct vgxy61_dev *sensor, + enum vgxy61_strobe_mode mode, + unsigned int idx) +{ + static const u8 index2val[] = {0x0, 0x1, 0x3}; + int reg; + + reg = vgxy61_read_reg(sensor, VGXY61_REG_SIGNALS_CTRL); + if (reg < 0) + return reg; + reg &= ~(0xf << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT)); + reg |= index2val[mode] << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT); + + return vgxy61_write_reg(sensor, VGXY61_REG_SIGNALS_CTRL, reg, NULL); +} + +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode hdr) +{ + unsigned int i; + int ret; + + switch (hdr) { + case VGXY61_HDR_LINEAR: + sensor->strobe_mode = VGXY61_STROBE_ENABLED; + break; + case VGXY61_HDR_SUB: + case VGXY61_NO_HDR: + sensor->strobe_mode = VGXY61_STROBE_LONG; + break; + default: + /* Should never happen */ + WARN_ON(true); + break; + } + + if (!sensor->streaming) + return 0; + + for (i = 0; i < VGXY61_NB_GPIOS; i++) { + ret = vgxy61_apply_gpiox_strobe_mode(sensor, + sensor->strobe_mode, + i); + if (ret) + return ret; + } + + return 0; +} + +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, + bool polarity) +{ + int ret = 0; + + if (sensor->streaming) + return -EBUSY; + + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_0_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_1_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_2_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_3_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_SIGNALS_POLARITY_CTRL, polarity, + &ret); + + return ret; +} + +static u32 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, + unsigned int short_expo_ratio) +{ + u32 first_rot_max_expo, second_rot_max_expo, third_rot_max_expo; + + /* Apply sensor's rules of thumb */ + /* + * Short exposure + height must be less than frame length to avoid bad + * pixel line at the botom of the image + */ + first_rot_max_expo = + ((sensor->frame_length - sensor->current_mode->crop.height - + sensor->rot_term) * short_expo_ratio) - 1; + + /* + * Total exposition time must be less than frame length to avoid sensor + * crash + */ + second_rot_max_expo = + (((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * + short_expo_ratio) / (short_expo_ratio + 1)) - 1; + + /* + * Short exposure times 71 must be less than frame length to avoid + * sensor crash + */ + third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio; + + /* Take the minimum from all rules */ + return min(min(first_rot_max_expo, second_rot_max_expo), + third_rot_max_expo); +} + +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, u16 new_expo_long, + enum vgxy61_hdr_mode hdr) +{ + struct i2c_client *client = sensor->i2c_client; + u16 new_expo_short = 0; + u16 expo_short_max = 0; + u16 expo_long_min = VGXY61_MIN_EXPOSURE; + u16 expo_long_max = 0; + + /* Compute short exposure according to hdr mode and long exposure */ + switch (hdr) { + case VGXY61_HDR_LINEAR: + /* + * Take ratio into account for minimal exposures in + * VGXY61_HDR_LINEAR + */ + expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO; + new_expo_long = max(expo_long_min, new_expo_long); + + expo_long_max = + vgxy61_get_expo_long_max(sensor, + VGXY61_HDR_LINEAR_RATIO); + expo_short_max = (expo_long_max + + (VGXY61_HDR_LINEAR_RATIO / 2)) / + VGXY61_HDR_LINEAR_RATIO; + new_expo_short = (new_expo_long + + (VGXY61_HDR_LINEAR_RATIO / 2)) / + VGXY61_HDR_LINEAR_RATIO; + break; + case VGXY61_HDR_SUB: + new_expo_long = max(expo_long_min, new_expo_long); + + expo_long_max = vgxy61_get_expo_long_max(sensor, 1); + /* Short and long are the same in VGXY61_HDR_SUB */ + expo_short_max = expo_long_max; + new_expo_short = new_expo_long; + break; + case VGXY61_NO_HDR: + new_expo_long = max(expo_long_min, new_expo_long); + + /* + * As short expo is 0 here, only the second rule of thumb + * applies, see vgxy61_get_expo_long_max for more + */ + expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM; + break; + default: + /* Should never happen */ + WARN_ON(true); + break; + } + + /* If this happens, something is wrong with formulas */ + WARN_ON(expo_long_min > expo_long_max); + + if (new_expo_long > expo_long_max) { + dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n", + new_expo_long, expo_long_max); + new_expo_long = expo_long_max; + new_expo_short = expo_short_max; + } + + sensor->expo_long = new_expo_long; + sensor->expo_short = new_expo_short; + sensor->expo_max = expo_long_max; + sensor->expo_min = expo_long_min; + + if (sensor->streaming) + return vgxy61_apply_exposure(sensor); + return 0; +} + +static int vgxy61_apply_framelength(struct vgxy61_dev *sensor) +{ + return vgxy61_write_reg(sensor, VGXY61_REG_FRAME_LENGTH, + sensor->frame_length, NULL); +} + +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, u16 vblank, + enum vgxy61_hdr_mode hdr) +{ + int ret; + + sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr); + sensor->vblank = max(sensor->vblank_min, vblank); + sensor->frame_length = sensor->current_mode->crop.height + + sensor->vblank; + + /* Update exposure according to vblank */ + ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr); + if (ret) + return ret; + + if (sensor->streaming) + return vgxy61_apply_framelength(sensor); + return 0; +} + +static int vgxy61_apply_hdr(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode index) +{ + static const u8 index2val[] = {0x1, 0x4, 0xa}; + + return vgxy61_write_reg(sensor, VGXY61_REG_HDR_CTRL, index2val[index], + NULL); +} + +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode index) +{ + int ret; + + /* + * vblank and short exposure change according to HDR mode, do it first + * as it can violate sensors 'rule of thumbs' and therefore will require + * to change the long exposure. + */ + ret = vgxy61_update_vblank(sensor, sensor->vblank, index); + if (ret) + return ret; + + /* Update strobe mode according to HDR */ + ret = vgxy61_update_gpios_strobe_mode(sensor, index); + if (ret) + return ret; + + sensor->hdr = index; + + if (sensor->streaming) + return vgxy61_apply_hdr(sensor, sensor->hdr); + return 0; +} + +static int vgxy61_apply_settings(struct vgxy61_dev *sensor) +{ + int ret; + unsigned int i; + + ret = vgxy61_apply_hdr(sensor, sensor->hdr); + if (ret) + return ret; + + ret = vgxy61_apply_framelength(sensor); + if (ret) + return ret; + + ret = vgxy61_apply_exposure(sensor); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_ANALOG_GAIN, + sensor->analog_gain, NULL); + if (ret) + return ret; + ret = vgxy61_apply_digital_gain(sensor, sensor->digital_gain); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_ORIENTATION, + sensor->hflip | (sensor->vflip << 1), NULL); + if (ret) + return ret; + + ret = vgxy61_apply_patgen(sensor, sensor->pattern); + if (ret) + return ret; + + for (i = 0; i < VGXY61_NB_GPIOS; i++) { + ret = vgxy61_apply_gpiox_strobe_mode(sensor, + sensor->strobe_mode, i); + if (ret) + return ret; + } + + return 0; +} + +static int vgxy61_stream_enable(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + const struct v4l2_rect *crop = &sensor->current_mode->crop; + int ret = 0; + + ret = vgxy61_check_bw(sensor); + if (ret) + return ret; + + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(&client->dev); + return ret; + } + + /* pm_runtime_get_sync() can return 1 as a valid return code */ + ret = 0; + + vgxy61_write_reg(sensor, VGXY61_REG_FORMAT_CTRL, + get_bpp_by_code(sensor->fmt.code), &ret); + vgxy61_write_reg(sensor, VGXY61_REG_OIF_ROI0_CTRL, + get_data_type_by_code(sensor->fmt.code), &ret); + + vgxy61_write_reg(sensor, VGXY61_REG_READOUT_CTRL, + sensor->current_mode->bin_mode, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_START_H, crop->left, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_END_H, + crop->left + crop->width - 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_START_V, crop->top, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_END_V, + crop->top + crop->height - 1, &ret); + if (ret) + goto err_rpm_put; + + ret = vgxy61_apply_settings(sensor); + if (ret) + goto err_rpm_put; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_REQ_START, NULL); + if (ret) + goto err_rpm_put; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_NO_REQ, VGXY61_TIMEOUT_MS); + if (ret) + goto err_rpm_put; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_STREAMING, + VGXY61_TIMEOUT_MS); + if (ret) + goto err_rpm_put; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(sensor->vflip_ctrl, true); + __v4l2_ctrl_grab(sensor->hflip_ctrl, true); + + return 0; + +err_rpm_put: + pm_runtime_put(&client->dev); + return ret; +} + +static int vgxy61_stream_disable(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + int ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_REQ_STOP, NULL); + if (ret) + goto err_str_dis; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_NO_REQ, 2000); + if (ret) + goto err_str_dis; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); + if (ret) + goto err_str_dis; + + __v4l2_ctrl_grab(sensor->vflip_ctrl, false); + __v4l2_ctrl_grab(sensor->hflip_ctrl, false); + +err_str_dis: + if (ret) + WARN(1, "Can't disable stream"); + pm_runtime_put(&client->dev); + + return ret; +} + +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + ret = enable ? vgxy61_stream_enable(sensor) : + vgxy61_stream_disable(sensor); + if (!ret) + sensor->streaming = enable; + + mutex_unlock(&sensor->lock); + + return ret; +} + +static int vgxy61_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *new_mode; + struct v4l2_mbus_framefmt *fmt; + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto out; + } + + ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode); + if (ret) + goto out; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + fmt = v4l2_subdev_get_try_format(sd, sd_state, 0); + *fmt = format->format; + } else if (sensor->current_mode != new_mode || + sensor->fmt.code != format->format.code) { + fmt = &sensor->fmt; + *fmt = format->format; + + sensor->current_mode = new_mode; + + /* Reset vblank and framelength to default */ + ret = vgxy61_update_vblank(sensor, + VGXY61_FRAME_LENGTH_DEF - + new_mode->crop.height, + sensor->hdr); + + /* Update controls to reflect new mode */ + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, + get_pixel_rate(sensor)); + __v4l2_ctrl_modify_range(sensor->vblank_ctrl, + sensor->vblank_min, + 0xffff - new_mode->crop.height, + 1, sensor->vblank); + __v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank); + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + } + +out: + mutex_unlock(&sensor->lock); + + return ret; +} + +static int vgxy61_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + struct v4l2_subdev_format fmt = { 0 }; + + sensor->current_mode = sensor->default_mode; + vgxy61_fill_framefmt(sensor, sensor->current_mode, &fmt.format, + VGXY61_MEDIA_BUS_FMT_DEF); + + return vgxy61_set_fmt(sd, sd_state, &fmt); +} + +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *cur_mode = sensor->current_mode; + int ret; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr); + ctrl->val = sensor->expo_long; + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = vgxy61_update_analog_gain(sensor, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = vgxy61_update_digital_gain(sensor, ctrl->val); + break; + case V4L2_CID_VFLIP: + case V4L2_CID_HFLIP: + if (sensor->streaming) { + ret = -EBUSY; + break; + } + if (ctrl->id == V4L2_CID_VFLIP) + sensor->vflip = ctrl->val; + if (ctrl->id == V4L2_CID_HFLIP) + sensor->hflip = ctrl->val; + ret = 0; + break; + case V4L2_CID_TEST_PATTERN: + ret = vgxy61_update_patgen(sensor, ctrl->val); + break; + case V4L2_CID_HDR_SENSOR_MODE: + ret = vgxy61_update_hdr(sensor, ctrl->val); + /* Update vblank and exposure controls to match new hdr */ + __v4l2_ctrl_modify_range(sensor->vblank_ctrl, + sensor->vblank_min, + 0xffff - cur_mode->crop.height, + 1, sensor->vblank); + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + break; + case V4L2_CID_VBLANK: + ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr); + /* Update exposure control to match new vblank */ + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = { + .s_ctrl = vgxy61_s_ctrl, +}; + +static int vgxy61_init_controls(struct vgxy61_dev *sensor) +{ + const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops; + struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler; + const struct vgxy61_mode_info *cur_mode = sensor->current_mode; + struct v4l2_ctrl *ctrl; + int ret; + + v4l2_ctrl_handler_init(hdl, 16); + /* We can use our own mutex for the ctrl lock */ + hdl->lock = &sensor->lock; + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, + sensor->analog_gain); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, + sensor->digital_gain); + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(vgxy61_test_pattern_menu) - 1, + 0, 0, vgxy61_test_pattern_menu); + ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, + sensor->line_length, 1, + sensor->line_length - cur_mode->width); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq) - 1, 0, link_freq); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_HDR_SENSOR_MODE, + ARRAY_SIZE(vgxy61_hdr_mode_menu) - 1, 0, + VGXY61_NO_HDR, vgxy61_hdr_mode_menu); + + /* + * Keep a pointer to these controls as we need to update them when + * setting the format + */ + sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_PIXEL_RATE, 1, + INT_MAX, 1, + get_pixel_rate(sensor)); + if (sensor->pixel_rate_ctrl) + sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, + sensor->vblank_min, + 0xffff - cur_mode->crop.height, + 1, sensor->vblank); + sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, + 0, 1, 1, sensor->vflip); + sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, + 0, 1, 1, sensor->hflip); + + if (hdl->error) { + ret = hdl->error; + goto free_ctrls; + } + + sensor->sd.ctrl_handler = hdl; + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +static const struct v4l2_subdev_video_ops vgxy61_video_ops = { + .s_stream = vgxy61_s_stream, +}; + +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = { + .init_cfg = vgxy61_init_cfg, + .enum_mbus_code = vgxy61_enum_mbus_code, + .get_fmt = vgxy61_get_fmt, + .set_fmt = vgxy61_set_fmt, + .get_selection = vgxy61_get_selection, + .enum_frame_size = vgxy61_enum_frame_size, +}; + +static const struct v4l2_subdev_ops vgxy61_subdev_ops = { + .video = &vgxy61_video_ops, + .pad = &vgxy61_pad_ops, +}; + +static const struct media_entity_operations vgxy61_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, + struct fwnode_handle *handle) +{ + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + struct i2c_client *client = sensor->i2c_client; + u32 log2phy[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; + u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; + int polarities[VGXY61_NB_POLARITIES] = {0, 0, 0, 0, 0}; + int l_nb; + unsigned int p, l, i; + int ret; + + ret = v4l2_fwnode_endpoint_alloc_parse(handle, &ep); + if (ret) + return -EINVAL; + + l_nb = ep.bus.mipi_csi2.num_data_lanes; + if (l_nb != 1 && l_nb != 2 && l_nb != 4) { + dev_err(&client->dev, "invalid data lane number %d\n", l_nb); + goto error_ep; + } + + /* Build log2phy, phy2log and polarities from ep info */ + log2phy[0] = ep.bus.mipi_csi2.clock_lane; + phy2log[log2phy[0]] = 0; + for (l = 1; l < l_nb + 1; l++) { + log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1]; + phy2log[log2phy[l]] = l; + } + /* + * Then fill remaining slots for every physical slot to have something + * valid for hardware stuff. + */ + for (p = 0; p < VGXY61_NB_POLARITIES; p++) { + if (phy2log[p] != ~0) + continue; + phy2log[p] = l; + log2phy[l] = p; + l++; + } + for (l = 0; l < l_nb + 1; l++) + polarities[l] = ep.bus.mipi_csi2.lane_polarities[l]; + + if (log2phy[0] != 0) { + dev_err(&client->dev, "clk lane must be map to physical lane 0\n"); + goto error_ep; + } + sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) + + (polarities[3] << 12) + ((phy2log[3] - 1) << 10) + + (polarities[2] << 9) + ((phy2log[2] - 1) << 7) + + (polarities[1] << 6) + ((phy2log[1] - 1) << 4) + + (polarities[0] << 3) + + l_nb; + sensor->nb_of_lane = l_nb; + + dev_dbg(&client->dev, "tx uses %d lanes", l_nb); + for (i = 0; i < 5; i++) { + dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]); + dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]); + dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]); + } + dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl); + + v4l2_fwnode_endpoint_free(&ep); + + return 0; + +error_ep: + v4l2_fwnode_endpoint_free(&ep); + + return -EINVAL; +} + +static int vgxy61_configure(struct vgxy61_dev *sensor) +{ + u32 sensor_freq; + u8 prediv, mult; + int line_length; + int ret = 0; + + compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult); + sensor_freq = (mult * sensor->clk_freq) / prediv; + /* Frequency to data rate is 1:1 ratio for MIPI */ + sensor->data_rate_in_mbps = sensor_freq; + /* Video timing ISP path (pixel clock) requires 804/5 mhz = 160 mhz */ + sensor->pclk = sensor_freq / 5; + + line_length = vgxy61_read_reg(sensor, VGXY61_REG_LINE_LENGTH); + if (line_length < 0) + return line_length; + sensor->line_length = line_length; + vgxy61_write_reg(sensor, VGXY61_REG_EXT_CLOCK, sensor->clk_freq, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_CLK_PLL_PREDIV, prediv, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_CLK_SYS_PLL_MULT, mult, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_OIF_CTRL, sensor->oif_ctrl, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_FRAME_CONTENT_CTRL, 0, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_BYPASS_CTRL, 4, &ret); + if (ret) + return ret; + vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity); + /* Set pattern generator solid to middle value */ + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_GR, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_R, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_B, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_GB, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_GR, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_R, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_B, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_GB, 0x800, &ret); + if (ret) + return ret; + + return 0; +} + +static int vgxy61_patch(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int patch, ret; + + ret = vgxy61_write_array(sensor, VGXY61_REG_FWPATCH_START_ADDR, + sizeof(patch_array), patch_array); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STBY, 0x10, NULL); + if (ret) + return ret; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STBY, 0, VGXY61_TIMEOUT_MS); + if (ret) + return ret; + + patch = vgxy61_read_reg(sensor, VGXY61_REG_FWPATCH_REVISION); + if (patch < 0) + return patch; + + if (patch != (VGXY61_FWPATCH_REVISION_MAJOR << 12) + + (VGXY61_FWPATCH_REVISION_MINOR << 8) + + VGXY61_FWPATCH_REVISION_MICRO) { + dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n", + VGXY61_FWPATCH_REVISION_MAJOR, + VGXY61_FWPATCH_REVISION_MINOR, + VGXY61_FWPATCH_REVISION_MICRO, + patch >> 12, (patch >> 8) & 0x0f, patch & 0xff); + return -ENODEV; + } + dev_dbg(&client->dev, "patch %d.%d.%d applied\n", + patch >> 12, (patch >> 8) & 0x0f, patch & 0xff); + + return 0; +} + +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int device_rev; + + device_rev = vgxy61_read_reg(sensor, VGXY61_REG_REVISION); + if (device_rev < 0) + return device_rev; + + switch (device_rev >> 8) { + case 0xA: + dev_dbg(&client->dev, "Cut1 detected\n"); + dev_err(&client->dev, "Cut1 not supported by this driver\n"); + return -ENODEV; + case 0xB: + dev_dbg(&client->dev, "Cut2 detected\n"); + return 0; + case 0xC: + dev_dbg(&client->dev, "Cut3 detected\n"); + return 0; + default: + dev_err(&client->dev, "Unable to detect cut version\n"); + return -ENODEV; + } +} + +static int vgxy61_detect(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + int id = 0; + int ret, st; + + id = vgxy61_read_reg(sensor, VGXY61_REG_MODEL_ID); + if (id < 0) + return id; + if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) { + dev_warn(&client->dev, "Unsupported sensor id %x\n", id); + return -ENODEV; + } + dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id); + sensor->id = id; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); + if (ret) + return ret; + + st = vgxy61_read_reg(sensor, VGXY61_REG_NVM); + if (st < 0) + return st; + if (st != VGXY61_NVM_OK) + dev_warn(&client->dev, "Bad nvm state got %d\n", st); + + ret = vgxy61_detect_cut_version(sensor); + if (ret) + return ret; + + return 0; +} + +/* Power/clock management functions */ +static int vgxy61_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + if (ret) { + dev_err(&client->dev, "failed to enable regulators %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(sensor->xclk); + if (ret) { + dev_err(&client->dev, "failed to enable clock %d\n", ret); + goto disable_bulk; + } + + if (sensor->reset_gpio) { + ret = vgxy61_apply_reset(sensor); + if (ret) { + dev_err(&client->dev, "sensor reset failed %d\n", ret); + goto disable_clock; + } + } + + ret = vgxy61_patch(sensor); + if (ret) { + dev_err(&client->dev, "sensor patch failed %d\n", ret); + goto disable_clock; + } + + ret = vgxy61_configure(sensor); + if (ret) { + dev_err(&client->dev, "sensor configuration failed %d\n", ret); + goto disable_clock; + } + + return 0; + +disable_clock: + clk_disable_unprepare(sensor->xclk); +disable_bulk: + regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + + return ret; +} + +static int vgxy61_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + clk_disable_unprepare(sensor->xclk); + regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + return 0; +} + +static void vgxy61_fill_sensor_param(struct vgxy61_dev *sensor) +{ + if (sensor->id == VG5761_MODEL_ID) { + sensor->sensor_width = VGX761_WIDTH; + sensor->sensor_height = VGX761_HEIGHT; + sensor->sensor_modes = vgx761_mode_data; + sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data); + sensor->default_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE]; + sensor->rot_term = VGX761_SHORT_ROT_TERM; + } else if (sensor->id == VG5661_MODEL_ID) { + sensor->sensor_width = VGX661_WIDTH; + sensor->sensor_height = VGX661_HEIGHT; + sensor->sensor_modes = vgx661_mode_data; + sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data); + sensor->default_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE]; + sensor->rot_term = VGX661_SHORT_ROT_TERM; + } else { + /* Should never happen */ + WARN_ON(true); + } + sensor->current_mode = sensor->default_mode; +} + +static int vgxy61_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *handle; + struct vgxy61_dev *sensor; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->i2c_client = client; + sensor->streaming = false; + sensor->hdr = VGXY61_NO_HDR; + sensor->expo_long = 200; + sensor->expo_short = 0; + sensor->hflip = false; + sensor->vflip = false; + sensor->analog_gain = 0; + sensor->digital_gain = 256; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!handle) { + dev_err(dev, "handle node not found\n"); + return -EINVAL; + } + + ret = vgxy61_tx_from_ep(sensor, handle); + fwnode_handle_put(handle); + if (ret) { + dev_err(dev, "Failed to parse handle %d\n", ret); + return ret; + } + + sensor->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(sensor->xclk); + } + sensor->clk_freq = clk_get_rate(sensor->xclk); + if (sensor->clk_freq < 6 * HZ_PER_MHZ || + sensor->clk_freq > 27 * HZ_PER_MHZ) { + dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n", + sensor->clk_freq / HZ_PER_MHZ); + return -EINVAL; + } + sensor->gpios_polarity = + device_property_read_bool(dev, "st,strobe-gpios-polarity"); + + v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops); + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.ops = &vgxy61_subdev_entity_ops; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + ret = vgxy61_get_regulators(sensor); + if (ret) { + dev_err(&client->dev, "failed to get regulators %d\n", ret); + return ret; + } + + ret = vgxy61_power_on(dev); + if (ret) + return ret; + + ret = vgxy61_detect(sensor); + if (ret) { + dev_err(&client->dev, "sensor detect failed %d\n", ret); + return ret; + } + + vgxy61_fill_sensor_param(sensor); + vgxy61_fill_framefmt(sensor, sensor->current_mode, &sensor->fmt, + VGXY61_MEDIA_BUS_FMT_DEF); + + ret = vgxy61_update_hdr(sensor, sensor->hdr); + if (ret) + return ret; + + mutex_init(&sensor->lock); + + ret = vgxy61_init_controls(sensor); + if (ret) { + dev_err(&client->dev, "controls initialization failed %d\n", + ret); + goto error_power_off; + } + + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) { + dev_err(&client->dev, "pads init failed %d\n", ret); + goto error_handler_free; + } + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = v4l2_async_register_subdev(&sensor->sd); + if (ret) { + dev_err(&client->dev, "async subdev register failed %d\n", ret); + goto error_pm_runtime; + } + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + dev_dbg(&client->dev, "vgxy61 probe successfully\n"); + + return 0; + +error_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + media_entity_cleanup(&sensor->sd.entity); +error_handler_free: + v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); + mutex_destroy(&sensor->lock); +error_power_off: + vgxy61_power_off(dev); + + return ret; +} + +static void vgxy61_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + mutex_destroy(&sensor->lock); + media_entity_cleanup(&sensor->sd.entity); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + vgxy61_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id vgxy61_dt_ids[] = { + { .compatible = "st,st-vgxy61" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids); + +static const struct dev_pm_ops vgxy61_pm_ops = { + SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL) +}; + +static struct i2c_driver vgxy61_i2c_driver = { + .driver = { + .name = "st-vgxy61", + .of_match_table = vgxy61_dt_ids, + .pm = &vgxy61_pm_ops, + }, + .probe_new = vgxy61_probe, + .remove = vgxy61_remove, +}; + +module_i2c_driver(vgxy61_i2c_driver); + +MODULE_AUTHOR("Benjamin Mugnier <benjamin.mugnier@foss.st.com>"); +MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>"); +MODULE_AUTHOR("Sylvain Petinot <sylvain.petinot@foss.st.com>"); +MODULE_DESCRIPTION("VGXY61 camera subdev driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index 200841c1f5cf..9197fa0b1bc2 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -1891,12 +1891,9 @@ static int tc358743_probe_of(struct tc358743_state *state) int ret; refclk = devm_clk_get(dev, "refclk"); - if (IS_ERR(refclk)) { - if (PTR_ERR(refclk) != -EPROBE_DEFER) - dev_err(dev, "failed to get refclk: %ld\n", - PTR_ERR(refclk)); - return PTR_ERR(refclk); - } + if (IS_ERR(refclk)) + return dev_err_probe(dev, PTR_ERR(refclk), + "failed to get refclk\n"); ep = of_graph_get_next_endpoint(dev->of_node, NULL); if (!ep) { diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c new file mode 100644 index 000000000000..d1f552bd81d4 --- /dev/null +++ b/drivers/media/i2c/tc358746.c @@ -0,0 +1,1694 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TC358746 - Parallel <-> CSI-2 Bridge + * + * Copyright 2022 Marco Felsch <kernel@pengutronix.de> + * + * Notes: + * - Currently only 'Parallel-in -> CSI-out' mode is supported! + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/phy/phy-mipi-dphy.h> +#include <linux/property.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/units.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> + +/* 16-bit registers */ +#define CHIPID_REG 0x0000 +#define CHIPID GENMASK(15, 8) + +#define SYSCTL_REG 0x0002 +#define SRESET BIT(0) + +#define CONFCTL_REG 0x0004 +#define PDATAF_MASK GENMASK(9, 8) +#define PDATAF_MODE0 0 +#define PDATAF_MODE1 1 +#define PDATAF_MODE2 2 +#define PDATAF(val) FIELD_PREP(PDATAF_MASK, (val)) +#define PPEN BIT(6) +#define DATALANE_MASK GENMASK(1, 0) + +#define FIFOCTL_REG 0x0006 +#define DATAFMT_REG 0x0008 +#define PDFMT(val) FIELD_PREP(GENMASK(7, 4), (val)) + +#define MCLKCTL_REG 0x000c +#define MCLK_HIGH_MASK GENMASK(15, 8) +#define MCLK_LOW_MASK GENMASK(7, 0) +#define MCLK_HIGH(val) FIELD_PREP(MCLK_HIGH_MASK, (val)) +#define MCLK_LOW(val) FIELD_PREP(MCLK_LOW_MASK, (val)) + +#define PLLCTL0_REG 0x0016 +#define PLL_PRD_MASK GENMASK(15, 12) +#define PLL_PRD(val) FIELD_PREP(PLL_PRD_MASK, (val)) +#define PLL_FBD_MASK GENMASK(8, 0) +#define PLL_FBD(val) FIELD_PREP(PLL_FBD_MASK, (val)) + +#define PLLCTL1_REG 0x0018 +#define PLL_FRS_MASK GENMASK(11, 10) +#define PLL_FRS(val) FIELD_PREP(PLL_FRS_MASK, (val)) +#define CKEN BIT(4) +#define RESETB BIT(1) +#define PLL_EN BIT(0) + +#define CLKCTL_REG 0x0020 +#define MCLKDIV_MASK GENMASK(3, 2) +#define MCLKDIV(val) FIELD_PREP(MCLKDIV_MASK, (val)) +#define MCLKDIV_8 0 +#define MCLKDIV_4 1 +#define MCLKDIV_2 2 + +#define WORDCNT_REG 0x0022 +#define PP_MISC_REG 0x0032 +#define FRMSTOP BIT(15) +#define RSTPTR BIT(14) + +/* 32-bit registers */ +#define CLW_DPHYCONTTX_REG 0x0100 +#define CLW_CNTRL_REG 0x0140 +#define D0W_CNTRL_REG 0x0144 +#define LANEDISABLE BIT(0) + +#define STARTCNTRL_REG 0x0204 +#define START BIT(0) + +#define LINEINITCNT_REG 0x0210 +#define LPTXTIMECNT_REG 0x0214 +#define TCLK_HEADERCNT_REG 0x0218 +#define TCLK_ZEROCNT(val) FIELD_PREP(GENMASK(15, 8), (val)) +#define TCLK_PREPARECNT(val) FIELD_PREP(GENMASK(6, 0), (val)) + +#define TCLK_TRAILCNT_REG 0x021C +#define THS_HEADERCNT_REG 0x0220 +#define THS_ZEROCNT(val) FIELD_PREP(GENMASK(14, 8), (val)) +#define THS_PREPARECNT(val) FIELD_PREP(GENMASK(6, 0), (val)) + +#define TWAKEUP_REG 0x0224 +#define TCLK_POSTCNT_REG 0x0228 +#define THS_TRAILCNT_REG 0x022C +#define HSTXVREGEN_REG 0x0234 +#define TXOPTIONCNTRL_REG 0x0238 +#define CSI_CONTROL_REG 0x040C +#define CSI_MODE BIT(15) +#define TXHSMD BIT(7) +#define NOL(val) FIELD_PREP(GENMASK(2, 1), (val)) + +#define CSI_CONFW_REG 0x0500 +#define MODE(val) FIELD_PREP(GENMASK(31, 29), (val)) +#define MODE_SET 0x5 +#define ADDRESS(val) FIELD_PREP(GENMASK(28, 24), (val)) +#define CSI_CONTROL_ADDRESS 0x3 +#define DATA(val) FIELD_PREP(GENMASK(15, 0), (val)) + +#define CSI_START_REG 0x0518 +#define STRT BIT(0) + +static const struct v4l2_mbus_framefmt tc358746_def_fmt = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + +static const char * const tc358746_supplies[] = { + "vddc", "vddio", "vddmipi" +}; + +enum { + TC358746_SINK, + TC358746_SOURCE, + TC358746_NR_PADS +}; + +struct tc358746 { + struct v4l2_subdev sd; + struct media_pad pads[TC358746_NR_PADS]; + struct v4l2_async_notifier notifier; + struct v4l2_fwnode_endpoint csi_vep; + + struct v4l2_ctrl_handler ctrl_hdl; + + struct regmap *regmap; + struct clk *refclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(tc358746_supplies)]; + + struct clk_hw mclk_hw; + unsigned long mclk_rate; + u8 mclk_prediv; + u16 mclk_postdiv; + + unsigned long pll_rate; + u8 pll_post_div; + u16 pll_pre_div; + u16 pll_mul; + +#define TC358746_VB_MAX_SIZE (511 * 32) +#define TC358746_VB_DEFAULT_SIZE (1 * 32) + unsigned int vb_size; /* Video buffer size in bits */ + + struct phy_configure_opts_mipi_dphy dphy_cfg; +}; + +static inline struct tc358746 *to_tc358746(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tc358746, sd); +} + +static inline struct tc358746 *clk_hw_to_tc358746(struct clk_hw *hw) +{ + return container_of(hw, struct tc358746, mclk_hw); +} + +struct tc358746_format { + u32 code; + bool csi_format; + unsigned char bus_width; + unsigned char bpp; + /* Register values */ + u8 pdformat; /* Peripheral Data Format */ + u8 pdataf; /* Parallel Data Format Option */ +}; + +enum { + PDFORMAT_RAW8 = 0, + PDFORMAT_RAW10, + PDFORMAT_RAW12, + PDFORMAT_RGB888, + PDFORMAT_RGB666, + PDFORMAT_RGB565, + PDFORMAT_YUV422_8BIT, + /* RESERVED = 7 */ + PDFORMAT_RAW14 = 8, + PDFORMAT_YUV422_10BIT, + PDFORMAT_YUV444, +}; + +/* Check tc358746_src_mbus_code() if you add new formats */ +static const struct tc358746_format tc358746_formats[] = { + { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .bus_width = 8, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE0, + }, { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .csi_format = true, + .bus_width = 16, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE1, + }, { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .csi_format = true, + .bus_width = 16, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE2, + }, { + .code = MEDIA_BUS_FMT_UYVY10_2X10, + .bus_width = 10, + .bpp = 20, + .pdformat = PDFORMAT_YUV422_10BIT, + .pdataf = PDATAF_MODE0, /* don't care */ + } +}; + +/* Get n-th format for pad */ +static const struct tc358746_format * +tc358746_get_format_by_idx(unsigned int pad, unsigned int index) +{ + unsigned int idx = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++) { + const struct tc358746_format *fmt = &tc358746_formats[i]; + + if ((pad == TC358746_SOURCE && fmt->csi_format) || + (pad == TC358746_SINK)) { + if (idx == index) + return fmt; + idx++; + } + } + + return ERR_PTR(-EINVAL); +} + +static const struct tc358746_format * +tc358746_get_format_by_code(unsigned int pad, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++) { + const struct tc358746_format *fmt = &tc358746_formats[i]; + + if (pad == TC358746_SINK && fmt->code == code) + return fmt; + + if (pad == TC358746_SOURCE && !fmt->csi_format) + continue; + + if (fmt->code == code) + return fmt; + } + + return ERR_PTR(-EINVAL); +} + +static u32 tc358746_src_mbus_code(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + return MEDIA_BUS_FMT_UYVY8_1X16; + case MEDIA_BUS_FMT_UYVY10_2X10: + return MEDIA_BUS_FMT_UYVY10_1X20; + default: + return code; + } +} + +static bool tc358746_valid_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CHIPID_REG ... CSI_START_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config tc358746_regmap_config = { + .name = "tc358746", + .reg_bits = 16, + .val_bits = 16, + .max_register = CSI_START_REG, + .writeable_reg = tc358746_valid_reg, + .readable_reg = tc358746_valid_reg, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int tc358746_write(struct tc358746 *tc358746, u32 reg, u32 val) +{ + size_t count; + int err; + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + count = reg < CLW_DPHYCONTTX_REG ? 1 : 2; + + err = regmap_bulk_write(tc358746->regmap, reg, &val, count); + if (err) + dev_err(tc358746->sd.dev, + "Failed to write reg:0x%04x err:%d\n", reg, err); + + return err; +} + +static int tc358746_read(struct tc358746 *tc358746, u32 reg, u32 *val) +{ + size_t count; + int err; + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + count = reg < CLW_DPHYCONTTX_REG ? 1 : 2; + *val = 0; + + err = regmap_bulk_read(tc358746->regmap, reg, val, count); + if (err) + dev_err(tc358746->sd.dev, + "Failed to read reg:0x%04x err:%d\n", reg, err); + + return err; +} + +static int +tc358746_update_bits(struct tc358746 *tc358746, u32 reg, u32 mask, u32 val) +{ + u32 tmp, orig; + int err; + + err = tc358746_read(tc358746, reg, &orig); + if (err) + return err; + + tmp = orig & ~mask; + tmp |= val & mask; + + return tc358746_write(tc358746, reg, tmp); +} + +static int tc358746_set_bits(struct tc358746 *tc358746, u32 reg, u32 bits) +{ + return tc358746_update_bits(tc358746, reg, bits, bits); +} + +static int tc358746_clear_bits(struct tc358746 *tc358746, u32 reg, u32 bits) +{ + return tc358746_update_bits(tc358746, reg, bits, 0); +} + +static int tc358746_sw_reset(struct tc358746 *tc358746) +{ + int err; + + err = tc358746_set_bits(tc358746, SYSCTL_REG, SRESET); + if (err) + return err; + + fsleep(10); + + return tc358746_clear_bits(tc358746, SYSCTL_REG, SRESET); +} + +static int +tc358746_apply_pll_config(struct tc358746 *tc358746) +{ + u8 post = tc358746->pll_post_div; + u16 pre = tc358746->pll_pre_div; + u16 mul = tc358746->pll_mul; + u32 val, mask; + int err; + + err = tc358746_read(tc358746, PLLCTL1_REG, &val); + if (err) + return err; + + /* Don't touch the PLL if running */ + if (FIELD_GET(PLL_EN, val) == 1) + return 0; + + /* Pre-div and Multiplicator have a internal +1 logic */ + val = PLL_PRD(pre - 1) | PLL_FBD(mul - 1); + mask = PLL_PRD_MASK | PLL_FBD_MASK; + err = tc358746_update_bits(tc358746, PLLCTL0_REG, mask, val); + if (err) + return err; + + val = PLL_FRS(ilog2(post)) | RESETB | PLL_EN; + mask = PLL_FRS_MASK | RESETB | PLL_EN; + tc358746_update_bits(tc358746, PLLCTL1_REG, mask, val); + if (err) + return err; + + fsleep(1000); + + return tc358746_set_bits(tc358746, PLLCTL1_REG, CKEN); +} + +static int tc358746_apply_misc_config(struct tc358746 *tc358746) +{ + const struct v4l2_mbus_framefmt *mbusfmt; + struct v4l2_subdev *sd = &tc358746->sd; + struct v4l2_subdev_state *sink_state; + const struct tc358746_format *fmt; + struct device *dev = sd->dev; + u32 val; + int err; + + sink_state = v4l2_subdev_lock_and_get_active_state(sd); + + mbusfmt = v4l2_subdev_get_pad_format(sd, sink_state, TC358746_SINK); + fmt = tc358746_get_format_by_code(TC358746_SINK, mbusfmt->code); + + /* Self defined CSI user data type id's are not supported yet */ + val = PDFMT(fmt->pdformat); + dev_dbg(dev, "DATAFMT: 0x%x\n", val); + err = tc358746_write(tc358746, DATAFMT_REG, val); + if (err) + goto out; + + val = PDATAF(fmt->pdataf); + dev_dbg(dev, "CONFCTL[PDATAF]: 0x%x\n", fmt->pdataf); + err = tc358746_update_bits(tc358746, CONFCTL_REG, PDATAF_MASK, val); + if (err) + goto out; + + val = tc358746->vb_size / 32; + dev_dbg(dev, "FIFOCTL: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, FIFOCTL_REG, val); + if (err) + goto out; + + /* Total number of bytes for each line/width */ + val = mbusfmt->width * fmt->bpp / 8; + dev_dbg(dev, "WORDCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, WORDCNT_REG, val); + +out: + v4l2_subdev_unlock_state(sink_state); + + return err; +} + +/* Use MHz as base so the div needs no u64 */ +static u32 tc358746_cfg_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz, + unsigned int time_base) +{ + return DIV_ROUND_UP(cfg_val * clk_mhz, time_base); +} + +static u32 tc358746_ps_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz) +{ + return tc358746_cfg_to_cnt(cfg_val, clk_mhz, USEC_PER_SEC); +} + +static u32 tc358746_us_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz) +{ + return tc358746_cfg_to_cnt(cfg_val, clk_mhz, 1); +} + +static int tc358746_apply_dphy_config(struct tc358746 *tc358746) +{ + struct phy_configure_opts_mipi_dphy *cfg = &tc358746->dphy_cfg; + bool non_cont_clk = !!(tc358746->csi_vep.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK); + struct device *dev = tc358746->sd.dev; + unsigned long hs_byte_clk, hf_clk; + u32 val, val2, lptxcnt; + int err; + + /* The hs_byte_clk is also called SYSCLK in the excel sheet */ + hs_byte_clk = cfg->hs_clk_rate / 8; + hs_byte_clk /= HZ_PER_MHZ; + hf_clk = hs_byte_clk / 2; + + val = tc358746_us_to_cnt(cfg->init, hf_clk) - 1; + dev_dbg(dev, "LINEINITCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, LINEINITCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->lpx, hs_byte_clk) - 1; + lptxcnt = val; + dev_dbg(dev, "LPTXTIMECNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, LPTXTIMECNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_prepare, hs_byte_clk) - 1; + val2 = tc358746_ps_to_cnt(cfg->clk_zero, hs_byte_clk) - 1; + dev_dbg(dev, "TCLK_PREPARECNT: %u (0x%x)\n", val, val); + dev_dbg(dev, "TCLK_ZEROCNT: %u (0x%x)\n", val2, val2); + dev_dbg(dev, "TCLK_HEADERCNT: 0x%x\n", + (u32)(TCLK_PREPARECNT(val) | TCLK_ZEROCNT(val2))); + err = tc358746_write(tc358746, TCLK_HEADERCNT_REG, + TCLK_PREPARECNT(val) | TCLK_ZEROCNT(val2)); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_trail, hs_byte_clk); + dev_dbg(dev, "TCLK_TRAILCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TCLK_TRAILCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->hs_prepare, hs_byte_clk) - 1; + val2 = tc358746_ps_to_cnt(cfg->hs_zero, hs_byte_clk) - 1; + dev_dbg(dev, "THS_PREPARECNT: %u (0x%x)\n", val, val); + dev_dbg(dev, "THS_ZEROCNT: %u (0x%x)\n", val2, val2); + dev_dbg(dev, "THS_HEADERCNT: 0x%x\n", + (u32)(THS_PREPARECNT(val) | THS_ZEROCNT(val2))); + err = tc358746_write(tc358746, THS_HEADERCNT_REG, + THS_PREPARECNT(val) | THS_ZEROCNT(val2)); + if (err) + return err; + + /* TWAKEUP > 1ms in lptxcnt steps */ + val = tc358746_us_to_cnt(cfg->wakeup, hs_byte_clk); + val = val / (lptxcnt + 1) - 1; + dev_dbg(dev, "TWAKEUP: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TWAKEUP_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_post, hs_byte_clk); + dev_dbg(dev, "TCLK_POSTCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TCLK_POSTCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->hs_trail, hs_byte_clk); + dev_dbg(dev, "THS_TRAILCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, THS_TRAILCNT_REG, val); + if (err) + return err; + + dev_dbg(dev, "CONTCLKMODE: %u", non_cont_clk ? 0 : 1); + + return tc358746_write(tc358746, TXOPTIONCNTRL_REG, non_cont_clk ? 0 : 1); +} + +#define MAX_DATA_LANES 4 + +static int tc358746_enable_csi_lanes(struct tc358746 *tc358746, int enable) +{ + unsigned int lanes = tc358746->dphy_cfg.lanes; + unsigned int lane; + u32 reg, val; + int err; + + err = tc358746_update_bits(tc358746, CONFCTL_REG, DATALANE_MASK, + lanes - 1); + if (err) + return err; + + /* Clock lane */ + val = enable ? 0 : LANEDISABLE; + dev_dbg(tc358746->sd.dev, "CLW_CNTRL: 0x%x\n", val); + err = tc358746_write(tc358746, CLW_CNTRL_REG, val); + if (err) + return err; + + for (lane = 0; lane < MAX_DATA_LANES; lane++) { + /* Data lanes */ + reg = D0W_CNTRL_REG + lane * 0x4; + val = (enable && lane < lanes) ? 0 : LANEDISABLE; + + dev_dbg(tc358746->sd.dev, "D%uW_CNTRL: 0x%x\n", lane, val); + err = tc358746_write(tc358746, reg, val); + if (err) + return err; + } + + val = 0; + if (enable) { + /* Clock lane */ + val |= BIT(0); + + /* Data lanes */ + for (lane = 1; lane <= lanes; lane++) + val |= BIT(lane); + } + + dev_dbg(tc358746->sd.dev, "HSTXVREGEN: 0x%x\n", val); + + return tc358746_write(tc358746, HSTXVREGEN_REG, val); +} + +static int tc358746_enable_csi_module(struct tc358746 *tc358746, int enable) +{ + unsigned int lanes = tc358746->dphy_cfg.lanes; + int err; + + /* + * START and STRT are only reseted/disabled by sw reset. This is + * required to put the lane state back into LP-11 state. The sw reset + * don't reset register values. + */ + if (!enable) + return tc358746_sw_reset(tc358746); + + err = tc358746_write(tc358746, STARTCNTRL_REG, START); + if (err) + return err; + + err = tc358746_write(tc358746, CSI_START_REG, STRT); + if (err) + return err; + + /* CSI_CONTROL_REG is only indirect accessible */ + return tc358746_write(tc358746, CSI_CONFW_REG, + MODE(MODE_SET) | + ADDRESS(CSI_CONTROL_ADDRESS) | + DATA(CSI_MODE | TXHSMD | NOL(lanes - 1))); +} + +static int tc358746_enable_parallel_port(struct tc358746 *tc358746, int enable) +{ + int err; + + if (enable) { + err = tc358746_write(tc358746, PP_MISC_REG, 0); + if (err) + return err; + + return tc358746_set_bits(tc358746, CONFCTL_REG, PPEN); + } + + err = tc358746_set_bits(tc358746, PP_MISC_REG, FRMSTOP); + if (err) + return err; + + err = tc358746_clear_bits(tc358746, CONFCTL_REG, PPEN); + if (err) + return err; + + return tc358746_set_bits(tc358746, PP_MISC_REG, RSTPTR); +} + +static inline struct v4l2_subdev *tc358746_get_remote_sd(struct media_pad *pad) +{ + pad = media_pad_remote_pad_first(pad); + if (!pad) + return NULL; + + return media_entity_to_v4l2_subdev(pad->entity); +} + +static int tc358746_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + struct v4l2_subdev *src; + int err; + + dev_dbg(sd->dev, "%sable\n", enable ? "en" : "dis"); + + src = tc358746_get_remote_sd(&tc358746->pads[TC358746_SINK]); + if (!src) + return -EPIPE; + + if (enable) { + err = pm_runtime_resume_and_get(sd->dev); + if (err) + return err; + + err = tc358746_apply_dphy_config(tc358746); + if (err) + goto err_out; + + err = tc358746_apply_misc_config(tc358746); + if (err) + goto err_out; + + err = tc358746_enable_csi_lanes(tc358746, 1); + if (err) + goto err_out; + + err = tc358746_enable_csi_module(tc358746, 1); + if (err) + goto err_out; + + err = tc358746_enable_parallel_port(tc358746, 1); + if (err) + goto err_out; + + err = v4l2_subdev_call(src, video, s_stream, 1); + if (err) + goto err_out; + + return 0; + +err_out: + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return err; + } + + /* + * The lanes must be disabled first (before the csi module) so the + * LP-11 state is entered correctly. + */ + err = tc358746_enable_csi_lanes(tc358746, 0); + if (err) + return err; + + err = tc358746_enable_csi_module(tc358746, 0); + if (err) + return err; + + err = tc358746_enable_parallel_port(tc358746, 0); + if (err) + return err; + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return v4l2_subdev_call(src, video, s_stream, 0); +} + +static int tc358746_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_pad_format(sd, state, TC358746_SINK); + *fmt = tc358746_def_fmt; + + fmt = v4l2_subdev_get_pad_format(sd, state, TC358746_SOURCE); + *fmt = tc358746_def_fmt; + fmt->code = tc358746_src_mbus_code(tc358746_def_fmt.code); + + return 0; +} + +static int tc358746_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct tc358746_format *fmt; + + fmt = tc358746_get_format_by_idx(code->pad, code->index); + if (IS_ERR(fmt)) + return PTR_ERR(fmt); + + code->code = fmt->code; + + return 0; +} + +static int tc358746_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *src_fmt, *sink_fmt; + const struct tc358746_format *fmt; + + /* Source follows the sink */ + if (format->pad == TC358746_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, format); + + sink_fmt = v4l2_subdev_get_pad_format(sd, sd_state, TC358746_SINK); + + fmt = tc358746_get_format_by_code(format->pad, format->format.code); + if (IS_ERR(fmt)) + fmt = tc358746_get_format_by_code(format->pad, tc358746_def_fmt.code); + + format->format.code = fmt->code; + format->format.field = V4L2_FIELD_NONE; + + dev_dbg(sd->dev, "Update format: %ux%u code:0x%x -> %ux%u code:0x%x", + sink_fmt->width, sink_fmt->height, sink_fmt->code, + format->format.width, format->format.height, format->format.code); + + *sink_fmt = format->format; + + src_fmt = v4l2_subdev_get_pad_format(sd, sd_state, TC358746_SOURCE); + *src_fmt = *sink_fmt; + src_fmt->code = tc358746_src_mbus_code(sink_fmt->code); + + return 0; +} + +static unsigned long tc358746_find_pll_settings(struct tc358746 *tc358746, + unsigned long refclk, + unsigned long fout) + +{ + struct device *dev = tc358746->sd.dev; + unsigned long best_freq = 0; + u32 min_delta = 0xffffffff; + u16 prediv_max = 17; + u16 prediv_min = 1; + u16 m_best, mul; + u16 p_best, p; + u8 postdiv; + + if (fout > 1000 * HZ_PER_MHZ) { + dev_err(dev, "HS-Clock above 1 Ghz are not supported\n"); + return 0; + } + + if (fout >= 500 * HZ_PER_MHZ) + postdiv = 1; + else if (fout >= 250 * HZ_PER_MHZ) + postdiv = 2; + else if (fout >= 125 * HZ_PER_MHZ) + postdiv = 4; + else + postdiv = 8; + + for (p = prediv_min; p <= prediv_max; p++) { + unsigned long delta, fin; + u64 tmp; + + fin = DIV_ROUND_CLOSEST(refclk, p); + if (fin < 4 * HZ_PER_MHZ || fin > 40 * HZ_PER_MHZ) + continue; + + tmp = fout * p * postdiv; + do_div(tmp, fin); + mul = tmp; + if (mul > 511) + continue; + + tmp = mul * fin; + do_div(tmp, p * postdiv); + + delta = abs(fout - tmp); + if (delta < min_delta) { + p_best = p; + m_best = mul; + min_delta = delta; + best_freq = tmp; + }; + + if (delta == 0) + break; + }; + + if (!best_freq) { + dev_err(dev, "Failed find PLL frequency\n"); + return 0; + } + + tc358746->pll_post_div = postdiv; + tc358746->pll_pre_div = p_best; + tc358746->pll_mul = m_best; + + if (best_freq != fout) + dev_warn(dev, "Request PLL freq:%lu, found PLL freq:%lu\n", + fout, best_freq); + + dev_dbg(dev, "Found PLL settings: freq:%lu prediv:%u multi:%u postdiv:%u\n", + best_freq, p_best, m_best, postdiv); + + return best_freq; +} + +#define TC358746_PRECISION 10 + +static int +tc358746_link_validate(struct v4l2_subdev *sd, struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + unsigned long csi_bitrate, source_bitrate; + struct v4l2_subdev_state *sink_state; + struct v4l2_mbus_framefmt *mbusfmt; + const struct tc358746_format *fmt; + unsigned int fifo_sz, tmp, n; + struct v4l2_subdev *source; + s64 source_link_freq; + int err; + + err = v4l2_subdev_link_validate_default(sd, link, source_fmt, sink_fmt); + if (err) + return err; + + sink_state = v4l2_subdev_lock_and_get_active_state(sd); + mbusfmt = v4l2_subdev_get_pad_format(sd, sink_state, TC358746_SINK); + + /* Check the FIFO settings */ + fmt = tc358746_get_format_by_code(TC358746_SINK, mbusfmt->code); + + source = media_entity_to_v4l2_subdev(link->source->entity); + source_link_freq = v4l2_get_link_freq(source->ctrl_handler, 0, 0); + if (source_link_freq <= 0) { + dev_err(tc358746->sd.dev, + "Failed to query or invalid source link frequency\n"); + v4l2_subdev_unlock_state(sink_state); + /* Return -EINVAL in case of source_link_freq is 0 */ + return source_link_freq ? : -EINVAL; + } + source_bitrate = source_link_freq * fmt->bus_width; + + csi_bitrate = tc358746->dphy_cfg.lanes * tc358746->pll_rate; + + dev_dbg(tc358746->sd.dev, + "Fifo settings params: source-bitrate:%lu csi-bitrate:%lu", + source_bitrate, csi_bitrate); + + /* Avoid possible FIFO overflows */ + if (csi_bitrate < source_bitrate) { + v4l2_subdev_unlock_state(sink_state); + return -EINVAL; + } + + /* Best case */ + if (csi_bitrate == source_bitrate) { + fifo_sz = TC358746_VB_DEFAULT_SIZE; + tc358746->vb_size = TC358746_VB_DEFAULT_SIZE; + goto out; + } + + /* + * Avoid possible FIFO underflow in case of + * csi_bitrate > source_bitrate. For such case the chip has a internal + * fifo which can be used to delay the line output. + * + * Fifo size calculation (excluding precision): + * + * fifo-sz, image-width - in bits + * sbr - source_bitrate in bits/s + * csir - csi_bitrate in bits/s + * + * image-width / csir >= (image-width - fifo-sz) / sbr + * image-width * sbr / csir >= image-width - fifo-sz + * fifo-sz >= image-width - image-width * sbr / csir; with n = csir/sbr + * fifo-sz >= image-width - image-width / n + */ + + source_bitrate /= TC358746_PRECISION; + n = csi_bitrate / source_bitrate; + tmp = (mbusfmt->width * TC358746_PRECISION) / n; + fifo_sz = mbusfmt->width - tmp; + fifo_sz *= fmt->bpp; + tc358746->vb_size = round_up(fifo_sz, 32); + +out: + dev_dbg(tc358746->sd.dev, + "Found FIFO size[bits]:%u -> aligned to size[bits]:%u\n", + fifo_sz, tc358746->vb_size); + + v4l2_subdev_unlock_state(sink_state); + + return tc358746->vb_size > TC358746_VB_MAX_SIZE ? -EINVAL : 0; +} + +static int tc358746_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *config) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + if (pad != TC358746_SOURCE) + return -EINVAL; + + config->type = V4L2_MBUS_CSI2_DPHY; + config->bus.mipi_csi2 = tc358746->csi_vep.bus.mipi_csi2; + + return 0; +} + +static int __maybe_unused +tc358746_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + reg->size = reg->reg < CLW_DPHYCONTTX_REG ? 2 : 4; + + if (!pm_runtime_get_if_in_use(sd->dev)) + return 0; + + tc358746_read(tc358746, reg->reg, (u32 *)®->val); + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return 0; +} + +static int __maybe_unused +tc358746_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + if (!pm_runtime_get_if_in_use(sd->dev)) + return 0; + + tc358746_write(tc358746, (u32)reg->reg, (u32)reg->val); + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_core_ops tc358746_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tc358746_g_register, + .s_register = tc358746_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops tc358746_video_ops = { + .s_stream = tc358746_s_stream, +}; + +static const struct v4l2_subdev_pad_ops tc358746_pad_ops = { + .init_cfg = tc358746_init_cfg, + .enum_mbus_code = tc358746_enum_mbus_code, + .set_fmt = tc358746_set_fmt, + .get_fmt = v4l2_subdev_get_fmt, + .link_validate = tc358746_link_validate, + .get_mbus_config = tc358746_get_mbus_config, +}; + +static const struct v4l2_subdev_ops tc358746_ops = { + .core = &tc358746_core_ops, + .video = &tc358746_video_ops, + .pad = &tc358746_pad_ops, +}; + +static const struct media_entity_operations tc358746_entity_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .link_validate = v4l2_subdev_link_validate, +}; + +static int tc358746_mclk_enable(struct clk_hw *hw) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + unsigned int div; + u32 val; + int err; + + div = tc358746->mclk_postdiv / 2; + val = MCLK_HIGH(div - 1) | MCLK_LOW(div - 1); + dev_dbg(tc358746->sd.dev, "MCLKCTL: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, MCLKCTL_REG, val); + if (err) + return err; + + if (tc358746->mclk_prediv == 8) + val = MCLKDIV(MCLKDIV_8); + else if (tc358746->mclk_prediv == 4) + val = MCLKDIV(MCLKDIV_4); + else + val = MCLKDIV(MCLKDIV_2); + + dev_dbg(tc358746->sd.dev, "CLKCTL[MCLKDIV]: %u (0x%x)\n", val, val); + + return tc358746_update_bits(tc358746, CLKCTL_REG, MCLKDIV_MASK, val); +} + +static void tc358746_mclk_disable(struct clk_hw *hw) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + tc358746_write(tc358746, MCLKCTL_REG, 0); +} + +static long +tc358746_find_mclk_settings(struct tc358746 *tc358746, unsigned long mclk_rate) +{ + unsigned long pll_rate = tc358746->pll_rate; + const unsigned char prediv[] = { 2, 4, 8 }; + unsigned int mclk_prediv, mclk_postdiv; + struct device *dev = tc358746->sd.dev; + unsigned int postdiv, mclkdiv; + unsigned long best_mclk_rate; + unsigned int i; + + /* + * MCLK-Div + * -------------------´`--------------------- + * ´ ` + * +-------------+ +------------------------+ + * | MCLK-PreDiv | | MCLK-PostDiv | + * PLL --> | (2/4/8) | --> | (mclk_low + mclk_high) | --> MCLK + * +-------------+ +------------------------+ + * + * The register value of mclk_low/high is mclk_low/high+1, i.e.: + * mclk_low/high = 1 --> 2 MCLK-Ref Counts + * mclk_low/high = 255 --> 256 MCLK-Ref Counts == max. + * If mclk_low and mclk_high are 0 then MCLK is disabled. + * + * Keep it simple and support 50/50 duty cycles only for now, + * so the calc will be: + * + * MCLK = PLL / (MCLK-PreDiv * 2 * MCLK-PostDiv) + */ + + if (mclk_rate == tc358746->mclk_rate) + return mclk_rate; + + /* Highest possible rate */ + mclkdiv = pll_rate / mclk_rate; + if (mclkdiv <= 8) { + mclk_prediv = 2; + mclk_postdiv = 4; + best_mclk_rate = pll_rate / (2 * 4); + goto out; + } + + /* First check the prediv */ + for (i = 0; i < ARRAY_SIZE(prediv); i++) { + postdiv = mclkdiv / prediv[i]; + + if (postdiv % 2) + continue; + + if (postdiv >= 4 && postdiv <= 512) { + mclk_prediv = prediv[i]; + mclk_postdiv = postdiv; + best_mclk_rate = pll_rate / (prediv[i] * postdiv); + goto out; + } + } + + /* No suitable prediv found, so try to adjust the postdiv */ + for (postdiv = 4; postdiv <= 512; postdiv += 2) { + unsigned int pre; + + pre = mclkdiv / postdiv; + if (pre == 2 || pre == 4 || pre == 8) { + mclk_prediv = pre; + mclk_postdiv = postdiv; + best_mclk_rate = pll_rate / (pre * postdiv); + goto out; + } + } + + /* The MCLK <-> PLL gap is to high -> use largest possible div */ + mclk_prediv = 8; + mclk_postdiv = 512; + best_mclk_rate = pll_rate / (8 * 512); + +out: + tc358746->mclk_prediv = mclk_prediv; + tc358746->mclk_postdiv = mclk_postdiv; + tc358746->mclk_rate = best_mclk_rate; + + if (best_mclk_rate != mclk_rate) + dev_warn(dev, "Request MCLK freq:%lu, found MCLK freq:%lu\n", + mclk_rate, best_mclk_rate); + + dev_dbg(dev, "Found MCLK settings: freq:%lu prediv:%u postdiv:%u\n", + best_mclk_rate, mclk_prediv, mclk_postdiv); + + return best_mclk_rate; +} + +static unsigned long +tc358746_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + unsigned int prediv, postdiv; + u32 val; + int err; + + err = tc358746_read(tc358746, MCLKCTL_REG, &val); + if (err) + return 0; + + postdiv = FIELD_GET(MCLK_LOW_MASK, val) + 1; + postdiv += FIELD_GET(MCLK_HIGH_MASK, val) + 1; + + err = tc358746_read(tc358746, CLKCTL_REG, &val); + if (err) + return 0; + + prediv = FIELD_GET(MCLKDIV_MASK, val); + if (prediv == MCLKDIV_8) + prediv = 8; + else if (prediv == MCLKDIV_4) + prediv = 4; + else + prediv = 2; + + return tc358746->pll_rate / (prediv * postdiv); +} + +static long tc358746_mclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + *parent_rate = tc358746->pll_rate; + + return tc358746_find_mclk_settings(tc358746, rate); +} + +static int tc358746_mclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + tc358746_find_mclk_settings(tc358746, rate); + + return tc358746_mclk_enable(hw); +} + +static const struct clk_ops tc358746_mclk_ops = { + .enable = tc358746_mclk_enable, + .disable = tc358746_mclk_disable, + .recalc_rate = tc358746_recalc_rate, + .round_rate = tc358746_mclk_round_rate, + .set_rate = tc358746_mclk_set_rate, +}; + +static int tc358746_setup_mclk_provider(struct tc358746 *tc358746) +{ + struct clk_init_data mclk_initdata = { }; + struct device *dev = tc358746->sd.dev; + const char *mclk_name; + int err; + + /* MCLK clk provider support is optional */ + if (!device_property_present(dev, "#clock-cells")) + return 0; + + /* Init to highest possibel MCLK */ + tc358746->mclk_postdiv = 512; + tc358746->mclk_prediv = 8; + + mclk_name = "tc358746-mclk"; + device_property_read_string(dev, "clock-output-names", &mclk_name); + + mclk_initdata.name = mclk_name; + mclk_initdata.ops = &tc358746_mclk_ops; + tc358746->mclk_hw.init = &mclk_initdata; + + err = devm_clk_hw_register(dev, &tc358746->mclk_hw); + if (err) { + dev_err(dev, "Failed to register mclk provider\n"); + return err; + } + + err = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &tc358746->mclk_hw); + if (err) + dev_err(dev, "Failed to add mclk provider\n"); + + return err; +} + +static int +tc358746_init_subdev(struct tc358746 *tc358746, struct i2c_client *client) +{ + struct v4l2_subdev *sd = &tc358746->sd; + int err; + + v4l2_i2c_subdev_init(sd, client, &tc358746_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &tc358746_entity_ops; + + tc358746->pads[TC358746_SINK].flags = MEDIA_PAD_FL_SINK; + tc358746->pads[TC358746_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + err = media_entity_pads_init(&sd->entity, TC358746_NR_PADS, + tc358746->pads); + if (err) + return err; + + err = v4l2_subdev_init_finalize(sd); + if (err) + media_entity_cleanup(&sd->entity); + + return err; +} + +static int +tc358746_init_output_port(struct tc358746 *tc358746, unsigned long refclk) +{ + struct device *dev = tc358746->sd.dev; + struct v4l2_fwnode_endpoint *vep; + unsigned long csi_link_rate; + struct fwnode_handle *ep; + unsigned char csi_lanes; + int err; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), TC358746_SOURCE, + 0, 0); + if (!ep) { + dev_err(dev, "Missing endpoint node\n"); + return -EINVAL; + } + + /* Currently we only support 'parallel in' -> 'csi out' */ + vep = &tc358746->csi_vep; + vep->bus_type = V4L2_MBUS_CSI2_DPHY; + err = v4l2_fwnode_endpoint_alloc_parse(ep, vep); + fwnode_handle_put(ep); + if (err) { + dev_err(dev, "Failed to parse source endpoint\n"); + return err; + } + + csi_lanes = vep->bus.mipi_csi2.num_data_lanes; + if (csi_lanes == 0 || csi_lanes > 4 || + vep->nr_of_link_frequencies == 0) { + dev_err(dev, "error: Invalid CSI-2 settings\n"); + err = -EINVAL; + goto err; + } + + /* TODO: Add support to handle multiple link frequencies */ + csi_link_rate = (unsigned long)vep->link_frequencies[0]; + tc358746->pll_rate = tc358746_find_pll_settings(tc358746, refclk, + csi_link_rate * 2); + if (!tc358746->pll_rate) { + err = -EINVAL; + goto err; + } + + err = phy_mipi_dphy_get_default_config_for_hsclk(tc358746->pll_rate, + csi_lanes, &tc358746->dphy_cfg); + if (err) + goto err; + + tc358746->vb_size = TC358746_VB_DEFAULT_SIZE; + + return 0; + +err: + v4l2_fwnode_endpoint_free(vep); + + return err; +} + +static int tc358746_init_hw(struct tc358746 *tc358746) +{ + struct device *dev = tc358746->sd.dev; + unsigned int chipid; + u32 val; + int err; + + err = pm_runtime_resume_and_get(dev); + if (err < 0) { + dev_err(dev, "Failed to resume the device\n"); + return err; + } + + /* Ensure that CSI interface is put into LP-11 state */ + err = tc358746_sw_reset(tc358746); + if (err) { + pm_runtime_put_sync(dev); + dev_err(dev, "Failed to reset the device\n"); + return err; + } + + err = tc358746_read(tc358746, CHIPID_REG, &val); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_sync_autosuspend(dev); + if (err) + return -ENODEV; + + chipid = FIELD_GET(CHIPID, val); + if (chipid != 0x44) { + dev_err(dev, "Invalid chipid 0x%02x\n", chipid); + return -ENODEV; + } + + return 0; +} + +static int tc358746_init_controls(struct tc358746 *tc358746) +{ + u64 *link_frequencies = tc358746->csi_vep.link_frequencies; + struct v4l2_ctrl *ctrl; + int err; + + err = v4l2_ctrl_handler_init(&tc358746->ctrl_hdl, 1); + if (err) + return err; + + /* + * The driver currently supports only one link-frequency, regardless of + * the input from the firmware, see: tc358746_init_output_port(). So + * report only the first frequency from the array of possible given + * frequencies. + */ + ctrl = v4l2_ctrl_new_int_menu(&tc358746->ctrl_hdl, NULL, + V4L2_CID_LINK_FREQ, 0, 0, + link_frequencies); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + err = tc358746->ctrl_hdl.error; + if (err) { + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); + return err; + } + + tc358746->sd.ctrl_handler = &tc358746->ctrl_hdl; + + return 0; +} + +static int tc358746_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct tc358746 *tc358746 = + container_of(notifier, struct tc358746, notifier); + u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE; + struct media_pad *sink = &tc358746->pads[TC358746_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, flags); +} + +static const struct v4l2_async_notifier_operations tc358746_notify_ops = { + .bound = tc358746_notify_bound, +}; + +static int tc358746_async_register(struct tc358746 *tc358746) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_PARALLEL, + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep; + int err; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(tc358746->sd.dev), + TC358746_SINK, 0, 0); + if (!ep) + return -ENOTCONN; + + err = v4l2_fwnode_endpoint_parse(ep, &vep); + if (err) { + fwnode_handle_put(ep); + return err; + } + + v4l2_async_nf_init(&tc358746->notifier); + asd = v4l2_async_nf_add_fwnode_remote(&tc358746->notifier, ep, + struct v4l2_async_subdev); + fwnode_handle_put(ep); + + if (IS_ERR(asd)) { + err = PTR_ERR(asd); + goto err_cleanup; + } + + tc358746->notifier.ops = &tc358746_notify_ops; + + err = v4l2_async_subdev_nf_register(&tc358746->sd, &tc358746->notifier); + if (err) + goto err_cleanup; + + tc358746->sd.fwnode = fwnode_graph_get_endpoint_by_id( + dev_fwnode(tc358746->sd.dev), TC358746_SOURCE, 0, 0); + + err = v4l2_async_register_subdev(&tc358746->sd); + if (err) + goto err_unregister; + + return 0; + +err_unregister: + fwnode_handle_put(tc358746->sd.fwnode); + v4l2_async_nf_unregister(&tc358746->notifier); +err_cleanup: + v4l2_async_nf_cleanup(&tc358746->notifier); + + return err; +} + +static int tc358746_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tc358746 *tc358746; + unsigned long refclk; + unsigned int i; + int err; + + tc358746 = devm_kzalloc(&client->dev, sizeof(*tc358746), GFP_KERNEL); + if (!tc358746) + return -ENOMEM; + + tc358746->regmap = devm_regmap_init_i2c(client, &tc358746_regmap_config); + if (IS_ERR(tc358746->regmap)) + return dev_err_probe(dev, PTR_ERR(tc358746->regmap), + "Failed to init regmap\n"); + + tc358746->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(tc358746->refclk)) + return dev_err_probe(dev, PTR_ERR(tc358746->refclk), + "Failed to get refclk\n"); + + err = clk_prepare_enable(tc358746->refclk); + if (err) + return dev_err_probe(dev, err, + "Failed to enable refclk\n"); + + refclk = clk_get_rate(tc358746->refclk); + clk_disable_unprepare(tc358746->refclk); + + if (refclk < 6 * HZ_PER_MHZ || refclk > 40 * HZ_PER_MHZ) + return dev_err_probe(dev, -EINVAL, "Invalid refclk range\n"); + + for (i = 0; i < ARRAY_SIZE(tc358746_supplies); i++) + tc358746->supplies[i].supply = tc358746_supplies[i]; + + err = devm_regulator_bulk_get(dev, ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + if (err) + return dev_err_probe(dev, err, "Failed to get supplies\n"); + + tc358746->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(tc358746->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(tc358746->reset_gpio), + "Failed to get reset-gpios\n"); + + err = tc358746_init_subdev(tc358746, client); + if (err) + return dev_err_probe(dev, err, "Failed to init subdev\n"); + + err = tc358746_init_output_port(tc358746, refclk); + if (err) + goto err_subdev; + + /* + * Keep this order since we need the output port link-frequencies + * information. + */ + err = tc358746_init_controls(tc358746); + if (err) + goto err_fwnode; + + dev_set_drvdata(dev, tc358746); + + /* Set to 1sec to give the stream reconfiguration enough time */ + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + err = tc358746_init_hw(tc358746); + if (err) + goto err_pm; + + err = tc358746_setup_mclk_provider(tc358746); + if (err) + goto err_pm; + + err = tc358746_async_register(tc358746); + if (err < 0) + goto err_pm; + + dev_dbg(dev, "%s found @ 0x%x (%s)\n", client->name, + client->addr, client->adapter->name); + + return 0; + +err_pm: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); +err_fwnode: + v4l2_fwnode_endpoint_free(&tc358746->csi_vep); +err_subdev: + v4l2_subdev_cleanup(&tc358746->sd); + media_entity_cleanup(&tc358746->sd.entity); + + return err; +} + +static void tc358746_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tc358746 *tc358746 = to_tc358746(sd); + + v4l2_subdev_cleanup(sd); + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); + v4l2_fwnode_endpoint_free(&tc358746->csi_vep); + v4l2_async_nf_unregister(&tc358746->notifier); + v4l2_async_nf_cleanup(&tc358746->notifier); + fwnode_handle_put(sd->fwnode); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + pm_runtime_disable(sd->dev); + pm_runtime_set_suspended(sd->dev); + pm_runtime_dont_use_autosuspend(sd->dev); +} + +static int tc358746_suspend(struct device *dev) +{ + struct tc358746 *tc358746 = dev_get_drvdata(dev); + int err; + + clk_disable_unprepare(tc358746->refclk); + + err = regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + if (err) + clk_prepare_enable(tc358746->refclk); + + return err; +} + +static int tc358746_resume(struct device *dev) +{ + struct tc358746 *tc358746 = dev_get_drvdata(dev); + int err; + + gpiod_set_value(tc358746->reset_gpio, 1); + + err = regulator_bulk_enable(ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + if (err) + return err; + + /* min. 200ns */ + usleep_range(10, 20); + + gpiod_set_value(tc358746->reset_gpio, 0); + + err = clk_prepare_enable(tc358746->refclk); + if (err) + goto err; + + /* min. 700us ... 1ms */ + usleep_range(1000, 1500); + + /* + * Enable the PLL here since it can be called by the clk-framework or by + * the .s_stream() callback. So this is the common place for both. + */ + err = tc358746_apply_pll_config(tc358746); + if (err) + goto err_clk; + + return 0; + +err_clk: + clk_disable_unprepare(tc358746->refclk); +err: + regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + return err; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tc358746_pm_ops, tc358746_suspend, + tc358746_resume, NULL); + +static const struct of_device_id __maybe_unused tc358746_of_match[] = { + { .compatible = "toshiba,tc358746" }, + { }, +}; +MODULE_DEVICE_TABLE(of, tc358746_of_match); + +static struct i2c_driver tc358746_driver = { + .driver = { + .name = "tc358746", + .pm = pm_ptr(&tc358746_pm_ops), + .of_match_table = tc358746_of_match, + }, + .probe_new = tc358746_probe, + .remove = tc358746_remove, +}; + +module_i2c_driver(tc358746_driver); + +MODULE_DESCRIPTION("Toshiba TC358746 Parallel to CSI-2 bridge driver"); +MODULE_AUTHOR("Marco Felsch <kernel@pengutronix.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/tda7432.c b/drivers/media/i2c/tda7432.c index 11e918311b13..bbceaac8e0b3 100644 --- a/drivers/media/i2c/tda7432.c +++ b/drivers/media/i2c/tda7432.c @@ -343,8 +343,7 @@ static const struct v4l2_subdev_ops tda7432_ops = { * i2c interface functions * * *********************** */ -static int tda7432_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda7432_probe(struct i2c_client *client) { struct tda7432 *t; struct v4l2_subdev *sd; @@ -410,7 +409,7 @@ static struct i2c_driver tda7432_driver = { .driver = { .name = "tda7432", }, - .probe = tda7432_probe, + .probe_new = tda7432_probe, .remove = tda7432_remove, .id_table = tda7432_id, }; diff --git a/drivers/media/i2c/tda9840.c b/drivers/media/i2c/tda9840.c index aaa74944fc7c..25fbd7e3950e 100644 --- a/drivers/media/i2c/tda9840.c +++ b/drivers/media/i2c/tda9840.c @@ -149,8 +149,7 @@ static const struct v4l2_subdev_ops tda9840_ops = { /* ----------------------------------------------------------------------- */ -static int tda9840_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda9840_probe(struct i2c_client *client) { struct v4l2_subdev *sd; @@ -192,7 +191,7 @@ static struct i2c_driver tda9840_driver = { .driver = { .name = "tda9840", }, - .probe = tda9840_probe, + .probe_new = tda9840_probe, .remove = tda9840_remove, .id_table = tda9840_id, }; diff --git a/drivers/media/i2c/tea6415c.c b/drivers/media/i2c/tea6415c.c index 50e74314f315..d375d2d24354 100644 --- a/drivers/media/i2c/tea6415c.c +++ b/drivers/media/i2c/tea6415c.c @@ -116,8 +116,7 @@ static const struct v4l2_subdev_ops tea6415c_ops = { .video = &tea6415c_video_ops, }; -static int tea6415c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tea6415c_probe(struct i2c_client *client) { struct v4l2_subdev *sd; @@ -151,7 +150,7 @@ static struct i2c_driver tea6415c_driver = { .driver = { .name = "tea6415c", }, - .probe = tea6415c_probe, + .probe_new = tea6415c_probe, .remove = tea6415c_remove, .id_table = tea6415c_id, }; diff --git a/drivers/media/i2c/tea6420.c b/drivers/media/i2c/tea6420.c index 246f2b10ccc7..9da1f3b02c57 100644 --- a/drivers/media/i2c/tea6420.c +++ b/drivers/media/i2c/tea6420.c @@ -87,8 +87,7 @@ static const struct v4l2_subdev_ops tea6420_ops = { .audio = &tea6420_audio_ops, }; -static int tea6420_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tea6420_probe(struct i2c_client *client) { struct v4l2_subdev *sd; int err, i; @@ -133,7 +132,7 @@ static struct i2c_driver tea6420_driver = { .driver = { .name = "tea6420", }, - .probe = tea6420_probe, + .probe_new = tea6420_probe, .remove = tea6420_remove, .id_table = tea6420_id, }; diff --git a/drivers/media/i2c/ths7303.c b/drivers/media/i2c/ths7303.c index 2a0f9a3d1a66..67de90cf696e 100644 --- a/drivers/media/i2c/ths7303.c +++ b/drivers/media/i2c/ths7303.c @@ -322,8 +322,7 @@ static const struct v4l2_subdev_ops ths7303_ops = { .video = &ths7303_video_ops, }; -static int ths7303_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ths7303_probe(struct i2c_client *client) { struct ths7303_platform_data *pdata = client->dev.platform_data; struct ths7303_state *state; @@ -377,7 +376,7 @@ static struct i2c_driver ths7303_driver = { .driver = { .name = "ths73x3", }, - .probe = ths7303_probe, + .probe_new = ths7303_probe, .remove = ths7303_remove, .id_table = ths7303_id, }; diff --git a/drivers/media/i2c/tlv320aic23b.c b/drivers/media/i2c/tlv320aic23b.c index 937fa1dbaecb..47198e803817 100644 --- a/drivers/media/i2c/tlv320aic23b.c +++ b/drivers/media/i2c/tlv320aic23b.c @@ -129,8 +129,7 @@ static const struct v4l2_subdev_ops tlv320aic23b_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int tlv320aic23b_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tlv320aic23b_probe(struct i2c_client *client) { struct tlv320aic23b_state *state; struct v4l2_subdev *sd; @@ -198,7 +197,7 @@ static struct i2c_driver tlv320aic23b_driver = { .driver = { .name = "tlv320aic23b", }, - .probe = tlv320aic23b_probe, + .probe_new = tlv320aic23b_probe, .remove = tlv320aic23b_remove, .id_table = tlv320aic23b_id, }; diff --git a/drivers/media/i2c/tw2804.c b/drivers/media/i2c/tw2804.c index c7c8dfe8a8a8..710790ece11b 100644 --- a/drivers/media/i2c/tw2804.c +++ b/drivers/media/i2c/tw2804.c @@ -343,8 +343,7 @@ static const struct v4l2_subdev_ops tw2804_ops = { .video = &tw2804_video_ops, }; -static int tw2804_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tw2804_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct tw2804 *state; @@ -424,7 +423,7 @@ static struct i2c_driver tw2804_driver = { .driver = { .name = "tw2804", }, - .probe = tw2804_probe, + .probe_new = tw2804_probe, .remove = tw2804_remove, .id_table = tw2804_id, }; diff --git a/drivers/media/i2c/tw9903.c b/drivers/media/i2c/tw9903.c index d7eef7986b75..428ee55787e1 100644 --- a/drivers/media/i2c/tw9903.c +++ b/drivers/media/i2c/tw9903.c @@ -189,8 +189,7 @@ static const struct v4l2_subdev_ops tw9903_ops = { /* --------------------------------------------------------------------------*/ -static int tw9903_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tw9903_probe(struct i2c_client *client) { struct tw9903 *dec; struct v4l2_subdev *sd; @@ -255,7 +254,7 @@ static struct i2c_driver tw9903_driver = { .driver = { .name = "tw9903", }, - .probe = tw9903_probe, + .probe_new = tw9903_probe, .remove = tw9903_remove, .id_table = tw9903_id, }; diff --git a/drivers/media/i2c/tw9906.c b/drivers/media/i2c/tw9906.c index 549ad8f72f12..7824ed9b04ed 100644 --- a/drivers/media/i2c/tw9906.c +++ b/drivers/media/i2c/tw9906.c @@ -157,8 +157,7 @@ static const struct v4l2_subdev_ops tw9906_ops = { .video = &tw9906_video_ops, }; -static int tw9906_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tw9906_probe(struct i2c_client *client) { struct tw9906 *dec; struct v4l2_subdev *sd; @@ -223,7 +222,7 @@ static struct i2c_driver tw9906_driver = { .driver = { .name = "tw9906", }, - .probe = tw9906_probe, + .probe_new = tw9906_probe, .remove = tw9906_remove, .id_table = tw9906_id, }; diff --git a/drivers/media/i2c/tw9910.c b/drivers/media/i2c/tw9910.c index 853b5acead32..459fa22f4341 100644 --- a/drivers/media/i2c/tw9910.c +++ b/drivers/media/i2c/tw9910.c @@ -928,8 +928,7 @@ static const struct v4l2_subdev_ops tw9910_subdev_ops = { * i2c_driver function */ -static int tw9910_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int tw9910_probe(struct i2c_client *client) { struct tw9910_priv *priv; @@ -1013,7 +1012,7 @@ static struct i2c_driver tw9910_i2c_driver = { .driver = { .name = "tw9910", }, - .probe = tw9910_probe, + .probe_new = tw9910_probe, .remove = tw9910_remove, .id_table = tw9910_id, }; diff --git a/drivers/media/i2c/uda1342.c b/drivers/media/i2c/uda1342.c index d0659c4392f2..b6873d866272 100644 --- a/drivers/media/i2c/uda1342.c +++ b/drivers/media/i2c/uda1342.c @@ -45,8 +45,7 @@ static const struct v4l2_subdev_ops uda1342_ops = { .audio = &uda1342_audio_ops, }; -static int uda1342_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int uda1342_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct v4l2_subdev *sd; @@ -89,7 +88,7 @@ static struct i2c_driver uda1342_driver = { .driver = { .name = "uda1342", }, - .probe = uda1342_probe, + .probe_new = uda1342_probe, .remove = uda1342_remove, .id_table = uda1342_id, }; diff --git a/drivers/media/i2c/upd64031a.c b/drivers/media/i2c/upd64031a.c index 4de26ed2ba00..47eed3aab060 100644 --- a/drivers/media/i2c/upd64031a.c +++ b/drivers/media/i2c/upd64031a.c @@ -183,8 +183,7 @@ static const struct v4l2_subdev_ops upd64031a_ops = { /* i2c implementation */ -static int upd64031a_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int upd64031a_probe(struct i2c_client *client) { struct upd64031a_state *state; struct v4l2_subdev *sd; @@ -229,7 +228,7 @@ static struct i2c_driver upd64031a_driver = { .driver = { .name = "upd64031a", }, - .probe = upd64031a_probe, + .probe_new = upd64031a_probe, .remove = upd64031a_remove, .id_table = upd64031a_id, }; diff --git a/drivers/media/i2c/upd64083.c b/drivers/media/i2c/upd64083.c index 2bfd5443d406..3f5a7d4853a1 100644 --- a/drivers/media/i2c/upd64083.c +++ b/drivers/media/i2c/upd64083.c @@ -154,8 +154,7 @@ static const struct v4l2_subdev_ops upd64083_ops = { /* i2c implementation */ -static int upd64083_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int upd64083_probe(struct i2c_client *client) { struct upd64083_state *state; struct v4l2_subdev *sd; @@ -200,7 +199,7 @@ static struct i2c_driver upd64083_driver = { .driver = { .name = "upd64083", }, - .probe = upd64083_probe, + .probe_new = upd64083_probe, .remove = upd64083_remove, .id_table = upd64083_id, }; diff --git a/drivers/media/i2c/vp27smpx.c b/drivers/media/i2c/vp27smpx.c index c832edad5fa7..ed1c58ea8ed3 100644 --- a/drivers/media/i2c/vp27smpx.c +++ b/drivers/media/i2c/vp27smpx.c @@ -138,8 +138,7 @@ static const struct v4l2_subdev_ops vp27smpx_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int vp27smpx_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vp27smpx_probe(struct i2c_client *client) { struct vp27smpx_state *state; struct v4l2_subdev *sd; @@ -182,7 +181,7 @@ static struct i2c_driver vp27smpx_driver = { .driver = { .name = "vp27smpx", }, - .probe = vp27smpx_probe, + .probe_new = vp27smpx_probe, .remove = vp27smpx_remove, .id_table = vp27smpx_id, }; diff --git a/drivers/media/i2c/vpx3220.c b/drivers/media/i2c/vpx3220.c index b481ec196b88..aa73d5dcc3e7 100644 --- a/drivers/media/i2c/vpx3220.c +++ b/drivers/media/i2c/vpx3220.c @@ -456,8 +456,7 @@ static const struct v4l2_subdev_ops vpx3220_ops = { * Client management code */ -static int vpx3220_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vpx3220_probe(struct i2c_client *client) { struct vpx3220 *decoder; struct v4l2_subdev *sd; @@ -547,7 +546,7 @@ static struct i2c_driver vpx3220_driver = { .driver = { .name = "vpx3220", }, - .probe = vpx3220_probe, + .probe_new = vpx3220_probe, .remove = vpx3220_remove, .id_table = vpx3220_id, }; diff --git a/drivers/media/i2c/vs6624.c b/drivers/media/i2c/vs6624.c index d496bb45f201..d35c5ec148f4 100644 --- a/drivers/media/i2c/vs6624.c +++ b/drivers/media/i2c/vs6624.c @@ -738,8 +738,7 @@ static const struct v4l2_subdev_ops vs6624_ops = { .pad = &vs6624_pad_ops, }; -static int vs6624_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vs6624_probe(struct i2c_client *client) { struct vs6624 *sensor; struct v4l2_subdev *sd; @@ -843,7 +842,7 @@ static struct i2c_driver vs6624_driver = { .driver = { .name = "vs6624", }, - .probe = vs6624_probe, + .probe_new = vs6624_probe, .remove = vs6624_remove, .id_table = vs6624_id, }; diff --git a/drivers/media/i2c/wm8739.c b/drivers/media/i2c/wm8739.c index 180b35347521..8b34a673ffd3 100644 --- a/drivers/media/i2c/wm8739.c +++ b/drivers/media/i2c/wm8739.c @@ -178,8 +178,7 @@ static const struct v4l2_subdev_ops wm8739_ops = { /* i2c implementation */ -static int wm8739_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int wm8739_probe(struct i2c_client *client) { struct wm8739_state *state; struct v4l2_subdev *sd; @@ -253,7 +252,7 @@ static struct i2c_driver wm8739_driver = { .driver = { .name = "wm8739", }, - .probe = wm8739_probe, + .probe_new = wm8739_probe, .remove = wm8739_remove, .id_table = wm8739_id, }; diff --git a/drivers/media/i2c/wm8775.c b/drivers/media/i2c/wm8775.c index 8ff97867d3cd..56d98518f7eb 100644 --- a/drivers/media/i2c/wm8775.c +++ b/drivers/media/i2c/wm8775.c @@ -190,8 +190,7 @@ static const struct v4l2_subdev_ops wm8775_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int wm8775_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int wm8775_probe(struct i2c_client *client) { struct wm8775_state *state; struct v4l2_subdev *sd; @@ -299,7 +298,7 @@ static struct i2c_driver wm8775_driver = { .driver = { .name = "wm8775", }, - .probe = wm8775_probe, + .probe_new = wm8775_probe, .remove = wm8775_remove, .id_table = wm8775_id, }; diff --git a/drivers/media/pci/bt8xx/bttv.h b/drivers/media/pci/bt8xx/bttv.h index d24b9ef9f59f..eed7eeb3b963 100644 --- a/drivers/media/pci/bt8xx/bttv.h +++ b/drivers/media/pci/bt8xx/bttv.h @@ -289,7 +289,6 @@ extern void bttv_init_card2(struct bttv *btv); extern void bttv_init_tuner(struct bttv *btv); /* card-specific functions */ -extern void tea5757_set_freq(struct bttv *btv, unsigned short freq); extern u32 bttv_tda9880_setnorm(struct bttv *btv, u32 gpiobits); /* extra tweaks for some chipsets */ diff --git a/drivers/media/pci/cx25821/cx25821-video.h b/drivers/media/pci/cx25821/cx25821-video.h index cf0d3f5509e7..080c8490a250 100644 --- a/drivers/media/pci/cx25821/cx25821-video.h +++ b/drivers/media/pci/cx25821/cx25821-video.h @@ -36,9 +36,6 @@ do { \ } while (0) #define FORMAT_FLAGS_PACKED 0x01 -extern void cx25821_video_wakeup(struct cx25821_dev *dev, - struct cx25821_dmaqueue *q, u32 count); - extern int cx25821_start_video_dma(struct cx25821_dev *dev, struct cx25821_dmaqueue *q, struct cx25821_buffer *buf, diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig index 39bd3be0b43d..65b0c1598fbf 100644 --- a/drivers/media/pci/intel/ipu3/Kconfig +++ b/drivers/media/pci/intel/ipu3/Kconfig @@ -21,6 +21,7 @@ config VIDEO_IPU3_CIO2 config CIO2_BRIDGE bool "IPU3 CIO2 Sensors Bridge" depends on VIDEO_IPU3_CIO2 && ACPI + depends on I2C help This extension provides an API for the ipu3-cio2 driver to create connections to cameras that are hidden in the SSDB buffer in ACPI. diff --git a/drivers/media/pci/mantis/Kconfig b/drivers/media/pci/mantis/Kconfig index 9dfaf2c9a7b3..374b8f382f62 100644 --- a/drivers/media/pci/mantis/Kconfig +++ b/drivers/media/pci/mantis/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config MANTIS_CORE tristate "Mantis/Hopper PCI bridge based devices" - depends on PCI && I2C && INPUT && RC_CORE + depends on PCI && I2C && INPUT && RC_CORE && DVB_CORE help Support for PCI cards based on the Mantis and Hopper PCi bridge. diff --git a/drivers/media/pci/pt3/pt3.c b/drivers/media/pci/pt3/pt3.c index f6deac85962e..246f73b8a9e7 100644 --- a/drivers/media/pci/pt3/pt3.c +++ b/drivers/media/pci/pt3/pt3.c @@ -707,18 +707,10 @@ static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (ret < 0) return ret; - ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)); - if (ret == 0) - dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)); - else { - ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); - if (ret == 0) - dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); - else { - dev_err(&pdev->dev, "Failed to set DMA mask\n"); - return ret; - } - dev_info(&pdev->dev, "Use 32bit DMA\n"); + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + dev_err(&pdev->dev, "Failed to set DMA mask\n"); + return ret; } pt3 = devm_kzalloc(&pdev->dev, sizeof(*pt3), GFP_KERNEL); diff --git a/drivers/media/pci/saa7134/saa7134.h b/drivers/media/pci/saa7134/saa7134.h index 49fe0f6bacba..5c9b2912a9d1 100644 --- a/drivers/media/pci/saa7134/saa7134.h +++ b/drivers/media/pci/saa7134/saa7134.h @@ -866,7 +866,6 @@ int saa7134_ts_stop(struct saa7134_dev *dev); /* saa7134-vbi.c */ extern const struct vb2_ops saa7134_vbi_qops; -extern struct video_device saa7134_vbi_template; int saa7134_vbi_init1(struct saa7134_dev *dev); int saa7134_vbi_fini(struct saa7134_dev *dev); @@ -897,9 +896,6 @@ void saa7134_enable_i2s(struct saa7134_dev *dev); /* ----------------------------------------------------------- */ /* saa7134-oss.c */ -extern const struct file_operations saa7134_dsp_fops; -extern const struct file_operations saa7134_mixer_fops; - int saa7134_oss_init1(struct saa7134_dev *dev); int saa7134_oss_fini(struct saa7134_dev *dev); void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status); diff --git a/drivers/media/pci/saa7164/saa7164-core.c b/drivers/media/pci/saa7164/saa7164-core.c index d5f32e3ff544..a8a004f28ca0 100644 --- a/drivers/media/pci/saa7164/saa7164-core.c +++ b/drivers/media/pci/saa7164/saa7164-core.c @@ -352,7 +352,7 @@ static void saa7164_work_enchandler(struct work_struct *w) container_of(w, struct saa7164_port, workenc); struct saa7164_dev *dev = port->dev; - u32 wp, mcb, rp, cnt = 0; + u32 wp, mcb, rp; port->last_svc_msecs_diff = port->last_svc_msecs; port->last_svc_msecs = jiffies_to_msecs(jiffies); @@ -405,7 +405,6 @@ static void saa7164_work_enchandler(struct work_struct *w) saa7164_work_enchandler_helper(port, rp); port->last_svc_rp = rp; - cnt++; if (rp == mcb) break; @@ -429,7 +428,7 @@ static void saa7164_work_vbihandler(struct work_struct *w) container_of(w, struct saa7164_port, workenc); struct saa7164_dev *dev = port->dev; - u32 wp, mcb, rp, cnt = 0; + u32 wp, mcb, rp; port->last_svc_msecs_diff = port->last_svc_msecs; port->last_svc_msecs = jiffies_to_msecs(jiffies); @@ -481,7 +480,6 @@ static void saa7164_work_vbihandler(struct work_struct *w) saa7164_work_enchandler_helper(port, rp); port->last_svc_rp = rp; - cnt++; if (rp == mcb) break; @@ -1259,7 +1257,7 @@ static int saa7164_initdev(struct pci_dev *pci_dev, if (saa7164_dev_setup(dev) < 0) { err = -EINVAL; - goto fail_free; + goto fail_dev; } /* print pci info */ @@ -1427,6 +1425,8 @@ fail_fw: fail_irq: saa7164_dev_unregister(dev); +fail_dev: + pci_disable_device(pci_dev); fail_free: v4l2_device_unregister(&dev->v4l2_dev); kfree(dev); diff --git a/drivers/media/pci/saa7164/saa7164.h b/drivers/media/pci/saa7164/saa7164.h index 4b4eb156e214..eede47b686a3 100644 --- a/drivers/media/pci/saa7164/saa7164.h +++ b/drivers/media/pci/saa7164/saa7164.h @@ -493,8 +493,6 @@ int saa7164_downloadfirmware(struct saa7164_dev *dev); /* saa7164-i2c.c */ extern int saa7164_i2c_register(struct saa7164_i2c *bus); extern int saa7164_i2c_unregister(struct saa7164_i2c *bus); -extern void saa7164_call_i2c_clients(struct saa7164_i2c *bus, - unsigned int cmd, void *arg); /* ----------------------------------------------------------- */ /* saa7164-bus.c */ diff --git a/drivers/media/pci/solo6x10/solo6x10-core.c b/drivers/media/pci/solo6x10/solo6x10-core.c index 4a546eeefe38..6d87fbb0ee04 100644 --- a/drivers/media/pci/solo6x10/solo6x10-core.c +++ b/drivers/media/pci/solo6x10/solo6x10-core.c @@ -420,6 +420,7 @@ static int solo_sysfs_init(struct solo_dev *solo_dev) solo_dev->nr_chans); if (device_register(dev)) { + put_device(dev); dev->parent = NULL; return -ENOMEM; } diff --git a/drivers/media/pci/zoran/zoran_device.h b/drivers/media/pci/zoran/zoran_device.h index 34fd5cc914eb..237e830ae726 100644 --- a/drivers/media/pci/zoran/zoran_device.h +++ b/drivers/media/pci/zoran/zoran_device.h @@ -47,8 +47,6 @@ void zr36057_restart(struct zoran *zr); extern const struct zoran_format zoran_formats[]; -extern int v4l_bufsize; -extern int jpg_bufsize; extern int pass_through; /* i2c */ diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index a9334263fa9b..ee579916f874 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -72,6 +72,7 @@ source "drivers/media/platform/chips-media/Kconfig" source "drivers/media/platform/intel/Kconfig" source "drivers/media/platform/marvell/Kconfig" source "drivers/media/platform/mediatek/Kconfig" +source "drivers/media/platform/microchip/Kconfig" source "drivers/media/platform/nvidia/Kconfig" source "drivers/media/platform/nxp/Kconfig" source "drivers/media/platform/qcom/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index a91f42024273..5453bb868e67 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -15,6 +15,7 @@ obj-y += chips-media/ obj-y += intel/ obj-y += marvell/ obj-y += mediatek/ +obj-y += microchip/ obj-y += nvidia/ obj-y += nxp/ obj-y += qcom/ diff --git a/drivers/media/platform/amphion/vdec.c b/drivers/media/platform/amphion/vdec.c index feb75dc204de..87f9f8e90ab1 100644 --- a/drivers/media/platform/amphion/vdec.c +++ b/drivers/media/platform/amphion/vdec.c @@ -69,72 +69,101 @@ struct vdec_t { static const struct vpu_format vdec_formats[] = { { .pixfmt = V4L2_PIX_FMT_NV12M_8L128, - .num_planes = 2, + .mem_planes = 2, + .comp_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .sibling = V4L2_PIX_FMT_NV12_8L128, + }, + { + .pixfmt = V4L2_PIX_FMT_NV12_8L128, + .mem_planes = 1, + .comp_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .sibling = V4L2_PIX_FMT_NV12M_8L128, }, { .pixfmt = V4L2_PIX_FMT_NV12M_10BE_8L128, - .num_planes = 2, + .mem_planes = 2, + .comp_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .sibling = V4L2_PIX_FMT_NV12_10BE_8L128, + }, + { + .pixfmt = V4L2_PIX_FMT_NV12_10BE_8L128, + .mem_planes = 1, + .comp_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .sibling = V4L2_PIX_FMT_NV12M_10BE_8L128 }, { .pixfmt = V4L2_PIX_FMT_H264, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_H264_MVC, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_HEVC, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_G, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_L, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_MPEG2, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_MPEG4, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_XVID, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_VP8, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, { .pixfmt = V4L2_PIX_FMT_H263, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED }, {0, 0, 0, 0}, }; @@ -256,23 +285,22 @@ static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) int ret = -EINVAL; vpu_inst_lock(inst); - if (!V4L2_TYPE_IS_OUTPUT(f->type) && vdec->fixed_fmt) { - if (f->index == 0) { - f->pixelformat = inst->cap_format.pixfmt; - f->flags = inst->cap_format.flags; - ret = 0; - } + if (V4L2_TYPE_IS_CAPTURE(f->type) && vdec->fixed_fmt) { + fmt = vpu_get_format(inst, f->type); + if (f->index == 1) + fmt = vpu_helper_find_sibling(inst, f->type, fmt->pixfmt); + if (f->index > 1) + fmt = NULL; } else { fmt = vpu_helper_enum_format(inst, f->type, f->index); - memset(f->reserved, 0, sizeof(f->reserved)); - if (!fmt) - goto exit; - - f->pixelformat = fmt->pixfmt; - f->flags = fmt->flags; - ret = 0; } + if (!fmt) + goto exit; + memset(f->reserved, 0, sizeof(f->reserved)); + f->pixelformat = fmt->pixfmt; + f->flags = fmt->flags; + ret = 0; exit: vpu_inst_unlock(inst); return ret; @@ -286,23 +314,25 @@ static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f) struct vpu_format *cur_fmt; int i; + vpu_inst_lock(inst); cur_fmt = vpu_get_format(inst, f->type); pixmp->pixelformat = cur_fmt->pixfmt; - pixmp->num_planes = cur_fmt->num_planes; + pixmp->num_planes = cur_fmt->mem_planes; pixmp->width = cur_fmt->width; pixmp->height = cur_fmt->height; pixmp->field = cur_fmt->field; pixmp->flags = cur_fmt->flags; for (i = 0; i < pixmp->num_planes; i++) { pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; - pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + pixmp->plane_fmt[i].sizeimage = vpu_get_fmt_plane_size(cur_fmt, i); } f->fmt.pix_mp.colorspace = vdec->codec_info.color_primaries; f->fmt.pix_mp.xfer_func = vdec->codec_info.transfer_chars; f->fmt.pix_mp.ycbcr_enc = vdec->codec_info.matrix_coeffs; f->fmt.pix_mp.quantization = vdec->codec_info.full_range; + vpu_inst_unlock(inst); return 0; } @@ -311,10 +341,19 @@ static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) { struct vpu_inst *inst = to_inst(file); struct vdec_t *vdec = inst->priv; - - vpu_try_fmt_common(inst, f); + struct vpu_format fmt; vpu_inst_lock(inst); + if (V4L2_TYPE_IS_CAPTURE(f->type) && vdec->fixed_fmt) { + struct vpu_format *cap_fmt = vpu_get_format(inst, f->type); + + if (!vpu_helper_match_format(inst, cap_fmt->type, cap_fmt->pixfmt, + f->fmt.pix_mp.pixelformat)) + f->fmt.pix_mp.pixelformat = cap_fmt->pixfmt; + } + + vpu_try_fmt_common(inst, f, &fmt); + if (vdec->fixed_fmt) { f->fmt.pix_mp.colorspace = vdec->codec_info.color_primaries; f->fmt.pix_mp.xfer_func = vdec->codec_info.transfer_chars; @@ -334,7 +373,7 @@ static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) static int vdec_s_fmt_common(struct vpu_inst *inst, struct v4l2_format *f) { struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; - const struct vpu_format *fmt; + struct vpu_format fmt; struct vpu_format *cur_fmt; struct vb2_queue *q; struct vdec_t *vdec = inst->priv; @@ -349,36 +388,30 @@ static int vdec_s_fmt_common(struct vpu_inst *inst, struct v4l2_format *f) if (vb2_is_busy(q)) return -EBUSY; - fmt = vpu_try_fmt_common(inst, f); - if (!fmt) + if (vpu_try_fmt_common(inst, f, &fmt)) return -EINVAL; cur_fmt = vpu_get_format(inst, f->type); if (V4L2_TYPE_IS_OUTPUT(f->type) && inst->state != VPU_CODEC_STATE_DEINIT) { - if (cur_fmt->pixfmt != fmt->pixfmt) { + if (cur_fmt->pixfmt != fmt.pixfmt) { vdec->reset_codec = true; vdec->fixed_fmt = false; } } - cur_fmt->pixfmt = fmt->pixfmt; if (V4L2_TYPE_IS_OUTPUT(f->type) || !vdec->fixed_fmt) { - cur_fmt->num_planes = fmt->num_planes; - cur_fmt->flags = fmt->flags; - cur_fmt->width = pixmp->width; - cur_fmt->height = pixmp->height; - for (i = 0; i < fmt->num_planes; i++) { - cur_fmt->sizeimage[i] = pixmp->plane_fmt[i].sizeimage; - cur_fmt->bytesperline[i] = pixmp->plane_fmt[i].bytesperline; - } - if (pixmp->field != V4L2_FIELD_ANY) - cur_fmt->field = pixmp->field; + memcpy(cur_fmt, &fmt, sizeof(*cur_fmt)); } else { - pixmp->num_planes = cur_fmt->num_planes; + if (vpu_helper_match_format(inst, f->type, cur_fmt->pixfmt, pixmp->pixelformat)) { + cur_fmt->pixfmt = fmt.pixfmt; + cur_fmt->mem_planes = fmt.mem_planes; + } + pixmp->pixelformat = cur_fmt->pixfmt; + pixmp->num_planes = cur_fmt->mem_planes; pixmp->width = cur_fmt->width; pixmp->height = cur_fmt->height; for (i = 0; i < pixmp->num_planes; i++) { pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; - pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + pixmp->plane_fmt[i].sizeimage = vpu_get_fmt_plane_size(cur_fmt, i); } pixmp->field = cur_fmt->field; } @@ -678,9 +711,11 @@ static struct vpu_vb2_buffer *vdec_find_buffer(struct vpu_inst *inst, u32 luma) static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) { struct vdec_t *vdec = inst->priv; + struct vpu_format *cur_fmt; struct vpu_vb2_buffer *vpu_buf; struct vb2_v4l2_buffer *vbuf; u32 sequence; + int i; if (!frame) return; @@ -699,6 +734,7 @@ static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) return; } + cur_fmt = vpu_get_format(inst, inst->cap_format.type); vbuf = &vpu_buf->m2m_buf.vb; if (vbuf->vb2_buf.index != frame->id) dev_err(inst->dev, "[%d] buffer id(%d, %d) dismatch\n", @@ -707,9 +743,9 @@ static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) if (vpu_get_buffer_state(vbuf) != VPU_BUF_STATE_DECODED) dev_err(inst->dev, "[%d] buffer(%d) ready without decoded\n", inst->id, frame->id); vpu_set_buffer_state(vbuf, VPU_BUF_STATE_READY); - vb2_set_plane_payload(&vbuf->vb2_buf, 0, inst->cap_format.sizeimage[0]); - vb2_set_plane_payload(&vbuf->vb2_buf, 1, inst->cap_format.sizeimage[1]); - vbuf->field = inst->cap_format.field; + for (i = 0; i < vbuf->vb2_buf.num_planes; i++) + vb2_set_plane_payload(&vbuf->vb2_buf, i, vpu_get_fmt_plane_size(cur_fmt, i)); + vbuf->field = cur_fmt->field; vbuf->sequence = sequence; dev_dbg(inst->dev, "[%d][OUTPUT TS]%32lld\n", inst->id, vbuf->vb2_buf.timestamp); @@ -747,15 +783,20 @@ static void vdec_stop_done(struct vpu_inst *inst) static bool vdec_check_source_change(struct vpu_inst *inst) { struct vdec_t *vdec = inst->priv; - const struct vpu_format *fmt; - int i; + const struct vpu_format *sibling; if (!inst->fh.m2m_ctx) return false; + if (vdec->reset_codec) + return false; + + sibling = vpu_helper_find_sibling(inst, inst->cap_format.type, inst->cap_format.pixfmt); + if (sibling && vdec->codec_info.pixfmt == sibling->pixfmt) + vdec->codec_info.pixfmt = inst->cap_format.pixfmt; + if (!vb2_is_streaming(v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx))) return true; - fmt = vpu_helper_find_format(inst, inst->cap_format.type, vdec->codec_info.pixfmt); if (inst->cap_format.pixfmt != vdec->codec_info.pixfmt) return true; if (inst->cap_format.width != vdec->codec_info.decoded_width) @@ -772,14 +813,6 @@ static bool vdec_check_source_change(struct vpu_inst *inst) return true; if (inst->crop.height != vdec->codec_info.height) return true; - if (fmt && inst->cap_format.num_planes != fmt->num_planes) - return true; - for (i = 0; i < inst->cap_format.num_planes; i++) { - if (inst->cap_format.bytesperline[i] != vdec->codec_info.bytesperline[i]) - return true; - if (inst->cap_format.sizeimage[i] != vdec->codec_info.sizeimage[i]) - return true; - } return false; } @@ -787,27 +820,21 @@ static bool vdec_check_source_change(struct vpu_inst *inst) static void vdec_init_fmt(struct vpu_inst *inst) { struct vdec_t *vdec = inst->priv; - const struct vpu_format *fmt; - int i; + struct v4l2_format f; - fmt = vpu_helper_find_format(inst, inst->cap_format.type, vdec->codec_info.pixfmt); - inst->out_format.width = vdec->codec_info.width; - inst->out_format.height = vdec->codec_info.height; - inst->cap_format.width = vdec->codec_info.decoded_width; - inst->cap_format.height = vdec->codec_info.decoded_height; - inst->cap_format.pixfmt = vdec->codec_info.pixfmt; - if (fmt) { - inst->cap_format.num_planes = fmt->num_planes; - inst->cap_format.flags = fmt->flags; - } - for (i = 0; i < inst->cap_format.num_planes; i++) { - inst->cap_format.bytesperline[i] = vdec->codec_info.bytesperline[i]; - inst->cap_format.sizeimage[i] = vdec->codec_info.sizeimage[i]; - } + memset(&f, 0, sizeof(f)); + f.type = inst->cap_format.type; + f.fmt.pix_mp.pixelformat = vdec->codec_info.pixfmt; + f.fmt.pix_mp.width = vdec->codec_info.decoded_width; + f.fmt.pix_mp.height = vdec->codec_info.decoded_height; if (vdec->codec_info.progressive) - inst->cap_format.field = V4L2_FIELD_NONE; + f.fmt.pix_mp.field = V4L2_FIELD_NONE; else - inst->cap_format.field = V4L2_FIELD_SEQ_TB; + f.fmt.pix_mp.field = V4L2_FIELD_SEQ_TB; + vpu_try_fmt_common(inst, &f, &inst->cap_format); + + inst->out_format.width = vdec->codec_info.width; + inst->out_format.height = vdec->codec_info.height; } static void vdec_init_crop(struct vpu_inst *inst) @@ -966,7 +993,10 @@ static int vdec_response_frame(struct vpu_inst *inst, struct vb2_v4l2_buffer *vb info.tag = vdec->seq_tag; info.luma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 0); info.luma_size = inst->cap_format.sizeimage[0]; - info.chroma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 1); + if (vbuf->vb2_buf.num_planes > 1) + info.chroma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 1); + else + info.chroma_addr = info.luma_addr + info.luma_size; info.chromau_size = inst->cap_format.sizeimage[1]; info.bytesperline = inst->cap_format.bytesperline[0]; ret = vpu_session_alloc_fs(inst, &info); @@ -975,7 +1005,7 @@ static int vdec_response_frame(struct vpu_inst *inst, struct vb2_v4l2_buffer *vb vpu_buf->tag = info.tag; vpu_buf->luma = info.luma_addr; - vpu_buf->chroma_u = info.chromau_size; + vpu_buf->chroma_u = info.chroma_addr; vpu_buf->chroma_v = 0; vpu_set_buffer_state(vbuf, VPU_BUF_STATE_INUSE); vdec->slots[info.id] = vpu_buf; @@ -1088,7 +1118,8 @@ static void vdec_event_seq_hdr(struct vpu_inst *inst, struct vpu_dec_codec_info vdec->seq_tag = vdec->codec_info.tag; if (vdec->is_source_changed) { vdec_update_state(inst, VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE, 0); - vpu_notify_source_change(inst); + vdec->source_change++; + vdec_handle_resolution_change(inst); vdec->is_source_changed = false; } } @@ -1335,6 +1366,8 @@ static void vdec_abort(struct vpu_inst *inst) vdec->decoded_frame_count, vdec->display_frame_count, vdec->sequence); + if (!vdec->seq_hdr_found) + vdec->reset_codec = true; vdec->params.end_flag = 0; vdec->drain = 0; vdec->params.frame_count = 0; @@ -1342,6 +1375,7 @@ static void vdec_abort(struct vpu_inst *inst) vdec->display_frame_count = 0; vdec->sequence = 0; vdec->aborting = false; + inst->extra_size = 0; } static void vdec_stop(struct vpu_inst *inst, bool free) @@ -1464,8 +1498,7 @@ static int vdec_start_session(struct vpu_inst *inst, u32 type) } if (V4L2_TYPE_IS_OUTPUT(type)) { - if (inst->state == VPU_CODEC_STATE_SEEK) - vdec_update_state(inst, vdec->state, 1); + vdec_update_state(inst, vdec->state, 1); vdec->eos_received = 0; vpu_process_output_buffer(inst); } else { @@ -1629,6 +1662,7 @@ static int vdec_open(struct file *file) return ret; vdec->fixed_fmt = false; + vdec->state = VPU_CODEC_STATE_ACTIVE; inst->min_buffer_cap = VDEC_MIN_BUFFER_CAP; inst->min_buffer_out = VDEC_MIN_BUFFER_OUT; vdec_init(file); diff --git a/drivers/media/platform/amphion/venc.c b/drivers/media/platform/amphion/venc.c index 37212f087fdd..3cbe8ce637e5 100644 --- a/drivers/media/platform/amphion/venc.c +++ b/drivers/media/platform/amphion/venc.c @@ -69,13 +69,24 @@ struct venc_frame_t { static const struct vpu_format venc_formats[] = { { .pixfmt = V4L2_PIX_FMT_NV12M, - .num_planes = 2, + .mem_planes = 2, + .comp_planes = 2, .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .sibling = V4L2_PIX_FMT_NV12, + }, + { + .pixfmt = V4L2_PIX_FMT_NV12, + .mem_planes = 1, + .comp_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .sibling = V4L2_PIX_FMT_NV12M, }, { .pixfmt = V4L2_PIX_FMT_H264, - .num_planes = 1, + .mem_planes = 1, + .comp_planes = 1, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .flags = V4L2_FMT_FLAG_COMPRESSED }, {0, 0, 0, 0}, }; @@ -173,14 +184,14 @@ static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) cur_fmt = vpu_get_format(inst, f->type); pixmp->pixelformat = cur_fmt->pixfmt; - pixmp->num_planes = cur_fmt->num_planes; + pixmp->num_planes = cur_fmt->mem_planes; pixmp->width = cur_fmt->width; pixmp->height = cur_fmt->height; pixmp->field = cur_fmt->field; pixmp->flags = cur_fmt->flags; for (i = 0; i < pixmp->num_planes; i++) { pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; - pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + pixmp->plane_fmt[i].sizeimage = vpu_get_fmt_plane_size(cur_fmt, i); } f->fmt.pix_mp.colorspace = venc->params.color.primaries; @@ -194,8 +205,9 @@ static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f) { struct vpu_inst *inst = to_inst(file); + struct vpu_format fmt; - vpu_try_fmt_common(inst, f); + vpu_try_fmt_common(inst, f, &fmt); return 0; } @@ -203,12 +215,11 @@ static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f) static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) { struct vpu_inst *inst = to_inst(file); - const struct vpu_format *fmt; + struct vpu_format fmt; struct vpu_format *cur_fmt; struct vb2_queue *q; struct venc_t *venc = inst->priv; struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; - int i; q = v4l2_m2m_get_vq(inst->fh.m2m_ctx, f->type); if (!q) @@ -216,24 +227,12 @@ static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) if (vb2_is_busy(q)) return -EBUSY; - fmt = vpu_try_fmt_common(inst, f); - if (!fmt) + if (vpu_try_fmt_common(inst, f, &fmt)) return -EINVAL; cur_fmt = vpu_get_format(inst, f->type); - cur_fmt->pixfmt = fmt->pixfmt; - cur_fmt->num_planes = fmt->num_planes; - cur_fmt->flags = fmt->flags; - cur_fmt->width = pix_mp->width; - cur_fmt->height = pix_mp->height; - for (i = 0; i < fmt->num_planes; i++) { - cur_fmt->sizeimage[i] = pix_mp->plane_fmt[i].sizeimage; - cur_fmt->bytesperline[i] = pix_mp->plane_fmt[i].bytesperline; - } - - if (pix_mp->field != V4L2_FIELD_ANY) - cur_fmt->field = pix_mp->field; + memcpy(cur_fmt, &fmt, sizeof(*cur_fmt)); if (V4L2_TYPE_IS_OUTPUT(f->type)) { venc->params.input_format = cur_fmt->pixfmt; diff --git a/drivers/media/platform/amphion/vpu.h b/drivers/media/platform/amphion/vpu.h index beac0309ca8d..3bfe193722af 100644 --- a/drivers/media/platform/amphion/vpu.h +++ b/drivers/media/platform/amphion/vpu.h @@ -13,6 +13,7 @@ #include <linux/mailbox_controller.h> #include <linux/kfifo.h> +#define VPU_TIMEOUT_WAKEUP msecs_to_jiffies(200) #define VPU_TIMEOUT msecs_to_jiffies(1000) #define VPU_INST_NULL_ID (-1L) #define VPU_MSG_BUFFER_SIZE (8192) @@ -84,7 +85,8 @@ struct vpu_dev { struct vpu_format { u32 pixfmt; - unsigned int num_planes; + u32 mem_planes; + u32 comp_planes; u32 type; u32 flags; u32 width; @@ -92,6 +94,7 @@ struct vpu_format { u32 sizeimage[VIDEO_MAX_PLANES]; u32 bytesperline[VIDEO_MAX_PLANES]; u32 field; + u32 sibling; }; struct vpu_core_resources { diff --git a/drivers/media/platform/amphion/vpu_cmds.c b/drivers/media/platform/amphion/vpu_cmds.c index f4d7ca78a621..fa581ba6bab2 100644 --- a/drivers/media/platform/amphion/vpu_cmds.c +++ b/drivers/media/platform/amphion/vpu_cmds.c @@ -269,7 +269,7 @@ exit: return flag; } -static int sync_session_response(struct vpu_inst *inst, unsigned long key) +static int sync_session_response(struct vpu_inst *inst, unsigned long key, long timeout, int try) { struct vpu_core *core; @@ -279,10 +279,12 @@ static int sync_session_response(struct vpu_inst *inst, unsigned long key) core = inst->core; call_void_vop(inst, wait_prepare); - wait_event_timeout(core->ack_wq, check_is_responsed(inst, key), VPU_TIMEOUT); + wait_event_timeout(core->ack_wq, check_is_responsed(inst, key), timeout); call_void_vop(inst, wait_finish); if (!check_is_responsed(inst, key)) { + if (try) + return -EINVAL; dev_err(inst->dev, "[%d] sync session timeout\n", inst->id); set_bit(inst->id, &core->hang_mask); mutex_lock(&inst->core->cmd_lock); @@ -294,6 +296,19 @@ static int sync_session_response(struct vpu_inst *inst, unsigned long key) return 0; } +static void vpu_core_keep_active(struct vpu_core *core) +{ + struct vpu_rpc_event pkt; + + memset(&pkt, 0, sizeof(pkt)); + vpu_iface_pack_cmd(core, &pkt, 0, VPU_CMD_ID_NOOP, NULL); + + dev_dbg(core->dev, "try to wake up\n"); + mutex_lock(&core->cmd_lock); + vpu_cmd_send(core, &pkt); + mutex_unlock(&core->cmd_lock); +} + static int vpu_session_send_cmd(struct vpu_inst *inst, u32 id, void *data) { unsigned long key; @@ -304,9 +319,25 @@ static int vpu_session_send_cmd(struct vpu_inst *inst, u32 id, void *data) return -EINVAL; ret = vpu_request_cmd(inst, id, data, &key, &sync); - if (!ret && sync) - ret = sync_session_response(inst, key); + if (ret) + goto exit; + + /* workaround for a firmware issue, + * firmware should be waked up by start or configure command, + * but there is a very small change that firmware failed to wakeup. + * in such case, try to wakeup firmware again by sending a noop command + */ + if (sync && (id == VPU_CMD_ID_CONFIGURE_CODEC || id == VPU_CMD_ID_START)) { + if (sync_session_response(inst, key, VPU_TIMEOUT_WAKEUP, 1)) + vpu_core_keep_active(inst->core); + else + goto exit; + } + + if (sync) + ret = sync_session_response(inst, key, VPU_TIMEOUT, 0); +exit: if (ret) dev_err(inst->dev, "[%d] send cmd(0x%x) fail\n", inst->id, id); diff --git a/drivers/media/platform/amphion/vpu_dbg.c b/drivers/media/platform/amphion/vpu_dbg.c index 260f1c4b8f8d..44b830ae01d8 100644 --- a/drivers/media/platform/amphion/vpu_dbg.c +++ b/drivers/media/platform/amphion/vpu_dbg.c @@ -90,9 +90,9 @@ static int vpu_dbg_instance(struct seq_file *s, void *data) vq->last_buffer_dequeued); if (seq_write(s, str, num)) return 0; - for (i = 0; i < inst->out_format.num_planes; i++) { + for (i = 0; i < inst->out_format.mem_planes; i++) { num = scnprintf(str, sizeof(str), " %d(%d)", - inst->out_format.sizeimage[i], + vpu_get_fmt_plane_size(&inst->out_format, i), inst->out_format.bytesperline[i]); if (seq_write(s, str, num)) return 0; @@ -114,9 +114,9 @@ static int vpu_dbg_instance(struct seq_file *s, void *data) vq->last_buffer_dequeued); if (seq_write(s, str, num)) return 0; - for (i = 0; i < inst->cap_format.num_planes; i++) { + for (i = 0; i < inst->cap_format.mem_planes; i++) { num = scnprintf(str, sizeof(str), " %d(%d)", - inst->cap_format.sizeimage[i], + vpu_get_fmt_plane_size(&inst->cap_format, i), inst->cap_format.bytesperline[i]); if (seq_write(s, str, num)) return 0; diff --git a/drivers/media/platform/amphion/vpu_drv.c b/drivers/media/platform/amphion/vpu_drv.c index 9d5a5075343d..f01ce49d27e8 100644 --- a/drivers/media/platform/amphion/vpu_drv.c +++ b/drivers/media/platform/amphion/vpu_drv.c @@ -245,7 +245,11 @@ static int __init vpu_driver_init(void) if (ret) return ret; - return vpu_core_driver_init(); + ret = vpu_core_driver_init(); + if (ret) + platform_driver_unregister(&hion_vpu_driver); + + return ret; } static void __exit vpu_driver_exit(void) diff --git a/drivers/media/platform/amphion/vpu_helpers.c b/drivers/media/platform/amphion/vpu_helpers.c index e9aeb3453dfc..019c77e84514 100644 --- a/drivers/media/platform/amphion/vpu_helpers.c +++ b/drivers/media/platform/amphion/vpu_helpers.c @@ -59,6 +59,36 @@ const struct vpu_format *vpu_helper_find_format(struct vpu_inst *inst, u32 type, return NULL; } +const struct vpu_format *vpu_helper_find_sibling(struct vpu_inst *inst, u32 type, u32 pixelfmt) +{ + const struct vpu_format *fmt; + const struct vpu_format *sibling; + + fmt = vpu_helper_find_format(inst, type, pixelfmt); + if (!fmt || !fmt->sibling) + return NULL; + + sibling = vpu_helper_find_format(inst, type, fmt->sibling); + if (!sibling || sibling->sibling != fmt->pixfmt || + sibling->comp_planes != fmt->comp_planes) + return NULL; + + return sibling; +} + +bool vpu_helper_match_format(struct vpu_inst *inst, u32 type, u32 fmta, u32 fmtb) +{ + const struct vpu_format *sibling; + + if (fmta == fmtb) + return true; + + sibling = vpu_helper_find_sibling(inst, type, fmta); + if (sibling && sibling->pixfmt == fmtb) + return true; + return false; +} + const struct vpu_format *vpu_helper_enum_format(struct vpu_inst *inst, u32 type, int index) { const struct vpu_format *pfmt; @@ -123,9 +153,10 @@ static u32 get_nv12_plane_size(u32 width, u32 height, int plane_no, u32 bytesperline; u32 size = 0; - bytesperline = ALIGN(width, stride); + bytesperline = width; if (pbl) bytesperline = max(bytesperline, *pbl); + bytesperline = ALIGN(bytesperline, stride); height = ALIGN(height, 2); if (plane_no == 0) size = bytesperline * height; @@ -148,13 +179,13 @@ static u32 get_tiled_8l128_plane_size(u32 fmt, u32 width, u32 height, int plane_ if (interlaced) hs++; - if (fmt == V4L2_PIX_FMT_NV12M_10BE_8L128) + if (fmt == V4L2_PIX_FMT_NV12M_10BE_8L128 || fmt == V4L2_PIX_FMT_NV12_10BE_8L128) bitdepth = 10; bytesperline = DIV_ROUND_UP(width * bitdepth, BITS_PER_BYTE); - bytesperline = ALIGN(bytesperline, 1 << ws); - bytesperline = ALIGN(bytesperline, stride); if (pbl) bytesperline = max(bytesperline, *pbl); + bytesperline = ALIGN(bytesperline, 1 << ws); + bytesperline = ALIGN(bytesperline, stride); height = ALIGN(height, 1 << hs); if (plane_no == 0) size = bytesperline * height; @@ -172,9 +203,10 @@ static u32 get_default_plane_size(u32 width, u32 height, int plane_no, u32 bytesperline; u32 size = 0; - bytesperline = ALIGN(width, stride); + bytesperline = width; if (pbl) bytesperline = max(bytesperline, *pbl); + bytesperline = ALIGN(bytesperline, stride); if (plane_no == 0) size = bytesperline * height; if (pbl) @@ -187,9 +219,12 @@ u32 vpu_helper_get_plane_size(u32 fmt, u32 w, u32 h, int plane_no, u32 stride, u32 interlaced, u32 *pbl) { switch (fmt) { + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV12M: return get_nv12_plane_size(w, h, plane_no, stride, interlaced, pbl); + case V4L2_PIX_FMT_NV12_8L128: case V4L2_PIX_FMT_NV12M_8L128: + case V4L2_PIX_FMT_NV12_10BE_8L128: case V4L2_PIX_FMT_NV12M_10BE_8L128: return get_tiled_8l128_plane_size(fmt, w, h, plane_no, stride, interlaced, pbl); default: diff --git a/drivers/media/platform/amphion/vpu_helpers.h b/drivers/media/platform/amphion/vpu_helpers.h index bc28350958be..0eaddb07190d 100644 --- a/drivers/media/platform/amphion/vpu_helpers.h +++ b/drivers/media/platform/amphion/vpu_helpers.h @@ -14,6 +14,8 @@ struct vpu_pair { int vpu_helper_find_in_array_u8(const u8 *array, u32 size, u32 x); bool vpu_helper_check_type(struct vpu_inst *inst, u32 type); const struct vpu_format *vpu_helper_find_format(struct vpu_inst *inst, u32 type, u32 pixelfmt); +const struct vpu_format *vpu_helper_find_sibling(struct vpu_inst *inst, u32 type, u32 pixelfmt); +bool vpu_helper_match_format(struct vpu_inst *inst, u32 type, u32 fmta, u32 fmtb); const struct vpu_format *vpu_helper_enum_format(struct vpu_inst *inst, u32 type, int index); u32 vpu_helper_valid_frame_width(struct vpu_inst *inst, u32 width); u32 vpu_helper_valid_frame_height(struct vpu_inst *inst, u32 height); diff --git a/drivers/media/platform/amphion/vpu_malone.c b/drivers/media/platform/amphion/vpu_malone.c index 51e0702f9ae1..2c9bfc6a5a72 100644 --- a/drivers/media/platform/amphion/vpu_malone.c +++ b/drivers/media/platform/amphion/vpu_malone.c @@ -583,7 +583,8 @@ bool vpu_malone_check_fmt(enum vpu_core_type type, u32 pixelfmt) if (!vpu_imx8q_check_fmt(type, pixelfmt)) return false; - if (pixelfmt == V4L2_PIX_FMT_NV12M_8L128 || pixelfmt == V4L2_PIX_FMT_NV12M_10BE_8L128) + if (pixelfmt == V4L2_PIX_FMT_NV12_8L128 || pixelfmt == V4L2_PIX_FMT_NV12_10BE_8L128 || + pixelfmt == V4L2_PIX_FMT_NV12M_8L128 || pixelfmt == V4L2_PIX_FMT_NV12M_10BE_8L128) return true; if (vpu_malone_format_remap(pixelfmt) == MALONE_FMT_NULL) return false; @@ -692,6 +693,7 @@ int vpu_malone_set_decode_params(struct vpu_shared_addr *shared, } static struct vpu_pair malone_cmds[] = { + {VPU_CMD_ID_NOOP, VID_API_CMD_NULL}, {VPU_CMD_ID_START, VID_API_CMD_START}, {VPU_CMD_ID_STOP, VID_API_CMD_STOP}, {VPU_CMD_ID_ABORT, VID_API_CMD_ABORT}, diff --git a/drivers/media/platform/amphion/vpu_msgs.c b/drivers/media/platform/amphion/vpu_msgs.c index d8247f36d84b..92672a802b49 100644 --- a/drivers/media/platform/amphion/vpu_msgs.c +++ b/drivers/media/platform/amphion/vpu_msgs.c @@ -43,6 +43,7 @@ static void vpu_session_handle_mem_request(struct vpu_inst *inst, struct vpu_rpc req_data.ref_frame_num, req_data.act_buf_size, req_data.act_buf_num); + vpu_inst_lock(inst); call_void_vop(inst, mem_request, req_data.enc_frame_size, req_data.enc_frame_num, @@ -50,6 +51,7 @@ static void vpu_session_handle_mem_request(struct vpu_inst *inst, struct vpu_rpc req_data.ref_frame_num, req_data.act_buf_size, req_data.act_buf_num); + vpu_inst_unlock(inst); } static void vpu_session_handle_stop_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt) diff --git a/drivers/media/platform/amphion/vpu_v4l2.c b/drivers/media/platform/amphion/vpu_v4l2.c index b779e0ba916c..6773b885597c 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.c +++ b/drivers/media/platform/amphion/vpu_v4l2.c @@ -65,18 +65,11 @@ unsigned int vpu_get_buffer_state(struct vb2_v4l2_buffer *vbuf) void vpu_v4l2_set_error(struct vpu_inst *inst) { - struct vb2_queue *src_q; - struct vb2_queue *dst_q; - vpu_inst_lock(inst); dev_err(inst->dev, "some error occurs in codec\n"); if (inst->fh.m2m_ctx) { - src_q = v4l2_m2m_get_src_vq(inst->fh.m2m_ctx); - dst_q = v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx); - src_q->error = 1; - dst_q->error = 1; - wake_up(&src_q->done_wq); - wake_up(&dst_q->done_wq); + vb2_queue_error(v4l2_m2m_get_src_vq(inst->fh.m2m_ctx)); + vb2_queue_error(v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx)); } vpu_inst_unlock(inst); } @@ -140,51 +133,136 @@ bool vpu_is_source_empty(struct vpu_inst *inst) return true; } -const struct vpu_format *vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f) +static int vpu_init_format(struct vpu_inst *inst, struct vpu_format *fmt) +{ + const struct vpu_format *info; + + info = vpu_helper_find_format(inst, fmt->type, fmt->pixfmt); + if (!info) { + info = vpu_helper_enum_format(inst, fmt->type, 0); + if (!info) + return -EINVAL; + } + memcpy(fmt, info, sizeof(*fmt)); + + return 0; +} + +static int vpu_calc_fmt_bytesperline(struct v4l2_format *f, struct vpu_format *fmt) { struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; - u32 type = f->type; + int i; + + if (fmt->flags & V4L2_FMT_FLAG_COMPRESSED) { + for (i = 0; i < fmt->comp_planes; i++) + fmt->bytesperline[i] = 0; + return 0; + } + if (pixmp->num_planes == fmt->comp_planes) { + for (i = 0; i < fmt->comp_planes; i++) + fmt->bytesperline[i] = pixmp->plane_fmt[i].bytesperline; + return 0; + } + if (pixmp->num_planes > 1) + return -EINVAL; + + /*amphion vpu only support nv12 and nv12 tiled, + * so the bytesperline of luma and chroma should be same + */ + for (i = 0; i < fmt->comp_planes; i++) + fmt->bytesperline[i] = pixmp->plane_fmt[0].bytesperline; + + return 0; +} + +static int vpu_calc_fmt_sizeimage(struct vpu_inst *inst, struct vpu_format *fmt) +{ u32 stride = 1; - u32 bytesperline; - u32 sizeimage; - const struct vpu_format *fmt; - const struct vpu_core_resources *res; int i; - fmt = vpu_helper_find_format(inst, type, pixmp->pixelformat); - if (!fmt) { - fmt = vpu_helper_enum_format(inst, type, 0); - if (!fmt) - return NULL; - pixmp->pixelformat = fmt->pixfmt; + if (!(fmt->flags & V4L2_FMT_FLAG_COMPRESSED)) { + const struct vpu_core_resources *res = vpu_get_resource(inst); + + if (res) + stride = res->stride; } - res = vpu_get_resource(inst); - if (res) - stride = res->stride; - if (pixmp->width) - pixmp->width = vpu_helper_valid_frame_width(inst, pixmp->width); - if (pixmp->height) - pixmp->height = vpu_helper_valid_frame_height(inst, pixmp->height); + for (i = 0; i < fmt->comp_planes; i++) { + fmt->sizeimage[i] = vpu_helper_get_plane_size(fmt->pixfmt, + fmt->width, + fmt->height, + i, + stride, + fmt->field != V4L2_FIELD_NONE ? 1 : 0, + &fmt->bytesperline[i]); + fmt->sizeimage[i] = max_t(u32, fmt->sizeimage[i], PAGE_SIZE); + if (fmt->flags & V4L2_FMT_FLAG_COMPRESSED) { + fmt->sizeimage[i] = clamp_val(fmt->sizeimage[i], SZ_128K, SZ_8M); + fmt->bytesperline[i] = 0; + } + } + + return 0; +} + +u32 vpu_get_fmt_plane_size(struct vpu_format *fmt, u32 plane_no) +{ + u32 size; + int i; + + if (plane_no >= fmt->mem_planes) + return 0; + + if (fmt->comp_planes == fmt->mem_planes) + return fmt->sizeimage[plane_no]; + if (plane_no < fmt->mem_planes - 1) + return fmt->sizeimage[plane_no]; + + size = fmt->sizeimage[plane_no]; + for (i = fmt->mem_planes; i < fmt->comp_planes; i++) + size += fmt->sizeimage[i]; + + return size; +} + +int vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f, struct vpu_format *fmt) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + int i; + int ret; + + fmt->pixfmt = pixmp->pixelformat; + fmt->type = f->type; + ret = vpu_init_format(inst, fmt); + if (ret < 0) + return ret; + + fmt->width = pixmp->width; + fmt->height = pixmp->height; + if (fmt->width) + fmt->width = vpu_helper_valid_frame_width(inst, fmt->width); + if (fmt->height) + fmt->height = vpu_helper_valid_frame_height(inst, fmt->height); + fmt->field = pixmp->field == V4L2_FIELD_ANY ? V4L2_FIELD_NONE : pixmp->field; + vpu_calc_fmt_bytesperline(f, fmt); + vpu_calc_fmt_sizeimage(inst, fmt); + if ((fmt->flags & V4L2_FMT_FLAG_COMPRESSED) && pixmp->plane_fmt[0].sizeimage) + fmt->sizeimage[0] = clamp_val(pixmp->plane_fmt[0].sizeimage, SZ_128K, SZ_8M); + + pixmp->pixelformat = fmt->pixfmt; + pixmp->width = fmt->width; + pixmp->height = fmt->height; pixmp->flags = fmt->flags; - pixmp->num_planes = fmt->num_planes; - if (pixmp->field == V4L2_FIELD_ANY) - pixmp->field = V4L2_FIELD_NONE; + pixmp->num_planes = fmt->mem_planes; + pixmp->field = fmt->field; + memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); for (i = 0; i < pixmp->num_planes; i++) { - bytesperline = max_t(s32, pixmp->plane_fmt[i].bytesperline, 0); - sizeimage = vpu_helper_get_plane_size(pixmp->pixelformat, - pixmp->width, - pixmp->height, - i, - stride, - pixmp->field > V4L2_FIELD_NONE ? 1 : 0, - &bytesperline); - sizeimage = max_t(s32, pixmp->plane_fmt[i].sizeimage, sizeimage); - pixmp->plane_fmt[i].bytesperline = bytesperline; - pixmp->plane_fmt[i].sizeimage = sizeimage; + pixmp->plane_fmt[i].bytesperline = fmt->bytesperline[i]; + pixmp->plane_fmt[i].sizeimage = vpu_get_fmt_plane_size(fmt, i); + memset(pixmp->plane_fmt[i].reserved, 0, sizeof(pixmp->plane_fmt[i].reserved)); } - return fmt; + return 0; } static bool vpu_check_ready(struct vpu_inst *inst, u32 type) @@ -249,8 +327,12 @@ int vpu_process_capture_buffer(struct vpu_inst *inst) struct vb2_v4l2_buffer *vpu_next_src_buf(struct vpu_inst *inst) { - struct vb2_v4l2_buffer *src_buf = v4l2_m2m_next_src_buf(inst->fh.m2m_ctx); + struct vb2_v4l2_buffer *src_buf = NULL; + + if (!inst->fh.m2m_ctx) + return NULL; + src_buf = v4l2_m2m_next_src_buf(inst->fh.m2m_ctx); if (!src_buf || vpu_get_buffer_state(src_buf) == VPU_BUF_STATE_IDLE) return NULL; @@ -273,7 +355,7 @@ void vpu_skip_frame(struct vpu_inst *inst, int count) enum vb2_buffer_state state; int i = 0; - if (count <= 0) + if (count <= 0 || !inst->fh.m2m_ctx) return; while (i < count) { @@ -389,10 +471,10 @@ static int vpu_vb2_queue_setup(struct vb2_queue *vq, cur_fmt = vpu_get_format(inst, vq->type); if (*plane_count) { - if (*plane_count != cur_fmt->num_planes) + if (*plane_count != cur_fmt->mem_planes) return -EINVAL; - for (i = 0; i < cur_fmt->num_planes; i++) { - if (psize[i] < cur_fmt->sizeimage[i]) + for (i = 0; i < cur_fmt->mem_planes; i++) { + if (psize[i] < vpu_get_fmt_plane_size(cur_fmt, i)) return -EINVAL; } return 0; @@ -402,9 +484,9 @@ static int vpu_vb2_queue_setup(struct vb2_queue *vq, *buf_count = max_t(unsigned int, *buf_count, inst->min_buffer_out); else *buf_count = max_t(unsigned int, *buf_count, inst->min_buffer_cap); - *plane_count = cur_fmt->num_planes; - for (i = 0; i < cur_fmt->num_planes; i++) - psize[i] = cur_fmt->sizeimage[i]; + *plane_count = cur_fmt->mem_planes; + for (i = 0; i < cur_fmt->mem_planes; i++) + psize[i] = vpu_get_fmt_plane_size(cur_fmt, i); return 0; } @@ -434,8 +516,8 @@ static int vpu_vb2_buf_prepare(struct vb2_buffer *vb) u32 i; cur_fmt = vpu_get_format(inst, vb->type); - for (i = 0; i < cur_fmt->num_planes; i++) { - if (vpu_get_vb_length(vb, i) < cur_fmt->sizeimage[i]) { + for (i = 0; i < cur_fmt->mem_planes; i++) { + if (vpu_get_vb_length(vb, i) < vpu_get_fmt_plane_size(cur_fmt, i)) { dev_dbg(inst->dev, "[%d] %s buf[%d] is invalid\n", inst->id, vpu_type_name(vb->type), vb->index); vpu_set_buffer_state(vbuf, VPU_BUF_STATE_ERROR); @@ -603,10 +685,6 @@ static int vpu_v4l2_release(struct vpu_inst *inst) inst->workqueue = NULL; } - if (inst->fh.m2m_ctx) { - v4l2_m2m_ctx_release(inst->fh.m2m_ctx); - inst->fh.m2m_ctx = NULL; - } v4l2_ctrl_handler_free(&inst->ctrl_handler); mutex_destroy(&inst->lock); v4l2_fh_del(&inst->fh); @@ -689,6 +767,13 @@ int vpu_v4l2_close(struct file *file) vpu_trace(vpu->dev, "tgid = %d, pid = %d, inst = %p\n", inst->tgid, inst->pid, inst); + vpu_inst_lock(inst); + if (inst->fh.m2m_ctx) { + v4l2_m2m_ctx_release(inst->fh.m2m_ctx); + inst->fh.m2m_ctx = NULL; + } + vpu_inst_unlock(inst); + call_void_vop(inst, release); vpu_inst_unregister(inst); vpu_inst_put(inst); diff --git a/drivers/media/platform/amphion/vpu_v4l2.h b/drivers/media/platform/amphion/vpu_v4l2.h index 795ca33a6a50..ef5de6b66e47 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.h +++ b/drivers/media/platform/amphion/vpu_v4l2.h @@ -16,7 +16,8 @@ unsigned int vpu_get_buffer_state(struct vb2_v4l2_buffer *vbuf); int vpu_v4l2_open(struct file *file, struct vpu_inst *inst); int vpu_v4l2_close(struct file *file); -const struct vpu_format *vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f); +u32 vpu_get_fmt_plane_size(struct vpu_format *fmt, u32 plane_no); +int vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f, struct vpu_format *fmt); int vpu_process_output_buffer(struct vpu_inst *inst); int vpu_process_capture_buffer(struct vpu_inst *inst); struct vb2_v4l2_buffer *vpu_next_src_buf(struct vpu_inst *inst); diff --git a/drivers/media/platform/amphion/vpu_windsor.c b/drivers/media/platform/amphion/vpu_windsor.c index 1526af2ef9da..b245ff6a1102 100644 --- a/drivers/media/platform/amphion/vpu_windsor.c +++ b/drivers/media/platform/amphion/vpu_windsor.c @@ -658,6 +658,7 @@ int vpu_windsor_get_stream_buffer_size(struct vpu_shared_addr *shared) } static struct vpu_pair windsor_cmds[] = { + {VPU_CMD_ID_NOOP, GTB_ENC_CMD_NOOP}, {VPU_CMD_ID_CONFIGURE_CODEC, GTB_ENC_CMD_CONFIGURE_CODEC}, {VPU_CMD_ID_START, GTB_ENC_CMD_STREAM_START}, {VPU_CMD_ID_STOP, GTB_ENC_CMD_STREAM_STOP}, @@ -775,6 +776,8 @@ static int vpu_windsor_fill_yuv_frame(struct vpu_shared_addr *shared, u32 instance, struct vb2_buffer *vb) { + struct vpu_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vpu_format *out_fmt; struct vpu_enc_yuv_desc *desc; struct vb2_v4l2_buffer *vbuf; @@ -782,6 +785,7 @@ static int vpu_windsor_fill_yuv_frame(struct vpu_shared_addr *shared, return -EINVAL; desc = get_yuv_desc(shared, instance); + out_fmt = vpu_get_format(inst, vb->type); vbuf = to_vb2_v4l2_buffer(vb); desc->frame_id = vbuf->sequence; @@ -790,7 +794,10 @@ static int vpu_windsor_fill_yuv_frame(struct vpu_shared_addr *shared, else desc->key_frame = 0; desc->luma_base = vpu_get_vb_phy_addr(vb, 0); - desc->chroma_base = vpu_get_vb_phy_addr(vb, 1); + if (vb->num_planes > 1) + desc->chroma_base = vpu_get_vb_phy_addr(vb, 1); + else + desc->chroma_base = desc->luma_base + out_fmt->sizeimage[0]; return 0; } diff --git a/drivers/media/platform/aspeed/Kconfig b/drivers/media/platform/aspeed/Kconfig index c871eda33570..16c5d8913488 100644 --- a/drivers/media/platform/aspeed/Kconfig +++ b/drivers/media/platform/aspeed/Kconfig @@ -4,6 +4,7 @@ comment "Aspeed media platform drivers" config VIDEO_ASPEED tristate "Aspeed AST2400 and AST2500 Video Engine driver" + depends on ARCH_ASPEED || COMPILE_TEST depends on V4L_PLATFORM_DRIVERS depends on VIDEO_DEV select VIDEOBUF2_DMA_CONTIG diff --git a/drivers/media/platform/aspeed/aspeed-video.c b/drivers/media/platform/aspeed/aspeed-video.c index 20f795ccc11b..794d4dc3a654 100644 --- a/drivers/media/platform/aspeed/aspeed-video.c +++ b/drivers/media/platform/aspeed/aspeed-video.c @@ -33,6 +33,7 @@ #include <media/v4l2-event.h> #include <media/v4l2-ioctl.h> #include <media/videobuf2-dma-contig.h> +#include <uapi/linux/aspeed-video.h> #define ASPEED_VIDEO_V4L2_MIN_BUF_REQ 3 @@ -59,6 +60,7 @@ #define VE_MAX_SRC_BUFFER_SIZE 0x8ca000 /* 1920 * 1200, 32bpp */ #define VE_JPEG_HEADER_SIZE 0x006000 /* 512 * 12 * 4 */ +#define VE_BCD_BUFF_SIZE 0x9000 /* (1920/8) * (1200/8) */ #define VE_PROTECTION_KEY 0x000 #define VE_PROTECTION_KEY_UNLOCK 0x1a038aa8 @@ -107,6 +109,13 @@ #define VE_SCALING_FILTER2 0x020 #define VE_SCALING_FILTER3 0x024 +#define VE_BCD_CTRL 0x02C +#define VE_BCD_CTRL_EN_BCD BIT(0) +#define VE_BCD_CTRL_EN_ABCD BIT(1) +#define VE_BCD_CTRL_EN_CB BIT(2) +#define VE_BCD_CTRL_THR GENMASK(23, 16) +#define VE_BCD_CTRL_ABCD_THR GENMASK(31, 24) + #define VE_CAP_WINDOW 0x030 #define VE_COMP_WINDOW 0x034 #define VE_COMP_PROC_OFFSET 0x038 @@ -115,6 +124,7 @@ #define VE_SRC0_ADDR 0x044 #define VE_SRC_SCANLINE_OFFSET 0x048 #define VE_SRC1_ADDR 0x04c +#define VE_BCD_ADDR 0x050 #define VE_COMP_ADDR 0x054 #define VE_STREAM_BUF_SIZE 0x058 @@ -135,6 +145,8 @@ #define VE_COMP_CTRL_HQ_DCT_CHR GENMASK(26, 22) #define VE_COMP_CTRL_HQ_DCT_LUM GENMASK(31, 27) +#define VE_CB_ADDR 0x06C + #define AST2400_VE_COMP_SIZE_READ_BACK 0x078 #define AST2600_VE_COMP_SIZE_READ_BACK 0x084 @@ -211,6 +223,12 @@ enum { VIDEO_CLOCKS_ON, }; +enum aspeed_video_format { + VIDEO_FMT_STANDARD = 0, + VIDEO_FMT_ASPEED, + VIDEO_FMT_MAX = VIDEO_FMT_ASPEED +}; + // for VE_CTRL_CAPTURE_FMT enum aspeed_video_capture_format { VIDEO_CAP_FMT_YUV_STUDIO_SWING = 0, @@ -245,16 +263,20 @@ struct aspeed_video_perf { /* * struct aspeed_video - driver data * - * res_work: holds the delayed_work for res-detection if unlock - * buffers: holds the list of buffer queued from user + * res_work: holds the delayed_work for res-detection if unlock + * buffers: holds the list of buffer queued from user * flags: holds the state of video * sequence: holds the last number of frame completed * max_compressed_size: holds max compressed stream's size * srcs: holds the buffer information for srcs * jpeg: holds the buffer information for jpeg header + * bcd: holds the buffer information for bcd work * yuv420: a flag raised if JPEG subsampling is 420 + * format: holds the video format + * hq_mode: a flag raised if HQ is enabled. Only for VIDEO_FMT_ASPEED * frame_rate: holds the frame_rate * jpeg_quality: holds jpeq's quality (0~11) + * jpeg_hq_quality: holds hq's quality (1~12) only if hq_mode enabled * frame_bottom: end position of video data in vertical direction * frame_left: start position of video data in horizontal direction * frame_right: end position of video data in horizontal direction @@ -290,10 +312,14 @@ struct aspeed_video { unsigned int max_compressed_size; struct aspeed_video_addr srcs[2]; struct aspeed_video_addr jpeg; + struct aspeed_video_addr bcd; bool yuv420; + enum aspeed_video_format format; + bool hq_mode; unsigned int frame_rate; unsigned int jpeg_quality; + unsigned int jpeg_hq_quality; unsigned int frame_bottom; unsigned int frame_left; @@ -458,8 +484,18 @@ static const struct v4l2_dv_timings_cap aspeed_video_timings_cap = { }, }; +static const char * const format_str[] = {"Standard JPEG", + "Aspeed JPEG"}; + static unsigned int debug; +static bool aspeed_video_alloc_buf(struct aspeed_video *video, + struct aspeed_video_addr *addr, + unsigned int size); + +static void aspeed_video_free_buf(struct aspeed_video *video, + struct aspeed_video_addr *addr); + static void aspeed_video_init_jpeg_table(u32 *table, bool yuv420) { int i; @@ -547,24 +583,39 @@ static int aspeed_video_start_frame(struct aspeed_video *video) unsigned long flags; struct aspeed_video_buffer *buf; u32 seq_ctrl = aspeed_video_read(video, VE_SEQ_CTRL); + bool bcd_buf_need = (video->format != VIDEO_FMT_STANDARD); if (video->v4l2_input_status) { - v4l2_warn(&video->v4l2_dev, "No signal; don't start frame\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "No signal; don't start frame\n"); return 0; } if (!(seq_ctrl & VE_SEQ_CTRL_COMP_BUSY) || !(seq_ctrl & VE_SEQ_CTRL_CAP_BUSY)) { - v4l2_warn(&video->v4l2_dev, "Engine busy; don't start frame\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Engine busy; don't start frame\n"); return -EBUSY; } + if (bcd_buf_need && !video->bcd.size) { + if (!aspeed_video_alloc_buf(video, &video->bcd, + VE_BCD_BUFF_SIZE)) { + dev_err(video->dev, "Failed to allocate BCD buffer\n"); + dev_err(video->dev, "don't start frame\n"); + return -ENOMEM; + } + aspeed_video_write(video, VE_BCD_ADDR, video->bcd.dma); + v4l2_dbg(1, debug, &video->v4l2_dev, "bcd addr(%pad) size(%d)\n", + &video->bcd.dma, video->bcd.size); + } else if (!bcd_buf_need && video->bcd.size) { + aspeed_video_free_buf(video, &video->bcd); + } + spin_lock_irqsave(&video->lock, flags); buf = list_first_entry_or_null(&video->buffers, struct aspeed_video_buffer, link); if (!buf) { spin_unlock_irqrestore(&video->lock, flags); - v4l2_warn(&video->v4l2_dev, "No buffers; don't start frame\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "No buffers; don't start frame\n"); return -EPROTO; } @@ -657,6 +708,24 @@ static void aspeed_video_irq_res_change(struct aspeed_video *video, ulong delay) schedule_delayed_work(&video->res_work, delay); } +static void aspeed_video_swap_src_buf(struct aspeed_video *v) +{ + if (v->format == VIDEO_FMT_STANDARD) + return; + + /* Reset bcd buffer to have a full frame update every 8 frames. */ + if (IS_ALIGNED(v->sequence, 8)) + memset((u8 *)v->bcd.virt, 0x00, VE_BCD_BUFF_SIZE); + + if (v->sequence & 0x01) { + aspeed_video_write(v, VE_SRC0_ADDR, v->srcs[1].dma); + aspeed_video_write(v, VE_SRC1_ADDR, v->srcs[0].dma); + } else { + aspeed_video_write(v, VE_SRC0_ADDR, v->srcs[0].dma); + aspeed_video_write(v, VE_SRC1_ADDR, v->srcs[1].dma); + } +} + static irqreturn_t aspeed_video_irq(int irq, void *arg) { struct aspeed_video *video = arg; @@ -705,6 +774,7 @@ static irqreturn_t aspeed_video_irq(int irq, void *arg) if (sts & VE_INTERRUPT_COMP_COMPLETE) { struct aspeed_video_buffer *buf; + bool empty = true; u32 frame_size = aspeed_video_read(video, video->comp_size_read); @@ -718,13 +788,23 @@ static irqreturn_t aspeed_video_irq(int irq, void *arg) if (buf) { vb2_set_plane_payload(&buf->vb.vb2_buf, 0, frame_size); - if (!list_is_last(&buf->link, &video->buffers)) { + /* + * aspeed_jpeg requires continuous update. + * On the contrary, standard jpeg can keep last buffer + * to always have the latest result. + */ + if (video->format == VIDEO_FMT_STANDARD && + list_is_last(&buf->link, &video->buffers)) { + empty = false; + v4l2_dbg(1, debug, &video->v4l2_dev, "skip to keep last frame updated\n"); + } else { buf->vb.vb2_buf.timestamp = ktime_get_ns(); buf->vb.sequence = video->sequence++; buf->vb.field = V4L2_FIELD_NONE; vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); list_del(&buf->link); + empty = list_empty(&video->buffers); } } spin_unlock(&video->lock); @@ -738,7 +818,10 @@ static irqreturn_t aspeed_video_irq(int irq, void *arg) aspeed_video_write(video, VE_INTERRUPT_STATUS, VE_INTERRUPT_COMP_COMPLETE); sts &= ~VE_INTERRUPT_COMP_COMPLETE; - if (test_bit(VIDEO_STREAMING, &video->flags) && buf) + + aspeed_video_swap_src_buf(video); + + if (test_bit(VIDEO_STREAMING, &video->flags) && !empty) aspeed_video_start_frame(video); } @@ -977,7 +1060,7 @@ static void aspeed_video_get_resolution(struct aspeed_video *video) res_check(video), MODE_DETECT_TIMEOUT); if (!rc) { - v4l2_warn(&video->v4l2_dev, "Timed out; first mode detect\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Timed out; first mode detect\n"); clear_bit(VIDEO_RES_DETECT, &video->flags); return; } @@ -998,7 +1081,7 @@ static void aspeed_video_get_resolution(struct aspeed_video *video) MODE_DETECT_TIMEOUT); clear_bit(VIDEO_RES_DETECT, &video->flags); if (!rc) { - v4l2_warn(&video->v4l2_dev, "Timed out; second mode detect\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Timed out; second mode detect\n"); return; } @@ -1021,7 +1104,7 @@ static void aspeed_video_get_resolution(struct aspeed_video *video) } while (invalid_resolution && (tries++ < INVALID_RESOLUTION_RETRIES)); if (invalid_resolution) { - v4l2_warn(&video->v4l2_dev, "Invalid resolution detected\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Invalid resolution detected\n"); return; } @@ -1085,10 +1168,14 @@ static void aspeed_video_set_resolution(struct aspeed_video *video) FIELD_PREP(VE_TGS_FIRST, video->frame_top) | FIELD_PREP(VE_TGS_LAST, video->frame_bottom + 1)); - aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_INT_DE); + aspeed_video_update(video, VE_CTRL, + VE_CTRL_INT_DE | VE_CTRL_DIRECT_FETCH, + VE_CTRL_INT_DE); } else { v4l2_dbg(1, debug, &video->v4l2_dev, "Capture: Direct Mode\n"); - aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_DIRECT_FETCH); + aspeed_video_update(video, VE_CTRL, + VE_CTRL_INT_DE | VE_CTRL_DIRECT_FETCH, + VE_CTRL_DIRECT_FETCH); } size *= 4; @@ -1121,21 +1208,65 @@ err_mem: aspeed_video_free_buf(video, &video->srcs[0]); } -static void aspeed_video_init_regs(struct aspeed_video *video) +static void aspeed_video_update_regs(struct aspeed_video *video) { - u32 comp_ctrl = VE_COMP_CTRL_RSVD | - FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | - FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10); - u32 ctrl = VE_CTRL_AUTO_OR_CURSOR | - FIELD_PREP(VE_CTRL_CAPTURE_FMT, VIDEO_CAP_FMT_YUV_FULL_SWING); - u32 seq_ctrl = video->jpeg_mode; + u8 jpeg_hq_quality = clamp((int)video->jpeg_hq_quality - 1, 0, + ASPEED_VIDEO_JPEG_NUM_QUALITIES - 1); + u32 comp_ctrl = FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | + FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10) | + FIELD_PREP(VE_COMP_CTRL_EN_HQ, video->hq_mode) | + FIELD_PREP(VE_COMP_CTRL_HQ_DCT_LUM, jpeg_hq_quality) | + FIELD_PREP(VE_COMP_CTRL_HQ_DCT_CHR, jpeg_hq_quality | 0x10); + u32 ctrl = 0; + u32 seq_ctrl = 0; + + v4l2_dbg(1, debug, &video->v4l2_dev, "framerate(%d)\n", + video->frame_rate); + v4l2_dbg(1, debug, &video->v4l2_dev, "jpeg format(%s) subsample(%s)\n", + format_str[video->format], + video->yuv420 ? "420" : "444"); + v4l2_dbg(1, debug, &video->v4l2_dev, "compression quality(%d)\n", + video->jpeg_quality); + v4l2_dbg(1, debug, &video->v4l2_dev, "hq_mode(%s) hq_quality(%d)\n", + video->hq_mode ? "on" : "off", video->jpeg_hq_quality); + + if (video->format == VIDEO_FMT_ASPEED) + aspeed_video_update(video, VE_BCD_CTRL, 0, VE_BCD_CTRL_EN_BCD); + else + aspeed_video_update(video, VE_BCD_CTRL, VE_BCD_CTRL_EN_BCD, 0); if (video->frame_rate) ctrl |= FIELD_PREP(VE_CTRL_FRC, video->frame_rate); + if (video->format == VIDEO_FMT_STANDARD) { + comp_ctrl &= ~FIELD_PREP(VE_COMP_CTRL_EN_HQ, video->hq_mode); + seq_ctrl |= video->jpeg_mode; + } + if (video->yuv420) seq_ctrl |= VE_SEQ_CTRL_YUV420; + if (video->jpeg.virt) + aspeed_video_update_jpeg_table(video->jpeg.virt, video->yuv420); + + /* Set control registers */ + aspeed_video_update(video, VE_SEQ_CTRL, + video->jpeg_mode | VE_SEQ_CTRL_YUV420, + seq_ctrl); + aspeed_video_update(video, VE_CTRL, VE_CTRL_FRC, ctrl); + aspeed_video_update(video, VE_COMP_CTRL, + VE_COMP_CTRL_DCT_LUM | VE_COMP_CTRL_DCT_CHR | + VE_COMP_CTRL_EN_HQ | VE_COMP_CTRL_HQ_DCT_LUM | + VE_COMP_CTRL_HQ_DCT_CHR | VE_COMP_CTRL_VQ_4COLOR | + VE_COMP_CTRL_VQ_DCT_ONLY, + comp_ctrl); +} + +static void aspeed_video_init_regs(struct aspeed_video *video) +{ + u32 ctrl = VE_CTRL_AUTO_OR_CURSOR | + FIELD_PREP(VE_CTRL_CAPTURE_FMT, VIDEO_CAP_FMT_YUV_FULL_SWING); + /* Unlock VE registers */ aspeed_video_write(video, VE_PROTECTION_KEY, VE_PROTECTION_KEY_UNLOCK); @@ -1150,9 +1281,8 @@ static void aspeed_video_init_regs(struct aspeed_video *video) aspeed_video_write(video, VE_JPEG_ADDR, video->jpeg.dma); /* Set control registers */ - aspeed_video_write(video, VE_SEQ_CTRL, seq_ctrl); aspeed_video_write(video, VE_CTRL, ctrl); - aspeed_video_write(video, VE_COMP_CTRL, comp_ctrl); + aspeed_video_write(video, VE_COMP_CTRL, VE_COMP_CTRL_RSVD); /* Don't downscale */ aspeed_video_write(video, VE_SCALING_FACTOR, 0x10001000); @@ -1168,6 +1298,8 @@ static void aspeed_video_init_regs(struct aspeed_video *video) FIELD_PREP(VE_MODE_DT_HOR_STABLE, 6) | FIELD_PREP(VE_MODE_DT_VER_STABLE, 6) | FIELD_PREP(VE_MODE_DT_EDG_THROD, 0x65)); + + aspeed_video_write(video, VE_BCD_CTRL, 0); } static void aspeed_video_start(struct aspeed_video *video) @@ -1201,6 +1333,9 @@ static void aspeed_video_stop(struct aspeed_video *video) if (video->srcs[1].size) aspeed_video_free_buf(video, &video->srcs[1]); + if (video->bcd.size) + aspeed_video_free_buf(video, &video->bcd); + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; video->flags = 0; } @@ -1219,10 +1354,12 @@ static int aspeed_video_querycap(struct file *file, void *fh, static int aspeed_video_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) { + struct aspeed_video *video = video_drvdata(file); + if (f->index) return -EINVAL; - f->pixelformat = V4L2_PIX_FMT_JPEG; + f->pixelformat = video->pix_fmt.pixelformat; return 0; } @@ -1237,6 +1374,29 @@ static int aspeed_video_get_format(struct file *file, void *fh, return 0; } +static int aspeed_video_set_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct aspeed_video *video = video_drvdata(file); + + if (vb2_is_busy(&video->queue)) + return -EBUSY; + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_JPEG: + video->format = VIDEO_FMT_STANDARD; + break; + case V4L2_PIX_FMT_AJPG: + video->format = VIDEO_FMT_ASPEED; + break; + default: + return -EINVAL; + } + video->pix_fmt.pixelformat = f->fmt.pix.pixelformat; + + return 0; +} + static int aspeed_video_enum_input(struct file *file, void *fh, struct v4l2_input *inp) { @@ -1454,7 +1614,7 @@ static const struct v4l2_ioctl_ops aspeed_video_ioctl_ops = { .vidioc_enum_fmt_vid_cap = aspeed_video_enum_format, .vidioc_g_fmt_vid_cap = aspeed_video_get_format, - .vidioc_s_fmt_vid_cap = aspeed_video_get_format, + .vidioc_s_fmt_vid_cap = aspeed_video_set_format, .vidioc_try_fmt_vid_cap = aspeed_video_get_format, .vidioc_reqbufs = vb2_ioctl_reqbufs, @@ -1486,27 +1646,6 @@ static const struct v4l2_ioctl_ops aspeed_video_ioctl_ops = { .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; -static void aspeed_video_update_jpeg_quality(struct aspeed_video *video) -{ - u32 comp_ctrl = FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | - FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10); - - aspeed_video_update(video, VE_COMP_CTRL, - VE_COMP_CTRL_DCT_LUM | VE_COMP_CTRL_DCT_CHR, - comp_ctrl); -} - -static void aspeed_video_update_subsampling(struct aspeed_video *video) -{ - if (video->jpeg.virt) - aspeed_video_update_jpeg_table(video->jpeg.virt, video->yuv420); - - if (video->yuv420) - aspeed_video_update(video, VE_SEQ_CTRL, 0, VE_SEQ_CTRL_YUV420); - else - aspeed_video_update(video, VE_SEQ_CTRL, VE_SEQ_CTRL_YUV420, 0); -} - static int aspeed_video_set_ctrl(struct v4l2_ctrl *ctrl) { struct aspeed_video *video = container_of(ctrl->handler, @@ -1516,16 +1655,23 @@ static int aspeed_video_set_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_JPEG_COMPRESSION_QUALITY: video->jpeg_quality = ctrl->val; - aspeed_video_update_jpeg_quality(video); + if (test_bit(VIDEO_STREAMING, &video->flags)) + aspeed_video_update_regs(video); break; case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: - if (ctrl->val == V4L2_JPEG_CHROMA_SUBSAMPLING_420) { - video->yuv420 = true; - aspeed_video_update_subsampling(video); - } else { - video->yuv420 = false; - aspeed_video_update_subsampling(video); - } + video->yuv420 = (ctrl->val == V4L2_JPEG_CHROMA_SUBSAMPLING_420); + if (test_bit(VIDEO_STREAMING, &video->flags)) + aspeed_video_update_regs(video); + break; + case V4L2_CID_ASPEED_HQ_MODE: + video->hq_mode = ctrl->val; + if (test_bit(VIDEO_STREAMING, &video->flags)) + aspeed_video_update_regs(video); + break; + case V4L2_CID_ASPEED_HQ_JPEG_QUALITY: + video->jpeg_hq_quality = ctrl->val; + if (test_bit(VIDEO_STREAMING, &video->flags)) + aspeed_video_update_regs(video); break; default: return -EINVAL; @@ -1538,6 +1684,28 @@ static const struct v4l2_ctrl_ops aspeed_video_ctrl_ops = { .s_ctrl = aspeed_video_set_ctrl, }; +static const struct v4l2_ctrl_config aspeed_ctrl_HQ_mode = { + .ops = &aspeed_video_ctrl_ops, + .id = V4L2_CID_ASPEED_HQ_MODE, + .name = "Aspeed HQ Mode", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = false, +}; + +static const struct v4l2_ctrl_config aspeed_ctrl_HQ_jpeg_quality = { + .ops = &aspeed_video_ctrl_ops, + .id = V4L2_CID_ASPEED_HQ_JPEG_QUALITY, + .name = "Aspeed HQ Quality", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 1, + .max = ASPEED_VIDEO_JPEG_NUM_QUALITIES, + .step = 1, + .def = 1, +}; + static void aspeed_video_resolution_work(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); @@ -1552,6 +1720,8 @@ static void aspeed_video_resolution_work(struct work_struct *work) aspeed_video_init_regs(video); + aspeed_video_update_regs(video); + aspeed_video_get_resolution(video); if (video->detected_timings.width != video->active_timings.width || @@ -1662,6 +1832,8 @@ static int aspeed_video_start_streaming(struct vb2_queue *q, video->perf.duration_max = 0; video->perf.duration_min = 0xffffffff; + aspeed_video_update_regs(video); + rc = aspeed_video_start_frame(video); if (rc) { aspeed_video_bufs_done(video, VB2_BUF_STATE_QUEUED); @@ -1683,7 +1855,7 @@ static void aspeed_video_stop_streaming(struct vb2_queue *q) !test_bit(VIDEO_FRAME_INPRG, &video->flags), STOP_TIMEOUT); if (!rc) { - v4l2_warn(&video->v4l2_dev, "Timed out when stopping streaming\n"); + v4l2_dbg(1, debug, &video->v4l2_dev, "Timed out when stopping streaming\n"); /* * Need to force stop any DMA and try and get HW into a good @@ -1732,9 +1904,29 @@ static const struct vb2_ops aspeed_video_vb2_ops = { static int aspeed_video_debugfs_show(struct seq_file *s, void *data) { struct aspeed_video *v = s->private; + u32 val08; seq_puts(s, "\n"); + seq_puts(s, "Capture:\n"); + val08 = aspeed_video_read(v, VE_CTRL); + if (FIELD_GET(VE_CTRL_DIRECT_FETCH, val08)) { + seq_printf(s, " %-20s:\tDirect fetch\n", "Mode"); + seq_printf(s, " %-20s:\t%s\n", "VGA bpp mode", + FIELD_GET(VE_CTRL_INT_DE, val08) ? "16" : "32"); + } else { + seq_printf(s, " %-20s:\tSync\n", "Mode"); + seq_printf(s, " %-20s:\t%s\n", "Video source", + FIELD_GET(VE_CTRL_SOURCE, val08) ? + "external" : "internal"); + seq_printf(s, " %-20s:\t%s\n", "DE source", + FIELD_GET(VE_CTRL_INT_DE, val08) ? + "internal" : "external"); + seq_printf(s, " %-20s:\t%s\n", "Cursor overlay", + FIELD_GET(VE_CTRL_AUTO_OR_CURSOR, val08) ? + "Without" : "With"); + } + seq_printf(s, " %-20s:\t%s\n", "Signal", v->v4l2_input_status ? "Unlock" : "Lock"); seq_printf(s, " %-20s:\t%d\n", "Width", v->pix_fmt.width); @@ -1743,29 +1935,33 @@ static int aspeed_video_debugfs_show(struct seq_file *s, void *data) seq_puts(s, "\n"); + seq_puts(s, "Compression:\n"); + seq_printf(s, " %-20s:\t%s\n", "Format", format_str[v->format]); + seq_printf(s, " %-20s:\t%s\n", "Subsampling", + v->yuv420 ? "420" : "444"); + seq_printf(s, " %-20s:\t%d\n", "Quality", v->jpeg_quality); + if (v->format == VIDEO_FMT_ASPEED) { + seq_printf(s, " %-20s:\t%s\n", "HQ Mode", + v->hq_mode ? "on" : "off"); + seq_printf(s, " %-20s:\t%d\n", "HQ Quality", + v->hq_mode ? v->jpeg_hq_quality : 0); + } + + seq_puts(s, "\n"); + seq_puts(s, "Performance:\n"); seq_printf(s, " %-20s:\t%d\n", "Frame#", v->sequence); seq_printf(s, " %-20s:\n", "Frame Duration(ms)"); seq_printf(s, " %-18s:\t%d\n", "Now", v->perf.duration); seq_printf(s, " %-18s:\t%d\n", "Min", v->perf.duration_min); seq_printf(s, " %-18s:\t%d\n", "Max", v->perf.duration_max); - seq_printf(s, " %-20s:\t%d\n", "FPS", 1000 / (v->perf.totaltime / v->sequence)); + seq_printf(s, " %-20s:\t%d\n", "FPS", + (v->perf.totaltime && v->sequence) ? + 1000 / (v->perf.totaltime / v->sequence) : 0); return 0; } - -static int aspeed_video_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, aspeed_video_debugfs_show, inode->i_private); -} - -static const struct file_operations aspeed_video_debugfs_ops = { - .owner = THIS_MODULE, - .open = aspeed_video_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(aspeed_video_debugfs); static struct dentry *debugfs_entry; @@ -1779,7 +1975,7 @@ static int aspeed_video_debugfs_create(struct aspeed_video *video) { debugfs_entry = debugfs_create_file(DEVICE_NAME, 0444, NULL, video, - &aspeed_video_debugfs_ops); + &aspeed_video_debugfs_fops); if (!debugfs_entry) aspeed_video_debugfs_remove(video); @@ -1800,6 +1996,7 @@ static int aspeed_video_setup_video(struct aspeed_video *video) struct v4l2_device *v4l2_dev = &video->v4l2_dev; struct vb2_queue *vbq = &video->queue; struct video_device *vdev = &video->vdev; + struct v4l2_ctrl_handler *hdl = &video->ctrl_handler; int rc; video->pix_fmt.pixelformat = V4L2_PIX_FMT_JPEG; @@ -1814,16 +2011,18 @@ static int aspeed_video_setup_video(struct aspeed_video *video) return rc; } - v4l2_ctrl_handler_init(&video->ctrl_handler, 2); - v4l2_ctrl_new_std(&video->ctrl_handler, &aspeed_video_ctrl_ops, + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &aspeed_video_ctrl_ops, V4L2_CID_JPEG_COMPRESSION_QUALITY, 0, ASPEED_VIDEO_JPEG_NUM_QUALITIES - 1, 1, 0); - v4l2_ctrl_new_std_menu(&video->ctrl_handler, &aspeed_video_ctrl_ops, + v4l2_ctrl_new_std_menu(hdl, &aspeed_video_ctrl_ops, V4L2_CID_JPEG_CHROMA_SUBSAMPLING, V4L2_JPEG_CHROMA_SUBSAMPLING_420, mask, V4L2_JPEG_CHROMA_SUBSAMPLING_444); + v4l2_ctrl_new_custom(hdl, &aspeed_ctrl_HQ_mode, NULL); + v4l2_ctrl_new_custom(hdl, &aspeed_ctrl_HQ_jpeg_quality, NULL); - rc = video->ctrl_handler.error; + rc = hdl->error; if (rc) { v4l2_ctrl_handler_free(&video->ctrl_handler); v4l2_device_unregister(v4l2_dev); @@ -1832,7 +2031,7 @@ static int aspeed_video_setup_video(struct aspeed_video *video) return rc; } - v4l2_dev->ctrl_handler = &video->ctrl_handler; + v4l2_dev->ctrl_handler = hdl; vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vbq->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; @@ -1980,6 +2179,7 @@ static int aspeed_video_probe(struct platform_device *pdev) video->comp_size_read = config->comp_size_read; video->frame_rate = 30; + video->jpeg_hq_quality = 1; video->dev = &pdev->dev; spin_lock_init(&video->lock); mutex_init(&video->video_lock); diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig index f399dba62e17..3866ccae07df 100644 --- a/drivers/media/platform/atmel/Kconfig +++ b/drivers/media/platform/atmel/Kconfig @@ -2,42 +2,6 @@ comment "Atmel media platform drivers" -config VIDEO_ATMEL_ISC - tristate "ATMEL Image Sensor Controller (ISC) support" - depends on V4L_PLATFORM_DRIVERS - depends on VIDEO_DEV && COMMON_CLK - depends on ARCH_AT91 || COMPILE_TEST - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select VIDEOBUF2_DMA_CONTIG - select REGMAP_MMIO - select V4L2_FWNODE - select VIDEO_ATMEL_ISC_BASE - help - This module makes the ATMEL Image Sensor Controller available - as a v4l2 device. - -config VIDEO_ATMEL_XISC - tristate "ATMEL eXtended Image Sensor Controller (XISC) support" - depends on V4L_PLATFORM_DRIVERS - depends on VIDEO_DEV && COMMON_CLK - depends on ARCH_AT91 || COMPILE_TEST - select VIDEOBUF2_DMA_CONTIG - select REGMAP_MMIO - select V4L2_FWNODE - select VIDEO_ATMEL_ISC_BASE - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - help - This module makes the ATMEL eXtended Image Sensor Controller - available as a v4l2 device. - -config VIDEO_ATMEL_ISC_BASE - tristate - default n - help - ATMEL ISC and XISC common code base. - config VIDEO_ATMEL_ISI tristate "ATMEL Image Sensor Interface (ISI) support" depends on V4L_PLATFORM_DRIVERS @@ -49,18 +13,3 @@ config VIDEO_ATMEL_ISI This module makes the ATMEL Image Sensor Interface available as a v4l2 device. -config VIDEO_MICROCHIP_CSI2DC - tristate "Microchip CSI2 Demux Controller" - depends on V4L_PLATFORM_DRIVERS - depends on VIDEO_DEV && COMMON_CLK && OF - depends on ARCH_AT91 || COMPILE_TEST - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_FWNODE - help - CSI2 Demux Controller driver. CSI2DC is a helper chip - that converts IDI interface byte stream to a parallel pixel stream. - It supports various RAW formats as input. - - To compile this driver as a module, choose M here: the - module will be called microchip-csi2dc. diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile index 794e8f739287..a14ac6b5211d 100644 --- a/drivers/media/platform/atmel/Makefile +++ b/drivers/media/platform/atmel/Makefile @@ -1,10 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -atmel-isc-objs = atmel-sama5d2-isc.o -atmel-xisc-objs = atmel-sama7g5-isc.o -atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o -obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o -obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o -obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o -obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o diff --git a/drivers/media/platform/chips-media/coda-bit.c b/drivers/media/platform/chips-media/coda-bit.c index 2736a902e3df..ed47d5bd8d61 100644 --- a/drivers/media/platform/chips-media/coda-bit.c +++ b/drivers/media/platform/chips-media/coda-bit.c @@ -854,7 +854,7 @@ static void coda_setup_iram(struct coda_ctx *ctx) /* Only H.264BP and H.263P3 are considered */ iram_info->buf_dbk_y_use = coda_iram_alloc(iram_info, w64); iram_info->buf_dbk_c_use = coda_iram_alloc(iram_info, w64); - if (!iram_info->buf_dbk_c_use) + if (!iram_info->buf_dbk_y_use || !iram_info->buf_dbk_c_use) goto out; iram_info->axi_sram_use |= dbk_bits; @@ -878,7 +878,7 @@ static void coda_setup_iram(struct coda_ctx *ctx) iram_info->buf_dbk_y_use = coda_iram_alloc(iram_info, w128); iram_info->buf_dbk_c_use = coda_iram_alloc(iram_info, w128); - if (!iram_info->buf_dbk_c_use) + if (!iram_info->buf_dbk_y_use || !iram_info->buf_dbk_c_use) goto out; iram_info->axi_sram_use |= dbk_bits; @@ -1084,10 +1084,16 @@ static int coda_start_encoding(struct coda_ctx *ctx) } if (dst_fourcc == V4L2_PIX_FMT_JPEG) { - if (!ctx->params.jpeg_qmat_tab[0]) + if (!ctx->params.jpeg_qmat_tab[0]) { ctx->params.jpeg_qmat_tab[0] = kmalloc(64, GFP_KERNEL); - if (!ctx->params.jpeg_qmat_tab[1]) + if (!ctx->params.jpeg_qmat_tab[0]) + return -ENOMEM; + } + if (!ctx->params.jpeg_qmat_tab[1]) { ctx->params.jpeg_qmat_tab[1] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[1]) + return -ENOMEM; + } coda_set_jpeg_compression_quality(ctx, ctx->params.jpeg_quality); } diff --git a/drivers/media/platform/chips-media/coda-jpeg.c b/drivers/media/platform/chips-media/coda-jpeg.c index 435e7030fc2a..ba8f41002917 100644 --- a/drivers/media/platform/chips-media/coda-jpeg.c +++ b/drivers/media/platform/chips-media/coda-jpeg.c @@ -1052,10 +1052,16 @@ static int coda9_jpeg_start_encoding(struct coda_ctx *ctx) v4l2_err(&dev->v4l2_dev, "error loading Huffman tables\n"); return ret; } - if (!ctx->params.jpeg_qmat_tab[0]) + if (!ctx->params.jpeg_qmat_tab[0]) { ctx->params.jpeg_qmat_tab[0] = kmalloc(64, GFP_KERNEL); - if (!ctx->params.jpeg_qmat_tab[1]) + if (!ctx->params.jpeg_qmat_tab[0]) + return -ENOMEM; + } + if (!ctx->params.jpeg_qmat_tab[1]) { ctx->params.jpeg_qmat_tab[1] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[1]) + return -ENOMEM; + } coda_set_jpeg_compression_quality(ctx, ctx->params.jpeg_quality); return 0; diff --git a/drivers/media/platform/mediatek/jpeg/Makefile b/drivers/media/platform/mediatek/jpeg/Makefile index 76c33aad0f3f..26e84852523e 100644 --- a/drivers/media/platform/mediatek/jpeg/Makefile +++ b/drivers/media/platform/mediatek/jpeg/Makefile @@ -1,6 +1,10 @@ # SPDX-License-Identifier: GPL-2.0-only -mtk_jpeg-objs := mtk_jpeg_core.o \ - mtk_jpeg_dec_hw.o \ - mtk_jpeg_dec_parse.o \ - mtk_jpeg_enc_hw.o -obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk_jpeg.o +obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk_jpeg.o \ + mtk-jpeg-enc-hw.o \ + mtk-jpeg-dec-hw.o + +mtk_jpeg-y := mtk_jpeg_core.o \ + mtk_jpeg_dec_parse.o + +mtk-jpeg-enc-hw-y := mtk_jpeg_enc_hw.o +mtk-jpeg-dec-hw-y := mtk_jpeg_dec_hw.o diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c index 3071b61946c3..969516a940ba 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c @@ -104,11 +104,11 @@ static struct mtk_jpeg_fmt mtk_jpeg_dec_formats[] = { #define MTK_JPEG_ENC_NUM_FORMATS ARRAY_SIZE(mtk_jpeg_enc_formats) #define MTK_JPEG_DEC_NUM_FORMATS ARRAY_SIZE(mtk_jpeg_dec_formats) +#define MTK_JPEG_MAX_RETRY_TIME 5000 -struct mtk_jpeg_src_buf { - struct vb2_v4l2_buffer b; - struct list_head list; - struct mtk_jpeg_dec_param dec_param; +enum { + MTK_JPEG_BUF_FLAGS_INIT = 0, + MTK_JPEG_BUF_FLAGS_LAST_FRAME = 1, }; static int debug; @@ -586,6 +586,31 @@ static int mtk_jpeg_enc_s_selection(struct file *file, void *priv, return 0; } +static int mtk_jpeg_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct v4l2_fh *fh = file->private_data; + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + struct vb2_queue *vq; + struct vb2_buffer *vb; + struct mtk_jpeg_src_buf *jpeg_src_buf; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + goto end; + + vq = v4l2_m2m_get_vq(fh->m2m_ctx, buf->type); + if (buf->index >= vq->num_buffers) { + dev_err(ctx->jpeg->dev, "buffer index out of range\n"); + return -EINVAL; + } + + vb = vq->bufs[buf->index]; + jpeg_src_buf = mtk_jpeg_vb2_to_srcbuf(vb); + jpeg_src_buf->bs_size = buf->m.planes[0].bytesused; + +end: + return v4l2_m2m_qbuf(file, fh->m2m_ctx, buf); +} + static const struct v4l2_ioctl_ops mtk_jpeg_enc_ioctl_ops = { .vidioc_querycap = mtk_jpeg_querycap, .vidioc_enum_fmt_vid_cap = mtk_jpeg_enum_fmt_vid_cap, @@ -611,6 +636,9 @@ static const struct v4l2_ioctl_ops mtk_jpeg_enc_ioctl_ops = { .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_encoder_cmd = v4l2_m2m_ioctl_encoder_cmd, + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, }; static const struct v4l2_ioctl_ops mtk_jpeg_dec_ioctl_ops = { @@ -623,7 +651,7 @@ static const struct v4l2_ioctl_ops mtk_jpeg_dec_ioctl_ops = { .vidioc_g_fmt_vid_out_mplane = mtk_jpeg_g_fmt_vid_mplane, .vidioc_s_fmt_vid_cap_mplane = mtk_jpeg_s_fmt_vid_cap_mplane, .vidioc_s_fmt_vid_out_mplane = mtk_jpeg_s_fmt_vid_out_mplane, - .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_qbuf = mtk_jpeg_qbuf, .vidioc_subscribe_event = mtk_jpeg_subscribe_event, .vidioc_g_selection = mtk_jpeg_dec_g_selection, @@ -637,6 +665,9 @@ static const struct v4l2_ioctl_ops mtk_jpeg_dec_ioctl_ops = { .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_decoder_cmd = v4l2_m2m_ioctl_decoder_cmd, + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, }; static int mtk_jpeg_queue_setup(struct vb2_queue *q, @@ -678,7 +709,7 @@ static int mtk_jpeg_buf_prepare(struct vb2_buffer *vb) { struct mtk_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); struct mtk_jpeg_q_data *q_data = NULL; - struct v4l2_plane_pix_format plane_fmt; + struct v4l2_plane_pix_format plane_fmt = {}; int i; q_data = mtk_jpeg_get_q_data(ctx, vb->vb2_queue->type); @@ -905,6 +936,148 @@ static int mtk_jpeg_set_dec_dst(struct mtk_jpeg_ctx *ctx, return 0; } +static int mtk_jpegenc_get_hw(struct mtk_jpeg_ctx *ctx) +{ + struct mtk_jpegenc_comp_dev *comp_jpeg; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + unsigned long flags; + int hw_id = -1; + int i; + + spin_lock_irqsave(&jpeg->hw_lock, flags); + for (i = 0; i < MTK_JPEGENC_HW_MAX; i++) { + comp_jpeg = jpeg->enc_hw_dev[i]; + if (comp_jpeg->hw_state == MTK_JPEG_HW_IDLE) { + hw_id = i; + comp_jpeg->hw_state = MTK_JPEG_HW_BUSY; + break; + } + } + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + + return hw_id; +} + +static int mtk_jpegenc_set_hw_param(struct mtk_jpeg_ctx *ctx, + int hw_id, + struct vb2_v4l2_buffer *src_buf, + struct vb2_v4l2_buffer *dst_buf) +{ + struct mtk_jpegenc_comp_dev *jpeg = ctx->jpeg->enc_hw_dev[hw_id]; + + jpeg->hw_param.curr_ctx = ctx; + jpeg->hw_param.src_buffer = src_buf; + jpeg->hw_param.dst_buffer = dst_buf; + + return 0; +} + +static int mtk_jpegenc_put_hw(struct mtk_jpeg_dev *jpeg, int hw_id) +{ + unsigned long flags; + + spin_lock_irqsave(&jpeg->hw_lock, flags); + jpeg->enc_hw_dev[hw_id]->hw_state = MTK_JPEG_HW_IDLE; + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + + return 0; +} + +static void mtk_jpegenc_worker(struct work_struct *work) +{ + struct mtk_jpegenc_comp_dev *comp_jpeg[MTK_JPEGENC_HW_MAX]; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + struct mtk_jpeg_src_buf *jpeg_dst_buf; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + int ret, i, hw_id = 0; + unsigned long flags; + + struct mtk_jpeg_ctx *ctx = container_of(work, + struct mtk_jpeg_ctx, + jpeg_work); + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + for (i = 0; i < MTK_JPEGENC_HW_MAX; i++) + comp_jpeg[i] = jpeg->enc_hw_dev[i]; + i = 0; + +retry_select: + hw_id = mtk_jpegenc_get_hw(ctx); + if (hw_id < 0) { + ret = wait_event_interruptible(jpeg->enc_hw_wq, + atomic_read(&jpeg->enchw_rdy) > 0); + if (ret != 0 || (i++ > MTK_JPEG_MAX_RETRY_TIME)) { + dev_err(jpeg->dev, "%s : %d, all HW are busy\n", + __func__, __LINE__); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return; + } + + goto retry_select; + } + + atomic_dec(&jpeg->enchw_rdy); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src_buf) + goto getbuf_fail; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!dst_buf) + goto getbuf_fail; + + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + mtk_jpegenc_set_hw_param(ctx, hw_id, src_buf, dst_buf); + ret = pm_runtime_get_sync(comp_jpeg[hw_id]->dev); + if (ret < 0) { + dev_err(jpeg->dev, "%s : %d, pm_runtime_get_sync fail !!!\n", + __func__, __LINE__); + goto enc_end; + } + + ret = clk_prepare_enable(comp_jpeg[hw_id]->venc_clk.clks->clk); + if (ret) { + dev_err(jpeg->dev, "%s : %d, jpegenc clk_prepare_enable fail\n", + __func__, __LINE__); + goto enc_end; + } + + schedule_delayed_work(&comp_jpeg[hw_id]->job_timeout_work, + msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); + + spin_lock_irqsave(&comp_jpeg[hw_id]->hw_lock, flags); + jpeg_dst_buf = mtk_jpeg_vb2_to_srcbuf(&dst_buf->vb2_buf); + jpeg_dst_buf->curr_ctx = ctx; + jpeg_dst_buf->frame_num = ctx->total_frame_num; + ctx->total_frame_num++; + mtk_jpeg_enc_reset(comp_jpeg[hw_id]->reg_base); + mtk_jpeg_set_enc_dst(ctx, + comp_jpeg[hw_id]->reg_base, + &dst_buf->vb2_buf); + mtk_jpeg_set_enc_src(ctx, + comp_jpeg[hw_id]->reg_base, + &src_buf->vb2_buf); + mtk_jpeg_set_enc_params(ctx, comp_jpeg[hw_id]->reg_base); + mtk_jpeg_enc_start(comp_jpeg[hw_id]->reg_base); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + spin_unlock_irqrestore(&comp_jpeg[hw_id]->hw_lock, flags); + + return; + +enc_end: + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); +getbuf_fail: + atomic_inc(&jpeg->enchw_rdy); + mtk_jpegenc_put_hw(jpeg, hw_id); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); +} + static void mtk_jpeg_enc_device_run(void *priv) { struct mtk_jpeg_ctx *ctx = priv; @@ -922,7 +1095,7 @@ static void mtk_jpeg_enc_device_run(void *priv) goto enc_end; schedule_delayed_work(&jpeg->job_timeout_work, - msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); + msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); spin_lock_irqsave(&jpeg->hw_lock, flags); @@ -947,6 +1120,189 @@ enc_end: v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); } +static void mtk_jpeg_multicore_enc_device_run(void *priv) +{ + struct mtk_jpeg_ctx *ctx = priv; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + queue_work(jpeg->workqueue, &ctx->jpeg_work); +} + +static int mtk_jpegdec_get_hw(struct mtk_jpeg_ctx *ctx) +{ + struct mtk_jpegdec_comp_dev *comp_jpeg; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + unsigned long flags; + int hw_id = -1; + int i; + + spin_lock_irqsave(&jpeg->hw_lock, flags); + for (i = 0; i < MTK_JPEGDEC_HW_MAX; i++) { + comp_jpeg = jpeg->dec_hw_dev[i]; + if (comp_jpeg->hw_state == MTK_JPEG_HW_IDLE) { + hw_id = i; + comp_jpeg->hw_state = MTK_JPEG_HW_BUSY; + break; + } + } + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + + return hw_id; +} + +static int mtk_jpegdec_put_hw(struct mtk_jpeg_dev *jpeg, int hw_id) +{ + unsigned long flags; + + spin_lock_irqsave(&jpeg->hw_lock, flags); + jpeg->dec_hw_dev[hw_id]->hw_state = + MTK_JPEG_HW_IDLE; + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + + return 0; +} + +static int mtk_jpegdec_set_hw_param(struct mtk_jpeg_ctx *ctx, + int hw_id, + struct vb2_v4l2_buffer *src_buf, + struct vb2_v4l2_buffer *dst_buf) +{ + struct mtk_jpegdec_comp_dev *jpeg = + ctx->jpeg->dec_hw_dev[hw_id]; + + jpeg->hw_param.curr_ctx = ctx; + jpeg->hw_param.src_buffer = src_buf; + jpeg->hw_param.dst_buffer = dst_buf; + + return 0; +} + +static void mtk_jpegdec_worker(struct work_struct *work) +{ + struct mtk_jpeg_ctx *ctx = container_of(work, struct mtk_jpeg_ctx, + jpeg_work); + struct mtk_jpegdec_comp_dev *comp_jpeg[MTK_JPEGDEC_HW_MAX]; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + struct mtk_jpeg_src_buf *jpeg_src_buf, *jpeg_dst_buf; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + int ret, i, hw_id = 0; + struct mtk_jpeg_bs bs; + struct mtk_jpeg_fb fb; + unsigned long flags; + + for (i = 0; i < MTK_JPEGDEC_HW_MAX; i++) + comp_jpeg[i] = jpeg->dec_hw_dev[i]; + i = 0; + +retry_select: + hw_id = mtk_jpegdec_get_hw(ctx); + if (hw_id < 0) { + ret = wait_event_interruptible_timeout(jpeg->dec_hw_wq, + atomic_read(&jpeg->dechw_rdy) > 0, + MTK_JPEG_HW_TIMEOUT_MSEC); + if (ret != 0 || (i++ > MTK_JPEG_MAX_RETRY_TIME)) { + dev_err(jpeg->dev, "%s : %d, all HW are busy\n", + __func__, __LINE__); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return; + } + + goto retry_select; + } + + atomic_dec(&jpeg->dechw_rdy); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src_buf) + goto getbuf_fail; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!dst_buf) + goto getbuf_fail; + + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + jpeg_src_buf = mtk_jpeg_vb2_to_srcbuf(&src_buf->vb2_buf); + jpeg_dst_buf = mtk_jpeg_vb2_to_srcbuf(&dst_buf->vb2_buf); + + if (mtk_jpeg_check_resolution_change(ctx, + &jpeg_src_buf->dec_param)) { + mtk_jpeg_queue_src_chg_event(ctx); + ctx->state = MTK_JPEG_SOURCE_CHANGE; + goto dec_end; + } + + jpeg_src_buf->curr_ctx = ctx; + jpeg_src_buf->frame_num = ctx->total_frame_num; + jpeg_dst_buf->curr_ctx = ctx; + jpeg_dst_buf->frame_num = ctx->total_frame_num; + + mtk_jpegdec_set_hw_param(ctx, hw_id, src_buf, dst_buf); + ret = pm_runtime_get_sync(comp_jpeg[hw_id]->dev); + if (ret < 0) { + dev_err(jpeg->dev, "%s : %d, pm_runtime_get_sync fail !!!\n", + __func__, __LINE__); + goto dec_end; + } + + ret = clk_prepare_enable(comp_jpeg[hw_id]->jdec_clk.clks->clk); + if (ret) { + dev_err(jpeg->dev, "%s : %d, jpegdec clk_prepare_enable fail\n", + __func__, __LINE__); + goto clk_end; + } + + schedule_delayed_work(&comp_jpeg[hw_id]->job_timeout_work, + msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); + + mtk_jpeg_set_dec_src(ctx, &src_buf->vb2_buf, &bs); + if (mtk_jpeg_set_dec_dst(ctx, + &jpeg_src_buf->dec_param, + &dst_buf->vb2_buf, &fb)) { + dev_err(jpeg->dev, "%s : %d, mtk_jpeg_set_dec_dst fail\n", + __func__, __LINE__); + goto setdst_end; + } + + spin_lock_irqsave(&comp_jpeg[hw_id]->hw_lock, flags); + ctx->total_frame_num++; + mtk_jpeg_dec_reset(comp_jpeg[hw_id]->reg_base); + mtk_jpeg_dec_set_config(comp_jpeg[hw_id]->reg_base, + &jpeg_src_buf->dec_param, + jpeg_src_buf->bs_size, + &bs, + &fb); + mtk_jpeg_dec_start(comp_jpeg[hw_id]->reg_base); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + spin_unlock_irqrestore(&comp_jpeg[hw_id]->hw_lock, flags); + + return; + +setdst_end: + clk_disable_unprepare(comp_jpeg[hw_id]->jdec_clk.clks->clk); +clk_end: + pm_runtime_put(comp_jpeg[hw_id]->dev); +dec_end: + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); +getbuf_fail: + atomic_inc(&jpeg->dechw_rdy); + mtk_jpegdec_put_hw(jpeg, hw_id); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); +} + +static void mtk_jpeg_multicore_dec_device_run(void *priv) +{ + struct mtk_jpeg_ctx *ctx = priv; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + queue_work(jpeg->workqueue, &ctx->jpeg_work); +} + static void mtk_jpeg_dec_device_run(void *priv) { struct mtk_jpeg_ctx *ctx = priv; @@ -984,8 +1340,10 @@ static void mtk_jpeg_dec_device_run(void *priv) spin_lock_irqsave(&jpeg->hw_lock, flags); mtk_jpeg_dec_reset(jpeg->reg_base); mtk_jpeg_dec_set_config(jpeg->reg_base, - &jpeg_src_buf->dec_param, &bs, &fb); - + &jpeg_src_buf->dec_param, + jpeg_src_buf->bs_size, + &bs, + &fb); mtk_jpeg_dec_start(jpeg->reg_base); spin_unlock_irqrestore(&jpeg->hw_lock, flags); return; @@ -1009,6 +1367,14 @@ static const struct v4l2_m2m_ops mtk_jpeg_enc_m2m_ops = { .device_run = mtk_jpeg_enc_device_run, }; +static const struct v4l2_m2m_ops mtk_jpeg_multicore_enc_m2m_ops = { + .device_run = mtk_jpeg_multicore_enc_device_run, +}; + +static const struct v4l2_m2m_ops mtk_jpeg_multicore_dec_m2m_ops = { + .device_run = mtk_jpeg_multicore_dec_device_run, +}; + static const struct v4l2_m2m_ops mtk_jpeg_dec_m2m_ops = { .device_run = mtk_jpeg_dec_device_run, .job_ready = mtk_jpeg_dec_job_ready, @@ -1209,6 +1575,14 @@ static int mtk_jpeg_open(struct file *file) goto free; } + if (jpeg->is_jpgenc_multihw) + INIT_WORK(&ctx->jpeg_work, mtk_jpegenc_worker); + + if (jpeg->is_jpgdec_multihw) + INIT_WORK(&ctx->jpeg_work, mtk_jpegdec_worker); + + INIT_LIST_HEAD(&ctx->dst_done_queue); + spin_lock_init(&ctx->done_queue_lock); v4l2_fh_init(&ctx->fh, vfd); file->private_data = &ctx->fh; v4l2_fh_add(&ctx->fh); @@ -1230,6 +1604,7 @@ static int mtk_jpeg_open(struct file *file) } else { v4l2_ctrl_handler_init(&ctx->ctrl_hdl, 0); } + mtk_jpeg_set_default_params(ctx); mutex_unlock(&jpeg->lock); return 0; @@ -1310,38 +1685,51 @@ static int mtk_jpeg_probe(struct platform_device *pdev) spin_lock_init(&jpeg->hw_lock); jpeg->dev = &pdev->dev; jpeg->variant = of_device_get_match_data(jpeg->dev); - INIT_DELAYED_WORK(&jpeg->job_timeout_work, mtk_jpeg_job_timeout_work); - jpeg->reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(jpeg->reg_base)) { - ret = PTR_ERR(jpeg->reg_base); - return ret; + ret = devm_of_platform_populate(&pdev->dev); + if (ret) { + v4l2_err(&jpeg->v4l2_dev, "Master of platform populate failed."); + return -EINVAL; } - jpeg_irq = platform_get_irq(pdev, 0); - if (jpeg_irq < 0) - return jpeg_irq; + if (list_empty(&pdev->dev.devres_head)) { + INIT_DELAYED_WORK(&jpeg->job_timeout_work, + mtk_jpeg_job_timeout_work); - ret = devm_request_irq(&pdev->dev, jpeg_irq, - jpeg->variant->irq_handler, 0, pdev->name, jpeg); - if (ret) { - dev_err(&pdev->dev, "Failed to request jpeg_irq %d (%d)\n", - jpeg_irq, ret); - goto err_req_irq; - } + jpeg->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(jpeg->reg_base)) { + ret = PTR_ERR(jpeg->reg_base); + return ret; + } - ret = devm_clk_bulk_get(jpeg->dev, jpeg->variant->num_clks, - jpeg->variant->clks); - if (ret) { - dev_err(&pdev->dev, "Failed to init clk, err %d\n", ret); - goto err_clk_init; + jpeg_irq = platform_get_irq(pdev, 0); + if (jpeg_irq < 0) + return jpeg_irq; + + ret = devm_request_irq(&pdev->dev, + jpeg_irq, + jpeg->variant->irq_handler, + 0, + pdev->name, jpeg); + if (ret) { + dev_err(&pdev->dev, "Failed to request jpeg_irq %d (%d)\n", + jpeg_irq, ret); + return ret; + } + + ret = devm_clk_bulk_get(jpeg->dev, + jpeg->variant->num_clks, + jpeg->variant->clks); + if (ret) { + dev_err(&pdev->dev, "Failed to init clk\n"); + return ret; + } } ret = v4l2_device_register(&pdev->dev, &jpeg->v4l2_dev); if (ret) { dev_err(&pdev->dev, "Failed to register v4l2 device\n"); - ret = -EINVAL; - goto err_dev_register; + return -EINVAL; } jpeg->m2m_dev = v4l2_m2m_init(jpeg->variant->m2m_ops); @@ -1399,12 +1787,6 @@ err_vfd_jpeg_alloc: err_m2m_init: v4l2_device_unregister(&jpeg->v4l2_dev); -err_dev_register: - -err_clk_init: - -err_req_irq: - return ret; } @@ -1494,6 +1876,29 @@ static const struct mtk_jpeg_variant mtk_jpeg_drvdata = { .cap_q_default_fourcc = V4L2_PIX_FMT_JPEG, }; +static struct mtk_jpeg_variant mtk8195_jpegenc_drvdata = { + .formats = mtk_jpeg_enc_formats, + .num_formats = MTK_JPEG_ENC_NUM_FORMATS, + .qops = &mtk_jpeg_enc_qops, + .m2m_ops = &mtk_jpeg_multicore_enc_m2m_ops, + .dev_name = "mtk-jpeg-enc", + .ioctl_ops = &mtk_jpeg_enc_ioctl_ops, + .out_q_default_fourcc = V4L2_PIX_FMT_YUYV, + .cap_q_default_fourcc = V4L2_PIX_FMT_JPEG, +}; + +static const struct mtk_jpeg_variant mtk8195_jpegdec_drvdata = { + .formats = mtk_jpeg_dec_formats, + .num_formats = MTK_JPEG_DEC_NUM_FORMATS, + .qops = &mtk_jpeg_dec_qops, + .m2m_ops = &mtk_jpeg_multicore_dec_m2m_ops, + .dev_name = "mtk-jpeg-dec", + .ioctl_ops = &mtk_jpeg_dec_ioctl_ops, + .out_q_default_fourcc = V4L2_PIX_FMT_JPEG, + .cap_q_default_fourcc = V4L2_PIX_FMT_YUV420M, +}; + +#if defined(CONFIG_OF) static const struct of_device_id mtk_jpeg_match[] = { { .compatible = "mediatek,mt8173-jpgdec", @@ -1507,17 +1912,26 @@ static const struct of_device_id mtk_jpeg_match[] = { .compatible = "mediatek,mtk-jpgenc", .data = &mtk_jpeg_drvdata, }, + { + .compatible = "mediatek,mt8195-jpgenc", + .data = &mtk8195_jpegenc_drvdata, + }, + { + .compatible = "mediatek,mt8195-jpgdec", + .data = &mtk8195_jpegdec_drvdata, + }, {}, }; MODULE_DEVICE_TABLE(of, mtk_jpeg_match); +#endif static struct platform_driver mtk_jpeg_driver = { .probe = mtk_jpeg_probe, .remove = mtk_jpeg_remove, .driver = { .name = MTK_JPEG_NAME, - .of_match_table = mtk_jpeg_match, + .of_match_table = of_match_ptr(mtk_jpeg_match), .pm = &mtk_jpeg_pm_ops, }, }; diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h index 3e4811a41ba2..b9126476be8f 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h @@ -9,14 +9,16 @@ #ifndef _MTK_JPEG_CORE_H #define _MTK_JPEG_CORE_H +#include <linux/clk.h> #include <linux/interrupt.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-fh.h> +#include <media/videobuf2-v4l2.h> -#define MTK_JPEG_NAME "mtk-jpeg" +#include "mtk_jpeg_dec_hw.h" -#define MTK_JPEG_COMP_MAX 3 +#define MTK_JPEG_NAME "mtk-jpeg" #define MTK_JPEG_FMT_FLAG_OUTPUT BIT(0) #define MTK_JPEG_FMT_FLAG_CAPTURE BIT(1) @@ -74,6 +76,115 @@ struct mtk_jpeg_variant { u32 cap_q_default_fourcc; }; +struct mtk_jpeg_src_buf { + u32 frame_num; + struct vb2_v4l2_buffer b; + struct list_head list; + u32 bs_size; + struct mtk_jpeg_dec_param dec_param; + + struct mtk_jpeg_ctx *curr_ctx; +}; + +enum mtk_jpeg_hw_state { + MTK_JPEG_HW_IDLE = 0, + MTK_JPEG_HW_BUSY = 1, +}; + +struct mtk_jpeg_hw_param { + struct vb2_v4l2_buffer *src_buffer; + struct vb2_v4l2_buffer *dst_buffer; + struct mtk_jpeg_ctx *curr_ctx; +}; + +enum mtk_jpegenc_hw_id { + MTK_JPEGENC_HW0, + MTK_JPEGENC_HW1, + MTK_JPEGENC_HW_MAX, +}; + +enum mtk_jpegdec_hw_id { + MTK_JPEGDEC_HW0, + MTK_JPEGDEC_HW1, + MTK_JPEGDEC_HW2, + MTK_JPEGDEC_HW_MAX, +}; + +/** + * struct mtk_jpegenc_clk - Structure used to store vcodec clock information + * @clks: JPEG encode clock + * @clk_num: JPEG encode clock numbers + */ +struct mtk_jpegenc_clk { + struct clk_bulk_data *clks; + int clk_num; +}; + +/** + * struct mtk_jpegdec_clk - Structure used to store vcodec clock information + * @clks: JPEG decode clock + * @clk_num: JPEG decode clock numbers + */ +struct mtk_jpegdec_clk { + struct clk_bulk_data *clks; + int clk_num; +}; + +/** + * struct mtk_jpegenc_comp_dev - JPEG COREX abstraction + * @dev: JPEG device + * @plat_dev: platform device data + * @reg_base: JPEG registers mapping + * @master_dev: mtk_jpeg_dev device + * @venc_clk: jpeg encode clock + * @jpegenc_irq: jpeg encode irq num + * @job_timeout_work: encode timeout workqueue + * @hw_param: jpeg encode hw parameters + * @hw_rdy: record hw ready + * @hw_state: record hw state + * @hw_lock: spinlock protecting the hw device resource + */ +struct mtk_jpegenc_comp_dev { + struct device *dev; + struct platform_device *plat_dev; + void __iomem *reg_base; + struct mtk_jpeg_dev *master_dev; + struct mtk_jpegenc_clk venc_clk; + int jpegenc_irq; + struct delayed_work job_timeout_work; + struct mtk_jpeg_hw_param hw_param; + enum mtk_jpeg_hw_state hw_state; + /* spinlock protecting the hw device resource */ + spinlock_t hw_lock; +}; + +/** + * struct mtk_jpegdec_comp_dev - JPEG COREX abstraction + * @dev: JPEG device + * @plat_dev: platform device data + * @reg_base: JPEG registers mapping + * @master_dev: mtk_jpeg_dev device + * @jdec_clk: mtk_jpegdec_clk + * @jpegdec_irq: jpeg decode irq num + * @job_timeout_work: decode timeout workqueue + * @hw_param: jpeg decode hw parameters + * @hw_state: record hw state + * @hw_lock: spinlock protecting hw + */ +struct mtk_jpegdec_comp_dev { + struct device *dev; + struct platform_device *plat_dev; + void __iomem *reg_base; + struct mtk_jpeg_dev *master_dev; + struct mtk_jpegdec_clk jdec_clk; + int jpegdec_irq; + struct delayed_work job_timeout_work; + struct mtk_jpeg_hw_param hw_param; + enum mtk_jpeg_hw_state hw_state; + /* spinlock protecting the hw device resource */ + spinlock_t hw_lock; +}; + /** * struct mtk_jpeg_dev - JPEG IP abstraction * @lock: the mutex protecting this structure @@ -87,6 +198,17 @@ struct mtk_jpeg_variant { * @reg_base: JPEG registers mapping * @job_timeout_work: IRQ timeout structure * @variant: driver variant to be used + * @reg_encbase: jpg encode register base addr + * @enc_hw_dev: jpg encode hardware device + * @is_jpgenc_multihw: the flag of multi-hw core + * @enc_hw_wq: jpg encode wait queue + * @enchw_rdy: jpg encode hw ready flag + * @reg_decbase: jpg decode register base addr + * @dec_hw_dev: jpg decode hardware device + * @is_jpgdec_multihw: the flag of dec multi-hw core + * @dec_hw_wq: jpg decode wait queue + * @dec_workqueue: jpg decode work queue + * @dechw_rdy: jpg decode hw ready flag */ struct mtk_jpeg_dev { struct mutex lock; @@ -100,6 +222,19 @@ struct mtk_jpeg_dev { void __iomem *reg_base; struct delayed_work job_timeout_work; const struct mtk_jpeg_variant *variant; + + void __iomem *reg_encbase[MTK_JPEGENC_HW_MAX]; + struct mtk_jpegenc_comp_dev *enc_hw_dev[MTK_JPEGENC_HW_MAX]; + bool is_jpgenc_multihw; + wait_queue_head_t enc_hw_wq; + atomic_t enchw_rdy; + + void __iomem *reg_decbase[MTK_JPEGDEC_HW_MAX]; + struct mtk_jpegdec_comp_dev *dec_hw_dev[MTK_JPEGDEC_HW_MAX]; + bool is_jpgdec_multihw; + wait_queue_head_t dec_hw_wq; + struct workqueue_struct *dec_workqueue; + atomic_t dechw_rdy; }; /** @@ -138,15 +273,20 @@ struct mtk_jpeg_q_data { /** * struct mtk_jpeg_ctx - the device context data - * @jpeg: JPEG IP device for this context - * @out_q: source (output) queue information - * @cap_q: destination (capture) queue queue information - * @fh: V4L2 file handle - * @state: state of the context - * @enable_exif: enable exif mode of jpeg encoder - * @enc_quality: jpeg encoder quality - * @restart_interval: jpeg encoder restart interval - * @ctrl_hdl: controls handler + * @jpeg: JPEG IP device for this context + * @out_q: source (output) queue information + * @cap_q: destination queue information + * @fh: V4L2 file handle + * @state: state of the context + * @enable_exif: enable exif mode of jpeg encoder + * @enc_quality: jpeg encoder quality + * @restart_interval: jpeg encoder restart interval + * @ctrl_hdl: controls handler + * @jpeg_work: jpeg encoder workqueue + * @total_frame_num: encoded frame number + * @dst_done_queue: encoded frame buffer queue + * @done_queue_lock: encoded frame operation spinlock + * @last_done_frame_num: the last encoded frame number */ struct mtk_jpeg_ctx { struct mtk_jpeg_dev *jpeg; @@ -158,6 +298,13 @@ struct mtk_jpeg_ctx { u8 enc_quality; u8 restart_interval; struct v4l2_ctrl_handler ctrl_hdl; + + struct work_struct jpeg_work; + u32 total_frame_num; + struct list_head dst_done_queue; + /* spinlock protecting the encode done buffer */ + spinlock_t done_queue_lock; + u32 last_done_frame_num; }; #endif /* _MTK_JPEG_CORE_H */ diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c index afbbfd5d02bc..8c07fa02fd9a 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c @@ -5,10 +5,26 @@ * Rick Chang <rick.chang@mediatek.com> */ +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/io.h> #include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <media/media-device.h> #include <media/videobuf2-core.h> - +#include <media/videobuf2-v4l2.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> + +#include "mtk_jpeg_core.h" #include "mtk_jpeg_dec_hw.h" #define MTK_JPEG_DUNUM_MASK(val) (((val) - 1) & 0x3) @@ -23,6 +39,16 @@ enum mtk_jpeg_color { MTK_JPEG_COLOR_400 = 0x00110000 }; +#if defined(CONFIG_OF) +static const struct of_device_id mtk_jpegdec_hw_ids[] = { + { + .compatible = "mediatek,mt8195-jpgdec-hw", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_jpegdec_hw_ids); +#endif + static inline int mtk_jpeg_verify_align(u32 val, int align, u32 reg) { if (val & (align - 1)) { @@ -188,6 +214,7 @@ int mtk_jpeg_dec_fill_param(struct mtk_jpeg_dec_param *param) return 0; } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_fill_param); u32 mtk_jpeg_dec_get_int_status(void __iomem *base) { @@ -199,6 +226,7 @@ u32 mtk_jpeg_dec_get_int_status(void __iomem *base) return ret; } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_get_int_status); u32 mtk_jpeg_dec_enum_result(u32 irq_result) { @@ -215,11 +243,13 @@ u32 mtk_jpeg_dec_enum_result(u32 irq_result) return MTK_JPEG_DEC_RESULT_ERROR_UNKNOWN; } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_enum_result); void mtk_jpeg_dec_start(void __iomem *base) { writel(0, base + JPGDEC_REG_TRIG); } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_start); static void mtk_jpeg_dec_soft_reset(void __iomem *base) { @@ -239,6 +269,7 @@ void mtk_jpeg_dec_reset(void __iomem *base) mtk_jpeg_dec_soft_reset(base); mtk_jpeg_dec_hard_reset(base); } +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_reset); static void mtk_jpeg_dec_set_brz_factor(void __iomem *base, u8 yscale_w, u8 yscale_h, u8 uvscale_w, u8 uvscale_h) @@ -299,12 +330,14 @@ static void mtk_jpeg_dec_set_bs_write_ptr(void __iomem *base, u32 ptr) writel(ptr, base + JPGDEC_REG_FILE_BRP); } -static void mtk_jpeg_dec_set_bs_info(void __iomem *base, u32 addr, u32 size) +static void mtk_jpeg_dec_set_bs_info(void __iomem *base, u32 addr, u32 size, + u32 bitstream_size) { mtk_jpeg_verify_align(addr, 16, JPGDEC_REG_FILE_ADDR); mtk_jpeg_verify_align(size, 128, JPGDEC_REG_FILE_TOTAL_SIZE); writel(addr, base + JPGDEC_REG_FILE_ADDR); writel(size, base + JPGDEC_REG_FILE_TOTAL_SIZE); + writel(bitstream_size, base + JPGDEC_REG_BIT_STREAM_SIZE); } static void mtk_jpeg_dec_set_comp_id(void __iomem *base, u32 id_y, u32 id_u, @@ -373,37 +406,275 @@ static void mtk_jpeg_dec_set_sampling_factor(void __iomem *base, u32 comp_num, } void mtk_jpeg_dec_set_config(void __iomem *base, - struct mtk_jpeg_dec_param *config, + struct mtk_jpeg_dec_param *cfg, + u32 bitstream_size, struct mtk_jpeg_bs *bs, struct mtk_jpeg_fb *fb) { - mtk_jpeg_dec_set_brz_factor(base, 0, 0, config->uv_brz_w, 0); + mtk_jpeg_dec_set_brz_factor(base, 0, 0, cfg->uv_brz_w, 0); mtk_jpeg_dec_set_dec_mode(base, 0); - mtk_jpeg_dec_set_comp0_du(base, config->unit_num); - mtk_jpeg_dec_set_total_mcu(base, config->total_mcu); - mtk_jpeg_dec_set_bs_info(base, bs->str_addr, bs->size); + mtk_jpeg_dec_set_comp0_du(base, cfg->unit_num); + mtk_jpeg_dec_set_total_mcu(base, cfg->total_mcu); + mtk_jpeg_dec_set_bs_info(base, bs->str_addr, bs->size, bitstream_size); mtk_jpeg_dec_set_bs_write_ptr(base, bs->end_addr); - mtk_jpeg_dec_set_du_membership(base, config->membership, 1, - (config->comp_num == 1) ? 1 : 0); - mtk_jpeg_dec_set_comp_id(base, config->comp_id[0], config->comp_id[1], - config->comp_id[2]); - mtk_jpeg_dec_set_q_table(base, config->qtbl_num[0], - config->qtbl_num[1], config->qtbl_num[2]); - mtk_jpeg_dec_set_sampling_factor(base, config->comp_num, - config->sampling_w[0], - config->sampling_h[0], - config->sampling_w[1], - config->sampling_h[1], - config->sampling_w[2], - config->sampling_h[2]); - mtk_jpeg_dec_set_mem_stride(base, config->mem_stride[0], - config->mem_stride[1]); - mtk_jpeg_dec_set_img_stride(base, config->img_stride[0], - config->img_stride[1]); + mtk_jpeg_dec_set_du_membership(base, cfg->membership, 1, + (cfg->comp_num == 1) ? 1 : 0); + mtk_jpeg_dec_set_comp_id(base, cfg->comp_id[0], cfg->comp_id[1], + cfg->comp_id[2]); + mtk_jpeg_dec_set_q_table(base, cfg->qtbl_num[0], + cfg->qtbl_num[1], cfg->qtbl_num[2]); + mtk_jpeg_dec_set_sampling_factor(base, cfg->comp_num, + cfg->sampling_w[0], + cfg->sampling_h[0], + cfg->sampling_w[1], + cfg->sampling_h[1], + cfg->sampling_w[2], + cfg->sampling_h[2]); + mtk_jpeg_dec_set_mem_stride(base, cfg->mem_stride[0], + cfg->mem_stride[1]); + mtk_jpeg_dec_set_img_stride(base, cfg->img_stride[0], + cfg->img_stride[1]); mtk_jpeg_dec_set_dst_bank0(base, fb->plane_addr[0], fb->plane_addr[1], fb->plane_addr[2]); mtk_jpeg_dec_set_dst_bank1(base, 0, 0, 0); - mtk_jpeg_dec_set_dma_group(base, config->dma_mcu, config->dma_group, - config->dma_last_mcu); - mtk_jpeg_dec_set_pause_mcu_idx(base, config->total_mcu); + mtk_jpeg_dec_set_dma_group(base, cfg->dma_mcu, cfg->dma_group, + cfg->dma_last_mcu); + mtk_jpeg_dec_set_pause_mcu_idx(base, cfg->total_mcu); +} +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_set_config); + +static void mtk_jpegdec_put_buf(struct mtk_jpegdec_comp_dev *jpeg) +{ + struct mtk_jpeg_src_buf *dst_done_buf, *tmp_dst_done_buf; + struct vb2_v4l2_buffer *dst_buffer; + struct list_head *temp_entry; + struct list_head *pos = NULL; + struct mtk_jpeg_ctx *ctx; + unsigned long flags; + + ctx = jpeg->hw_param.curr_ctx; + if (unlikely(!ctx)) { + dev_err(jpeg->dev, "comp_jpeg ctx fail !!!\n"); + return; + } + + dst_buffer = jpeg->hw_param.dst_buffer; + if (!dst_buffer) { + dev_err(jpeg->dev, "comp_jpeg dst_buffer fail !!!\n"); + return; + } + + dst_done_buf = container_of(dst_buffer, struct mtk_jpeg_src_buf, b); + + spin_lock_irqsave(&ctx->done_queue_lock, flags); + list_add_tail(&dst_done_buf->list, &ctx->dst_done_queue); + while (!list_empty(&ctx->dst_done_queue) && + (pos != &ctx->dst_done_queue)) { + list_for_each_prev_safe(pos, temp_entry, &ctx->dst_done_queue) { + tmp_dst_done_buf = list_entry(pos, + struct mtk_jpeg_src_buf, + list); + if (tmp_dst_done_buf->frame_num == + ctx->last_done_frame_num) { + list_del(&tmp_dst_done_buf->list); + v4l2_m2m_buf_done(&tmp_dst_done_buf->b, + VB2_BUF_STATE_DONE); + ctx->last_done_frame_num++; + } + } + } + spin_unlock_irqrestore(&ctx->done_queue_lock, flags); +} + +static void mtk_jpegdec_timeout_work(struct work_struct *work) +{ + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + struct mtk_jpegdec_comp_dev *cjpeg = + container_of(work, struct mtk_jpegdec_comp_dev, + job_timeout_work.work); + struct mtk_jpeg_dev *master_jpeg = cjpeg->master_dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = cjpeg->hw_param.src_buffer; + dst_buf = cjpeg->hw_param.dst_buffer; + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + mtk_jpeg_dec_reset(cjpeg->reg_base); + clk_disable_unprepare(cjpeg->jdec_clk.clks->clk); + pm_runtime_put(cjpeg->dev); + cjpeg->hw_state = MTK_JPEG_HW_IDLE; + atomic_inc(&master_jpeg->dechw_rdy); + wake_up(&master_jpeg->dec_hw_wq); + v4l2_m2m_buf_done(src_buf, buf_state); + mtk_jpegdec_put_buf(cjpeg); +} + +static irqreturn_t mtk_jpegdec_hw_irq_handler(int irq, void *priv) +{ + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mtk_jpeg_src_buf *jpeg_src_buf; + enum vb2_buffer_state buf_state; + struct mtk_jpeg_ctx *ctx; + u32 dec_irq_ret; + u32 irq_status; + int i; + + struct mtk_jpegdec_comp_dev *jpeg = priv; + struct mtk_jpeg_dev *master_jpeg = jpeg->master_dev; + + cancel_delayed_work(&jpeg->job_timeout_work); + + ctx = jpeg->hw_param.curr_ctx; + src_buf = jpeg->hw_param.src_buffer; + dst_buf = jpeg->hw_param.dst_buffer; + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + irq_status = mtk_jpeg_dec_get_int_status(jpeg->reg_base); + dec_irq_ret = mtk_jpeg_dec_enum_result(irq_status); + if (dec_irq_ret >= MTK_JPEG_DEC_RESULT_UNDERFLOW) + mtk_jpeg_dec_reset(jpeg->reg_base); + + if (dec_irq_ret != MTK_JPEG_DEC_RESULT_EOF_DONE) + dev_warn(jpeg->dev, "Jpg Dec occurs unknown Err."); + + jpeg_src_buf = + container_of(src_buf, struct mtk_jpeg_src_buf, b); + + for (i = 0; i < dst_buf->vb2_buf.num_planes; i++) + vb2_set_plane_payload(&dst_buf->vb2_buf, i, + jpeg_src_buf->dec_param.comp_size[i]); + + buf_state = VB2_BUF_STATE_DONE; + v4l2_m2m_buf_done(src_buf, buf_state); + mtk_jpegdec_put_buf(jpeg); + pm_runtime_put(ctx->jpeg->dev); + clk_disable_unprepare(jpeg->jdec_clk.clks->clk); + + jpeg->hw_state = MTK_JPEG_HW_IDLE; + wake_up(&master_jpeg->dec_hw_wq); + atomic_inc(&master_jpeg->dechw_rdy); + + return IRQ_HANDLED; +} + +static int mtk_jpegdec_hw_init_irq(struct mtk_jpegdec_comp_dev *dev) +{ + struct platform_device *pdev = dev->plat_dev; + int ret; + + dev->jpegdec_irq = platform_get_irq(pdev, 0); + if (dev->jpegdec_irq < 0) + return dev->jpegdec_irq; + + ret = devm_request_irq(&pdev->dev, + dev->jpegdec_irq, + mtk_jpegdec_hw_irq_handler, + 0, + pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "Failed to devm_request_irq %d (%d)", + dev->jpegdec_irq, ret); + return ret; + } + + return 0; } + +static void mtk_jpegdec_destroy_workqueue(void *data) +{ + destroy_workqueue(data); +} + +static int mtk_jpegdec_hw_probe(struct platform_device *pdev) +{ + struct mtk_jpegdec_clk *jpegdec_clk; + struct mtk_jpeg_dev *master_dev; + struct mtk_jpegdec_comp_dev *dev; + int ret, i; + + struct device *decs = &pdev->dev; + + if (!decs->parent) + return -EPROBE_DEFER; + + master_dev = dev_get_drvdata(decs->parent); + if (!master_dev) + return -EPROBE_DEFER; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->plat_dev = pdev; + dev->dev = &pdev->dev; + + if (!master_dev->is_jpgdec_multihw) { + master_dev->is_jpgdec_multihw = true; + for (i = 0; i < MTK_JPEGDEC_HW_MAX; i++) + master_dev->dec_hw_dev[i] = NULL; + + init_waitqueue_head(&master_dev->dec_hw_wq); + master_dev->workqueue = alloc_ordered_workqueue(MTK_JPEG_NAME, + WQ_MEM_RECLAIM + | WQ_FREEZABLE); + if (!master_dev->workqueue) + return -EINVAL; + + ret = devm_add_action_or_reset(&pdev->dev, mtk_jpegdec_destroy_workqueue, + master_dev->workqueue); + if (ret) + return ret; + } + + atomic_set(&master_dev->dechw_rdy, MTK_JPEGDEC_HW_MAX); + spin_lock_init(&dev->hw_lock); + dev->hw_state = MTK_JPEG_HW_IDLE; + + INIT_DELAYED_WORK(&dev->job_timeout_work, + mtk_jpegdec_timeout_work); + + jpegdec_clk = &dev->jdec_clk; + + jpegdec_clk->clk_num = devm_clk_bulk_get_all(&pdev->dev, + &jpegdec_clk->clks); + if (jpegdec_clk->clk_num < 0) + return dev_err_probe(&pdev->dev, + jpegdec_clk->clk_num, + "Failed to get jpegdec clock count.\n"); + + dev->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->reg_base)) + return PTR_ERR(dev->reg_base); + + ret = mtk_jpegdec_hw_init_irq(dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register JPEGDEC irq handler.\n"); + + for (i = 0; i < MTK_JPEGDEC_HW_MAX; i++) { + if (master_dev->dec_hw_dev[i]) + continue; + + master_dev->dec_hw_dev[i] = dev; + master_dev->reg_decbase[i] = dev->reg_base; + dev->master_dev = master_dev; + } + + platform_set_drvdata(pdev, dev); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static struct platform_driver mtk_jpegdec_hw_driver = { + .probe = mtk_jpegdec_hw_probe, + .driver = { + .name = "mtk-jpegdec-hw", + .of_match_table = of_match_ptr(mtk_jpegdec_hw_ids), + }, +}; + +module_platform_driver(mtk_jpegdec_hw_driver); + +MODULE_DESCRIPTION("MediaTek JPEG decode HW driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h index fa0d45fd7c34..8c31c6b12417 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h @@ -11,9 +11,10 @@ #include <media/videobuf2-core.h> -#include "mtk_jpeg_core.h" #include "mtk_jpeg_dec_reg.h" +#define MTK_JPEG_COMP_MAX 3 + enum { MTK_JPEG_DEC_RESULT_EOF_DONE = 0, MTK_JPEG_DEC_RESULT_PAUSE = 1, @@ -70,7 +71,8 @@ int mtk_jpeg_dec_fill_param(struct mtk_jpeg_dec_param *param); u32 mtk_jpeg_dec_get_int_status(void __iomem *dec_reg_base); u32 mtk_jpeg_dec_enum_result(u32 irq_result); void mtk_jpeg_dec_set_config(void __iomem *base, - struct mtk_jpeg_dec_param *config, + struct mtk_jpeg_dec_param *cfg, + u32 bitstream_size, struct mtk_jpeg_bs *bs, struct mtk_jpeg_fb *fb); void mtk_jpeg_dec_reset(void __iomem *dec_reg_base); diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h index 21ec8f96797f..27b7711ca341 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h @@ -45,5 +45,6 @@ #define JPGDEC_REG_QT_ID 0x0270 #define JPGDEC_REG_INTERRUPT_STATUS 0x0274 #define JPGDEC_REG_STATUS 0x0278 +#define JPGDEC_REG_BIT_STREAM_SIZE 0x0344 #endif /* _MTK_JPEG_REG_H */ diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c index 1cf037bf72dd..1bbb712d78d0 100644 --- a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c @@ -5,11 +5,27 @@ * */ +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/io.h> #include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <media/media-device.h> #include <media/videobuf2-core.h> #include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-v4l2.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> +#include "mtk_jpeg_core.h" #include "mtk_jpeg_enc_hw.h" static const struct mtk_jpeg_enc_qlt mtk_jpeg_enc_quality[] = { @@ -30,18 +46,30 @@ static const struct mtk_jpeg_enc_qlt mtk_jpeg_enc_quality[] = { {.quality_param = 97, .hardware_value = JPEG_ENC_QUALITY_Q97}, }; +#if defined(CONFIG_OF) +static const struct of_device_id mtk_jpegenc_drv_ids[] = { + { + .compatible = "mediatek,mt8195-jpgenc-hw", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_jpegenc_drv_ids); +#endif + void mtk_jpeg_enc_reset(void __iomem *base) { writel(0, base + JPEG_ENC_RSTB); writel(JPEG_ENC_RESET_BIT, base + JPEG_ENC_RSTB); writel(0, base + JPEG_ENC_CODEC_SEL); } +EXPORT_SYMBOL_GPL(mtk_jpeg_enc_reset); u32 mtk_jpeg_enc_get_file_size(void __iomem *base) { return readl(base + JPEG_ENC_DMA_ADDR0) - readl(base + JPEG_ENC_DST_ADDR0); } +EXPORT_SYMBOL_GPL(mtk_jpeg_enc_get_file_size); void mtk_jpeg_enc_start(void __iomem *base) { @@ -51,6 +79,7 @@ void mtk_jpeg_enc_start(void __iomem *base) value |= JPEG_ENC_CTRL_INT_EN_BIT | JPEG_ENC_CTRL_ENABLE_BIT; writel(value, base + JPEG_ENC_CTRL); } +EXPORT_SYMBOL_GPL(mtk_jpeg_enc_start); void mtk_jpeg_set_enc_src(struct mtk_jpeg_ctx *ctx, void __iomem *base, struct vb2_buffer *src_buf) @@ -67,6 +96,7 @@ void mtk_jpeg_set_enc_src(struct mtk_jpeg_ctx *ctx, void __iomem *base, writel(dma_addr, base + JPEG_ENC_SRC_CHROMA_ADDR); } } +EXPORT_SYMBOL_GPL(mtk_jpeg_set_enc_src); void mtk_jpeg_set_enc_dst(struct mtk_jpeg_ctx *ctx, void __iomem *base, struct vb2_buffer *dst_buf) @@ -86,6 +116,7 @@ void mtk_jpeg_set_enc_dst(struct mtk_jpeg_ctx *ctx, void __iomem *base, writel(dma_addr & ~0xf, base + JPEG_ENC_DST_ADDR0); writel((dma_addr + size) & ~0xf, base + JPEG_ENC_STALL_ADDR0); } +EXPORT_SYMBOL_GPL(mtk_jpeg_set_enc_dst); void mtk_jpeg_set_enc_params(struct mtk_jpeg_ctx *ctx, void __iomem *base) { @@ -152,3 +183,227 @@ void mtk_jpeg_set_enc_params(struct mtk_jpeg_ctx *ctx, void __iomem *base) writel(ctx->restart_interval, base + JPEG_ENC_RST_MCU_NUM); } +EXPORT_SYMBOL_GPL(mtk_jpeg_set_enc_params); + +static void mtk_jpegenc_put_buf(struct mtk_jpegenc_comp_dev *jpeg) +{ + struct mtk_jpeg_ctx *ctx; + struct vb2_v4l2_buffer *dst_buffer; + struct list_head *temp_entry; + struct list_head *pos = NULL; + struct mtk_jpeg_src_buf *dst_done_buf, *tmp_dst_done_buf; + unsigned long flags; + + ctx = jpeg->hw_param.curr_ctx; + if (!ctx) { + dev_err(jpeg->dev, "comp_jpeg ctx fail !!!\n"); + return; + } + + dst_buffer = jpeg->hw_param.dst_buffer; + if (!dst_buffer) { + dev_err(jpeg->dev, "comp_jpeg dst_buffer fail !!!\n"); + return; + } + + dst_done_buf = container_of(dst_buffer, + struct mtk_jpeg_src_buf, b); + + spin_lock_irqsave(&ctx->done_queue_lock, flags); + list_add_tail(&dst_done_buf->list, &ctx->dst_done_queue); + while (!list_empty(&ctx->dst_done_queue) && + (pos != &ctx->dst_done_queue)) { + list_for_each_prev_safe(pos, temp_entry, &ctx->dst_done_queue) { + tmp_dst_done_buf = list_entry(pos, + struct mtk_jpeg_src_buf, + list); + if (tmp_dst_done_buf->frame_num == + ctx->last_done_frame_num) { + list_del(&tmp_dst_done_buf->list); + v4l2_m2m_buf_done(&tmp_dst_done_buf->b, + VB2_BUF_STATE_DONE); + ctx->last_done_frame_num++; + } + } + } + spin_unlock_irqrestore(&ctx->done_queue_lock, flags); +} + +static void mtk_jpegenc_timeout_work(struct work_struct *work) +{ + struct delayed_work *dly_work = to_delayed_work(work); + struct mtk_jpegenc_comp_dev *cjpeg = + container_of(dly_work, + struct mtk_jpegenc_comp_dev, + job_timeout_work); + struct mtk_jpeg_dev *master_jpeg = cjpeg->master_dev; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = cjpeg->hw_param.src_buffer; + dst_buf = cjpeg->hw_param.dst_buffer; + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + mtk_jpeg_enc_reset(cjpeg->reg_base); + clk_disable_unprepare(cjpeg->venc_clk.clks->clk); + pm_runtime_put(cjpeg->dev); + cjpeg->hw_state = MTK_JPEG_HW_IDLE; + atomic_inc(&master_jpeg->enchw_rdy); + wake_up(&master_jpeg->enc_hw_wq); + v4l2_m2m_buf_done(src_buf, buf_state); + mtk_jpegenc_put_buf(cjpeg); +} + +static irqreturn_t mtk_jpegenc_hw_irq_handler(int irq, void *priv) +{ + struct vb2_v4l2_buffer *src_buf, *dst_buf; + enum vb2_buffer_state buf_state; + struct mtk_jpeg_ctx *ctx; + u32 result_size; + u32 irq_status; + + struct mtk_jpegenc_comp_dev *jpeg = priv; + struct mtk_jpeg_dev *master_jpeg = jpeg->master_dev; + + cancel_delayed_work(&jpeg->job_timeout_work); + + ctx = jpeg->hw_param.curr_ctx; + src_buf = jpeg->hw_param.src_buffer; + dst_buf = jpeg->hw_param.dst_buffer; + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + irq_status = readl(jpeg->reg_base + JPEG_ENC_INT_STS) & + JPEG_ENC_INT_STATUS_MASK_ALLIRQ; + if (irq_status) + writel(0, jpeg->reg_base + JPEG_ENC_INT_STS); + if (!(irq_status & JPEG_ENC_INT_STATUS_DONE)) + dev_warn(jpeg->dev, "Jpg Enc occurs unknown Err."); + + result_size = mtk_jpeg_enc_get_file_size(jpeg->reg_base); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, result_size); + buf_state = VB2_BUF_STATE_DONE; + v4l2_m2m_buf_done(src_buf, buf_state); + mtk_jpegenc_put_buf(jpeg); + pm_runtime_put(ctx->jpeg->dev); + clk_disable_unprepare(jpeg->venc_clk.clks->clk); + if (!list_empty(&ctx->fh.m2m_ctx->out_q_ctx.rdy_queue) || + !list_empty(&ctx->fh.m2m_ctx->cap_q_ctx.rdy_queue)) { + queue_work(master_jpeg->workqueue, &ctx->jpeg_work); + } + + jpeg->hw_state = MTK_JPEG_HW_IDLE; + wake_up(&master_jpeg->enc_hw_wq); + atomic_inc(&master_jpeg->enchw_rdy); + + return IRQ_HANDLED; +} + +static int mtk_jpegenc_hw_init_irq(struct mtk_jpegenc_comp_dev *dev) +{ + struct platform_device *pdev = dev->plat_dev; + int ret; + + dev->jpegenc_irq = platform_get_irq(pdev, 0); + if (dev->jpegenc_irq < 0) + return dev->jpegenc_irq; + + ret = devm_request_irq(&pdev->dev, + dev->jpegenc_irq, + mtk_jpegenc_hw_irq_handler, + 0, + pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "Failed to devm_request_irq %d (%d)", + dev->jpegenc_irq, ret); + return ret; + } + + return 0; +} + +static int mtk_jpegenc_hw_probe(struct platform_device *pdev) +{ + struct mtk_jpegenc_clk *jpegenc_clk; + struct mtk_jpeg_dev *master_dev; + struct mtk_jpegenc_comp_dev *dev; + int ret, i; + + struct device *decs = &pdev->dev; + + if (!decs->parent) + return -EPROBE_DEFER; + + master_dev = dev_get_drvdata(decs->parent); + if (!master_dev) + return -EPROBE_DEFER; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->plat_dev = pdev; + dev->dev = &pdev->dev; + + if (!master_dev->is_jpgenc_multihw) { + master_dev->is_jpgenc_multihw = true; + for (i = 0; i < MTK_JPEGENC_HW_MAX; i++) + master_dev->enc_hw_dev[i] = NULL; + + init_waitqueue_head(&master_dev->enc_hw_wq); + master_dev->workqueue = alloc_ordered_workqueue(MTK_JPEG_NAME, + WQ_MEM_RECLAIM + | WQ_FREEZABLE); + if (!master_dev->workqueue) + return -EINVAL; + } + + atomic_set(&master_dev->enchw_rdy, MTK_JPEGENC_HW_MAX); + spin_lock_init(&dev->hw_lock); + dev->hw_state = MTK_JPEG_HW_IDLE; + + INIT_DELAYED_WORK(&dev->job_timeout_work, + mtk_jpegenc_timeout_work); + + jpegenc_clk = &dev->venc_clk; + + jpegenc_clk->clk_num = devm_clk_bulk_get_all(&pdev->dev, + &jpegenc_clk->clks); + if (jpegenc_clk->clk_num < 0) + return dev_err_probe(&pdev->dev, jpegenc_clk->clk_num, + "Failed to get jpegenc clock count\n"); + + dev->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->reg_base)) + return PTR_ERR(dev->reg_base); + + ret = mtk_jpegenc_hw_init_irq(dev); + if (ret) + return ret; + + for (i = 0; i < MTK_JPEGENC_HW_MAX; i++) { + if (master_dev->enc_hw_dev[i]) + continue; + + master_dev->enc_hw_dev[i] = dev; + master_dev->reg_encbase[i] = dev->reg_base; + dev->master_dev = master_dev; + } + + platform_set_drvdata(pdev, dev); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static struct platform_driver mtk_jpegenc_hw_driver = { + .probe = mtk_jpegenc_hw_probe, + .driver = { + .name = "mtk-jpegenc-hw", + .of_match_table = of_match_ptr(mtk_jpegenc_drv_ids), + }, +}; + +module_platform_driver(mtk_jpegenc_hw_driver); + +MODULE_DESCRIPTION("MediaTek JPEG encode HW driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c index 1e3833f1c9ae..ad5fab2d8bfa 100644 --- a/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c @@ -52,9 +52,8 @@ int mtk_mdp_comp_init(struct device *dev, struct device_node *node, for (i = 0; i < ARRAY_SIZE(comp->clk); i++) { comp->clk[i] = of_clk_get(node, i); if (IS_ERR(comp->clk[i])) { - if (PTR_ERR(comp->clk[i]) != -EPROBE_DEFER) - dev_err(dev, "Failed to get clock\n"); - ret = PTR_ERR(comp->clk[i]); + ret = dev_err_probe(dev, PTR_ERR(comp->clk[i]), + "Failed to get clock\n"); goto put_dev; } diff --git a/drivers/media/platform/mediatek/mdp3/Kconfig b/drivers/media/platform/mediatek/mdp3/Kconfig index 50ae07b75b5f..846e759a8f6a 100644 --- a/drivers/media/platform/mediatek/mdp3/Kconfig +++ b/drivers/media/platform/mediatek/mdp3/Kconfig @@ -9,7 +9,6 @@ config VIDEO_MEDIATEK_MDP3 select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV select MTK_MMSYS - select VIDEO_MEDIATEK_VPU select MTK_CMDQ select MTK_SCP default n diff --git a/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h b/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h index 3e66ebaee2da..c7f231f8ea3e 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h +++ b/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h @@ -51,14 +51,14 @@ struct img_sw_addr { struct img_plane_format { u32 size; - u16 stride; + u32 stride; } __packed; struct img_pix_format { - u16 width; - u16 height; + u32 width; + u32 height; u32 colorformat; /* enum mdp_color */ - u16 ycbcr_prof; /* enum mdp_ycbcr_profile */ + u32 ycbcr_prof; /* enum mdp_ycbcr_profile */ struct img_plane_format plane_fmt[IMG_MAX_PLANES]; } __packed; @@ -72,10 +72,10 @@ struct img_image_buffer { #define IMG_SUBPIXEL_SHIFT 20 struct img_crop { - s16 left; - s16 top; - u16 width; - u16 height; + s32 left; + s32 top; + u32 width; + u32 height; u32 left_subpix; u32 top_subpix; u32 width_subpix; @@ -90,24 +90,24 @@ struct img_crop { struct img_input { struct img_image_buffer buffer; - u16 flags; /* HDR, DRE, dither */ + u32 flags; /* HDR, DRE, dither */ } __packed; struct img_output { struct img_image_buffer buffer; struct img_crop crop; - s16 rotation; - u16 flags; /* H-flip, sharpness, dither */ + s32 rotation; + u32 flags; /* H-flip, sharpness, dither */ } __packed; struct img_ipi_frameparam { u32 index; u32 frame_no; struct img_timeval timestamp; - u8 type; /* enum mdp_stream_type */ - u8 state; - u8 num_inputs; - u8 num_outputs; + u32 type; /* enum mdp_stream_type */ + u32 state; + u32 num_inputs; + u32 num_outputs; u64 drv_data; struct img_input inputs[IMG_MAX_HW_INPUTS]; struct img_output outputs[IMG_MAX_HW_OUTPUTS]; @@ -123,51 +123,51 @@ struct img_sw_buffer { } __packed; struct img_ipi_param { - u8 usage; + u32 usage; struct img_sw_buffer frm_param; } __packed; struct img_frameparam { struct list_head list_entry; struct img_ipi_frameparam frameparam; -}; +} __packed; /* ISP-MDP generic output information */ struct img_comp_frame { - u32 output_disable:1; - u32 bypass:1; - u16 in_width; - u16 in_height; - u16 out_width; - u16 out_height; + u32 output_disable; + u32 bypass; + u32 in_width; + u32 in_height; + u32 out_width; + u32 out_height; struct img_crop crop; - u16 in_total_width; - u16 out_total_width; + u32 in_total_width; + u32 out_total_width; } __packed; struct img_region { - s16 left; - s16 right; - s16 top; - s16 bottom; + s32 left; + s32 right; + s32 top; + s32 bottom; } __packed; struct img_offset { - s16 left; - s16 top; + s32 left; + s32 top; u32 left_subpix; u32 top_subpix; } __packed; struct img_comp_subfrm { - u32 tile_disable:1; + u32 tile_disable; struct img_region in; struct img_region out; struct img_offset luma; struct img_offset chroma; - s16 out_vertical; /* Output vertical index */ - s16 out_horizontal; /* Output horizontal index */ + s32 out_vertical; /* Output vertical index */ + s32 out_horizontal; /* Output horizontal index */ } __packed; #define IMG_MAX_SUBFRAMES 14 @@ -250,8 +250,8 @@ struct isp_data { } __packed; struct img_compparam { - u16 type; /* enum mdp_comp_type */ - u16 id; /* enum mtk_mdp_comp_id */ + u32 type; /* enum mdp_comp_id */ + u32 id; /* engine alias_id */ u32 input; u32 outputs[IMG_MAX_HW_OUTPUTS]; u32 num_outputs; @@ -273,12 +273,12 @@ struct img_mux { u32 reg; u32 value; u32 subsys_id; -}; +} __packed; struct img_mmsys_ctrl { struct img_mux sets[IMG_MAX_COMPONENTS * 2]; u32 num_sets; -}; +} __packed; struct img_config { struct img_compparam components[IMG_MAX_COMPONENTS]; diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c index 86c054600a08..124c1b96e96b 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c @@ -252,10 +252,9 @@ static int mdp_cmdq_pkt_create(struct cmdq_client *client, struct cmdq_pkt *pkt, dma_addr_t dma_addr; pkt->va_base = kzalloc(size, GFP_KERNEL); - if (!pkt->va_base) { - kfree(pkt); + if (!pkt->va_base) return -ENOMEM; - } + pkt->buf_size = size; pkt->cl = (void *)client; @@ -368,25 +367,30 @@ int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (!cmd) { ret = -ENOMEM; - goto err_cmdq_data; + goto err_cancel_job; } - if (mdp_cmdq_pkt_create(mdp->cmdq_clt, &cmd->pkt, SZ_16K)) { - ret = -ENOMEM; - goto err_cmdq_data; - } + ret = mdp_cmdq_pkt_create(mdp->cmdq_clt, &cmd->pkt, SZ_16K); + if (ret) + goto err_free_cmd; comps = kcalloc(param->config->num_components, sizeof(*comps), GFP_KERNEL); if (!comps) { ret = -ENOMEM; - goto err_cmdq_data; + goto err_destroy_pkt; } path = kzalloc(sizeof(*path), GFP_KERNEL); if (!path) { ret = -ENOMEM; - goto err_cmdq_data; + goto err_free_comps; + } + + ret = mtk_mutex_prepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); + if (ret) { + dev_err(dev, "Fail to enable mutex clk\n"); + goto err_free_path; } path->mdp_dev = mdp; @@ -406,15 +410,13 @@ int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) ret = mdp_path_ctx_init(mdp, path); if (ret) { dev_err(dev, "mdp_path_ctx_init error\n"); - goto err_cmdq_data; + goto err_free_path; } - mtk_mutex_prepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); - ret = mdp_path_config(mdp, cmd, path); if (ret) { dev_err(dev, "mdp_path_config error\n"); - goto err_cmdq_data; + goto err_free_path; } cmdq_pkt_finalize(&cmd->pkt); @@ -431,10 +433,8 @@ int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) cmd->mdp_ctx = param->mdp_ctx; ret = mdp_comp_clocks_on(&mdp->pdev->dev, cmd->comps, cmd->num_comps); - if (ret) { - dev_err(dev, "comp %d failed to enable clock!\n", ret); - goto err_clock_off; - } + if (ret) + goto err_free_path; dma_sync_single_for_device(mdp->cmdq_clt->chan->mbox->dev, cmd->pkt.pa_base, cmd->pkt.cmd_buf_size, @@ -450,17 +450,20 @@ int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) return 0; err_clock_off: - mtk_mutex_unprepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); mdp_comp_clocks_off(&mdp->pdev->dev, cmd->comps, cmd->num_comps); -err_cmdq_data: +err_free_path: + mtk_mutex_unprepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); kfree(path); - atomic_dec(&mdp->job_count); - wake_up(&mdp->callback_wq); - if (cmd && cmd->pkt.buf_size > 0) - mdp_cmdq_pkt_destroy(&cmd->pkt); +err_free_comps: kfree(comps); +err_destroy_pkt: + mdp_cmdq_pkt_destroy(&cmd->pkt); +err_free_cmd: kfree(cmd); +err_cancel_job: + atomic_dec(&mdp->job_count); + return ret; } EXPORT_SYMBOL_GPL(mdp_cmdq_send); diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c index d3eaf8884412..7bc05f42a23c 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c @@ -699,12 +699,22 @@ int mdp_comp_clock_on(struct device *dev, struct mdp_comp *comp) dev_err(dev, "Failed to enable clk %d. type:%d id:%d\n", i, comp->type, comp->id); - pm_runtime_put(comp->comp_dev); - return ret; + goto err_revert; } } return 0; + +err_revert: + while (--i >= 0) { + if (IS_ERR_OR_NULL(comp->clks[i])) + continue; + clk_disable_unprepare(comp->clks[i]); + } + if (comp->comp_dev) + pm_runtime_put_sync(comp->comp_dev); + + return ret; } void mdp_comp_clock_off(struct device *dev, struct mdp_comp *comp) @@ -723,11 +733,13 @@ void mdp_comp_clock_off(struct device *dev, struct mdp_comp *comp) int mdp_comp_clocks_on(struct device *dev, struct mdp_comp *comps, int num) { - int i; + int i, ret; - for (i = 0; i < num; i++) - if (mdp_comp_clock_on(dev, &comps[i]) != 0) - return ++i; + for (i = 0; i < num; i++) { + ret = mdp_comp_clock_on(dev, &comps[i]); + if (ret) + return ret; + } return 0; } diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c index c413e59d4286..2d1f6ae9f080 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c @@ -196,27 +196,27 @@ static int mdp_probe(struct platform_device *pdev) mm_pdev = __get_pdev_by_id(pdev, MDP_INFRA_MMSYS); if (!mm_pdev) { ret = -ENODEV; - goto err_return; + goto err_destroy_device; } mdp->mdp_mmsys = &mm_pdev->dev; mm_pdev = __get_pdev_by_id(pdev, MDP_INFRA_MUTEX); if (WARN_ON(!mm_pdev)) { ret = -ENODEV; - goto err_return; + goto err_destroy_device; } for (i = 0; i < MDP_PIPE_MAX; i++) { mdp->mdp_mutex[i] = mtk_mutex_get(&mm_pdev->dev); if (!mdp->mdp_mutex[i]) { ret = -ENODEV; - goto err_return; + goto err_free_mutex; } } ret = mdp_comp_config(mdp); if (ret) { dev_err(dev, "Failed to config mdp components\n"); - goto err_return; + goto err_free_mutex; } mdp->job_wq = alloc_workqueue(MDP_MODULE_NAME, WQ_FREEZABLE, 0); @@ -287,11 +287,12 @@ err_destroy_job_wq: destroy_workqueue(mdp->job_wq); err_deinit_comp: mdp_comp_destroy(mdp); -err_return: +err_free_mutex: for (i = 0; i < MDP_PIPE_MAX; i++) - if (mdp) - mtk_mutex_put(mdp->mdp_mutex[i]); + mtk_mutex_put(mdp->mdp_mutex[i]); +err_destroy_device: kfree(mdp); +err_return: dev_dbg(dev, "Errno %d\n", ret); return ret; } diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c index c45bd2599bb2..ffbcee04dc26 100644 --- a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c @@ -138,10 +138,13 @@ static void mtk_vdec_stateless_cap_to_disp(struct mtk_vcodec_ctx *ctx, int error state = VB2_BUF_STATE_DONE; vb2_dst = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); - v4l2_m2m_buf_done(vb2_dst, state); - - mtk_v4l2_debug(2, "free frame buffer id:%d to done list", - vb2_dst->vb2_buf.index); + if (vb2_dst) { + v4l2_m2m_buf_done(vb2_dst, state); + mtk_v4l2_debug(2, "free frame buffer id:%d to done list", + vb2_dst->vb2_buf.index); + } else { + mtk_v4l2_err("dst buffer is NULL"); + } if (src_buf_req) v4l2_ctrl_request_complete(src_buf_req, &ctx->ctrl_hdl); @@ -250,7 +253,7 @@ static void mtk_vdec_worker(struct work_struct *work) state = ret ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE; if (!IS_VDEC_LAT_ARCH(dev->vdec_pdata->hw_arch) || - ctx->current_codec == V4L2_PIX_FMT_VP8_FRAME || ret) { + ctx->current_codec == V4L2_PIX_FMT_VP8_FRAME) { v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, state); if (src_buf_req) v4l2_ctrl_request_complete(src_buf_req, &ctx->ctrl_hdl); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c index d810a78dde51..d65800a3b89d 100644 --- a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c @@ -1397,7 +1397,10 @@ int mtk_vcodec_enc_ctrls_setup(struct mtk_vcodec_ctx *ctx) 0, V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE); v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, - 0, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_MPEG_VIDEO_H264_LEVEL, h264_max_level, 0, V4L2_MPEG_VIDEO_H264_LEVEL_4_0); diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c index 4cc92700692b..955b2d0c8f53 100644 --- a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c @@ -471,14 +471,19 @@ static int vdec_h264_slice_core_decode(struct vdec_lat_buf *lat_buf) sizeof(share_info->h264_slice_params)); fb = ctx->dev->vdec_pdata->get_cap_buffer(ctx); - y_fb_dma = fb ? (u64)fb->base_y.dma_addr : 0; - vdec_fb_va = (unsigned long)fb; + if (!fb) { + err = -EBUSY; + mtk_vcodec_err(inst, "fb buffer is NULL"); + goto vdec_dec_end; + } + vdec_fb_va = (unsigned long)fb; + y_fb_dma = (u64)fb->base_y.dma_addr; if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 1) c_fb_dma = y_fb_dma + inst->ctx->picinfo.buf_w * inst->ctx->picinfo.buf_h; else - c_fb_dma = fb ? (u64)fb->base_c.dma_addr : 0; + c_fb_dma = (u64)fb->base_c.dma_addr; mtk_vcodec_debug(inst, "[h264-core] y/c addr = 0x%llx 0x%llx", y_fb_dma, c_fb_dma); @@ -539,6 +544,29 @@ vdec_dec_end: return 0; } +static void vdec_h264_insert_startcode(struct mtk_vcodec_dev *vcodec_dev, unsigned char *buf, + size_t *bs_size, struct mtk_h264_pps_param *pps) +{ + struct device *dev = &vcodec_dev->plat_dev->dev; + + /* Need to add pending data at the end of bitstream when bs_sz is small than + * 20 bytes for cavlc bitstream, or lat will decode fail. This pending data is + * useful for mt8192 and mt8195 platform. + * + * cavlc bitstream when entropy_coding_mode_flag is false. + */ + if (pps->entropy_coding_mode_flag || *bs_size > 20 || + !(of_device_is_compatible(dev->of_node, "mediatek,mt8192-vcodec-dec") || + of_device_is_compatible(dev->of_node, "mediatek,mt8195-vcodec-dec"))) + return; + + buf[*bs_size] = 0; + buf[*bs_size + 1] = 0; + buf[*bs_size + 2] = 1; + buf[*bs_size + 3] = 0xff; + (*bs_size) += 4; +} + static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, struct vdec_fb *fb, bool *res_chg) { @@ -582,9 +610,6 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, } inst->vsi->dec.nal_info = buf[nal_start_idx]; - inst->vsi->dec.bs_buf_addr = (u64)bs->dma_addr; - inst->vsi->dec.bs_buf_size = bs->size; - lat_buf->src_buf_req = src_buf_info->m2m_buf.vb.vb2_buf.req_obj.req; v4l2_m2m_buf_copy_metadata(&src_buf_info->m2m_buf.vb, &lat_buf->ts_info, true); @@ -592,6 +617,12 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, if (err) goto err_free_fb_out; + vdec_h264_insert_startcode(inst->ctx->dev, buf, &bs->size, + &share_info->h264_slice_params.pps); + + inst->vsi->dec.bs_buf_addr = (uint64_t)bs->dma_addr; + inst->vsi->dec.bs_buf_size = bs->size; + *res_chg = inst->resolution_changed; if (inst->resolution_changed) { mtk_vcodec_debug(inst, "- resolution changed -"); @@ -630,7 +661,7 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, err = vpu_dec_start(vpu, data, 2); if (err) { mtk_vcodec_debug(inst, "lat decode err: %d", err); - goto err_scp_decode; + goto err_free_fb_out; } share_info->trans_end = inst->ctx->msg_queue.wdma_addr.dma_addr + @@ -647,12 +678,17 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, /* wait decoder done interrupt */ timeout = mtk_vcodec_wait_for_done_ctx(inst->ctx, MTK_INST_IRQ_RECEIVED, WAIT_INTR_TIMEOUT_MS, MTK_VDEC_LAT0); + if (timeout) + mtk_vcodec_err(inst, "lat decode timeout: pic_%d", inst->slice_dec_num); inst->vsi->dec.timeout = !!timeout; err = vpu_dec_end(vpu); - if (err == SLICE_HEADER_FULL || timeout || err == TRANS_BUFFER_FULL) { - err = -EINVAL; - goto err_scp_decode; + if (err == SLICE_HEADER_FULL || err == TRANS_BUFFER_FULL) { + if (!IS_VDEC_INNER_RACING(inst->ctx->dev->dec_capability)) + vdec_msg_queue_qbuf(&inst->ctx->msg_queue.lat_ctx, lat_buf); + inst->slice_dec_num++; + mtk_vcodec_err(inst, "lat dec fail: pic_%d err:%d", inst->slice_dec_num, err); + return -EINVAL; } share_info->trans_end = inst->ctx->msg_queue.wdma_addr.dma_addr + @@ -669,10 +705,6 @@ static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, inst->slice_dec_num++; return 0; - -err_scp_decode: - if (!IS_VDEC_INNER_RACING(inst->ctx->dev->dec_capability)) - vdec_msg_queue_qbuf(&inst->ctx->msg_queue.lat_ctx, lat_buf); err_free_fb_out: vdec_msg_queue_qbuf(&inst->ctx->msg_queue.lat_ctx, lat_buf); mtk_vcodec_err(inst, "slice dec number: %d err: %d", inst->slice_dec_num, err); diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c index fb1c36a3592d..cbb6728b8a40 100644 --- a/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c @@ -2073,21 +2073,23 @@ static int vdec_vp9_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, return -EBUSY; } pfc = (struct vdec_vp9_slice_pfc *)lat_buf->private_data; - if (!pfc) - return -EINVAL; + if (!pfc) { + ret = -EINVAL; + goto err_free_fb_out; + } vsi = &pfc->vsi; ret = vdec_vp9_slice_setup_lat(instance, bs, lat_buf, pfc); if (ret) { mtk_vcodec_err(instance, "Failed to setup VP9 lat ret %d\n", ret); - return ret; + goto err_free_fb_out; } vdec_vp9_slice_vsi_to_remote(vsi, instance->vsi); ret = vpu_dec_start(&instance->vpu, NULL, 0); if (ret) { mtk_vcodec_err(instance, "Failed to dec VP9 ret %d\n", ret); - return ret; + goto err_free_fb_out; } if (instance->irq) { @@ -2107,7 +2109,7 @@ static int vdec_vp9_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, /* LAT trans full, no more UBE or decode timeout */ if (ret) { mtk_vcodec_err(instance, "VP9 decode error: %d\n", ret); - return ret; + goto err_free_fb_out; } mtk_vcodec_debug(instance, "lat dma addr: 0x%lx 0x%lx\n", @@ -2120,6 +2122,9 @@ static int vdec_vp9_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, vdec_msg_queue_qbuf(&ctx->dev->msg_queue_core_ctx, lat_buf); return 0; +err_free_fb_out: + vdec_msg_queue_qbuf(&ctx->msg_queue.lat_ctx, lat_buf); + return ret; } static int vdec_vp9_slice_decode(void *h_vdec, struct mtk_vcodec_mem *bs, diff --git a/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c b/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c index ae500980ad45..dc2004790a47 100644 --- a/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c +++ b/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c @@ -221,7 +221,7 @@ static void vdec_msg_queue_core_work(struct work_struct *work) mtk_vcodec_dec_disable_hardware(ctx, MTK_VDEC_CORE); vdec_msg_queue_qbuf(&ctx->msg_queue.lat_ctx, lat_buf); - if (!list_empty(&ctx->msg_queue.lat_ctx.ready_queue)) { + if (!list_empty(&dev->msg_queue_core_ctx.ready_queue)) { mtk_v4l2_debug(3, "re-schedule to decode for core: %d", dev->msg_queue_core_ctx.ready_num); queue_work(dev->core_workqueue, &msg_queue->core_work); diff --git a/drivers/media/platform/microchip/Kconfig b/drivers/media/platform/microchip/Kconfig new file mode 100644 index 000000000000..4734ecced029 --- /dev/null +++ b/drivers/media/platform/microchip/Kconfig @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Microchip Technology, Inc. media platform drivers" + +config VIDEO_MICROCHIP_ISC + tristate "Microchip Image Sensor Controller (ISC) support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK + depends on ARCH_AT91 || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + select V4L2_FWNODE + select VIDEO_MICROCHIP_ISC_BASE + help + This module makes the Microchip Image Sensor Controller available + as a v4l2 device. + + To compile this driver as a module, choose M here: the + module will be called microchip-isc. + +config VIDEO_MICROCHIP_XISC + tristate "Microchip eXtended Image Sensor Controller (XISC) support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK + depends on ARCH_AT91 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + select V4L2_FWNODE + select VIDEO_MICROCHIP_ISC_BASE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This module makes the Microchip eXtended Image Sensor Controller + available as a v4l2 device. + + To compile this driver as a module, choose M here: the + module will be called microchip-xisc. + +config VIDEO_MICROCHIP_ISC_BASE + tristate + default n + help + Microchip ISC and XISC common code base. + +config VIDEO_MICROCHIP_CSI2DC + tristate "Microchip CSI2 Demux Controller" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK && OF + depends on ARCH_AT91 || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + CSI2 Demux Controller driver. CSI2DC is a helper chip + that converts IDI interface byte stream to a parallel pixel stream. + It supports various RAW formats as input. + + To compile this driver as a module, choose M here: the + module will be called microchip-csi2dc. diff --git a/drivers/media/platform/microchip/Makefile b/drivers/media/platform/microchip/Makefile new file mode 100644 index 000000000000..bd8d6e779c51 --- /dev/null +++ b/drivers/media/platform/microchip/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +microchip-isc-objs = microchip-sama5d2-isc.o +microchip-xisc-objs = microchip-sama7g5-isc.o +microchip-isc-common-objs = microchip-isc-base.o microchip-isc-clk.o microchip-isc-scaler.o + +obj-$(CONFIG_VIDEO_MICROCHIP_ISC_BASE) += microchip-isc-common.o +obj-$(CONFIG_VIDEO_MICROCHIP_ISC) += microchip-isc.o +obj-$(CONFIG_VIDEO_MICROCHIP_XISC) += microchip-xisc.o +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/microchip/microchip-csi2dc.c index d5b359f607ae..d5b359f607ae 100644 --- a/drivers/media/platform/atmel/microchip-csi2dc.c +++ b/drivers/media/platform/microchip/microchip-csi2dc.c diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index 9e5317a7d516..e2994d48f10c 100644 --- a/drivers/media/platform/atmel/atmel-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -29,18 +29,13 @@ #include <media/v4l2-subdev.h> #include <media/videobuf2-dma-contig.h> -#include "atmel-isc-regs.h" -#include "atmel-isc.h" +#include "microchip-isc-regs.h" +#include "microchip-isc.h" static unsigned int debug; module_param(debug, int, 0644); MODULE_PARM_DESC(debug, "debug level (0-2)"); -static unsigned int sensor_preferred = 1; -module_param(sensor_preferred, uint, 0644); -MODULE_PARM_DESC(sensor_preferred, - "Sensor is preferred to output the specified format (1-on 0-off), default 1"); - #define ISC_IS_FORMAT_RAW(mbus_code) \ (((mbus_code) & 0xf000) == 0x3000) @@ -96,10 +91,9 @@ static inline void isc_reset_awb_ctrls(struct isc_device *isc) } } - static int isc_queue_setup(struct vb2_queue *vq, - unsigned int *nbuffers, unsigned int *nplanes, - unsigned int sizes[], struct device *alloc_devs[]) + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) { struct isc_device *isc = vb2_get_drv_priv(vq); unsigned int size = isc->fmt.fmt.pix.sizeimage; @@ -334,6 +328,13 @@ static int isc_configure(struct isc_device *isc) return isc_update_profile(isc); } +static int isc_prepare_streaming(struct vb2_queue *vq) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + + return media_pipeline_start(isc->video_dev.entity.pads, &isc->mpipe); +} + static int isc_start_streaming(struct vb2_queue *vq, unsigned int count) { struct isc_device *isc = vb2_get_drv_priv(vq); @@ -400,6 +401,14 @@ err_start_stream: return ret; } +static void isc_unprepare_streaming(struct vb2_queue *vq) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + + /* Stop media pipeline */ + media_pipeline_stop(isc->video_dev.entity.pads); +} + static void isc_stop_streaming(struct vb2_queue *vq) { struct isc_device *isc = vb2_get_drv_priv(vq); @@ -451,28 +460,13 @@ static void isc_buffer_queue(struct vb2_buffer *vb) spin_lock_irqsave(&isc->dma_queue_lock, flags); if (!isc->cur_frm && list_empty(&isc->dma_queue) && - vb2_start_streaming_called(vb->vb2_queue)) { + vb2_start_streaming_called(vb->vb2_queue)) { isc->cur_frm = buf; isc_start_dma(isc); - } else + } else { list_add_tail(&buf->list, &isc->dma_queue); - spin_unlock_irqrestore(&isc->dma_queue_lock, flags); -} - -static struct isc_format *find_format_by_fourcc(struct isc_device *isc, - unsigned int fourcc) -{ - unsigned int num_formats = isc->num_user_formats; - struct isc_format *fmt; - unsigned int i; - - for (i = 0; i < num_formats; i++) { - fmt = isc->user_formats[i]; - if (fmt->fourcc == fourcc) - return fmt; } - - return NULL; + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); } static const struct vb2_ops isc_vb2_ops = { @@ -483,15 +477,17 @@ static const struct vb2_ops isc_vb2_ops = { .start_streaming = isc_start_streaming, .stop_streaming = isc_stop_streaming, .buf_queue = isc_buffer_queue, + .prepare_streaming = isc_prepare_streaming, + .unprepare_streaming = isc_unprepare_streaming, }; static int isc_querycap(struct file *file, void *priv, - struct v4l2_capability *cap) + struct v4l2_capability *cap) { struct isc_device *isc = video_drvdata(file); strscpy(cap->driver, "microchip-isc", sizeof(cap->driver)); - strscpy(cap->card, "Atmel Image Sensor Controller", sizeof(cap->card)); + strscpy(cap->card, "Microchip Image Sensor Controller", sizeof(cap->card)); snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", isc->v4l2_dev.name); @@ -499,27 +495,61 @@ static int isc_querycap(struct file *file, void *priv, } static int isc_enum_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_fmtdesc *f) + struct v4l2_fmtdesc *f) { struct isc_device *isc = video_drvdata(file); u32 index = f->index; - u32 i, supported_index; + u32 i, supported_index = 0; + struct isc_format *fmt; + + /* + * If we are not asked a specific mbus_code, we have to report all + * the formats that we can output. + */ + if (!f->mbus_code) { + if (index >= isc->controller_formats_size) + return -EINVAL; - if (index < isc->controller_formats_size) { f->pixelformat = isc->controller_formats[index].fourcc; + return 0; } - index -= isc->controller_formats_size; + /* + * If a specific mbus_code is requested, check if we support + * this mbus_code as input for the ISC. + * If it's supported, then we report the corresponding pixelformat + * as first possible option for the ISC. + * E.g. mbus MEDIA_BUS_FMT_YUYV8_2X8 and report + * 'YUYV' (YUYV 4:2:2) + */ + fmt = isc_find_format_by_code(isc, f->mbus_code, &i); + if (!fmt) + return -EINVAL; - supported_index = 0; + if (!index) { + f->pixelformat = fmt->fourcc; - for (i = 0; i < isc->formats_list_size; i++) { - if (!ISC_IS_FORMAT_RAW(isc->formats_list[i].mbus_code) || - !isc->formats_list[i].sd_support) + return 0; + } + + supported_index++; + + /* If the index is not raw, we don't have anymore formats to report */ + if (!ISC_IS_FORMAT_RAW(f->mbus_code)) + return -EINVAL; + + /* + * We are asked for a specific mbus code, which is raw. + * We have to search through the formats we can convert to. + * We have to skip the raw formats, we cannot convert to raw. + * E.g. 'AR12' (16-bit ARGB 4-4-4-4), 'AR15' (16-bit ARGB 1-5-5-5), etc. + */ + for (i = 0; i < isc->controller_formats_size; i++) { + if (isc->controller_formats[i].raw) continue; - if (supported_index == index) { - f->pixelformat = isc->formats_list[i].fourcc; + if (index == supported_index) { + f->pixelformat = isc->controller_formats[i].fourcc; return 0; } supported_index++; @@ -529,7 +559,7 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv, } static int isc_g_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *fmt) + struct v4l2_format *fmt) { struct isc_device *isc = video_drvdata(file); @@ -590,20 +620,30 @@ static int isc_try_validate_formats(struct isc_device *isc) break; default: /* any other different formats are not supported */ + v4l2_err(&isc->v4l2_dev, "Requested unsupported format.\n"); ret = -EINVAL; } v4l2_dbg(1, debug, &isc->v4l2_dev, "Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n", rgb, yuv, grey, bayer); - /* we cannot output RAW if we do not receive RAW */ - if ((bayer) && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) + if (bayer && + !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + v4l2_err(&isc->v4l2_dev, "Cannot output RAW if we do not receive RAW.\n"); return -EINVAL; + } - /* we cannot output GREY if we do not receive RAW/GREY */ if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) && - !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) + !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) { + v4l2_err(&isc->v4l2_dev, "Cannot output GREY if we do not receive RAW/GREY.\n"); + return -EINVAL; + } + + if ((rgb || bayer || yuv) && + ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) { + v4l2_err(&isc->v4l2_dev, "Cannot convert GREY to another format.\n"); return -EINVAL; + } return ret; } @@ -831,7 +871,7 @@ static void isc_try_fse(struct isc_device *isc, * If we do not know yet which format the subdev is using, we cannot * do anything. */ - if (!isc->try_config.sd_format) + if (!isc->config.sd_format) return; fse.code = isc->try_config.sd_format->mbus_code; @@ -852,180 +892,139 @@ static void isc_try_fse(struct isc_device *isc, } } -static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f, - u32 *code) +static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f) { - int i; - struct isc_format *sd_fmt = NULL, *direct_fmt = NULL; struct v4l2_pix_format *pixfmt = &f->fmt.pix; - struct v4l2_subdev_pad_config pad_cfg = {}; - struct v4l2_subdev_state pad_state = { - .pads = &pad_cfg - }; - struct v4l2_subdev_format format = { - .which = V4L2_SUBDEV_FORMAT_TRY, - }; - u32 mbus_code; - int ret; - bool rlp_dma_direct_dump = false; + unsigned int i; if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; - /* Step 1: find a RAW format that is supported */ - for (i = 0; i < isc->num_user_formats; i++) { - if (ISC_IS_FORMAT_RAW(isc->user_formats[i]->mbus_code)) { - sd_fmt = isc->user_formats[i]; + isc->try_config.fourcc = isc->controller_formats[0].fourcc; + + /* find if the format requested is supported */ + for (i = 0; i < isc->controller_formats_size; i++) + if (isc->controller_formats[i].fourcc == pixfmt->pixelformat) { + isc->try_config.fourcc = pixfmt->pixelformat; break; } - } - /* Step 2: We can continue with this RAW format, or we can look - * for better: maybe sensor supports directly what we need. - */ - direct_fmt = find_format_by_fourcc(isc, pixfmt->pixelformat); - - /* Step 3: We have both. We decide given the module parameter which - * one to use. - */ - if (direct_fmt && sd_fmt && sensor_preferred) - sd_fmt = direct_fmt; - - /* Step 4: we do not have RAW but we have a direct format. Use it. */ - if (direct_fmt && !sd_fmt) - sd_fmt = direct_fmt; - - /* Step 5: if we are using a direct format, we need to package - * everything as 8 bit data and just dump it - */ - if (sd_fmt == direct_fmt) - rlp_dma_direct_dump = true; - - /* Step 6: We have no format. This can happen if the userspace - * requests some weird/invalid format. - * In this case, default to whatever we have - */ - if (!sd_fmt && !direct_fmt) { - sd_fmt = isc->user_formats[isc->num_user_formats - 1]; - v4l2_dbg(1, debug, &isc->v4l2_dev, - "Sensor not supporting %.4s, using %.4s\n", - (char *)&pixfmt->pixelformat, (char *)&sd_fmt->fourcc); - } - - if (!sd_fmt) { - ret = -EINVAL; - goto isc_try_fmt_err; - } - - /* Step 7: Print out what we decided for debugging */ - v4l2_dbg(1, debug, &isc->v4l2_dev, - "Preferring to have sensor using format %.4s\n", - (char *)&sd_fmt->fourcc); - - /* Step 8: at this moment we decided which format the subdev will use */ - isc->try_config.sd_format = sd_fmt; - - /* Limit to Atmel ISC hardware capabilities */ - if (pixfmt->width > isc->max_width) - pixfmt->width = isc->max_width; - if (pixfmt->height > isc->max_height) - pixfmt->height = isc->max_height; - - /* - * The mbus format is the one the subdev outputs. - * The pixels will be transferred in this format Sensor -> ISC - */ - mbus_code = sd_fmt->mbus_code; - /* - * Validate formats. If the required format is not OK, default to raw. - */ - - isc->try_config.fourcc = pixfmt->pixelformat; - - if (isc_try_validate_formats(isc)) { - pixfmt->pixelformat = isc->try_config.fourcc = sd_fmt->fourcc; - /* Re-try to validate the new format */ - ret = isc_try_validate_formats(isc); - if (ret) - goto isc_try_fmt_err; - } - - ret = isc_try_configure_rlp_dma(isc, rlp_dma_direct_dump); - if (ret) - goto isc_try_fmt_err; - - ret = isc_try_configure_pipeline(isc); - if (ret) - goto isc_try_fmt_err; - - /* Obtain frame sizes if possible to have crop requirements ready */ - isc_try_fse(isc, &pad_state); - - v4l2_fill_mbus_format(&format.format, pixfmt, mbus_code); - ret = v4l2_subdev_call(isc->current_subdev->sd, pad, set_fmt, - &pad_state, &format); - if (ret < 0) - goto isc_try_fmt_subdev_err; - - v4l2_fill_pix_format(pixfmt, &format.format); - - /* Limit to Atmel ISC hardware capabilities */ - if (pixfmt->width > isc->max_width) - pixfmt->width = isc->max_width; - if (pixfmt->height > isc->max_height) - pixfmt->height = isc->max_height; + isc_try_configure_rlp_dma(isc, false); + /* Limit to Microchip ISC hardware capabilities */ + v4l_bound_align_image(&pixfmt->width, 16, isc->max_width, 0, + &pixfmt->height, 16, isc->max_height, 0, 0); + /* If we did not find the requested format, we will fallback here */ + pixfmt->pixelformat = isc->try_config.fourcc; + pixfmt->colorspace = V4L2_COLORSPACE_SRGB; pixfmt->field = V4L2_FIELD_NONE; + pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3; pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) * pixfmt->height; - if (code) - *code = mbus_code; + isc->try_fmt = *f; return 0; +} -isc_try_fmt_err: - v4l2_err(&isc->v4l2_dev, "Could not find any possible format for a working pipeline\n"); -isc_try_fmt_subdev_err: - memset(&isc->try_config, 0, sizeof(isc->try_config)); +static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f) +{ + isc_try_fmt(isc, f); - return ret; + /* make the try configuration active */ + isc->config = isc->try_config; + isc->fmt = isc->try_fmt; + + v4l2_dbg(1, debug, &isc->v4l2_dev, "ISC set_fmt to %.4s @%dx%d\n", + (char *)&f->fmt.pix.pixelformat, + f->fmt.pix.width, f->fmt.pix.height); + + return 0; } -static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f) +static int isc_validate(struct isc_device *isc) { + int ret; + int i; + struct isc_format *sd_fmt = NULL; + struct v4l2_pix_format *pixfmt = &isc->fmt.fmt.pix; struct v4l2_subdev_format format = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = isc->remote_pad, + }; + struct v4l2_subdev_pad_config pad_cfg = {}; + struct v4l2_subdev_state pad_state = { + .pads = &pad_cfg, }; - u32 mbus_code = 0; - int ret; - ret = isc_try_fmt(isc, f, &mbus_code); + /* Get current format from subdev */ + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, get_fmt, NULL, + &format); if (ret) return ret; - v4l2_fill_mbus_format(&format.format, &f->fmt.pix, mbus_code); - ret = v4l2_subdev_call(isc->current_subdev->sd, pad, - set_fmt, NULL, &format); - if (ret < 0) - return ret; + /* Identify the subdev's format configuration */ + for (i = 0; i < isc->formats_list_size; i++) + if (isc->formats_list[i].mbus_code == format.format.code) { + sd_fmt = &isc->formats_list[i]; + break; + } - /* Limit to Atmel ISC hardware capabilities */ - if (f->fmt.pix.width > isc->max_width) - f->fmt.pix.width = isc->max_width; - if (f->fmt.pix.height > isc->max_height) - f->fmt.pix.height = isc->max_height; + /* Check if the format is not supported */ + if (!sd_fmt) { + v4l2_err(&isc->v4l2_dev, + "Current subdevice is streaming a media bus code that is not supported 0x%x\n", + format.format.code); + return -EPIPE; + } + + /* At this moment we know which format the subdev will use */ + isc->try_config.sd_format = sd_fmt; - isc->fmt = *f; + /* If the sensor is not RAW, we can only do a direct dump */ + if (!ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) + isc_try_configure_rlp_dma(isc, true); + + /* Limit to Microchip ISC hardware capabilities */ + v4l_bound_align_image(&format.format.width, 16, isc->max_width, 0, + &format.format.height, 16, isc->max_height, 0, 0); + + /* Check if the frame size is the same. Otherwise we may overflow */ + if (pixfmt->height != format.format.height || + pixfmt->width != format.format.width) { + v4l2_err(&isc->v4l2_dev, + "ISC not configured with the proper frame size: %dx%d\n", + format.format.width, format.format.height); + return -EPIPE; + } + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "Identified subdev using format %.4s with %dx%d %d bpp\n", + (char *)&sd_fmt->fourcc, pixfmt->width, pixfmt->height, + isc->try_config.bpp); + /* Reset and restart AWB if the subdevice changed the format */ if (isc->try_config.sd_format && isc->config.sd_format && isc->try_config.sd_format != isc->config.sd_format) { isc->ctrls.hist_stat = HIST_INIT; isc_reset_awb_ctrls(isc); isc_update_v4l2_ctrls(isc); } - /* make the try configuration active */ + + /* Validate formats */ + ret = isc_try_validate_formats(isc); + if (ret) + return ret; + + /* Obtain frame sizes if possible to have crop requirements ready */ + isc_try_fse(isc, &pad_state); + + /* Configure ISC pipeline for the config */ + ret = isc_try_configure_pipeline(isc); + if (ret) + return ret; + isc->config = isc->try_config; v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n"); @@ -1034,7 +1033,7 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f) } static int isc_s_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *f) + struct v4l2_format *f) { struct isc_device *isc = video_drvdata(file); @@ -1045,15 +1044,15 @@ static int isc_s_fmt_vid_cap(struct file *file, void *priv, } static int isc_try_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *f) + struct v4l2_format *f) { struct isc_device *isc = video_drvdata(file); - return isc_try_fmt(isc, f, NULL); + return isc_try_fmt(isc, f); } static int isc_enum_input(struct file *file, void *priv, - struct v4l2_input *inp) + struct v4l2_input *inp) { if (inp->index != 0) return -EINVAL; @@ -1104,10 +1103,6 @@ static int isc_enum_framesizes(struct file *file, void *fh, if (fsize->index) return -EINVAL; - for (i = 0; i < isc->num_user_formats; i++) - if (isc->user_formats[i]->fourcc == fsize->pixel_format) - ret = 0; - for (i = 0; i < isc->controller_formats_size; i++) if (isc->controller_formats[i].fourcc == fsize->pixel_format) ret = 0; @@ -1221,7 +1216,7 @@ static const struct v4l2_file_operations isc_fops = { .poll = vb2_fop_poll, }; -irqreturn_t isc_interrupt(int irq, void *dev_id) +irqreturn_t microchip_isc_interrupt(int irq, void *dev_id) { struct isc_device *isc = (struct isc_device *)dev_id; struct regmap *regmap = isc->regmap; @@ -1247,7 +1242,7 @@ irqreturn_t isc_interrupt(int irq, void *dev_id) if (!list_empty(&isc->dma_queue) && !isc->stop) { isc->cur_frm = list_first_entry(&isc->dma_queue, - struct isc_buffer, list); + struct isc_buffer, list); list_del(&isc->cur_frm->list); isc_start_dma(isc); @@ -1267,7 +1262,7 @@ irqreturn_t isc_interrupt(int irq, void *dev_id) return ret; } -EXPORT_SYMBOL_GPL(isc_interrupt); +EXPORT_SYMBOL_GPL(microchip_isc_interrupt); static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max) { @@ -1725,13 +1720,14 @@ static int isc_ctrl_init(struct isc_device *isc) } static int isc_async_bound(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *subdev, - struct v4l2_async_subdev *asd) + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) { struct isc_device *isc = container_of(notifier->v4l2_dev, struct isc_device, v4l2_dev); struct isc_subdev_entity *subdev_entity = container_of(notifier, struct isc_subdev_entity, notifier); + int pad; if (video_is_registered(&isc->video_dev)) { v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n"); @@ -1740,12 +1736,22 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier, subdev_entity->sd = subdev; + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) { + v4l2_err(&isc->v4l2_dev, "failed to find pad for %s\n", + subdev->name); + return pad; + } + + isc->remote_pad = pad; + return 0; } static void isc_async_unbind(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *subdev, - struct v4l2_async_subdev *asd) + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) { struct isc_device *isc = container_of(notifier->v4l2_dev, struct isc_device, v4l2_dev); @@ -1755,8 +1761,8 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier, v4l2_ctrl_handler_free(&isc->ctrls.handler); } -static struct isc_format *find_format_by_code(struct isc_device *isc, - unsigned int code, int *index) +struct isc_format *isc_find_format_by_code(struct isc_device *isc, + unsigned int code, int *index) { struct isc_format *fmt = &isc->formats_list[0]; unsigned int i; @@ -1772,52 +1778,7 @@ static struct isc_format *find_format_by_code(struct isc_device *isc, return NULL; } - -static int isc_formats_init(struct isc_device *isc) -{ - struct isc_format *fmt; - struct v4l2_subdev *subdev = isc->current_subdev->sd; - unsigned int num_fmts, i, j; - u32 list_size = isc->formats_list_size; - struct v4l2_subdev_mbus_code_enum mbus_code = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - - num_fmts = 0; - while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, - NULL, &mbus_code)) { - mbus_code.index++; - - fmt = find_format_by_code(isc, mbus_code.code, &i); - if (!fmt) { - v4l2_warn(&isc->v4l2_dev, "Mbus code %x not supported\n", - mbus_code.code); - continue; - } - - fmt->sd_support = true; - num_fmts++; - } - - if (!num_fmts) - return -ENXIO; - - isc->num_user_formats = num_fmts; - isc->user_formats = devm_kcalloc(isc->dev, - num_fmts, sizeof(*isc->user_formats), - GFP_KERNEL); - if (!isc->user_formats) - return -ENOMEM; - - fmt = &isc->formats_list[0]; - for (i = 0, j = 0; i < list_size; i++) { - if (fmt->sd_support) - isc->user_formats[j++] = fmt; - fmt++; - } - - return 0; -} +EXPORT_SYMBOL_GPL(isc_find_format_by_code); static int isc_set_default_fmt(struct isc_device *isc) { @@ -1827,12 +1788,12 @@ static int isc_set_default_fmt(struct isc_device *isc) .width = VGA_WIDTH, .height = VGA_HEIGHT, .field = V4L2_FIELD_NONE, - .pixelformat = isc->user_formats[0]->fourcc, + .pixelformat = isc->controller_formats[0].fourcc, }, }; int ret; - ret = isc_try_fmt(isc, &f, NULL); + ret = isc_try_fmt(isc, &f); if (ret) return ret; @@ -1887,13 +1848,6 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) spin_lock_init(&isc->dma_queue_lock); spin_lock_init(&isc->awb_lock); - ret = isc_formats_init(isc); - if (ret < 0) { - v4l2_err(&isc->v4l2_dev, - "Init format failed: %d\n", ret); - goto isc_async_complete_err; - } - ret = isc_set_default_fmt(isc); if (ret) { v4l2_err(&isc->v4l2_dev, "Could not set default format\n"); @@ -1916,7 +1870,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) vdev->queue = q; vdev->lock = &isc->lock; vdev->ctrl_handler = &isc->ctrls.handler; - vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_IO_MC; video_set_drvdata(vdev, isc); ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); @@ -1926,22 +1881,33 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) goto isc_async_complete_err; } + ret = isc_scaler_link(isc); + if (ret < 0) + goto isc_async_complete_unregister_device; + + ret = media_device_register(&isc->mdev); + if (ret < 0) + goto isc_async_complete_unregister_device; + return 0; +isc_async_complete_unregister_device: + video_unregister_device(vdev); + isc_async_complete_err: mutex_destroy(&isc->awb_mutex); mutex_destroy(&isc->lock); return ret; } -const struct v4l2_async_notifier_operations isc_async_ops = { +const struct v4l2_async_notifier_operations microchip_isc_async_ops = { .bound = isc_async_bound, .unbind = isc_async_unbind, .complete = isc_async_complete, }; -EXPORT_SYMBOL_GPL(isc_async_ops); +EXPORT_SYMBOL_GPL(microchip_isc_async_ops); -void isc_subdev_cleanup(struct isc_device *isc) +void microchip_isc_subdev_cleanup(struct isc_device *isc) { struct isc_subdev_entity *subdev_entity; @@ -1952,9 +1918,9 @@ void isc_subdev_cleanup(struct isc_device *isc) INIT_LIST_HEAD(&isc->subdev_entities); } -EXPORT_SYMBOL_GPL(isc_subdev_cleanup); +EXPORT_SYMBOL_GPL(microchip_isc_subdev_cleanup); -int isc_pipeline_init(struct isc_device *isc) +int microchip_isc_pipeline_init(struct isc_device *isc) { struct device *dev = isc->dev; struct regmap *regmap = isc->regmap; @@ -1993,19 +1959,82 @@ int isc_pipeline_init(struct isc_device *isc) return 0; } -EXPORT_SYMBOL_GPL(isc_pipeline_init); +EXPORT_SYMBOL_GPL(microchip_isc_pipeline_init); + +static int isc_link_validate(struct media_link *link) +{ + struct video_device *vdev = + media_entity_to_video_device(link->sink->entity); + struct isc_device *isc = video_get_drvdata(vdev); + int ret; + + ret = v4l2_subdev_link_validate(link); + if (ret) + return ret; + + return isc_validate(isc); +} + +static const struct media_entity_operations isc_entity_operations = { + .link_validate = isc_link_validate, +}; + +int isc_mc_init(struct isc_device *isc, u32 ver) +{ + const struct of_device_id *match; + int ret; + + isc->video_dev.entity.function = MEDIA_ENT_F_IO_V4L; + isc->video_dev.entity.flags = MEDIA_ENT_FL_DEFAULT; + isc->video_dev.entity.ops = &isc_entity_operations; + + isc->pads[ISC_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + + ret = media_entity_pads_init(&isc->video_dev.entity, ISC_PADS_NUM, + isc->pads); + if (ret < 0) { + dev_err(isc->dev, "media entity init failed\n"); + return ret; + } + + isc->mdev.dev = isc->dev; + + match = of_match_node(isc->dev->driver->of_match_table, + isc->dev->of_node); + + strscpy(isc->mdev.driver_name, KBUILD_MODNAME, + sizeof(isc->mdev.driver_name)); + strscpy(isc->mdev.model, match->compatible, sizeof(isc->mdev.model)); + snprintf(isc->mdev.bus_info, sizeof(isc->mdev.bus_info), "platform:%s", + isc->v4l2_dev.name); + isc->mdev.hw_revision = ver; + + media_device_init(&isc->mdev); + + isc->v4l2_dev.mdev = &isc->mdev; + + return isc_scaler_init(isc); +} +EXPORT_SYMBOL_GPL(isc_mc_init); + +void isc_mc_cleanup(struct isc_device *isc) +{ + media_entity_cleanup(&isc->video_dev.entity); + media_device_cleanup(&isc->mdev); +} +EXPORT_SYMBOL_GPL(isc_mc_cleanup); /* regmap configuration */ -#define ATMEL_ISC_REG_MAX 0xd5c -const struct regmap_config isc_regmap_config = { +#define MICROCHIP_ISC_REG_MAX 0xd5c +const struct regmap_config microchip_isc_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .max_register = ATMEL_ISC_REG_MAX, + .max_register = MICROCHIP_ISC_REG_MAX, }; -EXPORT_SYMBOL_GPL(isc_regmap_config); +EXPORT_SYMBOL_GPL(microchip_isc_regmap_config); MODULE_AUTHOR("Songjun Wu"); MODULE_AUTHOR("Eugen Hristev"); -MODULE_DESCRIPTION("Atmel ISC common code base"); +MODULE_DESCRIPTION("Microchip ISC common code base"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/atmel/atmel-isc-clk.c b/drivers/media/platform/microchip/microchip-isc-clk.c index 2059fe376b00..24358d804e75 100644 --- a/drivers/media/platform/atmel/atmel-isc-clk.c +++ b/drivers/media/platform/microchip/microchip-isc-clk.c @@ -14,8 +14,8 @@ #include <linux/pm_runtime.h> #include <linux/regmap.h> -#include "atmel-isc-regs.h" -#include "atmel-isc.h" +#include "microchip-isc-regs.h" +#include "microchip-isc.h" static int isc_wait_clk_stable(struct clk_hw *hw) { @@ -277,7 +277,7 @@ static int isc_clk_register(struct isc_device *isc, unsigned int id) return 0; } -int isc_clk_init(struct isc_device *isc) +int microchip_isc_clk_init(struct isc_device *isc) { unsigned int i; int ret; @@ -293,9 +293,9 @@ int isc_clk_init(struct isc_device *isc) return 0; } -EXPORT_SYMBOL_GPL(isc_clk_init); +EXPORT_SYMBOL_GPL(microchip_isc_clk_init); -void isc_clk_cleanup(struct isc_device *isc) +void microchip_isc_clk_cleanup(struct isc_device *isc) { unsigned int i; @@ -308,4 +308,4 @@ void isc_clk_cleanup(struct isc_device *isc) clk_unregister(isc_clk->clk); } } -EXPORT_SYMBOL_GPL(isc_clk_cleanup); +EXPORT_SYMBOL_GPL(microchip_isc_clk_cleanup); diff --git a/drivers/media/platform/atmel/atmel-isc-regs.h b/drivers/media/platform/microchip/microchip-isc-regs.h index d06b72228d4f..e77e1d9a1db8 100644 --- a/drivers/media/platform/atmel/atmel-isc-regs.h +++ b/drivers/media/platform/microchip/microchip-isc-regs.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __ATMEL_ISC_REGS_H -#define __ATMEL_ISC_REGS_H +#ifndef __MICROCHIP_ISC_REGS_H +#define __MICROCHIP_ISC_REGS_H #include <linux/bitops.h> @@ -71,10 +71,10 @@ /* ISC Clock Configuration Register */ #define ISC_CLKCFG 0x00000024 -#define ISC_CLKCFG_DIV_SHIFT(n) ((n)*16) -#define ISC_CLKCFG_DIV_MASK(n) GENMASK(((n)*16 + 7), (n)*16) -#define ISC_CLKCFG_SEL_SHIFT(n) ((n)*16 + 8) -#define ISC_CLKCFG_SEL_MASK(n) GENMASK(((n)*17 + 8), ((n)*16 + 8)) +#define ISC_CLKCFG_DIV_SHIFT(n) ((n) * 16) +#define ISC_CLKCFG_DIV_MASK(n) GENMASK(((n) * 16 + 7), (n) * 16) +#define ISC_CLKCFG_SEL_SHIFT(n) ((n) * 16 + 8) +#define ISC_CLKCFG_SEL_MASK(n) GENMASK(((n) * 17 + 8), ((n) * 16 + 8)) /* ISC Interrupt Enable Register */ #define ISC_INTEN 0x00000028 diff --git a/drivers/media/platform/microchip/microchip-isc-scaler.c b/drivers/media/platform/microchip/microchip-isc-scaler.c new file mode 100644 index 000000000000..0f29a32d15ce --- /dev/null +++ b/drivers/media/platform/microchip/microchip-isc-scaler.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microchip Image Sensor Controller (ISC) Scaler entity support + * + * Copyright (C) 2022 Microchip Technology, Inc. + * + * Author: Eugen Hristev <eugen.hristev@microchip.com> + * + */ + +#include <media/media-device.h> +#include <media/media-entity.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "microchip-isc-regs.h" +#include "microchip-isc.h" + +static void isc_scaler_prepare_fmt(struct v4l2_mbus_framefmt *framefmt) +{ + framefmt->colorspace = V4L2_COLORSPACE_SRGB; + framefmt->field = V4L2_FIELD_NONE; + framefmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + framefmt->quantization = V4L2_QUANTIZATION_DEFAULT; + framefmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +}; + +static int isc_scaler_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + struct v4l2_mbus_framefmt *v4l2_try_fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state, + format->pad); + format->format = *v4l2_try_fmt; + + return 0; + } + + format->format = isc->scaler_format[format->pad]; + + return 0; +} + +static int isc_scaler_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *req_fmt) +{ + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + struct v4l2_mbus_framefmt *v4l2_try_fmt; + struct isc_format *fmt; + unsigned int i; + + /* Source format is fixed, we cannot change it */ + if (req_fmt->pad == ISC_SCALER_PAD_SOURCE) { + req_fmt->format = isc->scaler_format[ISC_SCALER_PAD_SOURCE]; + return 0; + } + + /* There is no limit on the frame size on the sink pad */ + v4l_bound_align_image(&req_fmt->format.width, 16, UINT_MAX, 0, + &req_fmt->format.height, 16, UINT_MAX, 0, 0); + + isc_scaler_prepare_fmt(&req_fmt->format); + + fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i); + + if (!fmt) + fmt = &isc->formats_list[0]; + + req_fmt->format.code = fmt->mbus_code; + + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state, + req_fmt->pad); + *v4l2_try_fmt = req_fmt->format; + /* Trying on the sink pad makes the source pad change too */ + v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state, + ISC_SCALER_PAD_SOURCE); + *v4l2_try_fmt = req_fmt->format; + + v4l_bound_align_image(&v4l2_try_fmt->width, + 16, isc->max_width, 0, + &v4l2_try_fmt->height, + 16, isc->max_height, 0, 0); + /* if we are just trying, we are done */ + return 0; + } + + isc->scaler_format[ISC_SCALER_PAD_SINK] = req_fmt->format; + + /* The source pad is the same as the sink, but we have to crop it */ + isc->scaler_format[ISC_SCALER_PAD_SOURCE] = + isc->scaler_format[ISC_SCALER_PAD_SINK]; + v4l_bound_align_image + (&isc->scaler_format[ISC_SCALER_PAD_SOURCE].width, 16, + isc->max_width, 0, + &isc->scaler_format[ISC_SCALER_PAD_SOURCE].height, 16, + isc->max_height, 0, 0); + + return 0; +} + +static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + + /* + * All formats supported by the ISC are supported by the scaler. + * Advertise the formats which the ISC can take as input, as the scaler + * entity cropping is part of the PFE module (parallel front end) + */ + if (code->index < isc->formats_list_size) { + code->code = isc->formats_list[code->index].mbus_code; + return 0; + } + + return -EINVAL; +} + +static int isc_scaler_g_sel(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + + if (sel->pad == ISC_SCALER_PAD_SOURCE) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS && + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + sel->r.height = isc->scaler_format[ISC_SCALER_PAD_SOURCE].height; + sel->r.width = isc->scaler_format[ISC_SCALER_PAD_SOURCE].width; + + sel->r.left = 0; + sel->r.top = 0; + + return 0; +} + +static int isc_scaler_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *v4l2_try_fmt = + v4l2_subdev_get_try_format(sd, sd_state, 0); + struct v4l2_rect *try_crop; + struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); + + *v4l2_try_fmt = isc->scaler_format[ISC_SCALER_PAD_SOURCE]; + + try_crop = v4l2_subdev_get_try_crop(sd, sd_state, 0); + + try_crop->top = 0; + try_crop->left = 0; + try_crop->width = v4l2_try_fmt->width; + try_crop->height = v4l2_try_fmt->height; + + return 0; +} + +static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = { + .enum_mbus_code = isc_scaler_enum_mbus_code, + .set_fmt = isc_scaler_set_fmt, + .get_fmt = isc_scaler_get_fmt, + .get_selection = isc_scaler_g_sel, + .init_cfg = isc_scaler_init_cfg, +}; + +static const struct media_entity_operations isc_scaler_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = { + .pad = &isc_scaler_pad_ops, +}; + +int isc_scaler_init(struct isc_device *isc) +{ + int ret; + + v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops); + + isc->scaler_sd.owner = THIS_MODULE; + isc->scaler_sd.dev = isc->dev; + snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name), + "microchip_isc_scaler"); + + isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + isc->scaler_sd.entity.ops = &isc_scaler_entity_ops; + isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + isc_scaler_prepare_fmt(&isc->scaler_format[ISC_SCALER_PAD_SOURCE]); + isc->scaler_format[ISC_SCALER_PAD_SOURCE].height = isc->max_height; + isc->scaler_format[ISC_SCALER_PAD_SOURCE].width = isc->max_width; + isc->scaler_format[ISC_SCALER_PAD_SOURCE].code = + isc->formats_list[0].mbus_code; + + isc->scaler_format[ISC_SCALER_PAD_SINK] = + isc->scaler_format[ISC_SCALER_PAD_SOURCE]; + + ret = media_entity_pads_init(&isc->scaler_sd.entity, + ISC_SCALER_PADS_NUM, + isc->scaler_pads); + if (ret < 0) { + dev_err(isc->dev, "scaler sd media entity init failed\n"); + return ret; + } + + ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd); + if (ret < 0) { + dev_err(isc->dev, "scaler sd failed to register subdev\n"); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(isc_scaler_init); + +int isc_scaler_link(struct isc_device *isc) +{ + int ret; + + ret = media_create_pad_link(&isc->current_subdev->sd->entity, + isc->remote_pad, &isc->scaler_sd.entity, + ISC_SCALER_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + + if (ret < 0) { + dev_err(isc->dev, "Failed to create pad link: %s to %s\n", + isc->current_subdev->sd->entity.name, + isc->scaler_sd.entity.name); + return ret; + } + + dev_dbg(isc->dev, "link with %s pad: %d\n", + isc->current_subdev->sd->name, isc->remote_pad); + + ret = media_create_pad_link(&isc->scaler_sd.entity, + ISC_SCALER_PAD_SOURCE, + &isc->video_dev.entity, ISC_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + + if (ret < 0) { + dev_err(isc->dev, "Failed to create pad link: %s to %s\n", + isc->scaler_sd.entity.name, + isc->video_dev.entity.name); + return ret; + } + + dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name, + ISC_SCALER_PAD_SOURCE); + + return ret; +} +EXPORT_SYMBOL_GPL(isc_scaler_link); + diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/microchip/microchip-isc.h index ff60ba020cb9..e3a6c7367e70 100644 --- a/drivers/media/platform/atmel/atmel-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -8,7 +8,7 @@ * Author: Eugen Hristev <eugen.hristev@microchip.com> * */ -#ifndef _ATMEL_ISC_H_ +#ifndef _MICROCHIP_ISC_H_ #include <linux/clk-provider.h> #include <linux/platform_device.h> @@ -63,15 +63,16 @@ struct isc_subdev_entity { * @cfa_baycfg: If this format is RAW BAYER, indicate the type of bayer. this is either BGBG, RGRG, etc. * @pfe_cfg0_bps: Number of hardware data lines connected to the ISC + * @raw: If the format is raw bayer. */ struct isc_format { u32 fourcc; u32 mbus_code; u32 cfa_baycfg; - - bool sd_support; u32 pfe_cfg0_bps; + + bool raw; }; /* Pipeline bitmap */ @@ -183,6 +184,17 @@ struct isc_reg_offsets { u32 his_entry; }; +enum isc_mc_pads { + ISC_PAD_SINK = 0, + ISC_PADS_NUM = 1, +}; + +enum isc_scaler_pads { + ISC_SCALER_PAD_SINK = 0, + ISC_SCALER_PAD_SOURCE = 1, + ISC_SCALER_PADS_NUM = 2, +}; + /* * struct isc_device - ISC device driver data/config struct * @regmap: Register map @@ -205,8 +217,7 @@ struct isc_reg_offsets { * @comp: completion reference that signals frame completion * * @fmt: current v42l format - * @user_formats: list of formats that are supported and agreed with sd - * @num_user_formats: how many formats are in user_formats + * @try_fmt: current v4l2 try format * * @config: current ISC format configuration * @try_config: the current ISC try format , not yet activated @@ -259,6 +270,13 @@ struct isc_reg_offsets { * be used as an input to the controller * @controller_formats_size: size of controller_formats array * @formats_list_size: size of formats_list array + * @pads: media controller pads for isc video entity + * @mdev: media device that is registered by the isc + * @mpipe: media device pipeline used by the isc + * @remote_pad: remote pad on the connected subdevice + * @scaler_sd: subdevice for the scaler that isc registers + * @scaler_pads: media controller pads for the scaler subdevice + * @scaler_format: current format for the scaler subdevice */ struct isc_device { struct regmap *regmap; @@ -281,8 +299,7 @@ struct isc_device { struct completion comp; struct v4l2_format fmt; - struct isc_format **user_formats; - unsigned int num_user_formats; + struct v4l2_format try_fmt; struct fmt_config config; struct fmt_config try_config; @@ -348,15 +365,36 @@ struct isc_device { struct isc_format *formats_list; u32 controller_formats_size; u32 formats_list_size; + + struct { + struct media_pad pads[ISC_PADS_NUM]; + struct media_device mdev; + struct media_pipeline mpipe; + + u32 remote_pad; + }; + + struct { + struct v4l2_subdev scaler_sd; + struct media_pad scaler_pads[ISC_SCALER_PADS_NUM]; + struct v4l2_mbus_framefmt scaler_format[ISC_SCALER_PADS_NUM]; + }; }; -extern const struct regmap_config isc_regmap_config; -extern const struct v4l2_async_notifier_operations isc_async_ops; +extern const struct regmap_config microchip_isc_regmap_config; +extern const struct v4l2_async_notifier_operations microchip_isc_async_ops; + +irqreturn_t microchip_isc_interrupt(int irq, void *dev_id); +int microchip_isc_pipeline_init(struct isc_device *isc); +int microchip_isc_clk_init(struct isc_device *isc); +void microchip_isc_subdev_cleanup(struct isc_device *isc); +void microchip_isc_clk_cleanup(struct isc_device *isc); -irqreturn_t isc_interrupt(int irq, void *dev_id); -int isc_pipeline_init(struct isc_device *isc); -int isc_clk_init(struct isc_device *isc); -void isc_subdev_cleanup(struct isc_device *isc); -void isc_clk_cleanup(struct isc_device *isc); +int isc_scaler_link(struct isc_device *isc); +int isc_scaler_init(struct isc_device *isc); +int isc_mc_init(struct isc_device *isc, u32 ver); +void isc_mc_cleanup(struct isc_device *isc); +struct isc_format *isc_find_format_by_code(struct isc_device *isc, + unsigned int code, int *index); #endif diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c index 9881d89a645b..ac4715d91de6 100644 --- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -46,8 +46,8 @@ #include <media/v4l2-subdev.h> #include <media/videobuf2-dma-contig.h> -#include "atmel-isc-regs.h" -#include "atmel-isc.h" +#include "microchip-isc-regs.h" +#include "microchip-isc.h" #define ISC_SAMA5D2_MAX_SUPPORT_WIDTH 2592 #define ISC_SAMA5D2_MAX_SUPPORT_HEIGHT 1944 @@ -80,20 +80,40 @@ static const struct isc_format sama5d2_controller_formats[] = { .fourcc = V4L2_PIX_FMT_Y10, }, { .fourcc = V4L2_PIX_FMT_SBGGR8, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SGBRG8, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SGRBG8, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SRGGB8, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SBGGR10, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SGBRG10, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SGRBG10, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SRGGB10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .raw = true, }, }; @@ -385,7 +405,7 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) return ret; } -static int atmel_isc_probe(struct platform_device *pdev) +static int microchip_isc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct isc_device *isc; @@ -408,7 +428,7 @@ static int atmel_isc_probe(struct platform_device *pdev) if (IS_ERR(io_base)) return PTR_ERR(io_base); - isc->regmap = devm_regmap_init_mmio(dev, io_base, &isc_regmap_config); + isc->regmap = devm_regmap_init_mmio(dev, io_base, µchip_isc_regmap_config); if (IS_ERR(isc->regmap)) { ret = PTR_ERR(isc->regmap); dev_err(dev, "failed to init register map: %d\n", ret); @@ -419,8 +439,8 @@ static int atmel_isc_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = devm_request_irq(dev, irq, isc_interrupt, 0, - "atmel-sama5d2-isc", isc); + ret = devm_request_irq(dev, irq, microchip_isc_interrupt, 0, + "microchip-sama5d2-isc", isc); if (ret < 0) { dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", irq, ret); @@ -464,7 +484,7 @@ static int atmel_isc_probe(struct platform_device *pdev) /* sama5d2-isc : ISPCK is required and mandatory */ isc->ispck_required = true; - ret = isc_pipeline_init(isc); + ret = microchip_isc_pipeline_init(isc); if (ret) return ret; @@ -481,7 +501,7 @@ static int atmel_isc_probe(struct platform_device *pdev) return ret; } - ret = isc_clk_init(isc); + ret = microchip_isc_clk_init(isc); if (ret) { dev_err(dev, "failed to init isc clock: %d\n", ret); goto unprepare_hclk; @@ -523,7 +543,7 @@ static int atmel_isc_probe(struct platform_device *pdev) goto cleanup_subdev; } - subdev_entity->notifier.ops = &isc_async_ops; + subdev_entity->notifier.ops = µchip_isc_async_ops; ret = v4l2_async_nf_register(&isc->v4l2_dev, &subdev_entity->notifier); @@ -536,6 +556,12 @@ static int atmel_isc_probe(struct platform_device *pdev) break; } + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver); + + ret = isc_mc_init(isc, ver); + if (ret < 0) + goto isc_probe_mc_init_err; + pm_runtime_set_active(dev); pm_runtime_enable(dev); pm_request_idle(dev); @@ -555,7 +581,6 @@ static int atmel_isc_probe(struct platform_device *pdev) goto unprepare_clk; } - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver); dev_info(dev, "Microchip ISC version %x\n", ver); return 0; @@ -566,8 +591,11 @@ unprepare_clk: disable_pm: pm_runtime_disable(dev); +isc_probe_mc_init_err: + isc_mc_cleanup(isc); + cleanup_subdev: - isc_subdev_cleanup(isc); + microchip_isc_subdev_cleanup(isc); unregister_v4l2_device: v4l2_device_unregister(&isc->v4l2_dev); @@ -575,25 +603,27 @@ unregister_v4l2_device: unprepare_hclk: clk_disable_unprepare(isc->hclock); - isc_clk_cleanup(isc); + microchip_isc_clk_cleanup(isc); return ret; } -static int atmel_isc_remove(struct platform_device *pdev) +static int microchip_isc_remove(struct platform_device *pdev) { struct isc_device *isc = platform_get_drvdata(pdev); pm_runtime_disable(&pdev->dev); - isc_subdev_cleanup(isc); + isc_mc_cleanup(isc); + + microchip_isc_subdev_cleanup(isc); v4l2_device_unregister(&isc->v4l2_dev); clk_disable_unprepare(isc->ispck); clk_disable_unprepare(isc->hclock); - isc_clk_cleanup(isc); + microchip_isc_clk_cleanup(isc); return 0; } @@ -624,30 +654,30 @@ static int __maybe_unused isc_runtime_resume(struct device *dev) return ret; } -static const struct dev_pm_ops atmel_isc_dev_pm_ops = { +static const struct dev_pm_ops microchip_isc_dev_pm_ops = { SET_RUNTIME_PM_OPS(isc_runtime_suspend, isc_runtime_resume, NULL) }; #if IS_ENABLED(CONFIG_OF) -static const struct of_device_id atmel_isc_of_match[] = { +static const struct of_device_id microchip_isc_of_match[] = { { .compatible = "atmel,sama5d2-isc" }, { } }; -MODULE_DEVICE_TABLE(of, atmel_isc_of_match); +MODULE_DEVICE_TABLE(of, microchip_isc_of_match); #endif -static struct platform_driver atmel_isc_driver = { - .probe = atmel_isc_probe, - .remove = atmel_isc_remove, +static struct platform_driver microchip_isc_driver = { + .probe = microchip_isc_probe, + .remove = microchip_isc_remove, .driver = { - .name = "atmel-sama5d2-isc", - .pm = &atmel_isc_dev_pm_ops, - .of_match_table = of_match_ptr(atmel_isc_of_match), + .name = "microchip-sama5d2-isc", + .pm = µchip_isc_dev_pm_ops, + .of_match_table = of_match_ptr(microchip_isc_of_match), }, }; -module_platform_driver(atmel_isc_driver); +module_platform_driver(microchip_isc_driver); MODULE_AUTHOR("Songjun Wu"); -MODULE_DESCRIPTION("The V4L2 driver for Atmel-ISC"); +MODULE_DESCRIPTION("The V4L2 driver for Microchip-ISC"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index 8b11aa8340d7..d583eafe5cc1 100644 --- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -49,8 +49,8 @@ #include <media/v4l2-subdev.h> #include <media/videobuf2-dma-contig.h> -#include "atmel-isc-regs.h" -#include "atmel-isc.h" +#include "microchip-isc-regs.h" +#include "microchip-isc.h" #define ISC_SAMA7G5_MAX_SUPPORT_WIDTH 3264 #define ISC_SAMA7G5_MAX_SUPPORT_HEIGHT 2464 @@ -89,20 +89,40 @@ static const struct isc_format sama7g5_controller_formats[] = { .fourcc = V4L2_PIX_FMT_Y16, }, { .fourcc = V4L2_PIX_FMT_SBGGR8, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SGBRG8, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SGRBG8, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SRGGB8, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SBGGR10, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SGBRG10, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SGRBG10, + .raw = true, }, { .fourcc = V4L2_PIX_FMT_SRGGB10, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .raw = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .raw = true, }, }; @@ -397,7 +417,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) if (IS_ERR(io_base)) return PTR_ERR(io_base); - isc->regmap = devm_regmap_init_mmio(dev, io_base, &isc_regmap_config); + isc->regmap = devm_regmap_init_mmio(dev, io_base, µchip_isc_regmap_config); if (IS_ERR(isc->regmap)) { ret = PTR_ERR(isc->regmap); dev_err(dev, "failed to init register map: %d\n", ret); @@ -408,7 +428,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = devm_request_irq(dev, irq, isc_interrupt, 0, + ret = devm_request_irq(dev, irq, microchip_isc_interrupt, 0, "microchip-sama7g5-xisc", isc); if (ret < 0) { dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", @@ -453,7 +473,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) /* sama7g5-isc : ISPCK does not exist, ISC is clocked by MCK */ isc->ispck_required = false; - ret = isc_pipeline_init(isc); + ret = microchip_isc_pipeline_init(isc); if (ret) return ret; @@ -470,7 +490,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) return ret; } - ret = isc_clk_init(isc); + ret = microchip_isc_clk_init(isc); if (ret) { dev_err(dev, "failed to init isc clock: %d\n", ret); goto unprepare_hclk; @@ -513,7 +533,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) goto cleanup_subdev; } - subdev_entity->notifier.ops = &isc_async_ops; + subdev_entity->notifier.ops = µchip_isc_async_ops; ret = v4l2_async_nf_register(&isc->v4l2_dev, &subdev_entity->notifier); @@ -526,17 +546,25 @@ static int microchip_xisc_probe(struct platform_device *pdev) break; } + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver); + + ret = isc_mc_init(isc, ver); + if (ret < 0) + goto isc_probe_mc_init_err; + pm_runtime_set_active(dev); pm_runtime_enable(dev); pm_request_idle(dev); - regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver); dev_info(dev, "Microchip XISC version %x\n", ver); return 0; +isc_probe_mc_init_err: + isc_mc_cleanup(isc); + cleanup_subdev: - isc_subdev_cleanup(isc); + microchip_isc_subdev_cleanup(isc); unregister_v4l2_device: v4l2_device_unregister(&isc->v4l2_dev); @@ -544,7 +572,7 @@ unregister_v4l2_device: unprepare_hclk: clk_disable_unprepare(isc->hclock); - isc_clk_cleanup(isc); + microchip_isc_clk_cleanup(isc); return ret; } @@ -555,13 +583,15 @@ static int microchip_xisc_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); - isc_subdev_cleanup(isc); + isc_mc_cleanup(isc); + + microchip_isc_subdev_cleanup(isc); v4l2_device_unregister(&isc->v4l2_dev); clk_disable_unprepare(isc->hclock); - isc_clk_cleanup(isc); + microchip_isc_clk_cleanup(isc); return 0; } diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig index 5917634889b5..730f39971e1c 100644 --- a/drivers/media/platform/nxp/Kconfig +++ b/drivers/media/platform/nxp/Kconfig @@ -4,6 +4,19 @@ comment "NXP media platform drivers" +config VIDEO_IMX7_CSI + tristate "NXP CSI Bridge driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_DMA + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEOBUF2_DMA_CONTIG + select VIDEO_V4L2_SUBDEV_API + help + Driver for the NXP Camera Sensor Interface (CSI) Bridge. This device + is found in the i.MX6UL/L, i.MX7 and i.MX8M[MQ] SoCs. + config VIDEO_IMX_MIPI_CSIS tristate "NXP MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile index 81ab304ef31c..1a129b58d99e 100644 --- a/drivers/media/platform/nxp/Makefile +++ b/drivers/media/platform/nxp/Makefile @@ -3,6 +3,7 @@ obj-y += dw100/ obj-y += imx-jpeg/ +obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c index 9418fcf740a8..ef28122a5ed4 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c @@ -76,12 +76,14 @@ void print_wrapper_info(struct device *dev, void __iomem *reg) void mxc_jpeg_enable_irq(void __iomem *reg, int slot) { - writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); + writel(0xF0C, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); } void mxc_jpeg_disable_irq(void __iomem *reg, int slot) { writel(0x0, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); } void mxc_jpeg_sw_reset(void __iomem *reg) diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c index 32fd04a3d8bb..6cd015a35f7c 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -69,7 +69,8 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .fourcc = V4L2_PIX_FMT_JPEG, .subsampling = -1, .nc = -1, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .flags = MXC_JPEG_FMT_TYPE_ENC, }, { @@ -78,11 +79,13 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, .nc = 3, .depth = 24, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 3, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, .precision = 8, + .is_rgb = 1, }, { .name = "ABGR", /* ABGR packed format */ @@ -90,11 +93,13 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, .nc = 4, .depth = 32, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 3, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, .precision = 8, + .is_rgb = 1, }, { .name = "YUV420", /* 1st plane = Y, 2nd plane = UV */ @@ -102,7 +107,21 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, .nc = 3, .depth = 12, /* 6 bytes (4Y + UV) for 4 pixels */ - .colplanes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .mem_planes = 2, + .comp_planes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .h_align = 4, + .v_align = 4, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV420", /* 1st plane = Y, 2nd plane = UV */ + .fourcc = V4L2_PIX_FMT_NV12, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + .nc = 3, + .depth = 12, /* 6 bytes (4Y + UV) for 4 pixels */ + .mem_planes = 1, + .comp_planes = 2, /* 1 plane Y, 1 plane UV interleaved */ .h_align = 4, .v_align = 4, .flags = MXC_JPEG_FMT_TYPE_RAW, @@ -114,7 +133,8 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, .nc = 3, .depth = 16, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 4, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, @@ -126,7 +146,8 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, .nc = 3, .depth = 24, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 3, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, @@ -138,7 +159,8 @@ static const struct mxc_jpeg_fmt mxc_formats[] = { .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, .nc = 1, .depth = 8, - .colplanes = 1, + .mem_planes = 1, + .comp_planes = 1, .h_align = 3, .v_align = 3, .flags = MXC_JPEG_FMT_TYPE_RAW, @@ -330,6 +352,10 @@ static unsigned int debug; module_param(debug, int, 0644); MODULE_PARM_DESC(debug, "Debug level (0-3)"); +static unsigned int hw_timeout = 2000; +module_param(hw_timeout, int, 0644); +MODULE_PARM_DESC(hw_timeout, "MXC JPEG hw timeout, the number of milliseconds"); + static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, u32 precision); static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q); @@ -415,6 +441,7 @@ static enum mxc_jpeg_image_format mxc_jpeg_fourcc_to_imgfmt(u32 fourcc) return MXC_JPEG_GRAY; case V4L2_PIX_FMT_YUYV: return MXC_JPEG_YUV422; + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV12M: return MXC_JPEG_YUV420; case V4L2_PIX_FMT_YUV24: @@ -441,12 +468,17 @@ static void mxc_jpeg_addrs(struct mxc_jpeg_desc *desc, struct vb2_buffer *jpeg_buf, int offset) { int img_fmt = desc->stm_ctrl & STM_CTRL_IMAGE_FORMAT_MASK; + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(raw_buf->vb2_queue); + struct mxc_jpeg_q_data *q_data; + q_data = mxc_jpeg_get_q_data(ctx, raw_buf->type); desc->buf_base0 = vb2_dma_contig_plane_dma_addr(raw_buf, 0); desc->buf_base1 = 0; if (img_fmt == STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV420)) { - WARN_ON(raw_buf->num_planes < 2); - desc->buf_base1 = vb2_dma_contig_plane_dma_addr(raw_buf, 1); + if (raw_buf->num_planes == 2) + desc->buf_base1 = vb2_dma_contig_plane_dma_addr(raw_buf, 1); + else + desc->buf_base1 = desc->buf_base0 + q_data->sizeimage[0]; } desc->stm_bufbase = vb2_dma_contig_plane_dma_addr(jpeg_buf, 0) + offset; @@ -519,7 +551,6 @@ static bool mxc_jpeg_alloc_slot_data(struct mxc_jpeg_dev *jpeg, GFP_ATOMIC); if (!cfg_stm) goto err; - memset(cfg_stm, 0, MXC_JPEG_MAX_CFG_STREAM); jpeg->slot_data[slot].cfg_stream_vaddr = cfg_stm; skip_alloc: @@ -570,6 +601,48 @@ static void mxc_jpeg_check_and_set_last_buffer(struct mxc_jpeg_ctx *ctx, } } +static void mxc_jpeg_job_finish(struct mxc_jpeg_ctx *ctx, enum vb2_buffer_state state, bool reset) +{ + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + mxc_jpeg_check_and_set_last_buffer(ctx, src_buf, dst_buf); + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, state); + v4l2_m2m_buf_done(dst_buf, state); + + mxc_jpeg_disable_irq(reg, ctx->slot); + ctx->mxc_jpeg->slot_data[ctx->slot].used = false; + if (reset) + mxc_jpeg_sw_reset(reg); +} + +static u32 mxc_jpeg_get_plane_size(struct mxc_jpeg_q_data *q_data, u32 plane_no) +{ + const struct mxc_jpeg_fmt *fmt = q_data->fmt; + u32 size; + int i; + + if (plane_no >= fmt->mem_planes) + return 0; + + if (fmt->mem_planes == fmt->comp_planes) + return q_data->sizeimage[plane_no]; + + if (plane_no < fmt->mem_planes - 1) + return q_data->sizeimage[plane_no]; + + size = q_data->sizeimage[fmt->mem_planes - 1]; + for (i = fmt->mem_planes; i < fmt->comp_planes; i++) + size += q_data->sizeimage[i]; + + return size; +} + static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) { struct mxc_jpeg_dev *jpeg = priv; @@ -602,6 +675,9 @@ static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) goto job_unlock; } + if (!jpeg->slot_data[slot].used) + goto job_unlock; + dec_ret = readl(reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); writel(dec_ret, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); /* w1c */ @@ -646,11 +722,11 @@ static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) payload); } else { q_data = mxc_jpeg_get_q_data(ctx, cap_type); - payload = q_data->sizeimage[0]; + payload = mxc_jpeg_get_plane_size(q_data, 0); vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload); vb2_set_plane_payload(&dst_buf->vb2_buf, 1, 0); - if (q_data->fmt->colplanes == 2) { - payload = q_data->sizeimage[1]; + if (q_data->fmt->mem_planes == 2) { + payload = mxc_jpeg_get_plane_size(q_data, 1); vb2_set_plane_payload(&dst_buf->vb2_buf, 1, payload); } dev_dbg(dev, "Decoding finished, payload size: %ld + %ld\n", @@ -666,14 +742,9 @@ static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) buf_state = VB2_BUF_STATE_DONE; buffers_done: - mxc_jpeg_disable_irq(reg, ctx->slot); - jpeg->slot_data[slot].used = false; /* unused, but don't free */ - mxc_jpeg_check_and_set_last_buffer(ctx, src_buf, dst_buf); - v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); - v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); - v4l2_m2m_buf_done(src_buf, buf_state); - v4l2_m2m_buf_done(dst_buf, buf_state); + mxc_jpeg_job_finish(ctx, buf_state, false); spin_unlock(&jpeg->hw_lock); + cancel_delayed_work(&ctx->task_timer); v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); return IRQ_HANDLED; job_unlock: @@ -694,6 +765,7 @@ static int mxc_jpeg_fixup_sof(struct mxc_jpeg_sof *sof, _bswap16(&sof->width); switch (fourcc) { + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV12M: sof->components_no = 3; sof->comp[0].v = 0x2; @@ -730,6 +802,7 @@ static int mxc_jpeg_fixup_sos(struct mxc_jpeg_sos *sos, u8 *sof_u8 = (u8 *)sos; switch (fourcc) { + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV12M: sos->components_no = 3; break; @@ -902,8 +975,8 @@ static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, jpeg->slot_data[slot].cfg_stream_size = mxc_jpeg_setup_cfg_stream(cfg_stream_vaddr, q_data->fmt->fourcc, - q_data->w, - q_data->h); + q_data->crop.width, + q_data->crop.height); /* chain the config descriptor with the encoding descriptor */ cfg_desc->next_descpt_ptr = desc_handle | MXC_NXT_DESCPT_EN; @@ -920,11 +993,13 @@ static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, desc->next_descpt_ptr = 0; /* end of chain */ /* use adjusted resolution for CAST IP job */ - w = q_data->w_adjusted; - h = q_data->h_adjusted; + w = q_data->crop.width; + h = q_data->crop.height; + v4l_bound_align_image(&w, w, MXC_JPEG_MAX_WIDTH, q_data->fmt->h_align, + &h, h, MXC_JPEG_MAX_HEIGHT, q_data->fmt->v_align, 0); mxc_jpeg_set_res(desc, w, h); - mxc_jpeg_set_line_pitch(desc, w * (q_data->fmt->depth / 8)); - mxc_jpeg_set_bufsize(desc, desc->line_pitch * h); + mxc_jpeg_set_line_pitch(desc, q_data->bytesperline[0]); + mxc_jpeg_set_bufsize(desc, ALIGN(vb2_plane_size(dst_buf, 0), 1024)); img_fmt = mxc_jpeg_fourcc_to_imgfmt(q_data->fmt->fourcc); if (img_fmt == MXC_JPEG_INVALID) dev_err(jpeg->dev, "No valid image format detected\n"); @@ -943,6 +1018,32 @@ static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, mxc_jpeg_set_desc(cfg_desc_handle, reg, slot); } +static const struct mxc_jpeg_fmt *mxc_jpeg_get_sibling_format(const struct mxc_jpeg_fmt *fmt) +{ + int i; + + for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) { + if (mxc_formats[i].subsampling == fmt->subsampling && + mxc_formats[i].nc == fmt->nc && + mxc_formats[i].precision == fmt->precision && + mxc_formats[i].is_rgb == fmt->is_rgb && + mxc_formats[i].fourcc != fmt->fourcc) + return &mxc_formats[i]; + } + + return NULL; +} + +static bool mxc_jpeg_compare_format(const struct mxc_jpeg_fmt *fmt1, + const struct mxc_jpeg_fmt *fmt2) +{ + if (fmt1 == fmt2) + return true; + if (mxc_jpeg_get_sibling_format(fmt1) == fmt2) + return true; + return false; +} + static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, struct mxc_jpeg_src_buf *jpeg_src_buf) { @@ -953,6 +1054,8 @@ static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, return false; q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (mxc_jpeg_compare_format(q_data_cap->fmt, jpeg_src_buf->fmt)) + jpeg_src_buf->fmt = q_data_cap->fmt; if (q_data_cap->fmt != jpeg_src_buf->fmt || q_data_cap->w != jpeg_src_buf->w || q_data_cap->h != jpeg_src_buf->h) { @@ -973,6 +1076,10 @@ static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, q_data_cap->fmt = jpeg_src_buf->fmt; q_data_cap->w_adjusted = q_data_cap->w; q_data_cap->h_adjusted = q_data_cap->h; + q_data_cap->crop.left = 0; + q_data_cap->crop.top = 0; + q_data_cap->crop.width = jpeg_src_buf->w; + q_data_cap->crop.height = jpeg_src_buf->h; /* * align up the resolution for CAST IP, @@ -985,7 +1092,7 @@ static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, &q_data_cap->h_adjusted, q_data_cap->h_adjusted, /* adjust up */ MXC_JPEG_MAX_HEIGHT, - 0, + q_data_cap->fmt->v_align, 0); /* setup bytesperline/sizeimage for capture queue */ @@ -994,6 +1101,7 @@ static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, notify_src_chg(ctx); ctx->source_change = 1; } + return ctx->source_change ? true : false; } @@ -1004,6 +1112,23 @@ static int mxc_jpeg_job_ready(void *priv) return ctx->source_change ? 0 : 1; } +static void mxc_jpeg_device_run_timeout(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct mxc_jpeg_ctx *ctx = container_of(dwork, struct mxc_jpeg_ctx, task_timer); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + unsigned long flags; + + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); + if (ctx->slot < MXC_MAX_SLOTS && ctx->mxc_jpeg->slot_data[ctx->slot].used) { + dev_warn(jpeg->dev, "%s timeout, cancel it\n", + ctx->mxc_jpeg->mode == MXC_JPEG_DECODE ? "decode" : "encode"); + mxc_jpeg_job_finish(ctx, VB2_BUF_STATE_ERROR, true); + v4l2_m2m_job_finish(ctx->mxc_jpeg->m2m_dev, ctx->fh.m2m_ctx); + } + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); +} + static void mxc_jpeg_device_run(void *priv) { struct mxc_jpeg_ctx *ctx = priv; @@ -1035,9 +1160,9 @@ static void mxc_jpeg_device_run(void *priv) v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); jpeg_src_buf = vb2_to_mxc_buf(&src_buf->vb2_buf); - if (q_data_cap->fmt->colplanes != dst_buf->vb2_buf.num_planes) { + if (q_data_cap->fmt->mem_planes != dst_buf->vb2_buf.num_planes) { dev_err(dev, "Capture format %s has %d planes, but capture buffer has %d planes\n", - q_data_cap->fmt->name, q_data_cap->fmt->colplanes, + q_data_cap->fmt->name, q_data_cap->fmt->mem_planes, dst_buf->vb2_buf.num_planes); jpeg_src_buf->jpeg_parse_error = true; } @@ -1089,6 +1214,7 @@ static void mxc_jpeg_device_run(void *priv) &src_buf->vb2_buf, &dst_buf->vb2_buf); mxc_jpeg_dec_mode_go(dev, reg); } + schedule_delayed_work(&ctx->task_timer, msecs_to_jiffies(hw_timeout)); end: spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); } @@ -1098,6 +1224,7 @@ static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, { struct v4l2_fh *fh = file->private_data; struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + unsigned long flags; int ret; ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, cmd); @@ -1107,7 +1234,9 @@ static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, if (!vb2_is_streaming(v4l2_m2m_get_src_vq(fh->m2m_ctx))) return 0; + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); ret = v4l2_m2m_ioctl_decoder_cmd(file, priv, cmd); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); if (ret < 0) return ret; @@ -1128,6 +1257,7 @@ static int mxc_jpeg_encoder_cmd(struct file *file, void *priv, { struct v4l2_fh *fh = file->private_data; struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + unsigned long flags; int ret; ret = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, cmd); @@ -1138,7 +1268,9 @@ static int mxc_jpeg_encoder_cmd(struct file *file, void *priv, !vb2_is_streaming(v4l2_m2m_get_dst_vq(fh->m2m_ctx))) return 0; + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); ret = v4l2_m2m_ioctl_encoder_cmd(file, fh, cmd); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); if (ret < 0) return 0; @@ -1161,39 +1293,27 @@ static int mxc_jpeg_queue_setup(struct vb2_queue *q, { struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); struct mxc_jpeg_q_data *q_data = NULL; - struct mxc_jpeg_q_data tmp_q; int i; q_data = mxc_jpeg_get_q_data(ctx, q->type); if (!q_data) return -EINVAL; - tmp_q.fmt = q_data->fmt; - tmp_q.w = q_data->w_adjusted; - tmp_q.h = q_data->h_adjusted; - for (i = 0; i < MXC_JPEG_MAX_PLANES; i++) { - tmp_q.bytesperline[i] = q_data->bytesperline[i]; - tmp_q.sizeimage[i] = q_data->sizeimage[i]; - } - mxc_jpeg_sizeimage(&tmp_q); - for (i = 0; i < MXC_JPEG_MAX_PLANES; i++) - tmp_q.sizeimage[i] = max(tmp_q.sizeimage[i], q_data->sizeimage[i]); - /* Handle CREATE_BUFS situation - *nplanes != 0 */ if (*nplanes) { - if (*nplanes != q_data->fmt->colplanes) + if (*nplanes != q_data->fmt->mem_planes) return -EINVAL; for (i = 0; i < *nplanes; i++) { - if (sizes[i] < tmp_q.sizeimage[i]) + if (sizes[i] < mxc_jpeg_get_plane_size(q_data, i)) return -EINVAL; } return 0; } /* Handle REQBUFS situation */ - *nplanes = q_data->fmt->colplanes; + *nplanes = q_data->fmt->mem_planes; for (i = 0; i < *nplanes; i++) - sizes[i] = tmp_q.sizeimage[i]; + sizes[i] = mxc_jpeg_get_plane_size(q_data, i); return 0; } @@ -1238,7 +1358,8 @@ static void mxc_jpeg_stop_streaming(struct vb2_queue *q) v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); } - v4l2_m2m_update_stop_streaming_state(ctx->fh.m2m_ctx, q); + if (V4L2_TYPE_IS_OUTPUT(q->type) || !ctx->source_change) + v4l2_m2m_update_stop_streaming_state(ctx->fh.m2m_ctx, q); if (V4L2_TYPE_IS_OUTPUT(q->type) && v4l2_m2m_has_stopped(ctx->fh.m2m_ctx)) { notify_eos(ctx); @@ -1277,19 +1398,40 @@ static int mxc_jpeg_valid_comp_id(struct device *dev, return valid; } +static bool mxc_jpeg_match_image_format(const struct mxc_jpeg_fmt *fmt, + const struct v4l2_jpeg_header *header) +{ + if (fmt->subsampling != header->frame.subsampling || + fmt->nc != header->frame.num_components || + fmt->precision != header->frame.precision) + return false; + + /* + * If the transform flag from APP14 marker is 0, images that are + * encoded with 3 components have RGB colorspace, see Recommendation + * ITU-T T.872 chapter 6.5.3 APP14 marker segment for colour encoding + */ + if (header->frame.subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_444) { + u8 is_rgb = header->app14_tf == V4L2_JPEG_APP14_TF_CMYK_RGB ? 1 : 0; + + if (is_rgb != fmt->is_rgb) + return false; + } + return true; +} + static u32 mxc_jpeg_get_image_format(struct device *dev, const struct v4l2_jpeg_header *header) { int i; u32 fourcc = 0; - for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) - if (mxc_formats[i].subsampling == header->frame.subsampling && - mxc_formats[i].nc == header->frame.num_components && - mxc_formats[i].precision == header->frame.precision) { + for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) { + if (mxc_jpeg_match_image_format(&mxc_formats[i], header)) { fourcc = mxc_formats[i].fourcc; break; } + } if (fourcc == 0) { dev_err(dev, "Could not identify image format nc=%d, subsampling=%d, precision=%d\n", @@ -1298,17 +1440,6 @@ static u32 mxc_jpeg_get_image_format(struct device *dev, header->frame.precision); return fourcc; } - /* - * If the transform flag from APP14 marker is 0, images that are - * encoded with 3 components have RGB colorspace, see Recommendation - * ITU-T T.872 chapter 6.5.3 APP14 marker segment for colour encoding - */ - if (fourcc == V4L2_PIX_FMT_YUV24 || fourcc == V4L2_PIX_FMT_BGR24) { - if (header->app14_tf == V4L2_JPEG_APP14_TF_CMYK_RGB) - fourcc = V4L2_PIX_FMT_BGR24; - else - fourcc = V4L2_PIX_FMT_YUV24; - } return fourcc; } @@ -1325,17 +1456,17 @@ static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, u32 precision) * applies to the first plane and is divided by the same factor * as the width field for the other planes */ - q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8); + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8); q->bytesperline[1] = q->bytesperline[0]; } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_422) { - q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8) * 2; + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8) * 2; q->bytesperline[1] = 0; } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_444) { - q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8) * q->fmt->nc; + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8) * q->fmt->nc; q->bytesperline[1] = 0; } else { /* grayscale */ - q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8); + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8); q->bytesperline[1] = 0; } } @@ -1354,9 +1485,9 @@ static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q) /* jpeg stream size must be multiple of 1K */ q->sizeimage[0] = ALIGN(q->sizeimage[0], 1024); } else { - q->sizeimage[0] = q->bytesperline[0] * q->h; + q->sizeimage[0] = q->bytesperline[0] * q->h_adjusted; q->sizeimage[1] = 0; - if (q->fmt->fourcc == V4L2_PIX_FMT_NV12M) + if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_420) q->sizeimage[1] = q->sizeimage[0] / 2; } } @@ -1365,6 +1496,7 @@ static int mxc_jpeg_parse(struct mxc_jpeg_ctx *ctx, struct vb2_buffer *vb) { struct device *dev = ctx->mxc_jpeg->dev; struct mxc_jpeg_q_data *q_data_out; + struct mxc_jpeg_q_data *q_data_cap; u32 fourcc; struct v4l2_jpeg_header header; struct mxc_jpeg_sof *psof = NULL; @@ -1422,7 +1554,11 @@ static int mxc_jpeg_parse(struct mxc_jpeg_ctx *ctx, struct vb2_buffer *vb) if (!mxc_jpeg_valid_comp_id(dev, psof, psos)) dev_warn(dev, "JPEG component ids should be 0-3 or 1-4"); - fourcc = mxc_jpeg_get_image_format(dev, &header); + q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (q_data_cap->fmt && mxc_jpeg_match_image_format(q_data_cap->fmt, &header)) + fourcc = q_data_cap->fmt->fourcc; + else + fourcc = mxc_jpeg_get_image_format(dev, &header); if (fourcc == 0) return -EINVAL; @@ -1498,8 +1634,8 @@ static int mxc_jpeg_buf_prepare(struct vb2_buffer *vb) q_data = mxc_jpeg_get_q_data(ctx, vb->vb2_queue->type); if (!q_data) return -EINVAL; - for (i = 0; i < q_data->fmt->colplanes; i++) { - sizeimage = q_data->sizeimage[i]; + for (i = 0; i < q_data->fmt->mem_planes; i++) { + sizeimage = mxc_jpeg_get_plane_size(q_data, i); if (vb2_plane_size(vb, i) < sizeimage) { dev_err(dev, "plane %d too small (%lu < %lu)", i, vb2_plane_size(vb, i), sizeimage); @@ -1578,6 +1714,10 @@ static void mxc_jpeg_set_default_params(struct mxc_jpeg_ctx *ctx) q[i]->h = MXC_JPEG_DEFAULT_HEIGHT; q[i]->w_adjusted = MXC_JPEG_DEFAULT_WIDTH; q[i]->h_adjusted = MXC_JPEG_DEFAULT_HEIGHT; + q[i]->crop.left = 0; + q[i]->crop.top = 0; + q[i]->crop.width = MXC_JPEG_DEFAULT_WIDTH; + q[i]->crop.height = MXC_JPEG_DEFAULT_HEIGHT; mxc_jpeg_bytesperline(q[i], q[i]->fmt->precision); mxc_jpeg_sizeimage(q[i]); } @@ -1672,6 +1812,7 @@ static int mxc_jpeg_open(struct file *file) ctx->fh.ctrl_handler = &ctx->ctrl_handler; mxc_jpeg_set_default_params(ctx); ctx->slot = MXC_MAX_SLOTS; /* slot not allocated yet */ + INIT_DELAYED_WORK(&ctx->task_timer, mxc_jpeg_device_run_timeout); if (mxc_jpeg->mode == MXC_JPEG_DECODE) dev_dbg(dev, "Opened JPEG decoder instance %p\n", ctx); @@ -1721,10 +1862,25 @@ static int mxc_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, * (more precisely what was propagated on capture queue * after jpeg parse on the output buffer) */ - if (f->index) - return -EINVAL; - f->pixelformat = q_data->fmt->fourcc; - return 0; + int ret = -EINVAL; + const struct mxc_jpeg_fmt *sibling; + + switch (f->index) { + case 0: + f->pixelformat = q_data->fmt->fourcc; + ret = 0; + break; + case 1: + sibling = mxc_jpeg_get_sibling_format(q_data->fmt); + if (sibling) { + f->pixelformat = sibling->fourcc; + ret = 0; + } + break; + default: + break; + } + return ret; } } @@ -1744,55 +1900,105 @@ static int mxc_jpeg_enum_fmt_vid_out(struct file *file, void *priv, return 0; } -static int mxc_jpeg_try_fmt(struct v4l2_format *f, const struct mxc_jpeg_fmt *fmt, - struct mxc_jpeg_ctx *ctx, int q_type) +static u32 mxc_jpeg_get_fmt_type(struct mxc_jpeg_ctx *ctx, u32 type) +{ + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return V4L2_TYPE_IS_OUTPUT(type) ? MXC_JPEG_FMT_TYPE_ENC : MXC_JPEG_FMT_TYPE_RAW; + else + return V4L2_TYPE_IS_CAPTURE(type) ? MXC_JPEG_FMT_TYPE_ENC : MXC_JPEG_FMT_TYPE_RAW; +} + +static u32 mxc_jpeg_get_default_fourcc(struct mxc_jpeg_ctx *ctx, u32 type) +{ + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return V4L2_TYPE_IS_OUTPUT(type) ? V4L2_PIX_FMT_JPEG : MXC_JPEG_DEFAULT_PFMT; + else + return V4L2_TYPE_IS_CAPTURE(type) ? V4L2_PIX_FMT_JPEG : MXC_JPEG_DEFAULT_PFMT; +} + +static u32 mxc_jpeg_try_fourcc(struct mxc_jpeg_ctx *ctx, u32 fourcc) +{ + const struct mxc_jpeg_fmt *sibling; + struct mxc_jpeg_q_data *q_data_cap; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE) + return fourcc; + if (!ctx->header_parsed) + return fourcc; + + q_data_cap = &ctx->cap_q; + if (q_data_cap->fmt->fourcc == fourcc) + return fourcc; + + sibling = mxc_jpeg_get_sibling_format(q_data_cap->fmt); + if (sibling && sibling->fourcc == fourcc) + return sibling->fourcc; + + return q_data_cap->fmt->fourcc; +} + +static int mxc_jpeg_try_fmt(struct v4l2_format *f, + struct mxc_jpeg_ctx *ctx, struct mxc_jpeg_q_data *q_data) { + const struct mxc_jpeg_fmt *fmt; struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct v4l2_plane_pix_format *pfmt; + u32 fourcc = f->fmt.pix_mp.pixelformat; u32 w = (pix_mp->width < MXC_JPEG_MAX_WIDTH) ? pix_mp->width : MXC_JPEG_MAX_WIDTH; u32 h = (pix_mp->height < MXC_JPEG_MAX_HEIGHT) ? pix_mp->height : MXC_JPEG_MAX_HEIGHT; int i; - struct mxc_jpeg_q_data tmp_q; + + fmt = mxc_jpeg_find_format(ctx, fourcc); + if (!fmt || fmt->flags != mxc_jpeg_get_fmt_type(ctx, f->type)) { + dev_warn(ctx->mxc_jpeg->dev, "Format not supported: %c%c%c%c, use the default.\n", + (fourcc & 0xff), + (fourcc >> 8) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 24) & 0xff); + fourcc = mxc_jpeg_get_default_fourcc(ctx, f->type); + fmt = mxc_jpeg_find_format(ctx, fourcc); + if (!fmt) + return -EINVAL; + f->fmt.pix_mp.pixelformat = fourcc; + } + q_data->fmt = fmt; memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); pix_mp->field = V4L2_FIELD_NONE; - pix_mp->num_planes = fmt->colplanes; + pix_mp->num_planes = fmt->mem_planes; pix_mp->pixelformat = fmt->fourcc; - pix_mp->width = w; - pix_mp->height = h; - v4l_bound_align_image(&w, + q_data->w = w; + q_data->h = h; + q_data->w_adjusted = w; + q_data->h_adjusted = h; + v4l_bound_align_image(&q_data->w_adjusted, w, /* adjust upwards*/ MXC_JPEG_MAX_WIDTH, fmt->h_align, - &h, + &q_data->h_adjusted, h, /* adjust upwards*/ MXC_JPEG_MAX_HEIGHT, - 0, + fmt->v_align, 0); - - /* get user input into the tmp_q */ - tmp_q.w = w; - tmp_q.h = h; - tmp_q.fmt = fmt; for (i = 0; i < pix_mp->num_planes; i++) { pfmt = &pix_mp->plane_fmt[i]; - tmp_q.bytesperline[i] = pfmt->bytesperline; - tmp_q.sizeimage[i] = pfmt->sizeimage; + q_data->bytesperline[i] = pfmt->bytesperline; + q_data->sizeimage[i] = pfmt->sizeimage; } - /* calculate bytesperline & sizeimage into the tmp_q */ - mxc_jpeg_bytesperline(&tmp_q, fmt->precision); - mxc_jpeg_sizeimage(&tmp_q); + /* calculate bytesperline & sizeimage */ + mxc_jpeg_bytesperline(q_data, fmt->precision); + mxc_jpeg_sizeimage(q_data); /* adjust user format according to our calculations */ for (i = 0; i < pix_mp->num_planes; i++) { pfmt = &pix_mp->plane_fmt[i]; memset(pfmt->reserved, 0, sizeof(pfmt->reserved)); - pfmt->bytesperline = tmp_q.bytesperline[i]; - pfmt->sizeimage = tmp_q.sizeimage[i]; + pfmt->bytesperline = q_data->bytesperline[i]; + pfmt->sizeimage = mxc_jpeg_get_plane_size(q_data, i); } /* fix colorspace information to sRGB for both output & capture */ @@ -1806,6 +2012,16 @@ static int mxc_jpeg_try_fmt(struct v4l2_format *f, const struct mxc_jpeg_fmt *fm */ pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; + if (fmt->flags == MXC_JPEG_FMT_TYPE_RAW) { + q_data->crop.left = 0; + q_data->crop.top = 0; + q_data->crop.width = q_data->w; + q_data->crop.height = q_data->h; + } + + pix_mp->width = q_data->w_adjusted; + pix_mp->height = q_data->h_adjusted; + return 0; } @@ -1815,29 +2031,17 @@ static int mxc_jpeg_try_fmt_vid_cap(struct file *file, void *priv, struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; struct device *dev = jpeg->dev; - const struct mxc_jpeg_fmt *fmt; - u32 fourcc = f->fmt.pix_mp.pixelformat; - - int q_type = (jpeg->mode == MXC_JPEG_DECODE) ? - MXC_JPEG_FMT_TYPE_RAW : MXC_JPEG_FMT_TYPE_ENC; + struct mxc_jpeg_q_data tmp_q; if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); return -EINVAL; } - fmt = mxc_jpeg_find_format(ctx, fourcc); - if (!fmt || fmt->flags != q_type) { - dev_warn(dev, "Format not supported: %c%c%c%c, use the default.\n", - (fourcc & 0xff), - (fourcc >> 8) & 0xff, - (fourcc >> 16) & 0xff, - (fourcc >> 24) & 0xff); - f->fmt.pix_mp.pixelformat = (jpeg->mode == MXC_JPEG_DECODE) ? - MXC_JPEG_DEFAULT_PFMT : V4L2_PIX_FMT_JPEG; - fmt = mxc_jpeg_find_format(ctx, f->fmt.pix_mp.pixelformat); - } - return mxc_jpeg_try_fmt(f, fmt, ctx, q_type); + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE && V4L2_TYPE_IS_CAPTURE(f->type)) + f->fmt.pix_mp.pixelformat = mxc_jpeg_try_fourcc(ctx, f->fmt.pix_mp.pixelformat); + + return mxc_jpeg_try_fmt(f, ctx, &tmp_q); } static int mxc_jpeg_try_fmt_vid_out(struct file *file, void *priv, @@ -1846,88 +2050,55 @@ static int mxc_jpeg_try_fmt_vid_out(struct file *file, void *priv, struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; struct device *dev = jpeg->dev; - const struct mxc_jpeg_fmt *fmt; - u32 fourcc = f->fmt.pix_mp.pixelformat; - - int q_type = (jpeg->mode == MXC_JPEG_ENCODE) ? - MXC_JPEG_FMT_TYPE_RAW : MXC_JPEG_FMT_TYPE_ENC; + struct mxc_jpeg_q_data tmp_q; if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); return -EINVAL; } - fmt = mxc_jpeg_find_format(ctx, fourcc); - if (!fmt || fmt->flags != q_type) { - dev_warn(dev, "Format not supported: %c%c%c%c, use the default.\n", - (fourcc & 0xff), - (fourcc >> 8) & 0xff, - (fourcc >> 16) & 0xff, - (fourcc >> 24) & 0xff); - f->fmt.pix_mp.pixelformat = (jpeg->mode == MXC_JPEG_ENCODE) ? - MXC_JPEG_DEFAULT_PFMT : V4L2_PIX_FMT_JPEG; - fmt = mxc_jpeg_find_format(ctx, f->fmt.pix_mp.pixelformat); - } - return mxc_jpeg_try_fmt(f, fmt, ctx, q_type); + return mxc_jpeg_try_fmt(f, ctx, &tmp_q); +} + +static void mxc_jpeg_s_parsed_fmt(struct mxc_jpeg_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mxc_jpeg_q_data *q_data_cap; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE || !V4L2_TYPE_IS_CAPTURE(f->type)) + return; + if (!ctx->header_parsed) + return; + + q_data_cap = mxc_jpeg_get_q_data(ctx, f->type); + pix_mp->pixelformat = mxc_jpeg_try_fourcc(ctx, pix_mp->pixelformat); + pix_mp->width = q_data_cap->w; + pix_mp->height = q_data_cap->h; } static int mxc_jpeg_s_fmt(struct mxc_jpeg_ctx *ctx, struct v4l2_format *f) { struct vb2_queue *vq; - struct mxc_jpeg_q_data *q_data = NULL; - struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; - int i; vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; - q_data = mxc_jpeg_get_q_data(ctx, f->type); - if (vb2_is_busy(vq)) { v4l2_err(&jpeg->v4l2_dev, "queue busy\n"); return -EBUSY; } - q_data->fmt = mxc_jpeg_find_format(ctx, pix_mp->pixelformat); - q_data->w = pix_mp->width; - q_data->h = pix_mp->height; - - q_data->w_adjusted = q_data->w; - q_data->h_adjusted = q_data->h; - /* - * align up the resolution for CAST IP, - * but leave the buffer resolution unchanged - */ - v4l_bound_align_image(&q_data->w_adjusted, - q_data->w_adjusted, /* adjust upwards */ - MXC_JPEG_MAX_WIDTH, - q_data->fmt->h_align, - &q_data->h_adjusted, - q_data->h_adjusted, /* adjust upwards */ - MXC_JPEG_MAX_HEIGHT, - q_data->fmt->v_align, - 0); - - for (i = 0; i < pix_mp->num_planes; i++) { - q_data->bytesperline[i] = pix_mp->plane_fmt[i].bytesperline; - q_data->sizeimage[i] = pix_mp->plane_fmt[i].sizeimage; - } + mxc_jpeg_s_parsed_fmt(ctx, f); - return 0; + return mxc_jpeg_try_fmt(f, ctx, mxc_jpeg_get_q_data(ctx, f->type)); } static int mxc_jpeg_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - int ret; - - ret = mxc_jpeg_try_fmt_vid_cap(file, priv, f); - if (ret) - return ret; - return mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); } @@ -1941,10 +2112,6 @@ static int mxc_jpeg_s_fmt_vid_out(struct file *file, void *priv, enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; struct v4l2_format fc; - ret = mxc_jpeg_try_fmt_vid_out(file, priv, f); - if (ret) - return ret; - ret = mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); if (ret) return ret; @@ -1990,6 +2157,10 @@ static int mxc_jpeg_g_fmt_vid(struct file *file, void *priv, pix_mp->width = q_data->w; pix_mp->height = q_data->h; pix_mp->field = V4L2_FIELD_NONE; + if (q_data->fmt->flags == MXC_JPEG_FMT_TYPE_RAW) { + pix_mp->width = q_data->w_adjusted; + pix_mp->height = q_data->h_adjusted; + } /* fix colorspace information to sRGB for both output & capture */ pix_mp->colorspace = V4L2_COLORSPACE_SRGB; @@ -1997,15 +2168,109 @@ static int mxc_jpeg_g_fmt_vid(struct file *file, void *priv, pix_mp->xfer_func = V4L2_XFER_FUNC_SRGB; pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; - pix_mp->num_planes = q_data->fmt->colplanes; + pix_mp->num_planes = q_data->fmt->mem_planes; for (i = 0; i < pix_mp->num_planes; i++) { pix_mp->plane_fmt[i].bytesperline = q_data->bytesperline[i]; - pix_mp->plane_fmt[i].sizeimage = q_data->sizeimage[i]; + pix_mp->plane_fmt[i].sizeimage = mxc_jpeg_get_plane_size(q_data, i); + } + + return 0; +} + +static int mxc_jpeg_dec_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_cap; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + q_data_cap = mxc_jpeg_get_q_data(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r = q_data_cap->crop; + break; + case V4L2_SEL_TGT_COMPOSE_PADDED: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data_cap->w_adjusted; + s->r.height = q_data_cap->h_adjusted; + break; + default: + return -EINVAL; } return 0; } +static int mxc_jpeg_enc_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_out; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + q_data_out = mxc_jpeg_get_q_data(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data_out->w; + s->r.height = q_data_out->h; + break; + case V4L2_SEL_TGT_CROP: + s->r = q_data_out->crop; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mxc_jpeg_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return mxc_jpeg_dec_g_selection(file, fh, s); + else + return mxc_jpeg_enc_g_selection(file, fh, s); +} + +static int mxc_jpeg_s_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_out; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_ENCODE) + return -ENOTTY; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + if (s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + q_data_out = mxc_jpeg_get_q_data(ctx, s->type); + if (s->r.left || s->r.top) + return -EINVAL; + if (s->r.width > q_data_out->w || s->r.height > q_data_out->h) + return -EINVAL; + + q_data_out->crop.left = 0; + q_data_out->crop.top = 0; + q_data_out->crop.width = s->r.width; + q_data_out->crop.height = s->r.height; + + return 0; +} + static int mxc_jpeg_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { @@ -2035,6 +2300,9 @@ static const struct v4l2_ioctl_ops mxc_jpeg_ioctl_ops = { .vidioc_g_fmt_vid_cap_mplane = mxc_jpeg_g_fmt_vid, .vidioc_g_fmt_vid_out_mplane = mxc_jpeg_g_fmt_vid, + .vidioc_g_selection = mxc_jpeg_g_selection, + .vidioc_s_selection = mxc_jpeg_s_selection, + .vidioc_subscribe_event = mxc_jpeg_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, @@ -2163,6 +2431,8 @@ static int mxc_jpeg_probe(struct platform_device *pdev) unsigned int slot; of_id = of_match_node(mxc_jpeg_match, dev->of_node); + if (!of_id) + return -ENODEV; mode = *(const int *)of_id->data; jpeg = devm_kzalloc(dev, sizeof(struct mxc_jpeg_dev), GFP_KERNEL); diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h index c508d41a906f..8fa8c0aec5a2 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h @@ -45,11 +45,13 @@ enum mxc_jpeg_mode { * @subsampling: subsampling of jpeg components * @nc: number of color components * @depth: number of bits per pixel - * @colplanes: number of color planes (1 for packed formats) + * @mem_planes: number of memory planes (1 for packed formats) + * @comp_planes:number of component planes, which includes the alpha plane (1 to 4). * @h_align: horizontal alignment order (align to 2^h_align) * @v_align: vertical alignment order (align to 2^v_align) * @flags: flags describing format applicability * @precision: jpeg sample precision + * @is_rgb: is an RGB pixel format */ struct mxc_jpeg_fmt { const char *name; @@ -57,11 +59,13 @@ struct mxc_jpeg_fmt { enum v4l2_jpeg_chroma_subsampling subsampling; int nc; int depth; - int colplanes; + int mem_planes; + int comp_planes; int h_align; int v_align; u32 flags; u8 precision; + u8 is_rgb; }; struct mxc_jpeg_desc { @@ -84,6 +88,7 @@ struct mxc_jpeg_q_data { int h; int h_adjusted; unsigned int sequence; + struct v4l2_rect crop; }; struct mxc_jpeg_ctx { @@ -97,6 +102,7 @@ struct mxc_jpeg_ctx { bool header_parsed; struct v4l2_ctrl_handler ctrl_handler; u8 jpeg_quality; + struct delayed_work task_timer; }; struct mxc_jpeg_slot_data { diff --git a/drivers/media/platform/nxp/imx7-media-csi.c b/drivers/media/platform/nxp/imx7-media-csi.c new file mode 100644 index 000000000000..886374d3a6ff --- /dev/null +++ b/drivers/media/platform/nxp/imx7-media-csi.c @@ -0,0 +1,2408 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Capture CSI Subdev for Freescale i.MX6UL/L / i.MX7 SOC + * + * Copyright (c) 2019 Linaro Ltd + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> + +#define IMX7_CSI_PAD_SINK 0 +#define IMX7_CSI_PAD_SRC 1 +#define IMX7_CSI_PADS_NUM 2 + +/* csi control reg 1 */ +#define BIT_SWAP16_EN BIT(31) +#define BIT_EXT_VSYNC BIT(30) +#define BIT_EOF_INT_EN BIT(29) +#define BIT_PRP_IF_EN BIT(28) +#define BIT_CCIR_MODE BIT(27) +#define BIT_COF_INT_EN BIT(26) +#define BIT_SF_OR_INTEN BIT(25) +#define BIT_RF_OR_INTEN BIT(24) +#define BIT_SFF_DMA_DONE_INTEN BIT(22) +#define BIT_STATFF_INTEN BIT(21) +#define BIT_FB2_DMA_DONE_INTEN BIT(20) +#define BIT_FB1_DMA_DONE_INTEN BIT(19) +#define BIT_RXFF_INTEN BIT(18) +#define BIT_SOF_POL BIT(17) +#define BIT_SOF_INTEN BIT(16) +#define BIT_MCLKDIV(n) ((n) << 12) +#define BIT_MCLKDIV_MASK (0xf << 12) +#define BIT_HSYNC_POL BIT(11) +#define BIT_CCIR_EN BIT(10) +#define BIT_MCLKEN BIT(9) +#define BIT_FCC BIT(8) +#define BIT_PACK_DIR BIT(7) +#define BIT_CLR_STATFIFO BIT(6) +#define BIT_CLR_RXFIFO BIT(5) +#define BIT_GCLK_MODE BIT(4) +#define BIT_INV_DATA BIT(3) +#define BIT_INV_PCLK BIT(2) +#define BIT_REDGE BIT(1) +#define BIT_PIXEL_BIT BIT(0) + +/* control reg 2 */ +#define BIT_DMA_BURST_TYPE_RFF_INCR4 (1 << 30) +#define BIT_DMA_BURST_TYPE_RFF_INCR8 (2 << 30) +#define BIT_DMA_BURST_TYPE_RFF_INCR16 (3 << 30) +#define BIT_DMA_BURST_TYPE_RFF_MASK (3 << 30) + +/* control reg 3 */ +#define BIT_FRMCNT(n) ((n) << 16) +#define BIT_FRMCNT_MASK (0xffff << 16) +#define BIT_FRMCNT_RST BIT(15) +#define BIT_DMA_REFLASH_RFF BIT(14) +#define BIT_DMA_REFLASH_SFF BIT(13) +#define BIT_DMA_REQ_EN_RFF BIT(12) +#define BIT_DMA_REQ_EN_SFF BIT(11) +#define BIT_STATFF_LEVEL(n) ((n) << 8) +#define BIT_STATFF_LEVEL_MASK (0x7 << 8) +#define BIT_HRESP_ERR_EN BIT(7) +#define BIT_RXFF_LEVEL(n) ((n) << 4) +#define BIT_RXFF_LEVEL_MASK (0x7 << 4) +#define BIT_TWO_8BIT_SENSOR BIT(3) +#define BIT_ZERO_PACK_EN BIT(2) +#define BIT_ECC_INT_EN BIT(1) +#define BIT_ECC_AUTO_EN BIT(0) + +/* csi status reg */ +#define BIT_ADDR_CH_ERR_INT BIT(28) +#define BIT_FIELD0_INT BIT(27) +#define BIT_FIELD1_INT BIT(26) +#define BIT_SFF_OR_INT BIT(25) +#define BIT_RFF_OR_INT BIT(24) +#define BIT_DMA_TSF_DONE_SFF BIT(22) +#define BIT_STATFF_INT BIT(21) +#define BIT_DMA_TSF_DONE_FB2 BIT(20) +#define BIT_DMA_TSF_DONE_FB1 BIT(19) +#define BIT_RXFF_INT BIT(18) +#define BIT_EOF_INT BIT(17) +#define BIT_SOF_INT BIT(16) +#define BIT_F2_INT BIT(15) +#define BIT_F1_INT BIT(14) +#define BIT_COF_INT BIT(13) +#define BIT_HRESP_ERR_INT BIT(7) +#define BIT_ECC_INT BIT(1) +#define BIT_DRDY BIT(0) + +/* csi image parameter reg */ +#define BIT_IMAGE_WIDTH(n) ((n) << 16) +#define BIT_IMAGE_HEIGHT(n) (n) + +/* csi control reg 18 */ +#define BIT_CSI_HW_ENABLE BIT(31) +#define BIT_MIPI_DATA_FORMAT_RAW8 (0x2a << 25) +#define BIT_MIPI_DATA_FORMAT_RAW10 (0x2b << 25) +#define BIT_MIPI_DATA_FORMAT_RAW12 (0x2c << 25) +#define BIT_MIPI_DATA_FORMAT_RAW14 (0x2d << 25) +#define BIT_MIPI_DATA_FORMAT_YUV422_8B (0x1e << 25) +#define BIT_MIPI_DATA_FORMAT_MASK (0x3f << 25) +#define BIT_DATA_FROM_MIPI BIT(22) +#define BIT_MIPI_YU_SWAP BIT(21) +#define BIT_MIPI_DOUBLE_CMPNT BIT(20) +#define BIT_MASK_OPTION_FIRST_FRAME (0 << 18) +#define BIT_MASK_OPTION_CSI_EN (1 << 18) +#define BIT_MASK_OPTION_SECOND_FRAME (2 << 18) +#define BIT_MASK_OPTION_ON_DATA (3 << 18) +#define BIT_BASEADDR_CHG_ERR_EN BIT(9) +#define BIT_BASEADDR_SWITCH_SEL BIT(5) +#define BIT_BASEADDR_SWITCH_EN BIT(4) +#define BIT_PARALLEL24_EN BIT(3) +#define BIT_DEINTERLACE_EN BIT(2) +#define BIT_TVDECODER_IN_EN BIT(1) +#define BIT_NTSC_EN BIT(0) + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 + +#define CSI_CSICR1 0x00 +#define CSI_CSICR2 0x04 +#define CSI_CSICR3 0x08 +#define CSI_STATFIFO 0x0c +#define CSI_CSIRXFIFO 0x10 +#define CSI_CSIRXCNT 0x14 +#define CSI_CSISR 0x18 + +#define CSI_CSIDBG 0x1c +#define CSI_CSIDMASA_STATFIFO 0x20 +#define CSI_CSIDMATS_STATFIFO 0x24 +#define CSI_CSIDMASA_FB1 0x28 +#define CSI_CSIDMASA_FB2 0x2c +#define CSI_CSIFBUF_PARA 0x30 +#define CSI_CSIIMAG_PARA 0x34 + +#define CSI_CSICR18 0x48 +#define CSI_CSICR19 0x4c + +#define IMX7_CSI_VIDEO_NAME "imx-capture" +/* In bytes, per queue */ +#define IMX7_CSI_VIDEO_MEM_LIMIT SZ_512M +#define IMX7_CSI_VIDEO_EOF_TIMEOUT 2000 + +#define IMX7_CSI_DEF_MBUS_CODE MEDIA_BUS_FMT_UYVY8_2X8 +#define IMX7_CSI_DEF_PIX_FORMAT V4L2_PIX_FMT_UYVY +#define IMX7_CSI_DEF_PIX_WIDTH 640 +#define IMX7_CSI_DEF_PIX_HEIGHT 480 + +enum imx_csi_model { + IMX7_CSI_IMX7 = 0, + IMX7_CSI_IMX8MQ, +}; + +struct imx7_csi_pixfmt { + /* the in-memory FourCC pixel format */ + u32 fourcc; + /* + * the set of equivalent media bus codes for the fourcc. + * NOTE! codes pointer is NULL for in-memory-only formats. + */ + const u32 *codes; + int bpp; /* total bpp */ + bool yuv; +}; + +struct imx7_csi_vb2_buffer { + struct vb2_v4l2_buffer vbuf; + struct list_head list; +}; + +static inline struct imx7_csi_vb2_buffer * +to_imx7_csi_vb2_buffer(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + return container_of(vbuf, struct imx7_csi_vb2_buffer, vbuf); +} + +struct imx7_csi_dma_buf { + void *virt; + dma_addr_t dma_addr; + unsigned long len; +}; + +struct imx7_csi { + struct device *dev; + + /* Resources and locks */ + void __iomem *regbase; + int irq; + struct clk *mclk; + + struct mutex lock; /* Protects is_streaming, format_mbus, cc */ + spinlock_t irqlock; /* Protects last_eof */ + + /* Media and V4L2 device */ + struct media_device mdev; + struct v4l2_device v4l2_dev; + struct v4l2_async_notifier notifier; + struct media_pipeline pipe; + + struct v4l2_subdev *src_sd; + bool is_csi2; + + /* V4L2 subdev */ + struct v4l2_subdev sd; + struct media_pad pad[IMX7_CSI_PADS_NUM]; + + struct v4l2_mbus_framefmt format_mbus[IMX7_CSI_PADS_NUM]; + const struct imx7_csi_pixfmt *cc[IMX7_CSI_PADS_NUM]; + + /* Video device */ + struct video_device *vdev; /* Video device */ + struct media_pad vdev_pad; /* Video device pad */ + + struct v4l2_pix_format vdev_fmt; /* The user format */ + const struct imx7_csi_pixfmt *vdev_cc; + struct v4l2_rect vdev_compose; /* The compose rectangle */ + + struct mutex vdev_mutex; /* Protect vdev operations */ + + struct vb2_queue q; /* The videobuf2 queue */ + struct list_head ready_q; /* List of queued buffers */ + spinlock_t q_lock; /* Protect ready_q */ + + /* Buffers and streaming state */ + struct imx7_csi_vb2_buffer *active_vb2_buf[2]; + struct imx7_csi_dma_buf underrun_buf; + + bool is_streaming; + int buf_num; + u32 frame_sequence; + + bool last_eof; + struct completion last_eof_completion; + + enum imx_csi_model model; +}; + +static struct imx7_csi * +imx7_csi_notifier_to_dev(struct v4l2_async_notifier *n) +{ + return container_of(n, struct imx7_csi, notifier); +} + +/* ----------------------------------------------------------------------------- + * Hardware Configuration + */ + +static u32 imx7_csi_reg_read(struct imx7_csi *csi, unsigned int offset) +{ + return readl(csi->regbase + offset); +} + +static void imx7_csi_reg_write(struct imx7_csi *csi, unsigned int value, + unsigned int offset) +{ + writel(value, csi->regbase + offset); +} + +static u32 imx7_csi_irq_clear(struct imx7_csi *csi) +{ + u32 isr; + + isr = imx7_csi_reg_read(csi, CSI_CSISR); + imx7_csi_reg_write(csi, isr, CSI_CSISR); + + return isr; +} + +static void imx7_csi_init_default(struct imx7_csi *csi) +{ + imx7_csi_reg_write(csi, BIT_SOF_POL | BIT_REDGE | BIT_GCLK_MODE | + BIT_HSYNC_POL | BIT_FCC | BIT_MCLKDIV(1) | + BIT_MCLKEN, CSI_CSICR1); + imx7_csi_reg_write(csi, 0, CSI_CSICR2); + imx7_csi_reg_write(csi, BIT_FRMCNT_RST, CSI_CSICR3); + + imx7_csi_reg_write(csi, BIT_IMAGE_WIDTH(IMX7_CSI_DEF_PIX_WIDTH) | + BIT_IMAGE_HEIGHT(IMX7_CSI_DEF_PIX_HEIGHT), + CSI_CSIIMAG_PARA); + + imx7_csi_reg_write(csi, BIT_DMA_REFLASH_RFF, CSI_CSICR3); +} + +static void imx7_csi_hw_enable_irq(struct imx7_csi *csi) +{ + u32 cr1 = imx7_csi_reg_read(csi, CSI_CSICR1); + + cr1 |= BIT_RFF_OR_INT; + cr1 |= BIT_FB1_DMA_DONE_INTEN; + cr1 |= BIT_FB2_DMA_DONE_INTEN; + + imx7_csi_reg_write(csi, cr1, CSI_CSICR1); +} + +static void imx7_csi_hw_disable_irq(struct imx7_csi *csi) +{ + u32 cr1 = imx7_csi_reg_read(csi, CSI_CSICR1); + + cr1 &= ~BIT_RFF_OR_INT; + cr1 &= ~BIT_FB1_DMA_DONE_INTEN; + cr1 &= ~BIT_FB2_DMA_DONE_INTEN; + + imx7_csi_reg_write(csi, cr1, CSI_CSICR1); +} + +static void imx7_csi_hw_enable(struct imx7_csi *csi) +{ + u32 cr = imx7_csi_reg_read(csi, CSI_CSICR18); + + cr |= BIT_CSI_HW_ENABLE; + + imx7_csi_reg_write(csi, cr, CSI_CSICR18); +} + +static void imx7_csi_hw_disable(struct imx7_csi *csi) +{ + u32 cr = imx7_csi_reg_read(csi, CSI_CSICR18); + + cr &= ~BIT_CSI_HW_ENABLE; + + imx7_csi_reg_write(csi, cr, CSI_CSICR18); +} + +static void imx7_csi_dma_reflash(struct imx7_csi *csi) +{ + u32 cr3; + + cr3 = imx7_csi_reg_read(csi, CSI_CSICR3); + cr3 |= BIT_DMA_REFLASH_RFF; + imx7_csi_reg_write(csi, cr3, CSI_CSICR3); +} + +static void imx7_csi_rx_fifo_clear(struct imx7_csi *csi) +{ + u32 cr1 = imx7_csi_reg_read(csi, CSI_CSICR1) & ~BIT_FCC; + + imx7_csi_reg_write(csi, cr1, CSI_CSICR1); + imx7_csi_reg_write(csi, cr1 | BIT_CLR_RXFIFO, CSI_CSICR1); + imx7_csi_reg_write(csi, cr1 | BIT_FCC, CSI_CSICR1); +} + +static void imx7_csi_dmareq_rff_enable(struct imx7_csi *csi) +{ + u32 cr3 = imx7_csi_reg_read(csi, CSI_CSICR3); + + cr3 |= BIT_DMA_REQ_EN_RFF; + cr3 |= BIT_HRESP_ERR_EN; + cr3 &= ~BIT_RXFF_LEVEL_MASK; + cr3 |= BIT_RXFF_LEVEL(2); + + imx7_csi_reg_write(csi, cr3, CSI_CSICR3); +} + +static void imx7_csi_dmareq_rff_disable(struct imx7_csi *csi) +{ + u32 cr3 = imx7_csi_reg_read(csi, CSI_CSICR3); + + cr3 &= ~BIT_DMA_REQ_EN_RFF; + cr3 &= ~BIT_HRESP_ERR_EN; + imx7_csi_reg_write(csi, cr3, CSI_CSICR3); +} + +static void imx7_csi_update_buf(struct imx7_csi *csi, dma_addr_t dma_addr, + int buf_num) +{ + if (buf_num == 1) + imx7_csi_reg_write(csi, dma_addr, CSI_CSIDMASA_FB2); + else + imx7_csi_reg_write(csi, dma_addr, CSI_CSIDMASA_FB1); +} + +static struct imx7_csi_vb2_buffer *imx7_csi_video_next_buf(struct imx7_csi *csi); + +static void imx7_csi_setup_vb2_buf(struct imx7_csi *csi) +{ + struct imx7_csi_vb2_buffer *buf; + struct vb2_buffer *vb2_buf; + int i; + + for (i = 0; i < 2; i++) { + dma_addr_t dma_addr; + + buf = imx7_csi_video_next_buf(csi); + if (buf) { + csi->active_vb2_buf[i] = buf; + vb2_buf = &buf->vbuf.vb2_buf; + dma_addr = vb2_dma_contig_plane_dma_addr(vb2_buf, 0); + } else { + csi->active_vb2_buf[i] = NULL; + dma_addr = csi->underrun_buf.dma_addr; + } + + imx7_csi_update_buf(csi, dma_addr, i); + } +} + +static void imx7_csi_dma_unsetup_vb2_buf(struct imx7_csi *csi, + enum vb2_buffer_state return_status) +{ + struct imx7_csi_vb2_buffer *buf; + int i; + + /* return any remaining active frames with return_status */ + for (i = 0; i < 2; i++) { + buf = csi->active_vb2_buf[i]; + if (buf) { + struct vb2_buffer *vb = &buf->vbuf.vb2_buf; + + vb->timestamp = ktime_get_ns(); + vb2_buffer_done(vb, return_status); + csi->active_vb2_buf[i] = NULL; + } + } +} + +static void imx7_csi_free_dma_buf(struct imx7_csi *csi, + struct imx7_csi_dma_buf *buf) +{ + if (buf->virt) + dma_free_coherent(csi->dev, buf->len, buf->virt, buf->dma_addr); + + buf->virt = NULL; + buf->dma_addr = 0; +} + +static int imx7_csi_alloc_dma_buf(struct imx7_csi *csi, + struct imx7_csi_dma_buf *buf, int size) +{ + imx7_csi_free_dma_buf(csi, buf); + + buf->len = PAGE_ALIGN(size); + buf->virt = dma_alloc_coherent(csi->dev, buf->len, &buf->dma_addr, + GFP_DMA | GFP_KERNEL); + if (!buf->virt) + return -ENOMEM; + + return 0; +} + +static int imx7_csi_dma_setup(struct imx7_csi *csi) +{ + int ret; + + ret = imx7_csi_alloc_dma_buf(csi, &csi->underrun_buf, + csi->vdev_fmt.sizeimage); + if (ret < 0) { + v4l2_warn(&csi->sd, "consider increasing the CMA area\n"); + return ret; + } + + csi->frame_sequence = 0; + csi->last_eof = false; + init_completion(&csi->last_eof_completion); + + imx7_csi_setup_vb2_buf(csi); + + return 0; +} + +static void imx7_csi_dma_cleanup(struct imx7_csi *csi, + enum vb2_buffer_state return_status) +{ + imx7_csi_dma_unsetup_vb2_buf(csi, return_status); + imx7_csi_free_dma_buf(csi, &csi->underrun_buf); +} + +static void imx7_csi_dma_stop(struct imx7_csi *csi) +{ + unsigned long timeout_jiffies; + unsigned long flags; + int ret; + + /* mark next EOF interrupt as the last before stream off */ + spin_lock_irqsave(&csi->irqlock, flags); + csi->last_eof = true; + spin_unlock_irqrestore(&csi->irqlock, flags); + + /* + * and then wait for interrupt handler to mark completion. + */ + timeout_jiffies = msecs_to_jiffies(IMX7_CSI_VIDEO_EOF_TIMEOUT); + ret = wait_for_completion_timeout(&csi->last_eof_completion, + timeout_jiffies); + if (ret == 0) + v4l2_warn(&csi->sd, "wait last EOF timeout\n"); + + imx7_csi_hw_disable_irq(csi); +} + +static void imx7_csi_configure(struct imx7_csi *csi) +{ + struct v4l2_pix_format *out_pix = &csi->vdev_fmt; + int width = out_pix->width; + u32 stride = 0; + u32 cr3 = BIT_FRMCNT_RST; + u32 cr1, cr18; + + cr18 = imx7_csi_reg_read(csi, CSI_CSICR18); + + cr18 &= ~(BIT_CSI_HW_ENABLE | BIT_MIPI_DATA_FORMAT_MASK | + BIT_DATA_FROM_MIPI | BIT_MIPI_DOUBLE_CMPNT | + BIT_BASEADDR_CHG_ERR_EN | BIT_BASEADDR_SWITCH_SEL | + BIT_BASEADDR_SWITCH_EN | BIT_DEINTERLACE_EN); + + if (out_pix->field == V4L2_FIELD_INTERLACED) { + cr18 |= BIT_DEINTERLACE_EN; + stride = out_pix->width; + } + + if (!csi->is_csi2) { + cr1 = BIT_SOF_POL | BIT_REDGE | BIT_GCLK_MODE | BIT_HSYNC_POL + | BIT_FCC | BIT_MCLKDIV(1) | BIT_MCLKEN; + + cr18 |= BIT_BASEADDR_SWITCH_EN | BIT_BASEADDR_SWITCH_SEL | + BIT_BASEADDR_CHG_ERR_EN; + + if (out_pix->pixelformat == V4L2_PIX_FMT_UYVY || + out_pix->pixelformat == V4L2_PIX_FMT_YUYV) + width *= 2; + } else { + cr1 = BIT_SOF_POL | BIT_REDGE | BIT_HSYNC_POL | BIT_FCC + | BIT_MCLKDIV(1) | BIT_MCLKEN; + + cr18 |= BIT_DATA_FROM_MIPI; + + switch (csi->format_mbus[IMX7_CSI_PAD_SINK].code) { + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + cr18 |= BIT_MIPI_DATA_FORMAT_RAW8; + break; + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + cr3 |= BIT_TWO_8BIT_SENSOR; + cr18 |= BIT_MIPI_DATA_FORMAT_RAW10; + break; + case MEDIA_BUS_FMT_Y12_1X12: + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + cr3 |= BIT_TWO_8BIT_SENSOR; + cr18 |= BIT_MIPI_DATA_FORMAT_RAW12; + break; + case MEDIA_BUS_FMT_Y14_1X14: + case MEDIA_BUS_FMT_SBGGR14_1X14: + case MEDIA_BUS_FMT_SGBRG14_1X14: + case MEDIA_BUS_FMT_SGRBG14_1X14: + case MEDIA_BUS_FMT_SRGGB14_1X14: + cr3 |= BIT_TWO_8BIT_SENSOR; + cr18 |= BIT_MIPI_DATA_FORMAT_RAW14; + break; + + /* + * The CSI bridge has a 16-bit input bus. Depending on the + * connected source, data may be transmitted with 8 or 10 bits + * per clock sample (in bits [9:2] or [9:0] respectively) or + * with 16 bits per clock sample (in bits [15:0]). The data is + * then packed into a 32-bit FIFO (as shown in figure 13-11 of + * the i.MX8MM reference manual rev. 3). + * + * The data packing in a 32-bit FIFO input word is controlled by + * the CR3 TWO_8BIT_SENSOR field (also known as SENSOR_16BITS in + * the i.MX8MM reference manual). When set to 0, data packing + * groups four 8-bit input samples (bits [9:2]). When set to 1, + * data packing groups two 16-bit input samples (bits [15:0]). + * + * The register field CR18 MIPI_DOUBLE_CMPNT also needs to be + * configured according to the input format for YUV 4:2:2 data. + * The field controls the gasket between the CSI-2 receiver and + * the CSI bridge. On i.MX7 and i.MX8MM, the field must be set + * to 1 when the CSIS outputs 16-bit samples. On i.MX8MQ, the + * gasket ignores the MIPI_DOUBLE_CMPNT bit and YUV 4:2:2 always + * uses 16-bit samples. Setting MIPI_DOUBLE_CMPNT in that case + * has no effect, but doesn't cause any issue. + */ + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_YUYV8_2X8: + cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + cr3 |= BIT_TWO_8BIT_SENSOR; + cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B | + BIT_MIPI_DOUBLE_CMPNT; + break; + } + } + + imx7_csi_reg_write(csi, cr1, CSI_CSICR1); + imx7_csi_reg_write(csi, BIT_DMA_BURST_TYPE_RFF_INCR16, CSI_CSICR2); + imx7_csi_reg_write(csi, cr3, CSI_CSICR3); + imx7_csi_reg_write(csi, cr18, CSI_CSICR18); + + imx7_csi_reg_write(csi, (width * out_pix->height) >> 2, CSI_CSIRXCNT); + imx7_csi_reg_write(csi, BIT_IMAGE_WIDTH(width) | + BIT_IMAGE_HEIGHT(out_pix->height), + CSI_CSIIMAG_PARA); + imx7_csi_reg_write(csi, stride, CSI_CSIFBUF_PARA); +} + +static int imx7_csi_init(struct imx7_csi *csi) +{ + int ret; + + ret = clk_prepare_enable(csi->mclk); + if (ret < 0) + return ret; + + imx7_csi_configure(csi); + + ret = imx7_csi_dma_setup(csi); + if (ret < 0) + return ret; + + return 0; +} + +static void imx7_csi_deinit(struct imx7_csi *csi, + enum vb2_buffer_state return_status) +{ + imx7_csi_dma_cleanup(csi, return_status); + imx7_csi_init_default(csi); + imx7_csi_dmareq_rff_disable(csi); + clk_disable_unprepare(csi->mclk); +} + +static void imx7_csi_baseaddr_switch_on_second_frame(struct imx7_csi *csi) +{ + u32 cr18 = imx7_csi_reg_read(csi, CSI_CSICR18); + + cr18 |= BIT_BASEADDR_SWITCH_EN | BIT_BASEADDR_SWITCH_SEL | + BIT_BASEADDR_CHG_ERR_EN; + cr18 |= BIT_MASK_OPTION_SECOND_FRAME; + imx7_csi_reg_write(csi, cr18, CSI_CSICR18); +} + +static void imx7_csi_enable(struct imx7_csi *csi) +{ + /* Clear the Rx FIFO and reflash the DMA controller. */ + imx7_csi_rx_fifo_clear(csi); + imx7_csi_dma_reflash(csi); + + usleep_range(2000, 3000); + + /* Clear and enable the interrupts. */ + imx7_csi_irq_clear(csi); + imx7_csi_hw_enable_irq(csi); + + /* Enable the RxFIFO DMA and the CSI. */ + imx7_csi_dmareq_rff_enable(csi); + imx7_csi_hw_enable(csi); + + if (csi->model == IMX7_CSI_IMX8MQ) + imx7_csi_baseaddr_switch_on_second_frame(csi); +} + +static void imx7_csi_disable(struct imx7_csi *csi) +{ + imx7_csi_dma_stop(csi); + + imx7_csi_dmareq_rff_disable(csi); + + imx7_csi_hw_disable_irq(csi); + + imx7_csi_hw_disable(csi); +} + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static void imx7_csi_error_recovery(struct imx7_csi *csi) +{ + imx7_csi_hw_disable(csi); + + imx7_csi_rx_fifo_clear(csi); + + imx7_csi_dma_reflash(csi); + + imx7_csi_hw_enable(csi); +} + +static void imx7_csi_vb2_buf_done(struct imx7_csi *csi) +{ + struct imx7_csi_vb2_buffer *done, *next; + struct vb2_buffer *vb; + dma_addr_t dma_addr; + + done = csi->active_vb2_buf[csi->buf_num]; + if (done) { + done->vbuf.field = csi->vdev_fmt.field; + done->vbuf.sequence = csi->frame_sequence; + vb = &done->vbuf.vb2_buf; + vb->timestamp = ktime_get_ns(); + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } + csi->frame_sequence++; + + /* get next queued buffer */ + next = imx7_csi_video_next_buf(csi); + if (next) { + dma_addr = vb2_dma_contig_plane_dma_addr(&next->vbuf.vb2_buf, 0); + csi->active_vb2_buf[csi->buf_num] = next; + } else { + dma_addr = csi->underrun_buf.dma_addr; + csi->active_vb2_buf[csi->buf_num] = NULL; + } + + imx7_csi_update_buf(csi, dma_addr, csi->buf_num); +} + +static irqreturn_t imx7_csi_irq_handler(int irq, void *data) +{ + struct imx7_csi *csi = data; + u32 status; + + spin_lock(&csi->irqlock); + + status = imx7_csi_irq_clear(csi); + + if (status & BIT_RFF_OR_INT) { + dev_warn(csi->dev, "Rx fifo overflow\n"); + imx7_csi_error_recovery(csi); + } + + if (status & BIT_HRESP_ERR_INT) { + dev_warn(csi->dev, "Hresponse error detected\n"); + imx7_csi_error_recovery(csi); + } + + if (status & BIT_ADDR_CH_ERR_INT) { + imx7_csi_hw_disable(csi); + + imx7_csi_dma_reflash(csi); + + imx7_csi_hw_enable(csi); + } + + if ((status & BIT_DMA_TSF_DONE_FB1) && + (status & BIT_DMA_TSF_DONE_FB2)) { + /* + * For both FB1 and FB2 interrupter bits set case, + * CSI DMA is work in one of FB1 and FB2 buffer, + * but software can not know the state. + * Skip it to avoid base address updated + * when csi work in field0 and field1 will write to + * new base address. + */ + } else if (status & BIT_DMA_TSF_DONE_FB1) { + csi->buf_num = 0; + } else if (status & BIT_DMA_TSF_DONE_FB2) { + csi->buf_num = 1; + } + + if ((status & BIT_DMA_TSF_DONE_FB1) || + (status & BIT_DMA_TSF_DONE_FB2)) { + imx7_csi_vb2_buf_done(csi); + + if (csi->last_eof) { + complete(&csi->last_eof_completion); + csi->last_eof = false; + } + } + + spin_unlock(&csi->irqlock); + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * Format Helpers + */ + +#define IMX_BUS_FMTS(fmt...) (const u32[]) {fmt, 0} + +/* + * List of supported pixel formats for the subdevs. Keep V4L2_PIX_FMT_UYVY and + * MEDIA_BUS_FMT_UYVY8_2X8 first to match IMX7_CSI_DEF_PIX_FORMAT and + * IMX7_CSI_DEF_MBUS_CODE. + * + * TODO: Restrict the supported formats list based on the SoC integration. + * + * The CSI bridge can be configured to sample pixel components from the Rx queue + * in single (8bpp) or double (16bpp) component modes. Image format variants + * with different sample sizes (ie YUYV_2X8 vs YUYV_1X16) determine the pixel + * components sampling size per each clock cycle and their packing mode (see + * imx7_csi_configure() for details). + * + * As the CSI bridge can be interfaced with different IP blocks depending on the + * SoC model it is integrated on, the Rx queue sampling size should match the + * size of the samples transferred by the transmitting IP block. To avoid + * misconfigurations of the capture pipeline, the enumeration of the supported + * formats should be restricted to match the pixel source transmitting mode. + * + * Example: i.MX8MM SoC integrates the CSI bridge with the Samsung CSIS CSI-2 + * receiver which operates in dual pixel sampling mode. The CSI bridge should + * only expose the 1X16 formats variant which instructs it to operate in dual + * pixel sampling mode. When the CSI bridge is instead integrated on an i.MX7, + * which supports both serial and parallel input, it should expose both + * variants. + * + * This currently only applies to YUYV formats, but other formats might need to + * be handled in the same way. + */ +static const struct imx7_csi_pixfmt pixel_formats[] = { + /*** YUV formats start here ***/ + { + .fourcc = V4L2_PIX_FMT_UYVY, + .codes = IMX_BUS_FMTS( + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_UYVY8_1X16 + ), + .yuv = true, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .codes = IMX_BUS_FMTS( + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YUYV8_1X16 + ), + .yuv = true, + .bpp = 16, + }, + /*** raw bayer and grayscale formats start here ***/ + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR14_1X14), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG14_1X14), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG14_1X14), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB14_1X14), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_Y10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_Y12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_Y14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y14_1X14), + .bpp = 16, + }, +}; + +/* + * Search in the pixel_formats[] array for an entry with the given fourcc + * return it. + */ +static const struct imx7_csi_pixfmt *imx7_csi_find_pixel_format(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { + const struct imx7_csi_pixfmt *fmt = &pixel_formats[i]; + + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +/* + * Search in the pixel_formats[] array for an entry with the given media + * bus code and return it. + */ +static const struct imx7_csi_pixfmt *imx7_csi_find_mbus_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { + const struct imx7_csi_pixfmt *fmt = &pixel_formats[i]; + unsigned int j; + + if (!fmt->codes) + continue; + + for (j = 0; fmt->codes[j]; j++) { + if (code == fmt->codes[j]) + return fmt; + } + } + + return NULL; +} + +/* + * Enumerate entries in the pixel_formats[] array that match the + * requested search criteria. Return the media-bus code that matches + * the search criteria at the requested match index. + * + * @code: The returned media-bus code that matches the search criteria at + * the requested match index. + * @index: The requested match index. + */ +static int imx7_csi_enum_mbus_formats(u32 *code, u32 index) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { + const struct imx7_csi_pixfmt *fmt = &pixel_formats[i]; + unsigned int j; + + if (!fmt->codes) + continue; + + for (j = 0; fmt->codes[j]; j++) { + if (index == 0) { + *code = fmt->codes[j]; + return 0; + } + + index--; + } + } + + return -EINVAL; +} + +static int imx7_csi_mbus_fmt_to_pix_fmt(struct v4l2_pix_format *pix, + const struct v4l2_mbus_framefmt *mbus, + const struct imx7_csi_pixfmt *cc) +{ + u32 width; + u32 stride; + + if (!cc) { + cc = imx7_csi_find_mbus_format(mbus->code); + if (!cc) + return -EINVAL; + } + + /* Round up width for minimum burst size */ + width = round_up(mbus->width, 8); + + /* Round up stride for IDMAC line start address alignment */ + stride = round_up((width * cc->bpp) >> 3, 8); + + pix->width = width; + pix->height = mbus->height; + pix->pixelformat = cc->fourcc; + pix->colorspace = mbus->colorspace; + pix->xfer_func = mbus->xfer_func; + pix->ycbcr_enc = mbus->ycbcr_enc; + pix->quantization = mbus->quantization; + pix->field = mbus->field; + pix->bytesperline = stride; + pix->sizeimage = stride * pix->height; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Video Capture Device - IOCTLs + */ + +static int imx7_csi_video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct imx7_csi *csi = video_drvdata(file); + + strscpy(cap->driver, IMX7_CSI_VIDEO_NAME, sizeof(cap->driver)); + strscpy(cap->card, IMX7_CSI_VIDEO_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", dev_name(csi->dev)); + + return 0; +} + +static int imx7_csi_video_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + unsigned int index = f->index; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { + const struct imx7_csi_pixfmt *fmt = &pixel_formats[i]; + + /* + * If a media bus code is specified, only consider formats that + * match it. + */ + if (f->mbus_code) { + unsigned int j; + + if (!fmt->codes) + continue; + + for (j = 0; fmt->codes[j]; j++) { + if (f->mbus_code == fmt->codes[j]) + break; + } + + if (!fmt->codes[j]) + continue; + } + + if (index == 0) { + f->pixelformat = fmt->fourcc; + return 0; + } + + index--; + } + + return -EINVAL; +} + +static int imx7_csi_video_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct imx7_csi_pixfmt *cc; + + if (fsize->index > 0) + return -EINVAL; + + cc = imx7_csi_find_pixel_format(fsize->pixel_format); + if (!cc) + return -EINVAL; + + /* + * TODO: The constraints are hardware-specific and may depend on the + * pixel format. This should come from the driver using + * imx_media_capture. + */ + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = 1; + fsize->stepwise.max_width = 65535; + fsize->stepwise.min_height = 1; + fsize->stepwise.max_height = 65535; + fsize->stepwise.step_width = 1; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int imx7_csi_video_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct imx7_csi *csi = video_drvdata(file); + + f->fmt.pix = csi->vdev_fmt; + + return 0; +} + +static const struct imx7_csi_pixfmt * +__imx7_csi_video_try_fmt(struct v4l2_pix_format *pixfmt, + struct v4l2_rect *compose) +{ + struct v4l2_mbus_framefmt fmt_src; + const struct imx7_csi_pixfmt *cc; + + /* + * Find the pixel format, default to the first supported format if not + * found. + */ + cc = imx7_csi_find_pixel_format(pixfmt->pixelformat); + if (!cc) { + pixfmt->pixelformat = IMX7_CSI_DEF_PIX_FORMAT; + cc = imx7_csi_find_pixel_format(pixfmt->pixelformat); + } + + /* Allow IDMAC interweave but enforce field order from source. */ + if (V4L2_FIELD_IS_INTERLACED(pixfmt->field)) { + switch (pixfmt->field) { + case V4L2_FIELD_SEQ_TB: + pixfmt->field = V4L2_FIELD_INTERLACED_TB; + break; + case V4L2_FIELD_SEQ_BT: + pixfmt->field = V4L2_FIELD_INTERLACED_BT; + break; + default: + break; + } + } + + v4l2_fill_mbus_format(&fmt_src, pixfmt, 0); + imx7_csi_mbus_fmt_to_pix_fmt(pixfmt, &fmt_src, cc); + + if (compose) { + compose->width = fmt_src.width; + compose->height = fmt_src.height; + } + + return cc; +} + +static int imx7_csi_video_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + __imx7_csi_video_try_fmt(&f->fmt.pix, NULL); + return 0; +} + +static int imx7_csi_video_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct imx7_csi *csi = video_drvdata(file); + const struct imx7_csi_pixfmt *cc; + + if (vb2_is_busy(&csi->q)) { + dev_err(csi->dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + cc = __imx7_csi_video_try_fmt(&f->fmt.pix, &csi->vdev_compose); + + csi->vdev_cc = cc; + csi->vdev_fmt = f->fmt.pix; + + return 0; +} + +static int imx7_csi_video_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct imx7_csi *csi = video_drvdata(file); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + /* The compose rectangle is fixed to the source format. */ + s->r = csi->vdev_compose; + break; + case V4L2_SEL_TGT_COMPOSE_PADDED: + /* + * The hardware writes with a configurable but fixed DMA burst + * size. If the source format width is not burst size aligned, + * the written frame contains padding to the right. + */ + s->r.left = 0; + s->r.top = 0; + s->r.width = csi->vdev_fmt.width; + s->r.height = csi->vdev_fmt.height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ioctl_ops imx7_csi_video_ioctl_ops = { + .vidioc_querycap = imx7_csi_video_querycap, + + .vidioc_enum_fmt_vid_cap = imx7_csi_video_enum_fmt_vid_cap, + .vidioc_enum_framesizes = imx7_csi_video_enum_framesizes, + + .vidioc_g_fmt_vid_cap = imx7_csi_video_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = imx7_csi_video_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = imx7_csi_video_s_fmt_vid_cap, + + .vidioc_g_selection = imx7_csi_video_g_selection, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * Video Capture Device - Queue Operations + */ + +static int imx7_csi_video_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix = &csi->vdev_fmt; + unsigned int count = *nbuffers; + + if (vq->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (*nplanes) { + if (*nplanes != 1 || sizes[0] < pix->sizeimage) + return -EINVAL; + count += vq->num_buffers; + } + + count = min_t(__u32, IMX7_CSI_VIDEO_MEM_LIMIT / pix->sizeimage, count); + + if (*nplanes) + *nbuffers = (count < vq->num_buffers) ? 0 : + count - vq->num_buffers; + else + *nbuffers = count; + + *nplanes = 1; + sizes[0] = pix->sizeimage; + + return 0; +} + +static int imx7_csi_video_buf_init(struct vb2_buffer *vb) +{ + struct imx7_csi_vb2_buffer *buf = to_imx7_csi_vb2_buffer(vb); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int imx7_csi_video_buf_prepare(struct vb2_buffer *vb) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_pix_format *pix = &csi->vdev_fmt; + + if (vb2_plane_size(vb, 0) < pix->sizeimage) { + dev_err(csi->dev, + "data will not fit into plane (%lu < %lu)\n", + vb2_plane_size(vb, 0), (long)pix->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, pix->sizeimage); + + return 0; +} + +static bool imx7_csi_fast_track_buffer(struct imx7_csi *csi, + struct imx7_csi_vb2_buffer *buf) +{ + unsigned long flags; + dma_addr_t dma_addr; + int buf_num; + u32 isr; + + if (!csi->is_streaming) + return false; + + dma_addr = vb2_dma_contig_plane_dma_addr(&buf->vbuf.vb2_buf, 0); + + /* + * buf_num holds the framebuffer ID of the most recently (*not* the + * next anticipated) triggered interrupt. Without loss of generality, + * if buf_num is 0, the hardware is capturing to FB2. If FB1 has been + * programmed with a dummy buffer (as indicated by active_vb2_buf[0] + * being NULL), then we can fast-track the new buffer by programming + * its address in FB1 before the hardware completes FB2, instead of + * adding it to the buffer queue and incurring a delay of one + * additional frame. + * + * The irqlock prevents races with the interrupt handler that updates + * buf_num when it programs the next buffer, but we can still race with + * the hardware if we program the buffer in FB1 just after the hardware + * completes FB2 and switches to FB1 and before buf_num can be updated + * by the interrupt handler for FB2. The fast-tracked buffer would + * then be ignored by the hardware while the driver would think it has + * successfully been processed. + * + * To avoid this problem, if we can't avoid the race, we can detect + * that we have lost it by checking, after programming the buffer in + * FB1, if the interrupt flag indicating completion of FB2 has been + * raised. If that is not the case, fast-tracking succeeded, and we can + * update active_vb2_buf[0]. Otherwise, we may or may not have lost the + * race (as the interrupt flag may have been raised just after + * programming FB1 and before we read the interrupt status register), + * and we need to assume the worst case of a race loss and queue the + * buffer through the slow path. + */ + + spin_lock_irqsave(&csi->irqlock, flags); + + buf_num = csi->buf_num; + if (csi->active_vb2_buf[buf_num]) { + spin_unlock_irqrestore(&csi->irqlock, flags); + return false; + } + + imx7_csi_update_buf(csi, dma_addr, buf_num); + + isr = imx7_csi_reg_read(csi, CSI_CSISR); + if (isr & (buf_num ? BIT_DMA_TSF_DONE_FB1 : BIT_DMA_TSF_DONE_FB2)) { + /* + * The interrupt for the /other/ FB just came (the isr hasn't + * run yet though, because we have the lock here); we can't be + * sure we've programmed buf_num FB in time, so queue the buffer + * to the buffer queue normally. No need to undo writing the FB + * register, since we won't return it as active_vb2_buf is NULL, + * so it's okay to potentially write it to both FB1 and FB2; + * only the one where it was queued normally will be returned. + */ + spin_unlock_irqrestore(&csi->irqlock, flags); + return false; + } + + csi->active_vb2_buf[buf_num] = buf; + + spin_unlock_irqrestore(&csi->irqlock, flags); + return true; +} + +static void imx7_csi_video_buf_queue(struct vb2_buffer *vb) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vb->vb2_queue); + struct imx7_csi_vb2_buffer *buf = to_imx7_csi_vb2_buffer(vb); + unsigned long flags; + + if (imx7_csi_fast_track_buffer(csi, buf)) + return; + + spin_lock_irqsave(&csi->q_lock, flags); + + list_add_tail(&buf->list, &csi->ready_q); + + spin_unlock_irqrestore(&csi->q_lock, flags); +} + +static int imx7_csi_video_validate_fmt(struct imx7_csi *csi) +{ + struct v4l2_subdev_format fmt_src; + const struct imx7_csi_pixfmt *cc; + int ret; + + /* Retrieve the media bus format on the source subdev. */ + fmt_src.pad = IMX7_CSI_PAD_SRC; + fmt_src.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(&csi->sd, pad, get_fmt, NULL, &fmt_src); + if (ret) + return ret; + + /* + * Verify that the media bus size matches the size set on the video + * node. It is sufficient to check the compose rectangle size without + * checking the rounded size from pix_fmt, as the rounded size is + * derived directly from the compose rectangle size, and will thus + * always match if the compose rectangle matches. + */ + if (csi->vdev_compose.width != fmt_src.format.width || + csi->vdev_compose.height != fmt_src.format.height) + return -EPIPE; + + /* + * Verify that the media bus code is compatible with the pixel format + * set on the video node. + */ + cc = imx7_csi_find_mbus_format(fmt_src.format.code); + if (!cc || csi->vdev_cc->yuv != cc->yuv) + return -EPIPE; + + return 0; +} + +static int imx7_csi_video_start_streaming(struct vb2_queue *vq, + unsigned int count) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vq); + struct imx7_csi_vb2_buffer *buf, *tmp; + unsigned long flags; + int ret; + + ret = imx7_csi_video_validate_fmt(csi); + if (ret) { + dev_err(csi->dev, "capture format not valid\n"); + goto err_buffers; + } + + mutex_lock(&csi->mdev.graph_mutex); + + ret = __video_device_pipeline_start(csi->vdev, &csi->pipe); + if (ret) + goto err_unlock; + + ret = v4l2_subdev_call(&csi->sd, video, s_stream, 1); + if (ret) + goto err_stop; + + mutex_unlock(&csi->mdev.graph_mutex); + + return 0; + +err_stop: + __video_device_pipeline_stop(csi->vdev); +err_unlock: + mutex_unlock(&csi->mdev.graph_mutex); + dev_err(csi->dev, "pipeline start failed with %d\n", ret); +err_buffers: + spin_lock_irqsave(&csi->q_lock, flags); + list_for_each_entry_safe(buf, tmp, &csi->ready_q, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vbuf.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&csi->q_lock, flags); + return ret; +} + +static void imx7_csi_video_stop_streaming(struct vb2_queue *vq) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vq); + struct imx7_csi_vb2_buffer *frame; + struct imx7_csi_vb2_buffer *tmp; + unsigned long flags; + + mutex_lock(&csi->mdev.graph_mutex); + v4l2_subdev_call(&csi->sd, video, s_stream, 0); + __video_device_pipeline_stop(csi->vdev); + mutex_unlock(&csi->mdev.graph_mutex); + + /* release all active buffers */ + spin_lock_irqsave(&csi->q_lock, flags); + list_for_each_entry_safe(frame, tmp, &csi->ready_q, list) { + list_del(&frame->list); + vb2_buffer_done(&frame->vbuf.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&csi->q_lock, flags); +} + +static const struct vb2_ops imx7_csi_video_qops = { + .queue_setup = imx7_csi_video_queue_setup, + .buf_init = imx7_csi_video_buf_init, + .buf_prepare = imx7_csi_video_buf_prepare, + .buf_queue = imx7_csi_video_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = imx7_csi_video_start_streaming, + .stop_streaming = imx7_csi_video_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * Video Capture Device - File Operations + */ + +static int imx7_csi_video_open(struct file *file) +{ + struct imx7_csi *csi = video_drvdata(file); + int ret; + + if (mutex_lock_interruptible(&csi->vdev_mutex)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret) { + dev_err(csi->dev, "v4l2_fh_open failed\n"); + goto out; + } + + ret = v4l2_pipeline_pm_get(&csi->vdev->entity); + if (ret) + v4l2_fh_release(file); + +out: + mutex_unlock(&csi->vdev_mutex); + return ret; +} + +static int imx7_csi_video_release(struct file *file) +{ + struct imx7_csi *csi = video_drvdata(file); + struct vb2_queue *vq = &csi->q; + + mutex_lock(&csi->vdev_mutex); + + if (file->private_data == vq->owner) { + vb2_queue_release(vq); + vq->owner = NULL; + } + + v4l2_pipeline_pm_put(&csi->vdev->entity); + + v4l2_fh_release(file); + mutex_unlock(&csi->vdev_mutex); + return 0; +} + +static const struct v4l2_file_operations imx7_csi_video_fops = { + .owner = THIS_MODULE, + .open = imx7_csi_video_open, + .release = imx7_csi_video_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* ----------------------------------------------------------------------------- + * Video Capture Device - Init & Cleanup + */ + +static struct imx7_csi_vb2_buffer *imx7_csi_video_next_buf(struct imx7_csi *csi) +{ + struct imx7_csi_vb2_buffer *buf = NULL; + unsigned long flags; + + spin_lock_irqsave(&csi->q_lock, flags); + + /* get next queued buffer */ + if (!list_empty(&csi->ready_q)) { + buf = list_entry(csi->ready_q.next, struct imx7_csi_vb2_buffer, + list); + list_del(&buf->list); + } + + spin_unlock_irqrestore(&csi->q_lock, flags); + + return buf; +} + +static int imx7_csi_video_init_format(struct imx7_csi *csi) +{ + struct v4l2_subdev_format fmt_src = { + .pad = IMX7_CSI_PAD_SRC, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + fmt_src.format.code = IMX7_CSI_DEF_MBUS_CODE; + fmt_src.format.width = IMX7_CSI_DEF_PIX_WIDTH; + fmt_src.format.height = IMX7_CSI_DEF_PIX_HEIGHT; + + imx7_csi_mbus_fmt_to_pix_fmt(&csi->vdev_fmt, &fmt_src.format, NULL); + csi->vdev_compose.width = fmt_src.format.width; + csi->vdev_compose.height = fmt_src.format.height; + + csi->vdev_cc = imx7_csi_find_pixel_format(csi->vdev_fmt.pixelformat); + + return 0; +} + +static int imx7_csi_video_register(struct imx7_csi *csi) +{ + struct v4l2_subdev *sd = &csi->sd; + struct v4l2_device *v4l2_dev = sd->v4l2_dev; + struct video_device *vdev = csi->vdev; + int ret; + + vdev->v4l2_dev = v4l2_dev; + + /* Initialize the default format and compose rectangle. */ + ret = imx7_csi_video_init_format(csi); + if (ret < 0) + return ret; + + /* Register the video device. */ + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(csi->dev, "Failed to register video device\n"); + return ret; + } + + dev_info(csi->dev, "Registered %s as /dev/%s\n", vdev->name, + video_device_node_name(vdev)); + + /* Create the link from the CSI subdev to the video device. */ + ret = media_create_pad_link(&sd->entity, IMX7_CSI_PAD_SRC, + &vdev->entity, 0, MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(csi->dev, "failed to create link to device node\n"); + video_unregister_device(vdev); + return ret; + } + + return 0; +} + +static void imx7_csi_video_unregister(struct imx7_csi *csi) +{ + media_entity_cleanup(&csi->vdev->entity); + video_unregister_device(csi->vdev); +} + +static int imx7_csi_video_init(struct imx7_csi *csi) +{ + struct video_device *vdev; + struct vb2_queue *vq; + int ret; + + mutex_init(&csi->vdev_mutex); + INIT_LIST_HEAD(&csi->ready_q); + spin_lock_init(&csi->q_lock); + + /* Allocate and initialize the video device. */ + vdev = video_device_alloc(); + if (!vdev) + return -ENOMEM; + + vdev->fops = &imx7_csi_video_fops; + vdev->ioctl_ops = &imx7_csi_video_ioctl_ops; + vdev->minor = -1; + vdev->release = video_device_release; + vdev->vfl_dir = VFL_DIR_RX; + vdev->tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING + | V4L2_CAP_IO_MC; + vdev->lock = &csi->vdev_mutex; + vdev->queue = &csi->q; + + snprintf(vdev->name, sizeof(vdev->name), "%s capture", csi->sd.name); + + video_set_drvdata(vdev, csi); + csi->vdev = vdev; + + /* Initialize the video device pad. */ + csi->vdev_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad); + if (ret) { + video_device_release(vdev); + return ret; + } + + /* Initialize the vb2 queue. */ + vq = &csi->q; + vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vq->io_modes = VB2_MMAP | VB2_DMABUF; + vq->drv_priv = csi; + vq->buf_struct_size = sizeof(struct imx7_csi_vb2_buffer); + vq->ops = &imx7_csi_video_qops; + vq->mem_ops = &vb2_dma_contig_memops; + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vq->lock = &csi->vdev_mutex; + vq->min_buffers_needed = 2; + vq->dev = csi->dev; + + ret = vb2_queue_init(vq); + if (ret) { + dev_err(csi->dev, "vb2_queue_init failed\n"); + video_device_release(vdev); + return ret; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdev Operations + */ + +static int imx7_csi_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&csi->lock); + + if (!csi->src_sd) { + ret = -EPIPE; + goto out_unlock; + } + + if (csi->is_streaming == !!enable) + goto out_unlock; + + if (enable) { + ret = imx7_csi_init(csi); + if (ret < 0) + goto out_unlock; + + ret = v4l2_subdev_call(csi->src_sd, video, s_stream, 1); + if (ret < 0) { + imx7_csi_deinit(csi, VB2_BUF_STATE_QUEUED); + goto out_unlock; + } + + imx7_csi_enable(csi); + } else { + imx7_csi_disable(csi); + + v4l2_subdev_call(csi->src_sd, video, s_stream, 0); + + imx7_csi_deinit(csi, VB2_BUF_STATE_ERROR); + } + + csi->is_streaming = !!enable; + +out_unlock: + mutex_unlock(&csi->lock); + + return ret; +} + +static struct v4l2_mbus_framefmt * +imx7_csi_get_format(struct imx7_csi *csi, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&csi->sd, sd_state, pad); + + return &csi->format_mbus[pad]; +} + +static int imx7_csi_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + const enum v4l2_subdev_format_whence which = + sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + const struct imx7_csi_pixfmt *cc; + int i; + + cc = imx7_csi_find_mbus_format(IMX7_CSI_DEF_MBUS_CODE); + + for (i = 0; i < IMX7_CSI_PADS_NUM; i++) { + struct v4l2_mbus_framefmt *mf = + imx7_csi_get_format(csi, sd_state, i, which); + + mf->code = IMX7_CSI_DEF_MBUS_CODE; + mf->width = IMX7_CSI_DEF_PIX_WIDTH; + mf->height = IMX7_CSI_DEF_PIX_HEIGHT; + mf->field = V4L2_FIELD_NONE; + + mf->colorspace = V4L2_COLORSPACE_SRGB; + mf->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(mf->colorspace); + mf->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(mf->colorspace); + mf->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(!cc->yuv, + mf->colorspace, mf->ycbcr_enc); + + csi->cc[i] = cc; + } + + return 0; +} + +static int imx7_csi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *in_fmt; + int ret = 0; + + mutex_lock(&csi->lock); + + in_fmt = imx7_csi_get_format(csi, sd_state, IMX7_CSI_PAD_SINK, + code->which); + + switch (code->pad) { + case IMX7_CSI_PAD_SINK: + ret = imx7_csi_enum_mbus_formats(&code->code, code->index); + break; + case IMX7_CSI_PAD_SRC: + if (code->index != 0) { + ret = -EINVAL; + goto out_unlock; + } + + code->code = in_fmt->code; + break; + default: + ret = -EINVAL; + } + +out_unlock: + mutex_unlock(&csi->lock); + + return ret; +} + +static int imx7_csi_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + mutex_lock(&csi->lock); + + fmt = imx7_csi_get_format(csi, sd_state, sdformat->pad, + sdformat->which); + if (!fmt) { + ret = -EINVAL; + goto out_unlock; + } + + sdformat->format = *fmt; + +out_unlock: + mutex_unlock(&csi->lock); + + return ret; +} + +/* + * Default the colorspace in tryfmt to SRGB if set to an unsupported + * colorspace or not initialized. Then set the remaining colorimetry + * parameters based on the colorspace if they are uninitialized. + * + * tryfmt->code must be set on entry. + */ +static void imx7_csi_try_colorimetry(struct v4l2_mbus_framefmt *tryfmt) +{ + const struct imx7_csi_pixfmt *cc; + bool is_rgb = false; + + cc = imx7_csi_find_mbus_format(tryfmt->code); + if (cc && !cc->yuv) + is_rgb = true; + + switch (tryfmt->colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_JPEG: + case V4L2_COLORSPACE_SRGB: + case V4L2_COLORSPACE_BT2020: + case V4L2_COLORSPACE_OPRGB: + case V4L2_COLORSPACE_DCI_P3: + case V4L2_COLORSPACE_RAW: + break; + default: + tryfmt->colorspace = V4L2_COLORSPACE_SRGB; + break; + } + + if (tryfmt->xfer_func == V4L2_XFER_FUNC_DEFAULT) + tryfmt->xfer_func = + V4L2_MAP_XFER_FUNC_DEFAULT(tryfmt->colorspace); + + if (tryfmt->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) + tryfmt->ycbcr_enc = + V4L2_MAP_YCBCR_ENC_DEFAULT(tryfmt->colorspace); + + if (tryfmt->quantization == V4L2_QUANTIZATION_DEFAULT) + tryfmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, + tryfmt->colorspace, + tryfmt->ycbcr_enc); +} + +static int imx7_csi_try_fmt(struct imx7_csi *csi, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat, + const struct imx7_csi_pixfmt **cc) +{ + const struct imx7_csi_pixfmt *in_cc; + struct v4l2_mbus_framefmt *in_fmt; + u32 code; + + in_fmt = imx7_csi_get_format(csi, sd_state, IMX7_CSI_PAD_SINK, + sdformat->which); + if (!in_fmt) + return -EINVAL; + + switch (sdformat->pad) { + case IMX7_CSI_PAD_SRC: + in_cc = imx7_csi_find_mbus_format(in_fmt->code); + + sdformat->format.width = in_fmt->width; + sdformat->format.height = in_fmt->height; + sdformat->format.code = in_fmt->code; + sdformat->format.field = in_fmt->field; + *cc = in_cc; + + sdformat->format.colorspace = in_fmt->colorspace; + sdformat->format.xfer_func = in_fmt->xfer_func; + sdformat->format.quantization = in_fmt->quantization; + sdformat->format.ycbcr_enc = in_fmt->ycbcr_enc; + break; + case IMX7_CSI_PAD_SINK: + *cc = imx7_csi_find_mbus_format(sdformat->format.code); + if (!*cc) { + code = IMX7_CSI_DEF_MBUS_CODE; + *cc = imx7_csi_find_mbus_format(code); + sdformat->format.code = code; + } + + if (sdformat->format.field != V4L2_FIELD_INTERLACED) + sdformat->format.field = V4L2_FIELD_NONE; + break; + default: + return -EINVAL; + } + + imx7_csi_try_colorimetry(&sdformat->format); + + return 0; +} + +static int imx7_csi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + const struct imx7_csi_pixfmt *outcc; + struct v4l2_mbus_framefmt *outfmt; + const struct imx7_csi_pixfmt *cc; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_format format; + int ret = 0; + + if (sdformat->pad >= IMX7_CSI_PADS_NUM) + return -EINVAL; + + mutex_lock(&csi->lock); + + if (csi->is_streaming) { + ret = -EBUSY; + goto out_unlock; + } + + ret = imx7_csi_try_fmt(csi, sd_state, sdformat, &cc); + if (ret < 0) + goto out_unlock; + + fmt = imx7_csi_get_format(csi, sd_state, sdformat->pad, + sdformat->which); + if (!fmt) { + ret = -EINVAL; + goto out_unlock; + } + + *fmt = sdformat->format; + + if (sdformat->pad == IMX7_CSI_PAD_SINK) { + /* propagate format to source pads */ + format.pad = IMX7_CSI_PAD_SRC; + format.which = sdformat->which; + format.format = sdformat->format; + if (imx7_csi_try_fmt(csi, sd_state, &format, &outcc)) { + ret = -EINVAL; + goto out_unlock; + } + outfmt = imx7_csi_get_format(csi, sd_state, IMX7_CSI_PAD_SRC, + sdformat->which); + *outfmt = format.format; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + csi->cc[IMX7_CSI_PAD_SRC] = outcc; + } + + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + csi->cc[sdformat->pad] = cc; + +out_unlock: + mutex_unlock(&csi->lock); + + return ret; +} + +static int imx7_csi_pad_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + struct media_pad *pad = NULL; + unsigned int i; + int ret; + + if (!csi->src_sd) + return -EPIPE; + + /* + * Validate the source link, and record whether the source uses the + * parallel input or the CSI-2 receiver. + */ + ret = v4l2_subdev_link_validate_default(sd, link, source_fmt, sink_fmt); + if (ret) + return ret; + + switch (csi->src_sd->entity.function) { + case MEDIA_ENT_F_VID_IF_BRIDGE: + /* The input is the CSI-2 receiver. */ + csi->is_csi2 = true; + break; + + case MEDIA_ENT_F_VID_MUX: + /* The input is the mux, check its input. */ + for (i = 0; i < csi->src_sd->entity.num_pads; i++) { + struct media_pad *spad = &csi->src_sd->entity.pads[i]; + + if (!(spad->flags & MEDIA_PAD_FL_SINK)) + continue; + + pad = media_pad_remote_pad_first(spad); + if (pad) + break; + } + + if (!pad) + return -ENODEV; + + csi->is_csi2 = pad->entity->function == MEDIA_ENT_F_VID_IF_BRIDGE; + break; + + default: + /* + * The input is an external entity, it must use the parallel + * bus. + */ + csi->is_csi2 = false; + break; + } + + return 0; +} + +static int imx7_csi_registered(struct v4l2_subdev *sd) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + int ret; + + ret = imx7_csi_video_init(csi); + if (ret) + return ret; + + ret = imx7_csi_video_register(csi); + if (ret) + return ret; + + ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev); + if (ret) + goto err_unreg; + + ret = media_device_register(&csi->mdev); + if (ret) + goto err_unreg; + + return 0; + +err_unreg: + imx7_csi_video_unregister(csi); + return ret; +} + +static void imx7_csi_unregistered(struct v4l2_subdev *sd) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + + imx7_csi_video_unregister(csi); +} + +static const struct v4l2_subdev_video_ops imx7_csi_video_ops = { + .s_stream = imx7_csi_s_stream, +}; + +static const struct v4l2_subdev_pad_ops imx7_csi_pad_ops = { + .init_cfg = imx7_csi_init_cfg, + .enum_mbus_code = imx7_csi_enum_mbus_code, + .get_fmt = imx7_csi_get_fmt, + .set_fmt = imx7_csi_set_fmt, + .link_validate = imx7_csi_pad_link_validate, +}; + +static const struct v4l2_subdev_ops imx7_csi_subdev_ops = { + .video = &imx7_csi_video_ops, + .pad = &imx7_csi_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx7_csi_internal_ops = { + .registered = imx7_csi_registered, + .unregistered = imx7_csi_unregistered, +}; + +/* ----------------------------------------------------------------------------- + * Media Entity Operations + */ + +static const struct media_entity_operations imx7_csi_entity_ops = { + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +/* ----------------------------------------------------------------------------- + * Probe & Remove + */ + +static int imx7_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct imx7_csi *csi = imx7_csi_notifier_to_dev(notifier); + struct media_pad *sink = &csi->sd.entity.pads[IMX7_CSI_PAD_SINK]; + + csi->src_sd = sd; + + return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static int imx7_csi_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct imx7_csi *csi = imx7_csi_notifier_to_dev(notifier); + + return v4l2_device_register_subdev_nodes(&csi->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations imx7_csi_notify_ops = { + .bound = imx7_csi_notify_bound, + .complete = imx7_csi_notify_complete, +}; + +static int imx7_csi_async_register(struct imx7_csi *csi) +{ + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep; + int ret; + + v4l2_async_nf_init(&csi->notifier); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (ep) { + asd = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep, + struct v4l2_async_subdev); + + fwnode_handle_put(ep); + + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + /* OK if asd already exists */ + if (ret != -EEXIST) + return ret; + } + } + + csi->notifier.ops = &imx7_csi_notify_ops; + + ret = v4l2_async_nf_register(&csi->v4l2_dev, &csi->notifier); + if (ret) + return ret; + + return 0; +} + +static void imx7_csi_media_cleanup(struct imx7_csi *csi) +{ + v4l2_device_unregister(&csi->v4l2_dev); + media_device_unregister(&csi->mdev); + media_device_cleanup(&csi->mdev); +} + +static const struct media_device_ops imx7_csi_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +static int imx7_csi_media_dev_init(struct imx7_csi *csi) +{ + int ret; + + strscpy(csi->mdev.model, "imx-media", sizeof(csi->mdev.model)); + csi->mdev.ops = &imx7_csi_media_ops; + csi->mdev.dev = csi->dev; + + csi->v4l2_dev.mdev = &csi->mdev; + strscpy(csi->v4l2_dev.name, "imx-media", + sizeof(csi->v4l2_dev.name)); + snprintf(csi->mdev.bus_info, sizeof(csi->mdev.bus_info), + "platform:%s", dev_name(csi->mdev.dev)); + + media_device_init(&csi->mdev); + + ret = v4l2_device_register(csi->dev, &csi->v4l2_dev); + if (ret < 0) { + v4l2_err(&csi->v4l2_dev, + "Failed to register v4l2_device: %d\n", ret); + goto cleanup; + } + + return 0; + +cleanup: + media_device_cleanup(&csi->mdev); + + return ret; +} + +static int imx7_csi_media_init(struct imx7_csi *csi) +{ + unsigned int i; + int ret; + + /* add media device */ + ret = imx7_csi_media_dev_init(csi); + if (ret) + return ret; + + v4l2_subdev_init(&csi->sd, &imx7_csi_subdev_ops); + v4l2_set_subdevdata(&csi->sd, csi); + csi->sd.internal_ops = &imx7_csi_internal_ops; + csi->sd.entity.ops = &imx7_csi_entity_ops; + csi->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi->sd.dev = csi->dev; + csi->sd.owner = THIS_MODULE; + csi->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(csi->sd.name, sizeof(csi->sd.name), "csi"); + + for (i = 0; i < IMX7_CSI_PADS_NUM; i++) + csi->pad[i].flags = (i == IMX7_CSI_PAD_SINK) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&csi->sd.entity, IMX7_CSI_PADS_NUM, + csi->pad); + if (ret) + goto error; + + ret = v4l2_device_register_subdev(&csi->v4l2_dev, &csi->sd); + if (ret) + goto error; + + return 0; + +error: + imx7_csi_media_cleanup(csi); + return ret; +} + +static int imx7_csi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx7_csi *csi; + int ret; + + csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL); + if (!csi) + return -ENOMEM; + + csi->dev = dev; + platform_set_drvdata(pdev, csi); + + spin_lock_init(&csi->irqlock); + mutex_init(&csi->lock); + + /* Acquire resources and install interrupt handler. */ + csi->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(csi->mclk)) { + ret = PTR_ERR(csi->mclk); + dev_err(dev, "Failed to get mclk: %d", ret); + goto destroy_mutex; + } + + csi->irq = platform_get_irq(pdev, 0); + if (csi->irq < 0) { + ret = csi->irq; + goto destroy_mutex; + } + + csi->regbase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi->regbase)) { + ret = PTR_ERR(csi->regbase); + goto destroy_mutex; + } + + csi->model = (enum imx_csi_model)(uintptr_t)of_device_get_match_data(&pdev->dev); + + ret = devm_request_irq(dev, csi->irq, imx7_csi_irq_handler, 0, "csi", + (void *)csi); + if (ret < 0) { + dev_err(dev, "Request CSI IRQ failed.\n"); + goto destroy_mutex; + } + + /* Initialize all the media device infrastructure. */ + ret = imx7_csi_media_init(csi); + if (ret) + goto destroy_mutex; + + /* Set the default mbus formats. */ + ret = imx7_csi_init_cfg(&csi->sd, NULL); + if (ret) + goto media_cleanup; + + ret = imx7_csi_async_register(csi); + if (ret) + goto subdev_notifier_cleanup; + + return 0; + +subdev_notifier_cleanup: + v4l2_async_nf_unregister(&csi->notifier); + v4l2_async_nf_cleanup(&csi->notifier); +media_cleanup: + imx7_csi_media_cleanup(csi); + +destroy_mutex: + mutex_destroy(&csi->lock); + + return ret; +} + +static int imx7_csi_remove(struct platform_device *pdev) +{ + struct imx7_csi *csi = platform_get_drvdata(pdev); + + imx7_csi_media_cleanup(csi); + + v4l2_async_nf_unregister(&csi->notifier); + v4l2_async_nf_cleanup(&csi->notifier); + v4l2_async_unregister_subdev(&csi->sd); + + mutex_destroy(&csi->lock); + + return 0; +} + +static const struct of_device_id imx7_csi_of_match[] = { + { .compatible = "fsl,imx8mq-csi", .data = (void *)IMX7_CSI_IMX8MQ }, + { .compatible = "fsl,imx7-csi", .data = (void *)IMX7_CSI_IMX7 }, + { .compatible = "fsl,imx6ul-csi", .data = (void *)IMX7_CSI_IMX7 }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx7_csi_of_match); + +static struct platform_driver imx7_csi_driver = { + .probe = imx7_csi_probe, + .remove = imx7_csi_remove, + .driver = { + .of_match_table = imx7_csi_of_match, + .name = "imx7-csi", + }, +}; +module_platform_driver(imx7_csi_driver); + +MODULE_DESCRIPTION("i.MX7 CSI subdev driver"); +MODULE_AUTHOR("Rui Miguel Silva <rui.silva@linaro.org>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx7-csi"); diff --git a/drivers/media/platform/qcom/camss/camss-vfe-170.c b/drivers/media/platform/qcom/camss/camss-vfe-170.c index 600150cfc4f7..8e506a805d11 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe-170.c +++ b/drivers/media/platform/qcom/camss/camss-vfe-170.c @@ -687,7 +687,12 @@ out_unlock: */ static void vfe_pm_domain_off(struct vfe_device *vfe) { - /* nop */ + struct camss *camss = vfe->camss; + + if (vfe->id >= camss->vfe_num) + return; + + device_link_del(camss->genpd_link[vfe->id]); } /* @@ -696,6 +701,19 @@ static void vfe_pm_domain_off(struct vfe_device *vfe) */ static int vfe_pm_domain_on(struct vfe_device *vfe) { + struct camss *camss = vfe->camss; + enum vfe_line_id id = vfe->id; + + if (id >= camss->vfe_num) + return 0; + + camss->genpd_link[id] = device_link_add(camss->dev, camss->genpd[id], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[id]) + return -EINVAL; + return 0; } diff --git a/drivers/media/platform/qcom/camss/camss-vfe-480.c b/drivers/media/platform/qcom/camss/camss-vfe-480.c index 129585110393..3aa962b5663b 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe-480.c +++ b/drivers/media/platform/qcom/camss/camss-vfe-480.c @@ -494,7 +494,12 @@ out_unlock: */ static void vfe_pm_domain_off(struct vfe_device *vfe) { - /* nop */ + struct camss *camss = vfe->camss; + + if (vfe->id >= camss->vfe_num) + return; + + device_link_del(camss->genpd_link[vfe->id]); } /* @@ -503,6 +508,19 @@ static void vfe_pm_domain_off(struct vfe_device *vfe) */ static int vfe_pm_domain_on(struct vfe_device *vfe) { + struct camss *camss = vfe->camss; + enum vfe_line_id id = vfe->id; + + if (id >= camss->vfe_num) + return 0; + + camss->genpd_link[id] = device_link_add(camss->dev, camss->genpd[id], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[id]) + return -EINVAL; + return 0; } diff --git a/drivers/media/platform/qcom/camss/camss-video.c b/drivers/media/platform/qcom/camss/camss-video.c index 81fb3a5bc1d5..41deda232e4a 100644 --- a/drivers/media/platform/qcom/camss/camss-video.c +++ b/drivers/media/platform/qcom/camss/camss-video.c @@ -495,7 +495,7 @@ static int video_start_streaming(struct vb2_queue *q, unsigned int count) ret = video_device_pipeline_start(vdev, &video->pipe); if (ret < 0) - return ret; + goto flush_buffers; ret = video_check_format(video); if (ret < 0) @@ -524,6 +524,7 @@ static int video_start_streaming(struct vb2_queue *q, unsigned int count) error: video_device_pipeline_stop(vdev); +flush_buffers: video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED); return ret; diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 1118c40886d5..9cda284f1e71 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -1170,7 +1170,7 @@ static int camss_init_subdevices(struct camss *camss) } /* note: SM8250 requires VFE to be initialized before CSID */ - for (i = 0; i < camss->vfe_num; i++) { + for (i = 0; i < camss->vfe_num + camss->vfe_lite_num; i++) { ret = msm_vfe_subdev_init(camss, &camss->vfe[i], &vfe_res[i], i); if (ret < 0) { @@ -1242,7 +1242,7 @@ static int camss_register_entities(struct camss *camss) goto err_reg_ispif; } - for (i = 0; i < camss->vfe_num; i++) { + for (i = 0; i < camss->vfe_num + camss->vfe_lite_num; i++) { ret = msm_vfe_register_entities(&camss->vfe[i], &camss->v4l2_dev); if (ret < 0) { @@ -1314,7 +1314,7 @@ static int camss_register_entities(struct camss *camss) } } else { for (i = 0; i < camss->csid_num; i++) - for (k = 0; k < camss->vfe_num; k++) + for (k = 0; k < camss->vfe_num + camss->vfe_lite_num; k++) for (j = 0; j < camss->vfe[k].line_num; j++) { struct v4l2_subdev *csid = &camss->csid[i].subdev; struct v4l2_subdev *vfe = &camss->vfe[k].line[j].subdev; @@ -1338,7 +1338,7 @@ static int camss_register_entities(struct camss *camss) return 0; err_link: - i = camss->vfe_num; + i = camss->vfe_num + camss->vfe_lite_num; err_reg_vfe: for (i--; i >= 0; i--) msm_vfe_unregister_entities(&camss->vfe[i]); @@ -1377,7 +1377,7 @@ static void camss_unregister_entities(struct camss *camss) msm_ispif_unregister_entities(camss->ispif); - for (i = 0; i < camss->vfe_num; i++) + for (i = 0; i < camss->vfe_num + camss->vfe_lite_num; i++) msm_vfe_unregister_entities(&camss->vfe[i]); } @@ -1453,7 +1453,6 @@ static const struct media_device_ops camss_media_ops = { static int camss_configure_pd(struct camss *camss) { struct device *dev = camss->dev; - int last_pm_domain = 0; int i; int ret; @@ -1465,6 +1464,14 @@ static int camss_configure_pd(struct camss *camss) return camss->genpd_num; } + /* + * If a platform device has just one power domain, then it is attached + * at platform_probe() level, thus there shall be no need and even no + * option to attach it again, this is the case for CAMSS on MSM8916. + */ + if (camss->genpd_num == 1) + return 0; + camss->genpd = devm_kmalloc_array(dev, camss->genpd_num, sizeof(*camss->genpd), GFP_KERNEL); if (!camss->genpd) @@ -1476,32 +1483,34 @@ static int camss_configure_pd(struct camss *camss) if (!camss->genpd_link) return -ENOMEM; + /* + * VFE power domains are in the beginning of the list, and while all + * power domains should be attached, only if TITAN_TOP power domain is + * found in the list, it should be linked over here. + */ for (i = 0; i < camss->genpd_num; i++) { camss->genpd[i] = dev_pm_domain_attach_by_id(camss->dev, i); if (IS_ERR(camss->genpd[i])) { ret = PTR_ERR(camss->genpd[i]); goto fail_pm; } + } - camss->genpd_link[i] = device_link_add(camss->dev, camss->genpd[i], - DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | - DL_FLAG_RPM_ACTIVE); - if (!camss->genpd_link[i]) { - dev_pm_domain_detach(camss->genpd[i], true); + if (i > camss->vfe_num) { + camss->genpd_link[i - 1] = device_link_add(camss->dev, camss->genpd[i - 1], + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[i - 1]) { ret = -EINVAL; goto fail_pm; } - - last_pm_domain = i; } return 0; fail_pm: - for (i = 0; i < last_pm_domain; i++) { - device_link_del(camss->genpd_link[i]); + for (--i ; i >= 0; i--) dev_pm_domain_detach(camss->genpd[i], true); - } return ret; } @@ -1571,13 +1580,15 @@ static int camss_probe(struct platform_device *pdev) camss->version = CAMSS_845; camss->csiphy_num = 4; camss->csid_num = 3; - camss->vfe_num = 3; + camss->vfe_num = 2; + camss->vfe_lite_num = 1; } else if (of_device_is_compatible(dev->of_node, "qcom,sm8250-camss")) { camss->version = CAMSS_8250; camss->csiphy_num = 6; camss->csid_num = 4; - camss->vfe_num = 4; + camss->vfe_num = 2; + camss->vfe_lite_num = 2; } else { return -EINVAL; } @@ -1599,8 +1610,8 @@ static int camss_probe(struct platform_device *pdev) return -ENOMEM; } - camss->vfe = devm_kcalloc(dev, camss->vfe_num, sizeof(*camss->vfe), - GFP_KERNEL); + camss->vfe = devm_kcalloc(dev, camss->vfe_num + camss->vfe_lite_num, + sizeof(*camss->vfe), GFP_KERNEL); if (!camss->vfe) return -ENOMEM; @@ -1698,10 +1709,14 @@ void camss_delete(struct camss *camss) pm_runtime_disable(camss->dev); - for (i = 0; i < camss->genpd_num; i++) { - device_link_del(camss->genpd_link[i]); + if (camss->genpd_num == 1) + return; + + if (camss->genpd_num > camss->vfe_num) + device_link_del(camss->genpd_link[camss->genpd_num - 1]); + + for (i = 0; i < camss->genpd_num; i++) dev_pm_domain_detach(camss->genpd[i], true); - } } /* diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index 0db80cadbbaa..3acd2b3403e8 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -97,6 +97,7 @@ struct camss { struct csid_device *csid; struct ispif_device *ispif; int vfe_num; + int vfe_lite_num; struct vfe_device *vfe; atomic_t ref_count; int genpd_num; diff --git a/drivers/media/platform/qcom/venus/firmware.c b/drivers/media/platform/qcom/venus/firmware.c index 14b6f1d05991..142d4c74017c 100644 --- a/drivers/media/platform/qcom/venus/firmware.c +++ b/drivers/media/platform/qcom/venus/firmware.c @@ -38,8 +38,8 @@ static void venus_reset_cpu(struct venus_core *core) writel(fw_size, wrapper_base + WRAPPER_FW_END_ADDR); writel(0, wrapper_base + WRAPPER_CPA_START_ADDR); writel(fw_size, wrapper_base + WRAPPER_CPA_END_ADDR); - writel(fw_size, wrapper_base + WRAPPER_NONPIX_START_ADDR); - writel(fw_size, wrapper_base + WRAPPER_NONPIX_END_ADDR); + writel(0, wrapper_base + WRAPPER_NONPIX_START_ADDR); + writel(0, wrapper_base + WRAPPER_NONPIX_END_ADDR); if (IS_V6(core)) { /* Bring XTSS out of reset */ @@ -68,9 +68,11 @@ int venus_set_hw_state(struct venus_core *core, bool resume) venus_reset_cpu(core); } else { if (IS_V6(core)) - writel(1, core->wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + writel(WRAPPER_XTSS_SW_RESET_BIT, + core->wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); else - writel(1, core->wrapper_base + WRAPPER_A9SS_SW_RESET); + writel(WRAPPER_A9SS_SW_RESET_BIT, + core->wrapper_base + WRAPPER_A9SS_SW_RESET); } return 0; @@ -179,17 +181,15 @@ static int venus_shutdown_no_tz(struct venus_core *core) if (IS_V6(core)) { /* Assert the reset to XTSS */ - reg = readl_relaxed(wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + reg = readl(wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); reg |= WRAPPER_XTSS_SW_RESET_BIT; - writel_relaxed(reg, wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + writel(reg, wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); } else { /* Assert the reset to ARM9 */ - reg = readl_relaxed(wrapper_base + WRAPPER_A9SS_SW_RESET); + reg = readl(wrapper_base + WRAPPER_A9SS_SW_RESET); reg |= WRAPPER_A9SS_SW_RESET_BIT; - writel_relaxed(reg, wrapper_base + WRAPPER_A9SS_SW_RESET); + writel(reg, wrapper_base + WRAPPER_A9SS_SW_RESET); } - /* Make sure reset is asserted before the mapping is removed */ - mb(); iommu = core->fw.iommu_domain; diff --git a/drivers/media/platform/qcom/venus/pm_helpers.c b/drivers/media/platform/qcom/venus/pm_helpers.c index c93d2906e4c7..48c9084bb4db 100644 --- a/drivers/media/platform/qcom/venus/pm_helpers.c +++ b/drivers/media/platform/qcom/venus/pm_helpers.c @@ -869,8 +869,8 @@ static int vcodec_domains_get(struct venus_core *core) for (i = 0; i < res->vcodec_pmdomains_num; i++) { pd = dev_pm_domain_attach_by_name(dev, res->vcodec_pmdomains[i]); - if (IS_ERR(pd)) - return PTR_ERR(pd); + if (IS_ERR_OR_NULL(pd)) + return PTR_ERR(pd) ? : -ENODATA; core->pmdomains[i] = pd; } diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig index 9fd90672ea2d..ed788e991f74 100644 --- a/drivers/media/platform/renesas/Kconfig +++ b/drivers/media/platform/renesas/Kconfig @@ -41,6 +41,7 @@ config VIDEO_SH_VOU Support for the Video Output Unit (VOU) on SuperH SoCs. source "drivers/media/platform/renesas/rcar-vin/Kconfig" +source "drivers/media/platform/renesas/rzg2l-cru/Kconfig" # Mem2mem drivers diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile index 3ec226ef5fd2..55854e868887 100644 --- a/drivers/media/platform/renesas/Makefile +++ b/drivers/media/platform/renesas/Makefile @@ -4,6 +4,7 @@ # obj-y += rcar-vin/ +obj-y += rzg2l-cru/ obj-y += vsp1/ obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 2f7daa853ed8..5e53d6b7036c 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -1131,6 +1131,7 @@ static const struct rvin_info rcar_info_h1 = { .use_mc = false, .max_width = 2048, .max_height = 2048, + .scaler = rvin_scaler_gen2, }; static const struct rvin_info rcar_info_m1 = { @@ -1138,6 +1139,7 @@ static const struct rvin_info rcar_info_m1 = { .use_mc = false, .max_width = 2048, .max_height = 2048, + .scaler = rvin_scaler_gen2, }; static const struct rvin_info rcar_info_gen2 = { @@ -1145,6 +1147,7 @@ static const struct rvin_info rcar_info_gen2 = { .use_mc = false, .max_width = 2048, .max_height = 2048, + .scaler = rvin_scaler_gen2, }; static const struct rvin_group_route rcar_info_r8a774e1_routes[] = { @@ -1177,6 +1180,7 @@ static const struct rvin_info rcar_info_r8a7795 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a7795_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_group_route rcar_info_r8a7795es1_routes[] = { @@ -1212,6 +1216,7 @@ static const struct rvin_info rcar_info_r8a7796 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a7796_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_group_route rcar_info_r8a77965_routes[] = { @@ -1229,6 +1234,7 @@ static const struct rvin_info rcar_info_r8a77965 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a77965_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_group_route rcar_info_r8a77970_routes[] = { @@ -1271,6 +1277,7 @@ static const struct rvin_info rcar_info_r8a77990 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a77990_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_group_route rcar_info_r8a77995_routes[] = { @@ -1284,6 +1291,7 @@ static const struct rvin_info rcar_info_r8a77995 = { .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a77995_routes, + .scaler = rvin_scaler_gen3, }; static const struct rvin_info rcar_info_r8a779a0 = { @@ -1408,13 +1416,21 @@ static int rcar_vin_probe(struct platform_device *pdev) platform_set_drvdata(pdev, vin); - if (vin->info->use_isp) + if (vin->info->use_isp) { ret = rvin_isp_init(vin); - else if (vin->info->use_mc) + } else if (vin->info->use_mc) { ret = rvin_csi2_init(vin); - else + + if (vin->info->scaler && + rvin_group_id_to_master(vin->id) == vin->id) + vin->scaler = vin->info->scaler; + } else { ret = rvin_parallel_init(vin); + if (vin->info->scaler) + vin->scaler = vin->info->scaler; + } + if (ret) { rvin_dma_unregister(vin); return ret; diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index 3aea96d85165..98bfd445a649 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -74,6 +74,10 @@ /* Register offsets specific for Gen3 */ #define VNCSI_IFMD_REG 0x20 /* Video n CSI2 Interface Mode Register */ +#define VNUDS_CTRL_REG 0x80 /* Video n scaling control register */ +#define VNUDS_SCALE_REG 0x84 /* Video n scaling factor register */ +#define VNUDS_PASS_BWIDTH_REG 0x90 /* Video n passband register */ +#define VNUDS_CLIP_SIZE_REG 0xa4 /* Video n UDS output size clipping reg */ /* Register bit fields for R-Car VIN */ /* Video n Main Control Register bits */ @@ -140,6 +144,9 @@ #define VNCSI_IFMD_DES0 (1 << 25) #define VNCSI_IFMD_CSI_CHSEL(n) (((n) & 0xf) << 0) +/* Video n scaling control register (Gen3) */ +#define VNUDS_CTRL_AMD (1 << 30) + struct rvin_buffer { struct vb2_v4l2_buffer vb; struct list_head list; @@ -160,9 +167,17 @@ static u32 rvin_read(struct rvin_dev *vin, u32 offset) } /* ----------------------------------------------------------------------------- - * Crop and Scaling Gen2 + * Crop and Scaling */ +static bool rvin_scaler_needed(const struct rvin_dev *vin) +{ + return !(vin->crop.width == vin->format.width && + vin->compose.width == vin->format.width && + vin->crop.height == vin->format.height && + vin->compose.height == vin->format.height); +} + struct vin_coeff { unsigned short xs_value; u32 coeff_set[24]; @@ -535,7 +550,7 @@ static void rvin_set_coeff(struct rvin_dev *vin, unsigned short xs) rvin_write(vin, p_set->coeff_set[23], VNC8C_REG); } -static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin) +void rvin_scaler_gen2(struct rvin_dev *vin) { unsigned int crop_height; u32 xs, ys; @@ -583,6 +598,69 @@ static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin) 0, 0); } +static unsigned int rvin_uds_scale_ratio(unsigned int in, unsigned int out) +{ + unsigned int ratio; + + ratio = in * 4096 / out; + return ratio >= 0x10000 ? 0xffff : ratio; +} + +static unsigned int rvin_uds_filter_width(unsigned int ratio) +{ + if (ratio >= 0x1000) + return 64 * (ratio & 0xf000) / ratio; + + return 64; +} + +void rvin_scaler_gen3(struct rvin_dev *vin) +{ + unsigned int ratio_h, ratio_v; + unsigned int bwidth_h, bwidth_v; + u32 vnmc, clip_size; + + vnmc = rvin_read(vin, VNMC_REG); + + /* Disable scaler if not needed. */ + if (!rvin_scaler_needed(vin)) { + rvin_write(vin, vnmc & ~VNMC_SCLE, VNMC_REG); + return; + } + + ratio_h = rvin_uds_scale_ratio(vin->crop.width, vin->compose.width); + bwidth_h = rvin_uds_filter_width(ratio_h); + + ratio_v = rvin_uds_scale_ratio(vin->crop.height, vin->compose.height); + bwidth_v = rvin_uds_filter_width(ratio_v); + + clip_size = vin->compose.width << 16; + + switch (vin->format.field) { + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + clip_size |= vin->compose.height / 2; + break; + default: + clip_size |= vin->compose.height; + break; + } + + rvin_write(vin, vnmc | VNMC_SCLE, VNMC_REG); + rvin_write(vin, VNUDS_CTRL_AMD, VNUDS_CTRL_REG); + rvin_write(vin, (ratio_h << 16) | ratio_v, VNUDS_SCALE_REG); + rvin_write(vin, (bwidth_h << 16) | bwidth_v, VNUDS_PASS_BWIDTH_REG); + rvin_write(vin, clip_size, VNUDS_CLIP_SIZE_REG); + + vin_dbg(vin, "Pre-Clip: %ux%u@%u:%u Post-Clip: %ux%u@%u:%u\n", + vin->crop.width, vin->crop.height, vin->crop.left, + vin->crop.top, vin->compose.width, vin->compose.height, + vin->compose.left, vin->compose.top); +} + void rvin_crop_scale_comp(struct rvin_dev *vin) { const struct rvin_video_format *fmt; @@ -594,9 +672,8 @@ void rvin_crop_scale_comp(struct rvin_dev *vin) rvin_write(vin, vin->crop.top, VNSLPRC_REG); rvin_write(vin, vin->crop.top + vin->crop.height - 1, VNELPRC_REG); - /* TODO: Add support for the UDS scaler. */ - if (vin->info->model != RCAR_GEN3) - rvin_crop_scale_comp_gen2(vin); + if (vin->scaler) + vin->scaler(vin); fmt = rvin_format_from_pixel(vin, vin->format.pixelformat); stride = vin->format.bytesperline / fmt->bpp; @@ -984,12 +1061,12 @@ static int rvin_capture_start(struct rvin_dev *vin) for (slot = 0; slot < HW_BUFFER_NUM; slot++) rvin_fill_hw_slot(vin, slot); - rvin_crop_scale_comp(vin); - ret = rvin_setup(vin); if (ret) return ret; + rvin_crop_scale_comp(vin); + vin_dbg(vin, "Starting to capture\n"); /* Continuous Frame Capture Mode */ @@ -1234,9 +1311,16 @@ static int rvin_mc_validate_format(struct rvin_dev *vin, struct v4l2_subdev *sd, return -EPIPE; } - if (fmt.format.width != vin->format.width || - fmt.format.height != vin->format.height || - fmt.format.code != vin->mbus_code) + if (rvin_scaler_needed(vin)) { + if (!vin->scaler) + return -EPIPE; + } else { + if (fmt.format.width != vin->format.width || + fmt.format.height != vin->format.height) + return -EPIPE; + } + + if (fmt.format.code != vin->mbus_code) return -EPIPE; return 0; diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c index 576059f9bbe3..073f70c6ac68 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c @@ -226,10 +226,10 @@ static int rvin_reset_format(struct rvin_dev *vin) v4l2_fill_pix_format(&vin->format, &fmt.format); - vin->src_rect.top = 0; - vin->src_rect.left = 0; - vin->src_rect.width = vin->format.width; - vin->src_rect.height = vin->format.height; + vin->crop.top = 0; + vin->crop.left = 0; + vin->crop.width = vin->format.width; + vin->crop.height = vin->format.height; /* Make use of the hardware interlacer by default. */ if (vin->format.field == V4L2_FIELD_ALTERNATE) { @@ -239,8 +239,6 @@ static int rvin_reset_format(struct rvin_dev *vin) rvin_format_align(vin, &vin->format); - vin->crop = vin->src_rect; - vin->compose.top = 0; vin->compose.left = 0; vin->compose.width = vin->format.width; @@ -349,7 +347,6 @@ static int rvin_s_fmt_vid_cap(struct file *file, void *priv, v4l2_rect_map_inside(&vin->crop, &src_rect); v4l2_rect_map_inside(&vin->compose, &fmt_rect); - vin->src_rect = src_rect; return 0; } @@ -428,10 +425,60 @@ static int rvin_enum_fmt_vid_cap(struct file *file, void *priv, return -EINVAL; } +static int rvin_remote_rectangle(struct rvin_dev *vin, struct v4l2_rect *rect) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_subdev *sd; + unsigned int index; + int ret; + + if (vin->info->use_mc) { + struct media_pad *pad = media_pad_remote_pad_first(&vin->pad); + + if (!pad) + return -EINVAL; + + sd = media_entity_to_v4l2_subdev(pad->entity); + index = pad->index; + } else { + sd = vin_to_source(vin); + index = vin->parallel.source_pad; + } + + fmt.pad = index; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + rect->left = rect->top = 0; + rect->width = fmt.format.width; + rect->height = fmt.format.height; + + if (fmt.format.field == V4L2_FIELD_ALTERNATE) { + switch (vin->format.field) { + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + rect->height *= 2; + break; + } + } + + return 0; +} + static int rvin_g_selection(struct file *file, void *fh, struct v4l2_selection *s) { struct rvin_dev *vin = video_drvdata(file); + int ret; + + if (!vin->scaler) + return -ENOIOCTLCMD; if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; @@ -439,9 +486,10 @@ static int rvin_g_selection(struct file *file, void *fh, switch (s->target) { case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_CROP_DEFAULT: - s->r.left = s->r.top = 0; - s->r.width = vin->src_rect.width; - s->r.height = vin->src_rect.height; + ret = rvin_remote_rectangle(vin, &s->r); + if (ret) + return ret; + break; case V4L2_SEL_TGT_CROP: s->r = vin->crop; @@ -473,6 +521,10 @@ static int rvin_s_selection(struct file *file, void *fh, .width = 6, .height = 2, }; + int ret; + + if (!vin->scaler) + return -ENOIOCTLCMD; if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; @@ -482,23 +534,23 @@ static int rvin_s_selection(struct file *file, void *fh, switch (s->target) { case V4L2_SEL_TGT_CROP: /* Can't crop outside of source input */ - max_rect.top = max_rect.left = 0; - max_rect.width = vin->src_rect.width; - max_rect.height = vin->src_rect.height; + ret = rvin_remote_rectangle(vin, &max_rect); + if (ret) + return ret; + v4l2_rect_map_inside(&r, &max_rect); - v4l_bound_align_image(&r.width, 6, vin->src_rect.width, 0, - &r.height, 2, vin->src_rect.height, 0, 0); + v4l_bound_align_image(&r.width, 6, max_rect.width, 0, + &r.height, 2, max_rect.height, 0, 0); - r.top = clamp_t(s32, r.top, 0, - vin->src_rect.height - r.height); - r.left = clamp_t(s32, r.left, 0, vin->src_rect.width - r.width); + r.top = clamp_t(s32, r.top, 0, max_rect.height - r.height); + r.left = clamp_t(s32, r.left, 0, max_rect.width - r.width); vin->crop = s->r = r; vin_dbg(vin, "Cropped %dx%d@%d:%d of %dx%d\n", r.width, r.height, r.left, r.top, - vin->src_rect.width, vin->src_rect.height); + max_rect.width, max_rect.height); break; case V4L2_SEL_TGT_COMPOSE: /* Make sure compose rect fits inside output format */ @@ -866,6 +918,9 @@ static const struct v4l2_ioctl_ops rvin_mc_ioctl_ops = { .vidioc_s_fmt_vid_cap = rvin_mc_s_fmt_vid_cap, .vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap, + .vidioc_g_selection = rvin_g_selection, + .vidioc_s_selection = rvin_s_selection, + .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, .vidioc_querybuf = vb2_ioctl_querybuf, diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index 1f94589d9ef1..cb206d3976dd 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -31,6 +31,7 @@ /* Max number on VIN instances that can be in a system */ #define RCAR_VIN_NUM 32 +struct rvin_dev; struct rvin_group; enum model_id { @@ -155,6 +156,7 @@ struct rvin_group_route { * @max_height: max input height the VIN supports * @routes: list of possible routes from the CSI-2 recivers to * all VINs. The list mush be NULL terminated. + * @scaler: Optional scaler */ struct rvin_info { enum model_id model; @@ -165,6 +167,7 @@ struct rvin_info { unsigned int max_width; unsigned int max_height; const struct rvin_group_route *routes; + void (*scaler)(struct rvin_dev *vin); }; /** @@ -203,7 +206,7 @@ struct rvin_info { * * @crop: active cropping * @compose: active composing - * @src_rect: active size of the video source + * @scaler: Optional scaler * @std: active video standard of the video source * * @alpha: Alpha component to fill in for supported pixel formats @@ -247,7 +250,7 @@ struct rvin_dev { struct v4l2_rect crop; struct v4l2_rect compose; - struct v4l2_rect src_rect; + void (*scaler)(struct rvin_dev *vin); v4l2_std_id std; unsigned int alpha; @@ -304,6 +307,8 @@ const struct rvin_video_format *rvin_format_from_pixel(struct rvin_dev *vin, /* Cropping, composing and scaling */ +void rvin_scaler_gen2(struct rvin_dev *vin); +void rvin_scaler_gen3(struct rvin_dev *vin); void rvin_crop_scale_comp(struct rvin_dev *vin); int rvin_set_channel_routing(struct rvin_dev *vin, u8 chsel); diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig new file mode 100644 index 000000000000..b39818c1f053 --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 + +config VIDEO_RZG2L_CSI2 + tristate "RZ/G2L MIPI CSI-2 Receiver" + depends on ARCH_RENESAS || COMPILE_TEST + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + select MEDIA_CONTROLLER + select RESET_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Support for Renesas RZ/G2L (and alike SoC's) MIPI CSI-2 + Receiver driver. + + To compile this driver as a module, choose M here: the + module will be called rzg2l-csi2. + +config VIDEO_RZG2L_CRU + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" + depends on ARCH_RENESAS || COMPILE_TEST + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEOBUF2_DMA_CONTIG + select VIDEO_V4L2_SUBDEV_API + help + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving + Unit (CRU) driver. + + To compile this driver as a module, choose M here: the + module will be called rzg2l-cru. diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile new file mode 100644 index 000000000000..c4db2632874f --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o + +rzg2l-cru-objs = rzg2l-core.o rzg2l-ip.o rzg2l-video.o +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c new file mode 100644 index 000000000000..5939f5165a5e --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Renesas RZ/G2L CRU + * + * Copyright (C) 2022 Renesas Electronics Corp. + * + * Based on Renesas R-Car VIN + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * Copyright (C) 2008 Magnus Damm + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/mod_devicetable.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 <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> + +#include "rzg2l-cru.h" + +static inline struct rzg2l_cru_dev *notifier_to_cru(struct v4l2_async_notifier *n) +{ + return container_of(n, struct rzg2l_cru_dev, notifier); +} + +static const struct media_device_ops rzg2l_cru_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +/* ----------------------------------------------------------------------------- + * Group async notifier + */ + +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); + struct media_entity *source, *sink; + int ret; + + ret = rzg2l_cru_ip_subdev_register(cru); + if (ret) + return ret; + + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); + if (ret) { + dev_err(cru->dev, "Failed to register subdev nodes\n"); + return ret; + } + + ret = rzg2l_cru_video_register(cru); + if (ret) + return ret; + + /* + * CRU can be connected either to CSI2 or PARALLEL device + * For now we are only supporting CSI2 + * + * Create media device link between CSI-2 <-> CRU IP + */ + source = &cru->csi.subdev->entity; + sink = &cru->ip.subdev.entity; + ret = media_create_pad_link(source, 1, sink, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(cru->dev, "Error creating link from %s to %s\n", + source->name, sink->name); + return ret; + } + cru->csi.channel = 0; + cru->ip.remote = cru->csi.subdev; + + /* Create media device link between CRU IP <-> CRU OUTPUT */ + source = &cru->ip.subdev.entity; + sink = &cru->vdev.entity; + ret = media_create_pad_link(source, 1, sink, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(cru->dev, "Error creating link from %s to %s\n", + source->name, sink->name); + return ret; + } + + return 0; +} + +static void rzg2l_cru_group_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); + + rzg2l_cru_ip_subdev_unregister(cru); + + mutex_lock(&cru->mdev_lock); + + if (cru->csi.asd == asd) { + cru->csi.subdev = NULL; + dev_dbg(cru->dev, "Unbind CSI-2 %s\n", subdev->name); + } + + mutex_unlock(&cru->mdev_lock); +} + +static int rzg2l_cru_group_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); + + mutex_lock(&cru->mdev_lock); + + if (cru->csi.asd == asd) { + cru->csi.subdev = subdev; + dev_dbg(cru->dev, "Bound CSI-2 %s\n", subdev->name); + } + + mutex_unlock(&cru->mdev_lock); + + return 0; +} + +static const struct v4l2_async_notifier_operations rzg2l_cru_async_ops = { + .bound = rzg2l_cru_group_notify_bound, + .unbind = rzg2l_cru_group_notify_unbind, + .complete = rzg2l_cru_group_notify_complete, +}; + +static int rzg2l_cru_mc_parse_of(struct rzg2l_cru_dev *cru) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *ep, *fwnode; + struct v4l2_async_subdev *asd; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(cru->dev), 1, 0, 0); + if (!ep) + return 0; + + fwnode = fwnode_graph_get_remote_endpoint(ep); + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + fwnode_handle_put(ep); + if (ret) { + dev_err(cru->dev, "Failed to parse %pOF\n", to_of_node(fwnode)); + ret = -EINVAL; + goto out; + } + + if (!of_device_is_available(to_of_node(fwnode))) { + dev_dbg(cru->dev, "OF device %pOF disabled, ignoring\n", + to_of_node(fwnode)); + ret = -ENOTCONN; + goto out; + } + + asd = v4l2_async_nf_add_fwnode(&cru->notifier, fwnode, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out; + } + + cru->csi.asd = asd; + + dev_dbg(cru->dev, "Added OF device %pOF to slot %u\n", + to_of_node(fwnode), vep.base.id); +out: + fwnode_handle_put(fwnode); + + return ret; +} + +static int rzg2l_cru_mc_parse_of_graph(struct rzg2l_cru_dev *cru) +{ + int ret; + + v4l2_async_nf_init(&cru->notifier); + + ret = rzg2l_cru_mc_parse_of(cru); + if (ret) + return ret; + + cru->notifier.ops = &rzg2l_cru_async_ops; + + if (list_empty(&cru->notifier.asd_list)) + return 0; + + ret = v4l2_async_nf_register(&cru->v4l2_dev, &cru->notifier); + if (ret < 0) { + dev_err(cru->dev, "Notifier registration failed\n"); + v4l2_async_nf_cleanup(&cru->notifier); + return ret; + } + + return 0; +} + +static int rzg2l_cru_media_init(struct rzg2l_cru_dev *cru) +{ + struct media_device *mdev = NULL; + const struct of_device_id *match; + int ret; + + cru->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&cru->vdev.entity, 1, &cru->pad); + if (ret) + return ret; + + mutex_init(&cru->mdev_lock); + mdev = &cru->mdev; + mdev->dev = cru->dev; + mdev->ops = &rzg2l_cru_media_ops; + + match = of_match_node(cru->dev->driver->of_match_table, + cru->dev->of_node); + + strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); + strscpy(mdev->model, match->compatible, sizeof(mdev->model)); + + cru->v4l2_dev.mdev = &cru->mdev; + + media_device_init(mdev); + + ret = rzg2l_cru_mc_parse_of_graph(cru); + if (ret) { + mutex_lock(&cru->mdev_lock); + cru->v4l2_dev.mdev = NULL; + mutex_unlock(&cru->mdev_lock); + } + + return 0; +} + +static int rzg2l_cru_probe(struct platform_device *pdev) +{ + struct rzg2l_cru_dev *cru; + int ret; + + cru = devm_kzalloc(&pdev->dev, sizeof(*cru), GFP_KERNEL); + if (!cru) + return -ENOMEM; + + cru->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(cru->base)) + return PTR_ERR(cru->base); + + cru->presetn = devm_reset_control_get_shared(&pdev->dev, "presetn"); + if (IS_ERR(cru->presetn)) + return dev_err_probe(&pdev->dev, PTR_ERR(cru->presetn), + "Failed to get cpg presetn\n"); + + cru->aresetn = devm_reset_control_get_exclusive(&pdev->dev, "aresetn"); + if (IS_ERR(cru->aresetn)) + return dev_err_probe(&pdev->dev, PTR_ERR(cru->aresetn), + "Failed to get cpg aresetn\n"); + + cru->vclk = devm_clk_get(&pdev->dev, "video"); + if (IS_ERR(cru->vclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(cru->vclk), + "Failed to get video clock\n"); + + cru->dev = &pdev->dev; + cru->info = of_device_get_match_data(&pdev->dev); + + cru->image_conv_irq = platform_get_irq(pdev, 0); + if (cru->image_conv_irq < 0) + return cru->image_conv_irq; + + platform_set_drvdata(pdev, cru); + + ret = rzg2l_cru_dma_register(cru); + if (ret) + return ret; + + cru->num_buf = RZG2L_CRU_HW_BUFFER_DEFAULT; + pm_suspend_ignore_children(&pdev->dev, true); + pm_runtime_enable(&pdev->dev); + + ret = rzg2l_cru_media_init(cru); + if (ret) + goto error_dma_unregister; + + return 0; + +error_dma_unregister: + rzg2l_cru_dma_unregister(cru); + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int rzg2l_cru_remove(struct platform_device *pdev) +{ + struct rzg2l_cru_dev *cru = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + v4l2_async_nf_unregister(&cru->notifier); + v4l2_async_nf_cleanup(&cru->notifier); + + rzg2l_cru_video_unregister(cru); + media_device_cleanup(&cru->mdev); + mutex_destroy(&cru->mdev_lock); + + rzg2l_cru_dma_unregister(cru); + + return 0; +} + +static const struct of_device_id rzg2l_cru_of_id_table[] = { + { .compatible = "renesas,rzg2l-cru", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rzg2l_cru_of_id_table); + +static struct platform_driver rzg2l_cru_driver = { + .driver = { + .name = "rzg2l-cru", + .of_match_table = rzg2l_cru_of_id_table, + }, + .probe = rzg2l_cru_probe, + .remove = rzg2l_cru_remove, +}; + +module_platform_driver(rzg2l_cru_driver); + +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); +MODULE_DESCRIPTION("Renesas RZ/G2L CRU driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h new file mode 100644 index 000000000000..0b682cbae3eb --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for Renesas RZ/G2L CRU + * + * Copyright (C) 2022 Renesas Electronics Corp. + */ + +#ifndef __RZG2L_CRU__ +#define __RZG2L_CRU__ + +#include <linux/reset.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/videobuf2-v4l2.h> + +/* Number of HW buffers */ +#define RZG2L_CRU_HW_BUFFER_MAX 8 +#define RZG2L_CRU_HW_BUFFER_DEFAULT 3 + +/* Address alignment mask for HW buffers */ +#define RZG2L_CRU_HW_BUFFER_MASK 0x1ff + +/* Maximum number of CSI2 virtual channels */ +#define RZG2L_CRU_CSI2_VCHANNEL 4 + +#define RZG2L_CRU_MIN_INPUT_WIDTH 320 +#define RZG2L_CRU_MAX_INPUT_WIDTH 2800 +#define RZG2L_CRU_MIN_INPUT_HEIGHT 240 +#define RZG2L_CRU_MAX_INPUT_HEIGHT 4095 + +/** + * enum rzg2l_cru_dma_state - DMA states + * @RZG2L_CRU_DMA_STOPPED: No operation in progress + * @RZG2L_CRU_DMA_STARTING: Capture starting up + * @RZG2L_CRU_DMA_RUNNING: Operation in progress have buffers + * @RZG2L_CRU_DMA_STOPPING: Stopping operation + */ +enum rzg2l_cru_dma_state { + RZG2L_CRU_DMA_STOPPED = 0, + RZG2L_CRU_DMA_STARTING, + RZG2L_CRU_DMA_RUNNING, + RZG2L_CRU_DMA_STOPPING, +}; + +struct rzg2l_cru_csi { + struct v4l2_async_subdev *asd; + struct v4l2_subdev *subdev; + u32 channel; +}; + +struct rzg2l_cru_ip { + struct v4l2_subdev subdev; + struct media_pad pads[2]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *remote; +}; + +/** + * struct rzg2l_cru_dev - Renesas CRU device structure + * @dev: (OF) device + * @base: device I/O register space remapped to virtual memory + * @info: info about CRU instance + * + * @presetn: CRU_PRESETN reset line + * @aresetn: CRU_ARESETN reset line + * + * @vclk: CRU Main clock + * + * @image_conv_irq: Holds image conversion interrupt number + * + * @vdev: V4L2 video device associated with CRU + * @v4l2_dev: V4L2 device + * @num_buf: Holds the current number of buffers enabled + * @notifier: V4L2 asynchronous subdevs notifier + * + * @ip: Image processing subdev info + * @csi: CSI info + * @mdev: media device + * @mdev_lock: protects the count, notifier and csi members + * @pad: media pad for the video device entity + * + * @lock: protects @queue + * @queue: vb2 buffers queue + * @scratch: cpu address for scratch buffer + * @scratch_phys: physical address of the scratch buffer + * + * @qlock: protects @queue_buf, @buf_list, @sequence + * @state + * @queue_buf: Keeps track of buffers given to HW slot + * @buf_list: list of queued buffers + * @sequence: V4L2 buffers sequence number + * @state: keeps track of operation state + * + * @format: active V4L2 pixel format + */ +struct rzg2l_cru_dev { + struct device *dev; + void __iomem *base; + const struct rzg2l_cru_info *info; + + struct reset_control *presetn; + struct reset_control *aresetn; + + struct clk *vclk; + + int image_conv_irq; + + struct video_device vdev; + struct v4l2_device v4l2_dev; + u8 num_buf; + + struct v4l2_async_notifier notifier; + + struct rzg2l_cru_ip ip; + struct rzg2l_cru_csi csi; + struct media_device mdev; + struct mutex mdev_lock; + struct media_pad pad; + + struct mutex lock; + struct vb2_queue queue; + void *scratch; + dma_addr_t scratch_phys; + + spinlock_t qlock; + struct vb2_v4l2_buffer *queue_buf[RZG2L_CRU_HW_BUFFER_MAX]; + struct list_head buf_list; + unsigned int sequence; + enum rzg2l_cru_dma_state state; + + struct v4l2_pix_format format; +}; + +void rzg2l_cru_vclk_unprepare(struct rzg2l_cru_dev *cru); +int rzg2l_cru_vclk_prepare(struct rzg2l_cru_dev *cru); + +int rzg2l_cru_start_image_processing(struct rzg2l_cru_dev *cru); +void rzg2l_cru_stop_image_processing(struct rzg2l_cru_dev *cru); + +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru); +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru); + +int rzg2l_cru_video_register(struct rzg2l_cru_dev *cru); +void rzg2l_cru_video_unregister(struct rzg2l_cru_dev *cru); + +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format); + +int rzg2l_cru_ip_subdev_register(struct rzg2l_cru_dev *cru); +void rzg2l_cru_ip_subdev_unregister(struct rzg2l_cru_dev *cru); +struct v4l2_mbus_framefmt *rzg2l_cru_ip_get_src_fmt(struct rzg2l_cru_dev *cru); + +#endif diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c new file mode 100644 index 000000000000..33e08efa3039 --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Renesas RZ/G2L MIPI CSI-2 Receiver + * + * Copyright (C) 2022 Renesas Electronics Corp. + */ + +#include <linux/clk.h> +#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/reset.h> +#include <linux/sys_soc.h> +#include <linux/units.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> + +/* LINK registers */ +/* Module Configuration Register */ +#define CSI2nMCG 0x0 +#define CSI2nMCG_SDLN GENMASK(11, 8) + +/* Module Control Register 0 */ +#define CSI2nMCT0 0x10 +#define CSI2nMCT0_VDLN(x) ((x) << 0) + +/* Module Control Register 2 */ +#define CSI2nMCT2 0x18 +#define CSI2nMCT2_FRRSKW(x) ((x) << 16) +#define CSI2nMCT2_FRRCLK(x) ((x) << 0) + +/* Module Control Register 3 */ +#define CSI2nMCT3 0x1c +#define CSI2nMCT3_RXEN BIT(0) + +/* Reset Control Register */ +#define CSI2nRTCT 0x28 +#define CSI2nRTCT_VSRST BIT(0) + +/* Reset Status Register */ +#define CSI2nRTST 0x2c +#define CSI2nRTST_VSRSTS BIT(0) + +/* Receive Data Type Enable Low Register */ +#define CSI2nDTEL 0x60 + +/* Receive Data Type Enable High Register */ +#define CSI2nDTEH 0x64 + +/* DPHY registers */ +/* D-PHY Control Register 0 */ +#define CSIDPHYCTRL0 0x400 +#define CSIDPHYCTRL0_EN_LDO1200 BIT(1) +#define CSIDPHYCTRL0_EN_BGR BIT(0) + +/* D-PHY Timing Register 0 */ +#define CSIDPHYTIM0 0x404 +#define CSIDPHYTIM0_TCLK_MISS(x) ((x) << 24) +#define CSIDPHYTIM0_T_INIT(x) ((x) << 0) + +/* D-PHY Timing Register 1 */ +#define CSIDPHYTIM1 0x408 +#define CSIDPHYTIM1_THS_PREPARE(x) ((x) << 24) +#define CSIDPHYTIM1_TCLK_PREPARE(x) ((x) << 16) +#define CSIDPHYTIM1_THS_SETTLE(x) ((x) << 8) +#define CSIDPHYTIM1_TCLK_SETTLE(x) ((x) << 0) + +/* D-PHY Skew Adjustment Function */ +#define CSIDPHYSKW0 0x460 +#define CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(x) ((x) & 0x3) +#define CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(x) (((x) & 0x3) << 4) +#define CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(x) (((x) & 0x3) << 8) +#define CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(x) (((x) & 0x3) << 12) +#define CSIDPHYSKW0_DEFAULT_SKW CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(1) | \ + CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(1) | \ + CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(1) | \ + CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(1) + +#define VSRSTS_RETRIES 20 + +#define RZG2L_CSI2_MIN_WIDTH 320 +#define RZG2L_CSI2_MIN_HEIGHT 240 +#define RZG2L_CSI2_MAX_WIDTH 2800 +#define RZG2L_CSI2_MAX_HEIGHT 4095 + +#define RZG2L_CSI2_DEFAULT_WIDTH RZG2L_CSI2_MIN_WIDTH +#define RZG2L_CSI2_DEFAULT_HEIGHT RZG2L_CSI2_MIN_HEIGHT +#define RZG2L_CSI2_DEFAULT_FMT MEDIA_BUS_FMT_UYVY8_1X16 + +enum rzg2l_csi2_pads { + RZG2L_CSI2_SINK = 0, + RZG2L_CSI2_SOURCE, + NR_OF_RZG2L_CSI2_PAD, +}; + +struct rzg2l_csi2 { + struct device *dev; + void __iomem *base; + struct reset_control *presetn; + struct reset_control *cmn_rstb; + struct clk *sysclk; + unsigned long vclk_rate; + + struct v4l2_subdev subdev; + struct media_pad pads[NR_OF_RZG2L_CSI2_PAD]; + + struct v4l2_async_notifier notifier; + struct v4l2_subdev *remote_source; + + unsigned short lanes; + unsigned long hsfreq; + + bool dphy_enabled; +}; + +struct rzg2l_csi2_timings { + u32 t_init; + u32 tclk_miss; + u32 tclk_settle; + u32 ths_settle; + u32 tclk_prepare; + u32 ths_prepare; + u32 max_hsfreq; +}; + +static const struct rzg2l_csi2_timings rzg2l_csi2_global_timings[] = { + { + .max_hsfreq = 80, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 23, + .ths_settle = 31, + .tclk_prepare = 10, + .ths_prepare = 19, + }, + { + .max_hsfreq = 125, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 23, + .ths_settle = 28, + .tclk_prepare = 10, + .ths_prepare = 19, + }, + { + .max_hsfreq = 250, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 23, + .ths_settle = 22, + .tclk_prepare = 10, + .ths_prepare = 16, + }, + { + .max_hsfreq = 360, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 18, + .ths_settle = 19, + .tclk_prepare = 10, + .ths_prepare = 10, + }, + { + .max_hsfreq = 1500, + .t_init = 79801, + .tclk_miss = 4, + .tclk_settle = 18, + .ths_settle = 18, + .tclk_prepare = 10, + .ths_prepare = 10, + }, +}; + +struct rzg2l_csi2_format { + u32 code; + unsigned int datatype; + unsigned int bpp; +}; + +static const struct rzg2l_csi2_format rzg2l_csi2_formats[] = { + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, +}; + +static inline struct rzg2l_csi2 *sd_to_csi2(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rzg2l_csi2, subdev); +} + +static const struct rzg2l_csi2_format *rzg2l_csi2_code_to_fmt(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_formats); i++) + if (rzg2l_csi2_formats[i].code == code) + return &rzg2l_csi2_formats[i]; + + return NULL; +} + +static inline struct rzg2l_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) +{ + return container_of(n, struct rzg2l_csi2, notifier); +} + +static u32 rzg2l_csi2_read(struct rzg2l_csi2 *csi2, unsigned int reg) +{ + return ioread32(csi2->base + reg); +} + +static void rzg2l_csi2_write(struct rzg2l_csi2 *csi2, unsigned int reg, + u32 data) +{ + iowrite32(data, csi2->base + reg); +} + +static void rzg2l_csi2_set(struct rzg2l_csi2 *csi2, unsigned int reg, u32 set) +{ + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) | set); +} + +static void rzg2l_csi2_clr(struct rzg2l_csi2 *csi2, unsigned int reg, u32 clr) +{ + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) & ~clr); +} + +static int rzg2l_csi2_calc_mbps(struct rzg2l_csi2 *csi2) +{ + struct v4l2_subdev *source = csi2->remote_source; + const struct rzg2l_csi2_format *format; + const struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_state *state; + struct v4l2_ctrl *ctrl; + u64 mbps; + + /* Read the pixel rate control from remote. */ + ctrl = v4l2_ctrl_find(source->ctrl_handler, V4L2_CID_PIXEL_RATE); + if (!ctrl) { + dev_err(csi2->dev, "no pixel rate control in subdev %s\n", + source->name); + return -EINVAL; + } + + state = v4l2_subdev_lock_and_get_active_state(&csi2->subdev); + fmt = v4l2_subdev_get_pad_format(&csi2->subdev, state, RZG2L_CSI2_SINK); + format = rzg2l_csi2_code_to_fmt(fmt->code); + v4l2_subdev_unlock_state(state); + + /* + * Calculate hsfreq in Mbps + * hsfreq = (pixel_rate * bits_per_sample) / number_of_lanes + */ + mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * format->bpp; + do_div(mbps, csi2->lanes * 1000000); + + return mbps; +} + +/* ----------------------------------------------------------------------------- + * DPHY setting + */ + +static int rzg2l_csi2_dphy_disable(struct rzg2l_csi2 *csi2) +{ + int ret; + + /* Reset the CRU (D-PHY) */ + ret = reset_control_assert(csi2->cmn_rstb); + if (ret) + return ret; + + /* Stop the D-PHY clock */ + clk_disable_unprepare(csi2->sysclk); + + /* Cancel the EN_LDO1200 register setting */ + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); + + /* Cancel the EN_BGR register setting */ + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); + + csi2->dphy_enabled = false; + + return 0; +} + +static int rzg2l_csi2_dphy_enable(struct rzg2l_csi2 *csi2) +{ + const struct rzg2l_csi2_timings *dphy_timing; + u32 dphytim0, dphytim1; + unsigned int i; + int mbps; + int ret; + + mbps = rzg2l_csi2_calc_mbps(csi2); + if (mbps < 0) + return mbps; + + csi2->hsfreq = mbps; + + /* Set DPHY timing parameters */ + for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_global_timings); ++i) { + dphy_timing = &rzg2l_csi2_global_timings[i]; + + if (csi2->hsfreq <= dphy_timing->max_hsfreq) + break; + } + + if (i >= ARRAY_SIZE(rzg2l_csi2_global_timings)) + return -EINVAL; + + /* Set D-PHY timing parameters */ + dphytim0 = CSIDPHYTIM0_TCLK_MISS(dphy_timing->tclk_miss) | + CSIDPHYTIM0_T_INIT(dphy_timing->t_init); + dphytim1 = CSIDPHYTIM1_THS_PREPARE(dphy_timing->ths_prepare) | + CSIDPHYTIM1_TCLK_PREPARE(dphy_timing->tclk_prepare) | + CSIDPHYTIM1_THS_SETTLE(dphy_timing->ths_settle) | + CSIDPHYTIM1_TCLK_SETTLE(dphy_timing->tclk_settle); + rzg2l_csi2_write(csi2, CSIDPHYTIM0, dphytim0); + rzg2l_csi2_write(csi2, CSIDPHYTIM1, dphytim1); + + /* Enable D-PHY power control 0 */ + rzg2l_csi2_write(csi2, CSIDPHYSKW0, CSIDPHYSKW0_DEFAULT_SKW); + + /* Set the EN_BGR bit */ + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); + + /* Delay 20us to be stable */ + usleep_range(20, 40); + + /* Enable D-PHY power control 1 */ + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); + + /* Delay 10us to be stable */ + usleep_range(10, 20); + + /* Start supplying the internal clock for the D-PHY block */ + ret = clk_prepare_enable(csi2->sysclk); + if (ret) + rzg2l_csi2_dphy_disable(csi2); + + csi2->dphy_enabled = true; + + return ret; +} + +static int rzg2l_csi2_dphy_setting(struct v4l2_subdev *sd, bool on) +{ + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); + + if (on) + return rzg2l_csi2_dphy_enable(csi2); + + return rzg2l_csi2_dphy_disable(csi2); +} + +static void rzg2l_csi2_mipi_link_enable(struct rzg2l_csi2 *csi2) +{ + unsigned long vclk_rate = csi2->vclk_rate / HZ_PER_MHZ; + u32 frrskw, frrclk, frrskw_coeff, frrclk_coeff; + + /* Select data lanes */ + rzg2l_csi2_write(csi2, CSI2nMCT0, CSI2nMCT0_VDLN(csi2->lanes)); + + frrskw_coeff = 3 * vclk_rate * 8; + frrclk_coeff = frrskw_coeff / 2; + frrskw = DIV_ROUND_UP(frrskw_coeff, csi2->hsfreq); + frrclk = DIV_ROUND_UP(frrclk_coeff, csi2->hsfreq); + rzg2l_csi2_write(csi2, CSI2nMCT2, CSI2nMCT2_FRRSKW(frrskw) | + CSI2nMCT2_FRRCLK(frrclk)); + + /* + * Select data type. + * FS, FE, LS, LE, Generic Short Packet Codes 1 to 8, + * Generic Long Packet Data Types 1 to 4 YUV422 8-bit, + * RGB565, RGB888, RAW8 to RAW20, User-defined 8-bit + * data types 1 to 8 + */ + rzg2l_csi2_write(csi2, CSI2nDTEL, 0xf778ff0f); + rzg2l_csi2_write(csi2, CSI2nDTEH, 0x00ffff1f); + + /* Enable LINK reception */ + rzg2l_csi2_write(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); +} + +static void rzg2l_csi2_mipi_link_disable(struct rzg2l_csi2 *csi2) +{ + unsigned int timeout = VSRSTS_RETRIES; + + /* Stop LINK reception */ + rzg2l_csi2_clr(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); + + /* Request a software reset of the LINK Video Pixel Interface */ + rzg2l_csi2_write(csi2, CSI2nRTCT, CSI2nRTCT_VSRST); + + /* Make sure CSI2nRTST.VSRSTS bit is cleared */ + while (--timeout) { + if (!(rzg2l_csi2_read(csi2, CSI2nRTST) & CSI2nRTST_VSRSTS)) + break; + usleep_range(100, 200); + }; + + if (!timeout) + dev_err(csi2->dev, "Clearing CSI2nRTST.VSRSTS timed out\n"); +} + +static int rzg2l_csi2_mipi_link_setting(struct v4l2_subdev *sd, bool on) +{ + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); + + if (on) + rzg2l_csi2_mipi_link_enable(csi2); + else + rzg2l_csi2_mipi_link_disable(csi2); + + return 0; +} + +static int rzg2l_csi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); + int s_stream_ret = 0; + int ret; + + if (enable) { + ret = pm_runtime_resume_and_get(csi2->dev); + if (ret) + return ret; + + ret = rzg2l_csi2_mipi_link_setting(sd, 1); + if (ret) + goto err_pm_put; + + ret = reset_control_deassert(csi2->cmn_rstb); + if (ret) + goto err_mipi_link_disable; + } + + ret = v4l2_subdev_call(csi2->remote_source, video, s_stream, enable); + if (ret) + s_stream_ret = ret; + + if (enable && ret) + goto err_assert_rstb; + + if (!enable) { + ret = rzg2l_csi2_dphy_setting(sd, 0); + if (ret && !s_stream_ret) + s_stream_ret = ret; + ret = rzg2l_csi2_mipi_link_setting(sd, 0); + if (ret && !s_stream_ret) + s_stream_ret = ret; + + pm_runtime_put_sync(csi2->dev); + } + + return s_stream_ret; + +err_assert_rstb: + reset_control_assert(csi2->cmn_rstb); +err_mipi_link_disable: + rzg2l_csi2_mipi_link_setting(sd, 0); +err_pm_put: + pm_runtime_put_sync(csi2->dev); + return ret; +} + +static int rzg2l_csi2_pre_streamon(struct v4l2_subdev *sd, u32 flags) +{ + return rzg2l_csi2_dphy_setting(sd, 1); +} + +static int rzg2l_csi2_post_streamoff(struct v4l2_subdev *sd) +{ + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); + + /* + * In ideal case D-PHY will be disabled in s_stream(0) callback + * as mentioned in the HW manual. The below will only happen when + * pre_streamon succeeds and further down the line s_stream(1) + * fails so we need to undo things in post_streamoff. + */ + if (csi2->dphy_enabled) + return rzg2l_csi2_dphy_setting(sd, 0); + + return 0; +} + +static int rzg2l_csi2_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *src_format; + struct v4l2_mbus_framefmt *sink_format; + + src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE); + if (fmt->pad == RZG2L_CSI2_SOURCE) { + fmt->format = *src_format; + return 0; + } + + sink_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SINK); + + if (!rzg2l_csi2_code_to_fmt(fmt->format.code)) + sink_format->code = rzg2l_csi2_formats[0].code; + else + sink_format->code = fmt->format.code; + + sink_format->field = V4L2_FIELD_NONE; + sink_format->colorspace = fmt->format.colorspace; + sink_format->xfer_func = fmt->format.xfer_func; + sink_format->ycbcr_enc = fmt->format.ycbcr_enc; + sink_format->quantization = fmt->format.quantization; + sink_format->width = clamp_t(u32, fmt->format.width, + RZG2L_CSI2_MIN_WIDTH, RZG2L_CSI2_MAX_WIDTH); + sink_format->height = clamp_t(u32, fmt->format.height, + RZG2L_CSI2_MIN_HEIGHT, RZG2L_CSI2_MAX_HEIGHT); + fmt->format = *sink_format; + + /* propagate format to source pad */ + *src_format = *sink_format; + + return 0; +} + +static int rzg2l_csi2_init_config(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { .pad = RZG2L_CSI2_SINK, }; + + fmt.format.width = RZG2L_CSI2_DEFAULT_WIDTH; + fmt.format.height = RZG2L_CSI2_DEFAULT_HEIGHT; + fmt.format.field = V4L2_FIELD_NONE; + fmt.format.code = RZG2L_CSI2_DEFAULT_FMT; + fmt.format.colorspace = V4L2_COLORSPACE_SRGB; + fmt.format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return rzg2l_csi2_set_format(sd, sd_state, &fmt); +} + +static int rzg2l_csi2_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(rzg2l_csi2_formats)) + return -EINVAL; + + code->code = rzg2l_csi2_formats[code->index].code; + + return 0; +} + +static int rzg2l_csi2_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index != 0) + return -EINVAL; + + fse->min_width = RZG2L_CSI2_MIN_WIDTH; + fse->min_height = RZG2L_CSI2_MIN_HEIGHT; + fse->max_width = RZG2L_CSI2_MAX_WIDTH; + fse->max_height = RZG2L_CSI2_MAX_HEIGHT; + + return 0; +} + +static const struct v4l2_subdev_video_ops rzg2l_csi2_video_ops = { + .s_stream = rzg2l_csi2_s_stream, + .pre_streamon = rzg2l_csi2_pre_streamon, + .post_streamoff = rzg2l_csi2_post_streamoff, +}; + +static const struct v4l2_subdev_pad_ops rzg2l_csi2_pad_ops = { + .enum_mbus_code = rzg2l_csi2_enum_mbus_code, + .init_cfg = rzg2l_csi2_init_config, + .enum_frame_size = rzg2l_csi2_enum_frame_size, + .set_fmt = rzg2l_csi2_set_format, + .get_fmt = v4l2_subdev_get_fmt, +}; + +static const struct v4l2_subdev_ops rzg2l_csi2_subdev_ops = { + .video = &rzg2l_csi2_video_ops, + .pad = &rzg2l_csi2_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Async handling and registration of subdevices and links. + */ + +static int rzg2l_csi2_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); + + csi2->remote_source = subdev; + + dev_dbg(csi2->dev, "Bound subdev: %s pad\n", subdev->name); + + return media_create_pad_link(&subdev->entity, RZG2L_CSI2_SINK, + &csi2->subdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static void rzg2l_csi2_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); + + csi2->remote_source = NULL; + + dev_dbg(csi2->dev, "Unbind subdev %s\n", subdev->name); +} + +static const struct v4l2_async_notifier_operations rzg2l_csi2_notify_ops = { + .bound = rzg2l_csi2_notify_bound, + .unbind = rzg2l_csi2_notify_unbind, +}; + +static int rzg2l_csi2_parse_v4l2(struct rzg2l_csi2 *csi2, + struct v4l2_fwnode_endpoint *vep) +{ + /* Only port 0 endpoint 0 is valid. */ + if (vep->base.port || vep->base.id) + return -ENOTCONN; + + csi2->lanes = vep->bus.mipi_csi2.num_data_lanes; + + return 0; +} + +static int rzg2l_csi2_parse_dt(struct rzg2l_csi2 *csi2) +{ + struct v4l2_fwnode_endpoint v4l2_ep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi2->dev), 0, 0, 0); + if (!ep) { + dev_err(csi2->dev, "Not connected to subdevice\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep); + if (ret) { + dev_err(csi2->dev, "Could not parse v4l2 endpoint\n"); + fwnode_handle_put(ep); + return -EINVAL; + } + + ret = rzg2l_csi2_parse_v4l2(csi2, &v4l2_ep); + if (ret) { + fwnode_handle_put(ep); + return ret; + } + + fwnode = fwnode_graph_get_remote_endpoint(ep); + fwnode_handle_put(ep); + + v4l2_async_nf_init(&csi2->notifier); + csi2->notifier.ops = &rzg2l_csi2_notify_ops; + + asd = v4l2_async_nf_add_fwnode(&csi2->notifier, fwnode, + struct v4l2_async_subdev); + fwnode_handle_put(fwnode); + if (IS_ERR(asd)) + return PTR_ERR(asd); + + ret = v4l2_async_subdev_nf_register(&csi2->subdev, &csi2->notifier); + if (ret) + v4l2_async_nf_cleanup(&csi2->notifier); + + return ret; +} + +static int rzg2l_validate_csi2_lanes(struct rzg2l_csi2 *csi2) +{ + int lanes; + int ret; + + if (csi2->lanes != 1 && csi2->lanes != 2 && csi2->lanes != 4) { + dev_err(csi2->dev, "Unsupported number of data-lanes: %u\n", + csi2->lanes); + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(csi2->dev); + if (ret) + return ret; + + /* Checking the maximum lanes support for CSI-2 module */ + lanes = (rzg2l_csi2_read(csi2, CSI2nMCG) & CSI2nMCG_SDLN) >> 8; + if (lanes < csi2->lanes) { + dev_err(csi2->dev, + "Failed to support %d data lanes\n", csi2->lanes); + ret = -EINVAL; + } + + pm_runtime_put_sync(csi2->dev); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver. + */ + +static const struct media_entity_operations rzg2l_csi2_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int rzg2l_csi2_probe(struct platform_device *pdev) +{ + struct rzg2l_csi2 *csi2; + struct clk *vclk; + int ret; + + csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); + if (!csi2) + return -ENOMEM; + + csi2->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2->base)) + return PTR_ERR(csi2->base); + + csi2->cmn_rstb = devm_reset_control_get_exclusive(&pdev->dev, "cmn-rstb"); + if (IS_ERR(csi2->cmn_rstb)) + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->cmn_rstb), + "Failed to get cpg cmn-rstb\n"); + + csi2->presetn = devm_reset_control_get_shared(&pdev->dev, "presetn"); + if (IS_ERR(csi2->presetn)) + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->presetn), + "Failed to get cpg presetn\n"); + + csi2->sysclk = devm_clk_get(&pdev->dev, "system"); + if (IS_ERR(csi2->sysclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->sysclk), + "Failed to get system clk\n"); + + vclk = clk_get(&pdev->dev, "video"); + if (IS_ERR(vclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(vclk), + "Failed to get video clock\n"); + csi2->vclk_rate = clk_get_rate(vclk); + clk_put(vclk); + + csi2->dev = &pdev->dev; + + platform_set_drvdata(pdev, csi2); + + ret = rzg2l_csi2_parse_dt(csi2); + if (ret) + return ret; + + pm_runtime_enable(&pdev->dev); + + ret = rzg2l_validate_csi2_lanes(csi2); + if (ret) + goto error_pm; + + csi2->subdev.dev = &pdev->dev; + v4l2_subdev_init(&csi2->subdev, &rzg2l_csi2_subdev_ops); + v4l2_set_subdevdata(&csi2->subdev, &pdev->dev); + snprintf(csi2->subdev.name, sizeof(csi2->subdev.name), + "csi-%s", dev_name(&pdev->dev)); + csi2->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + csi2->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2->subdev.entity.ops = &rzg2l_csi2_entity_ops; + + csi2->pads[RZG2L_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; + /* + * TODO: RZ/G2L CSI2 supports 4 virtual channels, as virtual + * channels should be implemented by streams API which is under + * development lets hardcode to VC0 for now. + */ + csi2->pads[RZG2L_CSI2_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&csi2->subdev.entity, 2, csi2->pads); + if (ret) + goto error_pm; + + ret = v4l2_subdev_init_finalize(&csi2->subdev); + if (ret < 0) + goto error_async; + + ret = v4l2_async_register_subdev(&csi2->subdev); + if (ret < 0) + goto error_subdev; + + return 0; + +error_subdev: + v4l2_subdev_cleanup(&csi2->subdev); +error_async: + v4l2_async_nf_unregister(&csi2->notifier); + v4l2_async_nf_cleanup(&csi2->notifier); + media_entity_cleanup(&csi2->subdev.entity); +error_pm: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int rzg2l_csi2_remove(struct platform_device *pdev) +{ + struct rzg2l_csi2 *csi2 = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&csi2->notifier); + v4l2_async_nf_cleanup(&csi2->notifier); + v4l2_async_unregister_subdev(&csi2->subdev); + v4l2_subdev_cleanup(&csi2->subdev); + media_entity_cleanup(&csi2->subdev.entity); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused rzg2l_csi2_pm_runtime_suspend(struct device *dev) +{ + struct rzg2l_csi2 *csi2 = dev_get_drvdata(dev); + + reset_control_assert(csi2->presetn); + + return 0; +} + +static int __maybe_unused rzg2l_csi2_pm_runtime_resume(struct device *dev) +{ + struct rzg2l_csi2 *csi2 = dev_get_drvdata(dev); + + return reset_control_deassert(csi2->presetn); +} + +static const struct dev_pm_ops rzg2l_csi2_pm_ops = { + SET_RUNTIME_PM_OPS(rzg2l_csi2_pm_runtime_suspend, rzg2l_csi2_pm_runtime_resume, NULL) +}; + +static const struct of_device_id rzg2l_csi2_of_table[] = { + { .compatible = "renesas,rzg2l-csi2", }, + { /* sentinel */ } +}; + +static struct platform_driver rzg2l_csi2_pdrv = { + .remove = rzg2l_csi2_remove, + .probe = rzg2l_csi2_probe, + .driver = { + .name = "rzg2l-csi2", + .of_match_table = rzg2l_csi2_of_table, + .pm = &rzg2l_csi2_pm_ops, + }, +}; + +module_platform_driver(rzg2l_csi2_pdrv); + +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); +MODULE_DESCRIPTION("Renesas RZ/G2L MIPI CSI2 receiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c new file mode 100644 index 000000000000..4dcd2faff5bb --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Renesas RZ/G2L CRU + * + * Copyright (C) 2022 Renesas Electronics Corp. + */ + +#include "rzg2l-cru.h" + +struct rzg2l_cru_ip_format { + u32 code; + unsigned int datatype; + unsigned int bpp; +}; + +static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, +}; + +enum rzg2l_csi2_pads { + RZG2L_CRU_IP_SINK = 0, + RZG2L_CRU_IP_SOURCE, +}; + +static const struct rzg2l_cru_ip_format *rzg2l_cru_ip_code_to_fmt(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_ip_formats); i++) + if (rzg2l_cru_ip_formats[i].code == code) + return &rzg2l_cru_ip_formats[i]; + + return NULL; +} + +struct v4l2_mbus_framefmt *rzg2l_cru_ip_get_src_fmt(struct rzg2l_cru_dev *cru) +{ + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + + state = v4l2_subdev_lock_and_get_active_state(&cru->ip.subdev); + fmt = v4l2_subdev_get_pad_format(&cru->ip.subdev, state, 1); + v4l2_subdev_unlock_state(state); + + return fmt; +} + +static int rzg2l_cru_ip_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rzg2l_cru_dev *cru; + int s_stream_ret = 0; + int ret; + + cru = v4l2_get_subdevdata(sd); + + if (!enable) { + ret = v4l2_subdev_call(cru->ip.remote, video, s_stream, enable); + if (ret) + s_stream_ret = ret; + + ret = v4l2_subdev_call(cru->ip.remote, video, post_streamoff); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret && !s_stream_ret) + s_stream_ret = ret; + rzg2l_cru_stop_image_processing(cru); + } else { + ret = v4l2_subdev_call(cru->ip.remote, video, pre_streamon, 0); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) + return ret; + + ret = rzg2l_cru_start_image_processing(cru); + if (ret) { + v4l2_subdev_call(cru->ip.remote, video, post_streamoff); + return ret; + } + + rzg2l_cru_vclk_unprepare(cru); + + ret = v4l2_subdev_call(cru->ip.remote, video, s_stream, enable); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (!ret) { + ret = rzg2l_cru_vclk_prepare(cru); + if (!ret) + return 0; + } else { + /* enable back vclk so that s_stream in error path disables it */ + if (rzg2l_cru_vclk_prepare(cru)) + dev_err(cru->dev, "Failed to enable vclk\n"); + } + + s_stream_ret = ret; + + v4l2_subdev_call(cru->ip.remote, video, post_streamoff); + rzg2l_cru_stop_image_processing(cru); + } + + return s_stream_ret; +} + +static int rzg2l_cru_ip_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *src_format; + struct v4l2_mbus_framefmt *sink_format; + + src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CRU_IP_SOURCE); + if (fmt->pad == RZG2L_CRU_IP_SOURCE) { + fmt->format = *src_format; + return 0; + } + + sink_format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); + + if (!rzg2l_cru_ip_code_to_fmt(fmt->format.code)) + sink_format->code = rzg2l_cru_ip_formats[0].code; + else + sink_format->code = fmt->format.code; + + sink_format->field = V4L2_FIELD_NONE; + sink_format->colorspace = fmt->format.colorspace; + sink_format->xfer_func = fmt->format.xfer_func; + sink_format->ycbcr_enc = fmt->format.ycbcr_enc; + sink_format->quantization = fmt->format.quantization; + sink_format->width = clamp_t(u32, fmt->format.width, + RZG2L_CRU_MIN_INPUT_WIDTH, RZG2L_CRU_MAX_INPUT_WIDTH); + sink_format->height = clamp_t(u32, fmt->format.height, + RZG2L_CRU_MIN_INPUT_HEIGHT, RZG2L_CRU_MAX_INPUT_HEIGHT); + + fmt->format = *sink_format; + + /* propagate format to source pad */ + *src_format = *sink_format; + + return 0; +} + +static int rzg2l_cru_ip_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(rzg2l_cru_ip_formats)) + return -EINVAL; + + code->code = rzg2l_cru_ip_formats[code->index].code; + return 0; +} + +static int rzg2l_cru_ip_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index != 0) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_UYVY8_1X16) + return -EINVAL; + + fse->min_width = RZG2L_CRU_MIN_INPUT_WIDTH; + fse->min_height = RZG2L_CRU_MIN_INPUT_HEIGHT; + fse->max_width = RZG2L_CRU_MAX_INPUT_WIDTH; + fse->max_height = RZG2L_CRU_MAX_INPUT_HEIGHT; + + return 0; +} + +static int rzg2l_cru_ip_init_config(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { .pad = RZG2L_CRU_IP_SINK, }; + + fmt.format.width = RZG2L_CRU_MIN_INPUT_WIDTH; + fmt.format.height = RZG2L_CRU_MIN_INPUT_HEIGHT; + fmt.format.field = V4L2_FIELD_NONE; + fmt.format.code = MEDIA_BUS_FMT_UYVY8_1X16; + fmt.format.colorspace = V4L2_COLORSPACE_SRGB; + fmt.format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return rzg2l_cru_ip_set_format(sd, sd_state, &fmt); +} + +static const struct v4l2_subdev_video_ops rzg2l_cru_ip_video_ops = { + .s_stream = rzg2l_cru_ip_s_stream, +}; + +static const struct v4l2_subdev_pad_ops rzg2l_cru_ip_pad_ops = { + .enum_mbus_code = rzg2l_cru_ip_enum_mbus_code, + .enum_frame_size = rzg2l_cru_ip_enum_frame_size, + .init_cfg = rzg2l_cru_ip_init_config, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = rzg2l_cru_ip_set_format, +}; + +static const struct v4l2_subdev_ops rzg2l_cru_ip_subdev_ops = { + .video = &rzg2l_cru_ip_video_ops, + .pad = &rzg2l_cru_ip_pad_ops, +}; + +static const struct media_entity_operations rzg2l_cru_ip_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +int rzg2l_cru_ip_subdev_register(struct rzg2l_cru_dev *cru) +{ + struct rzg2l_cru_ip *ip = &cru->ip; + int ret; + + ip->subdev.dev = cru->dev; + v4l2_subdev_init(&ip->subdev, &rzg2l_cru_ip_subdev_ops); + v4l2_set_subdevdata(&ip->subdev, cru); + snprintf(ip->subdev.name, sizeof(ip->subdev.name), + "cru-ip-%s", dev_name(cru->dev)); + ip->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + ip->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + ip->subdev.entity.ops = &rzg2l_cru_ip_entity_ops; + + ip->pads[0].flags = MEDIA_PAD_FL_SINK; + ip->pads[1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&ip->subdev.entity, 2, ip->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(&ip->subdev); + if (ret < 0) + goto entity_cleanup; + + ret = v4l2_device_register_subdev(&cru->v4l2_dev, &ip->subdev); + if (ret < 0) + goto error_subdev; + + return 0; +error_subdev: + v4l2_subdev_cleanup(&ip->subdev); +entity_cleanup: + media_entity_cleanup(&ip->subdev.entity); + + return ret; +} + +void rzg2l_cru_ip_subdev_unregister(struct rzg2l_cru_dev *cru) +{ + struct rzg2l_cru_ip *ip = &cru->ip; + + media_entity_cleanup(&ip->subdev.entity); + v4l2_subdev_cleanup(&ip->subdev); + v4l2_device_unregister_subdev(&ip->subdev); +} diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c new file mode 100644 index 000000000000..91b57c7c2e56 --- /dev/null +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Renesas RZ/G2L CRU + * + * Copyright (C) 2022 Renesas Electronics Corp. + * + * Based on Renesas R-Car VIN + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * Copyright (C) 2008 Magnus Damm + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> + +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#include "rzg2l-cru.h" + +/* HW CRU Registers Definition */ + +/* CRU Control Register */ +#define CRUnCTRL 0x0 +#define CRUnCTRL_VINSEL(x) ((x) << 0) + +/* CRU Interrupt Enable Register */ +#define CRUnIE 0x4 +#define CRUnIE_EFE BIT(17) + +/* CRU Interrupt Status Register */ +#define CRUnINTS 0x8 +#define CRUnINTS_SFS BIT(16) + +/* CRU Reset Register */ +#define CRUnRST 0xc +#define CRUnRST_VRESETN BIT(0) + +/* Memory Bank Base Address (Lower) Register for CRU Image Data */ +#define AMnMBxADDRL(x) (0x100 + ((x) * 8)) + +/* Memory Bank Base Address (Higher) Register for CRU Image Data */ +#define AMnMBxADDRH(x) (0x104 + ((x) * 8)) + +/* Memory Bank Enable Register for CRU Image Data */ +#define AMnMBVALID 0x148 +#define AMnMBVALID_MBVALID(x) GENMASK(x, 0) + +/* Memory Bank Status Register for CRU Image Data */ +#define AMnMBS 0x14c +#define AMnMBS_MBSTS 0x7 + +/* AXI Master FIFO Pointer Register for CRU Image Data */ +#define AMnFIFOPNTR 0x168 +#define AMnFIFOPNTR_FIFOWPNTR GENMASK(7, 0) +#define AMnFIFOPNTR_FIFORPNTR_Y GENMASK(23, 16) + +/* AXI Master Transfer Stop Register for CRU Image Data */ +#define AMnAXISTP 0x174 +#define AMnAXISTP_AXI_STOP BIT(0) + +/* AXI Master Transfer Stop Status Register for CRU Image Data */ +#define AMnAXISTPACK 0x178 +#define AMnAXISTPACK_AXI_STOP_ACK BIT(0) + +/* CRU Image Processing Enable Register */ +#define ICnEN 0x200 +#define ICnEN_ICEN BIT(0) + +/* CRU Image Processing Main Control Register */ +#define ICnMC 0x208 +#define ICnMC_CSCTHR BIT(5) +#define ICnMC_INF_YUV8_422 (0x1e << 16) +#define ICnMC_INF_USER (0x30 << 16) +#define ICnMC_VCSEL(x) ((x) << 22) +#define ICnMC_INF_MASK GENMASK(21, 16) + +/* CRU Module Status Register */ +#define ICnMS 0x254 +#define ICnMS_IA BIT(2) + +/* CRU Data Output Mode Register */ +#define ICnDMR 0x26c +#define ICnDMR_YCMODE_UYVY (1 << 4) + +#define RZG2L_TIMEOUT_MS 100 +#define RZG2L_RETRIES 10 + +#define RZG2L_CRU_DEFAULT_FORMAT V4L2_PIX_FMT_UYVY +#define RZG2L_CRU_DEFAULT_WIDTH RZG2L_CRU_MIN_INPUT_WIDTH +#define RZG2L_CRU_DEFAULT_HEIGHT RZG2L_CRU_MIN_INPUT_HEIGHT +#define RZG2L_CRU_DEFAULT_FIELD V4L2_FIELD_NONE +#define RZG2L_CRU_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB + +struct rzg2l_cru_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +#define to_buf_list(vb2_buffer) \ + (&container_of(vb2_buffer, struct rzg2l_cru_buffer, vb)->list) + +/* ----------------------------------------------------------------------------- + * DMA operations + */ +static void rzg2l_cru_write(struct rzg2l_cru_dev *cru, u32 offset, u32 value) +{ + iowrite32(value, cru->base + offset); +} + +static u32 rzg2l_cru_read(struct rzg2l_cru_dev *cru, u32 offset) +{ + return ioread32(cru->base + offset); +} + +/* Need to hold qlock before calling */ +static void return_unused_buffers(struct rzg2l_cru_dev *cru, + enum vb2_buffer_state state) +{ + struct rzg2l_cru_buffer *buf, *node; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&cru->qlock, flags); + for (i = 0; i < cru->num_buf; i++) { + if (cru->queue_buf[i]) { + vb2_buffer_done(&cru->queue_buf[i]->vb2_buf, + state); + cru->queue_buf[i] = NULL; + } + } + + list_for_each_entry_safe(buf, node, &cru->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + spin_unlock_irqrestore(&cru->qlock, flags); +} + +static int rzg2l_cru_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); + + /* Make sure the image size is large enough. */ + if (*nplanes) + return sizes[0] < cru->format.sizeimage ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = cru->format.sizeimage; + + return 0; +}; + +static int rzg2l_cru_buffer_prepare(struct vb2_buffer *vb) +{ + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = cru->format.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(cru->dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void rzg2l_cru_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&cru->qlock, flags); + + list_add_tail(to_buf_list(vbuf), &cru->buf_list); + + spin_unlock_irqrestore(&cru->qlock, flags); +} + +static int rzg2l_cru_mc_validate_format(struct rzg2l_cru_dev *cru, + struct v4l2_subdev *sd, + struct media_pad *pad) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + fmt.pad = pad->index; + if (v4l2_subdev_call_state_active(sd, pad, get_fmt, &fmt)) + return -EPIPE; + + switch (fmt.format.code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + break; + default: + return -EPIPE; + } + + switch (fmt.format.field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + break; + default: + return -EPIPE; + } + + if (fmt.format.width != cru->format.width || + fmt.format.height != cru->format.height) + return -EPIPE; + + return 0; +} + +static void rzg2l_cru_set_slot_addr(struct rzg2l_cru_dev *cru, + int slot, dma_addr_t addr) +{ + /* + * The address needs to be 512 bytes aligned. Driver should never accept + * settings that do not satisfy this in the first place... + */ + if (WARN_ON((addr) & RZG2L_CRU_HW_BUFFER_MASK)) + return; + + /* Currently, we just use the buffer in 32 bits address */ + rzg2l_cru_write(cru, AMnMBxADDRL(slot), addr); + rzg2l_cru_write(cru, AMnMBxADDRH(slot), 0); +} + +/* + * Moves a buffer from the queue to the HW slot. If no buffer is + * available use the scratch buffer. The scratch buffer is never + * returned to userspace, its only function is to enable the capture + * loop to keep running. + */ +static void rzg2l_cru_fill_hw_slot(struct rzg2l_cru_dev *cru, int slot) +{ + struct vb2_v4l2_buffer *vbuf; + struct rzg2l_cru_buffer *buf; + dma_addr_t phys_addr; + + /* A already populated slot shall never be overwritten. */ + if (WARN_ON(cru->queue_buf[slot])) + return; + + dev_dbg(cru->dev, "Filling HW slot: %d\n", slot); + + if (list_empty(&cru->buf_list)) { + cru->queue_buf[slot] = NULL; + phys_addr = cru->scratch_phys; + } else { + /* Keep track of buffer we give to HW */ + buf = list_entry(cru->buf_list.next, + struct rzg2l_cru_buffer, list); + vbuf = &buf->vb; + list_del_init(to_buf_list(vbuf)); + cru->queue_buf[slot] = vbuf; + + /* Setup DMA */ + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + } + + rzg2l_cru_set_slot_addr(cru, slot, phys_addr); +} + +static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) +{ + unsigned int slot; + + /* + * Set image data memory banks. + * Currently, we will use maximum address. + */ + rzg2l_cru_write(cru, AMnMBVALID, AMnMBVALID_MBVALID(cru->num_buf - 1)); + + for (slot = 0; slot < cru->num_buf; slot++) + rzg2l_cru_fill_hw_slot(cru, slot); +} + +static void rzg2l_cru_csi2_setup(struct rzg2l_cru_dev *cru, bool *input_is_yuv, + struct v4l2_mbus_framefmt *ip_sd_fmt) +{ + u32 icnmc; + + switch (ip_sd_fmt->code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + icnmc = ICnMC_INF_YUV8_422; + *input_is_yuv = true; + break; + default: + *input_is_yuv = false; + icnmc = ICnMC_INF_USER; + break; + } + + icnmc |= (rzg2l_cru_read(cru, ICnMC) & ~ICnMC_INF_MASK); + + /* Set virtual channel CSI2 */ + icnmc |= ICnMC_VCSEL(cru->csi.channel); + + rzg2l_cru_write(cru, ICnMC, icnmc); +} + +static int rzg2l_cru_initialize_image_conv(struct rzg2l_cru_dev *cru, + struct v4l2_mbus_framefmt *ip_sd_fmt) +{ + bool output_is_yuv = false; + bool input_is_yuv = false; + u32 icndmr; + + rzg2l_cru_csi2_setup(cru, &input_is_yuv, ip_sd_fmt); + + /* Output format */ + switch (cru->format.pixelformat) { + case V4L2_PIX_FMT_UYVY: + icndmr = ICnDMR_YCMODE_UYVY; + output_is_yuv = true; + break; + default: + dev_err(cru->dev, "Invalid pixelformat (0x%x)\n", + cru->format.pixelformat); + return -EINVAL; + } + + /* If input and output use same colorspace, do bypass mode */ + if (output_is_yuv == input_is_yuv) + rzg2l_cru_write(cru, ICnMC, + rzg2l_cru_read(cru, ICnMC) | ICnMC_CSCTHR); + else + rzg2l_cru_write(cru, ICnMC, + rzg2l_cru_read(cru, ICnMC) & (~ICnMC_CSCTHR)); + + /* Set output data format */ + rzg2l_cru_write(cru, ICnDMR, icndmr); + + return 0; +} + +void rzg2l_cru_stop_image_processing(struct rzg2l_cru_dev *cru) +{ + u32 amnfifopntr, amnfifopntr_w, amnfifopntr_r_y; + unsigned int retries = 0; + unsigned long flags; + u32 icnms; + + spin_lock_irqsave(&cru->qlock, flags); + + /* Disable and clear the interrupt */ + rzg2l_cru_write(cru, CRUnIE, 0); + rzg2l_cru_write(cru, CRUnINTS, 0x001F0F0F); + + /* Stop the operation of image conversion */ + rzg2l_cru_write(cru, ICnEN, 0); + + /* Wait for streaming to stop */ + while ((rzg2l_cru_read(cru, ICnMS) & ICnMS_IA) && retries++ < RZG2L_RETRIES) { + spin_unlock_irqrestore(&cru->qlock, flags); + msleep(RZG2L_TIMEOUT_MS); + spin_lock_irqsave(&cru->qlock, flags); + } + + icnms = rzg2l_cru_read(cru, ICnMS) & ICnMS_IA; + if (icnms) + dev_err(cru->dev, "Failed stop HW, something is seriously broken\n"); + + cru->state = RZG2L_CRU_DMA_STOPPED; + + /* Wait until the FIFO becomes empty */ + for (retries = 5; retries > 0; retries--) { + amnfifopntr = rzg2l_cru_read(cru, AMnFIFOPNTR); + + amnfifopntr_w = amnfifopntr & AMnFIFOPNTR_FIFOWPNTR; + amnfifopntr_r_y = + (amnfifopntr & AMnFIFOPNTR_FIFORPNTR_Y) >> 16; + if (amnfifopntr_w == amnfifopntr_r_y) + break; + + usleep_range(10, 20); + } + + /* Notify that FIFO is not empty here */ + if (!retries) + dev_err(cru->dev, "Failed to empty FIFO\n"); + + /* Stop AXI bus */ + rzg2l_cru_write(cru, AMnAXISTP, AMnAXISTP_AXI_STOP); + + /* Wait until the AXI bus stop */ + for (retries = 5; retries > 0; retries--) { + if (rzg2l_cru_read(cru, AMnAXISTPACK) & + AMnAXISTPACK_AXI_STOP_ACK) + break; + + usleep_range(10, 20); + }; + + /* Notify that AXI bus can not stop here */ + if (!retries) + dev_err(cru->dev, "Failed to stop AXI bus\n"); + + /* Cancel the AXI bus stop request */ + rzg2l_cru_write(cru, AMnAXISTP, 0); + + /* Reset the CRU (AXI-master) */ + reset_control_assert(cru->aresetn); + + /* Resets the image processing module */ + rzg2l_cru_write(cru, CRUnRST, 0); + + spin_unlock_irqrestore(&cru->qlock, flags); +} + +int rzg2l_cru_start_image_processing(struct rzg2l_cru_dev *cru) +{ + struct v4l2_mbus_framefmt *fmt = rzg2l_cru_ip_get_src_fmt(cru); + unsigned long flags; + int ret; + + spin_lock_irqsave(&cru->qlock, flags); + + /* Initialize image convert */ + ret = rzg2l_cru_initialize_image_conv(cru, fmt); + if (ret) { + spin_unlock_irqrestore(&cru->qlock, flags); + return ret; + } + + /* Select a video input */ + rzg2l_cru_write(cru, CRUnCTRL, CRUnCTRL_VINSEL(0)); + + /* Cancel the software reset for image processing block */ + rzg2l_cru_write(cru, CRUnRST, CRUnRST_VRESETN); + + /* Disable and clear the interrupt before using */ + rzg2l_cru_write(cru, CRUnIE, 0); + rzg2l_cru_write(cru, CRUnINTS, 0x001f000f); + + /* Initialize the AXI master */ + rzg2l_cru_initialize_axi(cru); + + /* Enable interrupt */ + rzg2l_cru_write(cru, CRUnIE, CRUnIE_EFE); + + /* Enable image processing reception */ + rzg2l_cru_write(cru, ICnEN, ICnEN_ICEN); + + spin_unlock_irqrestore(&cru->qlock, flags); + + return 0; +} + +void rzg2l_cru_vclk_unprepare(struct rzg2l_cru_dev *cru) +{ + clk_disable_unprepare(cru->vclk); +} + +int rzg2l_cru_vclk_prepare(struct rzg2l_cru_dev *cru) +{ + return clk_prepare_enable(cru->vclk); +} + +static int rzg2l_cru_set_stream(struct rzg2l_cru_dev *cru, int on) +{ + struct media_pipeline *pipe; + struct v4l2_subdev *sd; + struct media_pad *pad; + int ret; + + pad = media_pad_remote_pad_first(&cru->pad); + if (!pad) + return -EPIPE; + + sd = media_entity_to_v4l2_subdev(pad->entity); + + if (!on) { + int stream_off_ret = 0; + + ret = v4l2_subdev_call(sd, video, s_stream, 0); + if (ret) + stream_off_ret = ret; + + ret = v4l2_subdev_call(sd, video, post_streamoff); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret && !stream_off_ret) + stream_off_ret = ret; + + video_device_pipeline_stop(&cru->vdev); + + pm_runtime_put_sync(cru->dev); + clk_disable_unprepare(cru->vclk); + + return stream_off_ret; + } + + ret = pm_runtime_resume_and_get(cru->dev); + if (ret) + return ret; + + ret = clk_prepare_enable(cru->vclk); + if (ret) + goto err_pm_put; + + ret = rzg2l_cru_mc_validate_format(cru, sd, pad); + if (ret) + goto err_vclk_disable; + + pipe = media_entity_pipeline(&sd->entity) ? : &cru->vdev.pipe; + ret = video_device_pipeline_start(&cru->vdev, pipe); + if (ret) + goto err_vclk_disable; + + ret = v4l2_subdev_call(sd, video, pre_streamon, 0); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) + goto pipe_line_stop; + + ret = v4l2_subdev_call(sd, video, s_stream, 1); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) + goto err_s_stream; + + return 0; + +err_s_stream: + v4l2_subdev_call(sd, video, post_streamoff); + +pipe_line_stop: + video_device_pipeline_stop(&cru->vdev); + +err_vclk_disable: + clk_disable_unprepare(cru->vclk); + +err_pm_put: + pm_runtime_put_sync(cru->dev); + + return ret; +} + +static void rzg2l_cru_stop_streaming(struct rzg2l_cru_dev *cru) +{ + cru->state = RZG2L_CRU_DMA_STOPPING; + + rzg2l_cru_set_stream(cru, 0); +} + +static irqreturn_t rzg2l_cru_irq(int irq, void *data) +{ + struct rzg2l_cru_dev *cru = data; + unsigned int handled = 0; + unsigned long flags; + u32 irq_status; + u32 amnmbs; + int slot; + + spin_lock_irqsave(&cru->qlock, flags); + + irq_status = rzg2l_cru_read(cru, CRUnINTS); + if (!irq_status) + goto done; + + handled = 1; + + rzg2l_cru_write(cru, CRUnINTS, rzg2l_cru_read(cru, CRUnINTS)); + + /* Nothing to do if capture status is 'RZG2L_CRU_DMA_STOPPED' */ + if (cru->state == RZG2L_CRU_DMA_STOPPED) { + dev_dbg(cru->dev, "IRQ while state stopped\n"); + goto done; + } + + /* Increase stop retries if capture status is 'RZG2L_CRU_DMA_STOPPING' */ + if (cru->state == RZG2L_CRU_DMA_STOPPING) { + if (irq_status & CRUnINTS_SFS) + dev_dbg(cru->dev, "IRQ while state stopping\n"); + goto done; + } + + /* Prepare for capture and update state */ + amnmbs = rzg2l_cru_read(cru, AMnMBS); + slot = amnmbs & AMnMBS_MBSTS; + + /* + * AMnMBS.MBSTS indicates the destination of Memory Bank (MB). + * Recalculate to get the current transfer complete MB. + */ + if (slot == 0) + slot = cru->num_buf - 1; + else + slot--; + + /* + * To hand buffers back in a known order to userspace start + * to capture first from slot 0. + */ + if (cru->state == RZG2L_CRU_DMA_STARTING) { + if (slot != 0) { + dev_dbg(cru->dev, "Starting sync slot: %d\n", slot); + goto done; + } + + dev_dbg(cru->dev, "Capture start synced!\n"); + cru->state = RZG2L_CRU_DMA_RUNNING; + } + + /* Capture frame */ + if (cru->queue_buf[slot]) { + cru->queue_buf[slot]->field = cru->format.field; + cru->queue_buf[slot]->sequence = cru->sequence; + cru->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&cru->queue_buf[slot]->vb2_buf, + VB2_BUF_STATE_DONE); + cru->queue_buf[slot] = NULL; + } else { + /* Scratch buffer was used, dropping frame. */ + dev_dbg(cru->dev, "Dropping frame %u\n", cru->sequence); + } + + cru->sequence++; + + /* Prepare for next frame */ + rzg2l_cru_fill_hw_slot(cru, slot); + +done: + spin_unlock_irqrestore(&cru->qlock, flags); + + return IRQ_RETVAL(handled); +} + +static int rzg2l_cru_start_streaming_vq(struct vb2_queue *vq, unsigned int count) +{ + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); + int ret; + + /* Release reset state */ + ret = reset_control_deassert(cru->aresetn); + if (ret) { + dev_err(cru->dev, "failed to deassert aresetn\n"); + return ret; + } + + ret = reset_control_deassert(cru->presetn); + if (ret) { + reset_control_assert(cru->aresetn); + dev_err(cru->dev, "failed to deassert presetn\n"); + return ret; + } + + ret = request_irq(cru->image_conv_irq, rzg2l_cru_irq, + IRQF_SHARED, KBUILD_MODNAME, cru); + if (ret) { + dev_err(cru->dev, "failed to request irq\n"); + goto assert_resets; + } + + /* Allocate scratch buffer. */ + cru->scratch = dma_alloc_coherent(cru->dev, cru->format.sizeimage, + &cru->scratch_phys, GFP_KERNEL); + if (!cru->scratch) { + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); + dev_err(cru->dev, "Failed to allocate scratch buffer\n"); + ret = -ENOMEM; + goto free_image_conv_irq; + } + + cru->sequence = 0; + + ret = rzg2l_cru_set_stream(cru, 1); + if (ret) { + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); + goto out; + } + + cru->state = RZG2L_CRU_DMA_STARTING; + dev_dbg(cru->dev, "Starting to capture\n"); + return 0; + +out: + if (ret) + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, + cru->scratch_phys); +free_image_conv_irq: + free_irq(cru->image_conv_irq, cru); + +assert_resets: + reset_control_assert(cru->presetn); + reset_control_assert(cru->aresetn); + + return ret; +} + +static void rzg2l_cru_stop_streaming_vq(struct vb2_queue *vq) +{ + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); + + rzg2l_cru_stop_streaming(cru); + + /* Free scratch buffer */ + dma_free_coherent(cru->dev, cru->format.sizeimage, + cru->scratch, cru->scratch_phys); + + free_irq(cru->image_conv_irq, cru); + reset_control_assert(cru->presetn); + + return_unused_buffers(cru, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops rzg2l_cru_qops = { + .queue_setup = rzg2l_cru_queue_setup, + .buf_prepare = rzg2l_cru_buffer_prepare, + .buf_queue = rzg2l_cru_buffer_queue, + .start_streaming = rzg2l_cru_start_streaming_vq, + .stop_streaming = rzg2l_cru_stop_streaming_vq, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru) +{ + mutex_destroy(&cru->lock); + + v4l2_device_unregister(&cru->v4l2_dev); + vb2_queue_release(&cru->queue); +} + +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru) +{ + struct vb2_queue *q = &cru->queue; + unsigned int i; + int ret; + + /* Initialize the top-level structure */ + ret = v4l2_device_register(cru->dev, &cru->v4l2_dev); + if (ret) + return ret; + + mutex_init(&cru->lock); + INIT_LIST_HEAD(&cru->buf_list); + + spin_lock_init(&cru->qlock); + + cru->state = RZG2L_CRU_DMA_STOPPED; + + for (i = 0; i < RZG2L_CRU_HW_BUFFER_MAX; i++) + cru->queue_buf[i] = NULL; + + /* buffer queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->lock = &cru->lock; + q->drv_priv = cru; + q->buf_struct_size = sizeof(struct rzg2l_cru_buffer); + q->ops = &rzg2l_cru_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 4; + q->dev = cru->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(cru->dev, "failed to initialize VB2 queue\n"); + goto error; + } + + return 0; + +error: + mutex_destroy(&cru->lock); + v4l2_device_unregister(&cru->v4l2_dev); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 stuff + */ + +static const struct v4l2_format_info rzg2l_cru_formats[] = { + { + .format = V4L2_PIX_FMT_UYVY, + .bpp[0] = 2, + }, +}; + +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_formats); i++) + if (rzg2l_cru_formats[i].format == format) + return rzg2l_cru_formats + i; + + return NULL; +} + +static u32 rzg2l_cru_format_bytesperline(struct v4l2_pix_format *pix) +{ + const struct v4l2_format_info *fmt; + + fmt = rzg2l_cru_format_from_pixel(pix->pixelformat); + + if (WARN_ON(!fmt)) + return -EINVAL; + + return pix->width * fmt->bpp[0]; +} + +static u32 rzg2l_cru_format_sizeimage(struct v4l2_pix_format *pix) +{ + return pix->bytesperline * pix->height; +} + +static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, + struct v4l2_pix_format *pix) +{ + if (!rzg2l_cru_format_from_pixel(pix->pixelformat)) + pix->pixelformat = RZG2L_CRU_DEFAULT_FORMAT; + + switch (pix->field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + break; + default: + pix->field = RZG2L_CRU_DEFAULT_FIELD; + break; + } + + /* Limit to CRU capabilities */ + v4l_bound_align_image(&pix->width, 320, RZG2L_CRU_MAX_INPUT_WIDTH, 1, + &pix->height, 240, RZG2L_CRU_MAX_INPUT_HEIGHT, 2, 0); + + pix->bytesperline = rzg2l_cru_format_bytesperline(pix); + pix->sizeimage = rzg2l_cru_format_sizeimage(pix); + + dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", + pix->width, pix->height, pix->bytesperline, pix->sizeimage); +} + +static void rzg2l_cru_try_format(struct rzg2l_cru_dev *cru, + struct v4l2_pix_format *pix) +{ + /* + * The V4L2 specification clearly documents the colorspace fields + * as being set by drivers for capture devices. Using the values + * supplied by userspace thus wouldn't comply with the API. Until + * the API is updated force fixed values. + */ + pix->colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace, + pix->ycbcr_enc); + + rzg2l_cru_format_align(cru, pix); +} + +static int rzg2l_cru_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "RZG2L_CRU", sizeof(cap->card)); + + return 0; +} + +static int rzg2l_cru_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + + rzg2l_cru_try_format(cru, &f->fmt.pix); + + return 0; +} + +static int rzg2l_cru_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + + if (vb2_is_busy(&cru->queue)) + return -EBUSY; + + rzg2l_cru_try_format(cru, &f->fmt.pix); + + cru->format = f->fmt.pix; + + return 0; +} + +static int rzg2l_cru_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + + f->fmt.pix = cru->format; + + return 0; +} + +static int rzg2l_cru_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(rzg2l_cru_formats)) + return -EINVAL; + + f->pixelformat = rzg2l_cru_formats[f->index].format; + + return 0; +} + +static const struct v4l2_ioctl_ops rzg2l_cru_ioctl_ops = { + .vidioc_querycap = rzg2l_cru_querycap, + .vidioc_try_fmt_vid_cap = rzg2l_cru_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = rzg2l_cru_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = rzg2l_cru_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = rzg2l_cru_enum_fmt_vid_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * Media controller file operations + */ + +static int rzg2l_cru_open(struct file *file) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + int ret; + + ret = mutex_lock_interruptible(&cru->lock); + if (ret) + return ret; + + file->private_data = cru; + ret = v4l2_fh_open(file); + if (ret) + goto err_unlock; + + mutex_unlock(&cru->lock); + + return 0; + +err_unlock: + mutex_unlock(&cru->lock); + + return ret; +} + +static int rzg2l_cru_release(struct file *file) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + int ret; + + mutex_lock(&cru->lock); + + /* the release helper will cleanup any on-going streaming. */ + ret = _vb2_fop_release(file, NULL); + + mutex_unlock(&cru->lock); + + return ret; +} + +static const struct v4l2_file_operations rzg2l_cru_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = rzg2l_cru_open, + .release = rzg2l_cru_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +static void rzg2l_cru_v4l2_init(struct rzg2l_cru_dev *cru) +{ + struct video_device *vdev = &cru->vdev; + + vdev->v4l2_dev = &cru->v4l2_dev; + vdev->queue = &cru->queue; + snprintf(vdev->name, sizeof(vdev->name), "CRU output"); + vdev->release = video_device_release_empty; + vdev->lock = &cru->lock; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + vdev->device_caps |= V4L2_CAP_IO_MC; + vdev->fops = &rzg2l_cru_fops; + vdev->ioctl_ops = &rzg2l_cru_ioctl_ops; + + /* Set a default format */ + cru->format.pixelformat = RZG2L_CRU_DEFAULT_FORMAT; + cru->format.width = RZG2L_CRU_DEFAULT_WIDTH; + cru->format.height = RZG2L_CRU_DEFAULT_HEIGHT; + cru->format.field = RZG2L_CRU_DEFAULT_FIELD; + cru->format.colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; + rzg2l_cru_format_align(cru, &cru->format); +} + +void rzg2l_cru_video_unregister(struct rzg2l_cru_dev *cru) +{ + media_device_unregister(&cru->mdev); + video_unregister_device(&cru->vdev); +} + +int rzg2l_cru_video_register(struct rzg2l_cru_dev *cru) +{ + struct video_device *vdev = &cru->vdev; + int ret; + + if (video_is_registered(&cru->vdev)) { + struct media_entity *entity; + + entity = &cru->vdev.entity; + if (!entity->graph_obj.mdev) + entity->graph_obj.mdev = &cru->mdev; + return 0; + } + + rzg2l_cru_v4l2_init(cru); + video_set_drvdata(vdev, cru); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(cru->dev, "Failed to register video device\n"); + return ret; + } + + ret = media_device_register(&cru->mdev); + if (ret) { + video_unregister_device(&cru->vdev); + return ret; + } + + return 0; +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c index d8731ebbf479..3482f7d707b7 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c @@ -715,7 +715,7 @@ static void rkisp1_aec_config_v12(struct rkisp1_params *params, u32 exp_ctrl; u32 block_hsize, block_vsize; u32 wnd_num_idx = 1; - const u32 ae_wnd_num[] = { 5, 9, 15, 15 }; + static const u32 ae_wnd_num[] = { 5, 9, 15, 15 }; /* avoid to override the old enable value */ exp_ctrl = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_EXP_CTRL); @@ -822,7 +822,7 @@ static void rkisp1_hst_config_v12(struct rkisp1_params *params, u32 block_hsize, block_vsize; u32 wnd_num_idx, hist_weight_num, hist_ctrl, value; u8 weight15x15[RKISP1_CIF_ISP_HIST_WEIGHT_REG_SIZE_V12]; - const u32 hist_wnd_num[] = { 5, 9, 15, 15 }; + static const u32 hist_wnd_num[] = { 5, 9, 15, 15 }; /* now we just support 9x9 window */ wnd_num_idx = 1; diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-core.c b/drivers/media/platform/samsung/exynos4-is/fimc-core.c index 91cc8d58a663..1791100b6935 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-core.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-core.c @@ -1173,7 +1173,7 @@ int __init fimc_register_driver(void) return platform_driver_register(&fimc_driver); } -void __exit fimc_unregister_driver(void) +void fimc_unregister_driver(void) { platform_driver_unregister(&fimc_driver); } diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.c b/drivers/media/platform/samsung/exynos4-is/media-dev.c index 52b43ea04030..98a60f01129d 100644 --- a/drivers/media/platform/samsung/exynos4-is/media-dev.c +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.c @@ -1380,9 +1380,7 @@ static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, /* Find platform data for this sensor subdev */ for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++) - if (fmd->sensor[i].asd && - fmd->sensor[i].asd->match.fwnode == - of_fwnode_handle(subdev->dev->of_node)) + if (fmd->sensor[i].asd == asd) si = &fmd->sensor[i]; if (si == NULL) @@ -1473,9 +1471,7 @@ static int fimc_md_probe(struct platform_device *pdev) pinctrl = devm_pinctrl_get(dev); if (IS_ERR(pinctrl)) { - ret = PTR_ERR(pinctrl); - if (ret != EPROBE_DEFER) - dev_err(dev, "Failed to get pinctrl: %d\n", ret); + ret = dev_err_probe(dev, PTR_ERR(pinctrl), "Failed to get pinctrl\n"); goto err_clk; } @@ -1586,7 +1582,11 @@ static int __init fimc_md_init(void) if (ret) return ret; - return platform_driver_register(&fimc_md_driver); + ret = platform_driver_register(&fimc_md_driver); + if (ret) + fimc_unregister_driver(); + + return ret; } static void __exit fimc_md_exit(void) diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c index fca5c6405eec..f3e4cdac1ef3 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c @@ -36,7 +36,7 @@ #define S5P_MFC_ENC_NAME "s5p-mfc-enc" int mfc_debug_level; -module_param_named(debug, mfc_debug_level, int, S_IRUGO | S_IWUSR); +module_param_named(debug, mfc_debug_level, int, 0644); MODULE_PARM_DESC(debug, "Debug level - higher value produces more verbose messages"); static char *mfc_mem_size; @@ -148,11 +148,13 @@ static void s5p_mfc_watchdog(struct timer_list *t) if (test_bit(0, &dev->hw_lock)) atomic_inc(&dev->watchdog_cnt); if (atomic_read(&dev->watchdog_cnt) >= MFC_WATCHDOG_CNT) { - /* This means that hw is busy and no interrupts were + /* + * This means that hw is busy and no interrupts were * generated by hw for the Nth time of running this * watchdog timer. This usually means a serious hw * error. Now it is time to kill all instances and - * reset the MFC. */ + * reset the MFC. + */ mfc_err("Time out during waiting for HW\n"); schedule_work(&dev->watchdog_work); } @@ -172,8 +174,10 @@ static void s5p_mfc_watchdog_worker(struct work_struct *work) dev = container_of(work, struct s5p_mfc_dev, watchdog_work); mfc_err("Driver timeout error handling\n"); - /* Lock the mutex that protects open and release. - * This is necessary as they may load and unload firmware. */ + /* + * Lock the mutex that protects open and release. + * This is necessary as they may load and unload firmware. + */ mutex_locked = mutex_trylock(&dev->mfc_mutex); if (!mutex_locked) mfc_err("Error: some instance may be closing/opening\n"); @@ -197,8 +201,10 @@ static void s5p_mfc_watchdog_worker(struct work_struct *work) /* De-init MFC */ s5p_mfc_deinit_hw(dev); - /* Double check if there is at least one instance running. - * If no instance is in memory than no firmware should be present */ + /* + * Double check if there is at least one instance running. + * If no instance is in memory than no firmware should be present + */ if (dev->num_inst > 0) { ret = s5p_mfc_load_firmware(dev); if (ret) { @@ -260,8 +266,10 @@ static void s5p_mfc_handle_frame_copy_time(struct s5p_mfc_ctx *ctx) return; dec_y_addr = (u32)s5p_mfc_hw_call(dev->mfc_ops, get_dec_y_adr, dev); - /* Copy timestamp / timecode from decoded src to dst and set - appropriate flags. */ + /* + * Copy timestamp / timecode from decoded src to dst and set + * appropriate flags. + */ src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); list_for_each_entry(dst_buf, &ctx->dst_queue, list) { u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0); @@ -289,8 +297,10 @@ static void s5p_mfc_handle_frame_copy_time(struct s5p_mfc_ctx *ctx) V4L2_BUF_FLAG_BFRAME; break; default: - /* Don't know how to handle - S5P_FIMV_DECODE_FRAME_OTHER_FRAME. */ + /* + * Don't know how to handle + * S5P_FIMV_DECODE_FRAME_OTHER_FRAME. + */ mfc_debug(2, "Unexpected frame type: %d\n", frame_type); } @@ -322,8 +332,10 @@ static void s5p_mfc_handle_frame_new(struct s5p_mfc_ctx *ctx, unsigned int err) return; } ctx->sequence++; - /* The MFC returns address of the buffer, now we have to - * check which vb2_buffer does it correspond to */ + /* + * The MFC returns address of the buffer, now we have to + * check which vb2_buffer does it correspond to + */ list_for_each_entry(dst_buf, &ctx->dst_queue, list) { u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0); @@ -476,8 +488,10 @@ static void s5p_mfc_handle_error(struct s5p_mfc_dev *dev, case MFCINST_FINISHING: case MFCINST_FINISHED: case MFCINST_RUNNING: - /* It is highly probable that an error occurred - * while decoding a frame */ + /* + * It is highly probable that an error occurred + * while decoding a frame + */ clear_work_bit(ctx); ctx->state = MFCINST_ERROR; /* Mark all dst buffers as having an error */ @@ -535,6 +549,7 @@ static void s5p_mfc_handle_seq_done(struct s5p_mfc_ctx *ctx, ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC) && !list_empty(&ctx->src_queue)) { struct s5p_mfc_buf *src_buf; + src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); if (s5p_mfc_hw_call(dev->mfc_ops, get_consumed_stream, @@ -951,7 +966,7 @@ static int s5p_mfc_release(struct file *file) /* * If instance was initialised and not yet freed, * return instance and free resources - */ + */ if (ctx->state != MFCINST_FREE && ctx->state != MFCINST_INIT) { mfc_debug(2, "Has to free instance\n"); s5p_mfc_close_mfc_inst(dev, ctx); @@ -1047,10 +1062,10 @@ static int s5p_mfc_mmap(struct file *file, struct vm_area_struct *vma) int ret; if (offset < DST_QUEUE_OFF_BASE) { - mfc_debug(2, "mmaping source\n"); + mfc_debug(2, "mmapping source\n"); ret = vb2_mmap(&ctx->vq_src, vma); } else { /* capture */ - mfc_debug(2, "mmaping destination\n"); + mfc_debug(2, "mmapping destination\n"); vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT); ret = vb2_mmap(&ctx->vq_dst, vma); } @@ -1149,7 +1164,6 @@ static int s5p_mfc_configure_2port_memory(struct s5p_mfc_dev *mfc_dev) bank2_virt = dma_alloc_coherent(mfc_dev->mem_dev[BANK_R_CTX], align_size, &bank2_dma_addr, GFP_KERNEL); if (!bank2_virt) { - mfc_err("Allocating bank2 base failed\n"); s5p_mfc_release_firmware(mfc_dev); device_unregister(mfc_dev->mem_dev[BANK_R_CTX]); device_unregister(mfc_dev->mem_dev[BANK_L_CTX]); @@ -1318,7 +1332,7 @@ static int s5p_mfc_probe(struct platform_device *pdev) /* * Load fails if fs isn't mounted. Try loading anyway. - * _open() will load it, it it fails now. Ignore failure. + * _open() will load it, it fails now. Ignore failure. */ s5p_mfc_load_firmware(dev); @@ -1429,7 +1443,7 @@ static int s5p_mfc_remove(struct platform_device *pdev) * Clear ctx dev pointer to avoid races between s5p_mfc_remove() * and s5p_mfc_release() and s5p_mfc_release() accessing ctx->dev * after s5p_mfc_remove() is run during unbind. - */ + */ mutex_lock(&dev->mfc_mutex); for (i = 0; i < MFC_NUM_CONTEXTS; i++) { ctx = dev->ctx[i]; @@ -1576,8 +1590,18 @@ static struct s5p_mfc_variant mfc_drvdata_v7 = { .port_num = MFC_NUM_PORTS_V7, .buf_size = &buf_size_v7, .fw_name[0] = "s5p-mfc-v7.fw", - .clk_names = {"mfc", "sclk_mfc"}, - .num_clocks = 2, + .clk_names = {"mfc"}, + .num_clocks = 1, +}; + +static struct s5p_mfc_variant mfc_drvdata_v7_3250 = { + .version = MFC_VERSION_V7, + .version_bit = MFC_V7_BIT, + .port_num = MFC_NUM_PORTS_V7, + .buf_size = &buf_size_v7, + .fw_name[0] = "s5p-mfc-v7.fw", + .clk_names = {"mfc", "sclk_mfc"}, + .num_clocks = 2, }; static struct s5p_mfc_buf_size_v6 mfc_buf_size_v8 = { @@ -1648,6 +1672,9 @@ static const struct of_device_id exynos_mfc_match[] = { .compatible = "samsung,mfc-v7", .data = &mfc_drvdata_v7, }, { + .compatible = "samsung,exynos3250-mfc", + .data = &mfc_drvdata_v7_3250, + }, { .compatible = "samsung,mfc-v8", .data = &mfc_drvdata_v8, }, { diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c index 72d70984e99a..6d3c92045c05 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c @@ -468,8 +468,10 @@ void s5p_mfc_close_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx) s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); /* Wait until instance is returned or timeout occurred */ if (s5p_mfc_wait_for_done_ctx(ctx, - S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET, 0)) + S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET, 0)){ + clear_work_bit_irqsave(ctx); mfc_err("Err returning instance\n"); + } /* Free resources */ s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, ctx); diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c index b65e506665af..f62703cebb77 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c @@ -1218,6 +1218,7 @@ static int enc_post_frame_start(struct s5p_mfc_ctx *ctx) unsigned long mb_y_addr, mb_c_addr; int slice_type; unsigned int strm_size; + bool src_ready; slice_type = s5p_mfc_hw_call(dev->mfc_ops, get_enc_slice_type, dev); strm_size = s5p_mfc_hw_call(dev->mfc_ops, get_enc_strm_size, dev); @@ -1257,7 +1258,8 @@ static int enc_post_frame_start(struct s5p_mfc_ctx *ctx) } } } - if ((ctx->src_queue_cnt > 0) && (ctx->state == MFCINST_RUNNING)) { + if (ctx->src_queue_cnt > 0 && (ctx->state == MFCINST_RUNNING || + ctx->state == MFCINST_FINISHING)) { mb_entry = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); if (mb_entry->flags & MFC_BUF_FLAG_USED) { @@ -1288,7 +1290,13 @@ static int enc_post_frame_start(struct s5p_mfc_ctx *ctx) vb2_set_plane_payload(&mb_entry->b->vb2_buf, 0, strm_size); vb2_buffer_done(&mb_entry->b->vb2_buf, VB2_BUF_STATE_DONE); } - if ((ctx->src_queue_cnt == 0) || (ctx->dst_queue_cnt == 0)) + + src_ready = true; + if (ctx->state == MFCINST_RUNNING && ctx->src_queue_cnt == 0) + src_ready = false; + if (ctx->state == MFCINST_FINISHING && ctx->ref_queue_cnt == 0) + src_ready = false; + if (!src_ready || ctx->dst_queue_cnt == 0) clear_work_bit(ctx); return 0; diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c index 8227004f6746..c0df5ac9fcff 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c @@ -1060,7 +1060,7 @@ static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) } /* aspect ratio VUI */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 5); reg |= ((p_h264->vui_sar & 0x1) << 5); writel(reg, mfc_regs->e_h264_options); @@ -1083,7 +1083,7 @@ static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) /* intra picture period for H.264 open GOP */ /* control */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 4); reg |= ((p_h264->open_gop & 0x1) << 4); writel(reg, mfc_regs->e_h264_options); @@ -1097,23 +1097,23 @@ static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) } /* 'WEIGHTED_BI_PREDICTION' for B is disable */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x3 << 9); writel(reg, mfc_regs->e_h264_options); /* 'CONSTRAINED_INTRA_PRED_ENABLE' is disable */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 14); writel(reg, mfc_regs->e_h264_options); /* ASO */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 6); reg |= ((p_h264->aso & 0x1) << 6); writel(reg, mfc_regs->e_h264_options); /* hier qp enable */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 8); reg |= ((p_h264->open_gop & 0x1) << 8); writel(reg, mfc_regs->e_h264_options); @@ -1134,7 +1134,7 @@ static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) writel(reg, mfc_regs->e_h264_num_t_layer); /* frame packing SEI generation */ - readl(mfc_regs->e_h264_options); + reg = readl(mfc_regs->e_h264_options); reg &= ~(0x1 << 25); reg |= ((p_h264->sei_frame_packing & 0x1) << 25); writel(reg, mfc_regs->e_h264_options); diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c index cefe6b7bfdc4..c38b62d4f1ae 100644 --- a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c @@ -24,16 +24,18 @@ #include <linux/module.h> #include <linux/of_gpio.h> #include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pinctrl/pinctrl.h> #include <linux/platform_device.h> -#include <linux/usb.h> #include <linux/slab.h> #include <linux/time.h> +#include <linux/usb.h> #include <linux/wait.h> -#include <linux/pinctrl/pinctrl.h> -#include "c8sectpfe-core.h" #include "c8sectpfe-common.h" +#include "c8sectpfe-core.h" #include "c8sectpfe-debugfs.h" + #include <media/dmxdev.h> #include <media/dvb_demux.h> #include <media/dvb_frontend.h> @@ -925,6 +927,7 @@ static int configure_channels(struct c8sectpfei *fei) if (ret) { dev_err(fei->dev, "configure_memdma_and_inputblock failed\n"); + of_node_put(child); goto err_unmap; } index++; diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index 37458d4d9564..ad8e9742e1ae 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -1946,12 +1946,9 @@ static int dcmi_probe(struct platform_device *pdev) return -ENOMEM; dcmi->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); - if (IS_ERR(dcmi->rstc)) { - if (PTR_ERR(dcmi->rstc) != -EPROBE_DEFER) - dev_err(&pdev->dev, "Could not get reset control\n"); - - return PTR_ERR(dcmi->rstc); - } + if (IS_ERR(dcmi->rstc)) + return dev_err_probe(&pdev->dev, PTR_ERR(dcmi->rstc), + "Could not get reset control\n"); /* Get bus characteristics from devicetree */ np = of_graph_get_next_endpoint(np, NULL); @@ -1997,26 +1994,18 @@ static int dcmi_probe(struct platform_device *pdev) } dcmi->regs = devm_ioremap_resource(&pdev->dev, dcmi->res); - if (IS_ERR(dcmi->regs)) { - dev_err(&pdev->dev, "Could not map registers\n"); + if (IS_ERR(dcmi->regs)) return PTR_ERR(dcmi->regs); - } mclk = devm_clk_get(&pdev->dev, "mclk"); - if (IS_ERR(mclk)) { - if (PTR_ERR(mclk) != -EPROBE_DEFER) - dev_err(&pdev->dev, "Unable to get mclk\n"); - return PTR_ERR(mclk); - } + if (IS_ERR(mclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(mclk), + "Unable to get mclk\n"); chan = dma_request_chan(&pdev->dev, "tx"); - if (IS_ERR(chan)) { - ret = PTR_ERR(chan); - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, - "Failed to request DMA channel: %d\n", ret); - return ret; - } + if (IS_ERR(chan)) + return dev_err_probe(&pdev->dev, PTR_ERR(chan), + "Failed to request DMA channel\n"); dcmi->dma_max_burst = UINT_MAX; ret = dma_get_slave_caps(chan, &caps); diff --git a/drivers/media/platform/sunxi/sun6i-csi/Makefile b/drivers/media/platform/sunxi/sun6i-csi/Makefile index e7e315347804..87e7a715140a 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/Makefile +++ b/drivers/media/platform/sunxi/sun6i-csi/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -sun6i-csi-y += sun6i_video.o sun6i_csi.o +sun6i-csi-y += sun6i_csi.o sun6i_csi_bridge.o sun6i_csi_capture.o obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c index 8b99c17e8403..e3e6650181c8 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c @@ -1,18 +1,14 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> */ #include <linux/clk.h> -#include <linux/delay.h> -#include <linux/dma-mapping.h> #include <linux/err.h> -#include <linux/fs.h> #include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/ioctl.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> @@ -20,561 +16,56 @@ #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/reset.h> -#include <linux/sched.h> -#include <linux/sizes.h> -#include <linux/slab.h> +#include <media/v4l2-device.h> #include <media/v4l2-mc.h> #include "sun6i_csi.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_capture.h" #include "sun6i_csi_reg.h" -/* Helpers */ +/* ISP */ -/* TODO add 10&12 bit YUV, RGB support */ -bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev, - u32 pixformat, u32 mbus_code) +int sun6i_csi_isp_complete(struct sun6i_csi_device *csi_dev, + struct v4l2_device *v4l2_dev) { - struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; - - /* - * Some video receivers have the ability to be compatible with - * 8bit and 16bit bus width. - * Identify the media bus format from device tree. - */ - if ((v4l2->v4l2_ep.bus_type == V4L2_MBUS_PARALLEL - || v4l2->v4l2_ep.bus_type == V4L2_MBUS_BT656) - && v4l2->v4l2_ep.bus.parallel.bus_width == 16) { - switch (pixformat) { - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_YUV422P: - switch (mbus_code) { - case MEDIA_BUS_FMT_UYVY8_1X16: - case MEDIA_BUS_FMT_VYUY8_1X16: - case MEDIA_BUS_FMT_YUYV8_1X16: - case MEDIA_BUS_FMT_YVYU8_1X16: - return true; - default: - dev_dbg(csi_dev->dev, - "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - default: - dev_dbg(csi_dev->dev, "Unsupported pixformat: 0x%x\n", - pixformat); - break; - } - return false; - } + if (csi_dev->v4l2_dev && csi_dev->v4l2_dev != v4l2_dev) + return -EINVAL; - switch (pixformat) { - case V4L2_PIX_FMT_SBGGR8: - return (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8); - case V4L2_PIX_FMT_SGBRG8: - return (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8); - case V4L2_PIX_FMT_SGRBG8: - return (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8); - case V4L2_PIX_FMT_SRGGB8: - return (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8); - case V4L2_PIX_FMT_SBGGR10: - return (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10); - case V4L2_PIX_FMT_SGBRG10: - return (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10); - case V4L2_PIX_FMT_SGRBG10: - return (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10); - case V4L2_PIX_FMT_SRGGB10: - return (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10); - case V4L2_PIX_FMT_SBGGR12: - return (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12); - case V4L2_PIX_FMT_SGBRG12: - return (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12); - case V4L2_PIX_FMT_SGRBG12: - return (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12); - case V4L2_PIX_FMT_SRGGB12: - return (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12); - - case V4L2_PIX_FMT_YUYV: - return (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8); - case V4L2_PIX_FMT_YVYU: - return (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8); - case V4L2_PIX_FMT_UYVY: - return (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8); - case V4L2_PIX_FMT_VYUY: - return (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8); - - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_YUV422P: - switch (mbus_code) { - case MEDIA_BUS_FMT_UYVY8_2X8: - case MEDIA_BUS_FMT_VYUY8_2X8: - case MEDIA_BUS_FMT_YUYV8_2X8: - case MEDIA_BUS_FMT_YVYU8_2X8: - return true; - default: - dev_dbg(csi_dev->dev, "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - - case V4L2_PIX_FMT_RGB565: - return (mbus_code == MEDIA_BUS_FMT_RGB565_2X8_LE); - case V4L2_PIX_FMT_RGB565X: - return (mbus_code == MEDIA_BUS_FMT_RGB565_2X8_BE); - - case V4L2_PIX_FMT_JPEG: - return (mbus_code == MEDIA_BUS_FMT_JPEG_1X8); - - default: - dev_dbg(csi_dev->dev, "Unsupported pixformat: 0x%x\n", - pixformat); - break; - } + csi_dev->v4l2_dev = v4l2_dev; + csi_dev->media_dev = v4l2_dev->mdev; - return false; + return sun6i_csi_capture_setup(csi_dev); } -int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable) +static int sun6i_csi_isp_detect(struct sun6i_csi_device *csi_dev) { struct device *dev = csi_dev->dev; - struct regmap *regmap = csi_dev->regmap; - int ret; - - if (!enable) { - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0); - pm_runtime_put(dev); + struct fwnode_handle *handle; + /* + * ISP is not available if not connected via fwnode graph. + * This will also check that the remote parent node is available. + */ + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + SUN6I_CSI_PORT_ISP, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!handle) return 0; - } - - ret = pm_runtime_resume_and_get(dev); - if (ret < 0) - return ret; - - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN); - - return 0; -} - -static enum csi_input_fmt get_csi_input_format(struct sun6i_csi_device *csi_dev, - u32 mbus_code, u32 pixformat) -{ - /* non-YUV */ - if ((mbus_code & 0xF000) != 0x2000) - return CSI_INPUT_FORMAT_RAW; - - switch (pixformat) { - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - return CSI_INPUT_FORMAT_RAW; - default: - break; - } - /* not support YUV420 input format yet */ - dev_dbg(csi_dev->dev, "Select YUV422 as default input format of CSI.\n"); - return CSI_INPUT_FORMAT_YUV422; -} - -static enum csi_output_fmt -get_csi_output_format(struct sun6i_csi_device *csi_dev, u32 pixformat, - u32 field) -{ - bool buf_interlaced = false; - - if (field == V4L2_FIELD_INTERLACED - || field == V4L2_FIELD_INTERLACED_TB - || field == V4L2_FIELD_INTERLACED_BT) - buf_interlaced = true; - - switch (pixformat) { - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SGBRG8: - case V4L2_PIX_FMT_SGRBG8: - case V4L2_PIX_FMT_SRGGB8: - return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; - case V4L2_PIX_FMT_SBGGR10: - case V4L2_PIX_FMT_SGBRG10: - case V4L2_PIX_FMT_SGRBG10: - case V4L2_PIX_FMT_SRGGB10: - return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10; - case V4L2_PIX_FMT_SBGGR12: - case V4L2_PIX_FMT_SGBRG12: - case V4L2_PIX_FMT_SGRBG12: - case V4L2_PIX_FMT_SRGGB12: - return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12; - - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; - - case V4L2_PIX_FMT_NV12_16L16: - return buf_interlaced ? CSI_FRAME_MB_YUV420 : - CSI_FIELD_MB_YUV420; - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 : - CSI_FIELD_UV_CB_YUV420; - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 : - CSI_FIELD_PLANAR_YUV420; - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 : - CSI_FIELD_UV_CB_YUV422; - case V4L2_PIX_FMT_YUV422P: - return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 : - CSI_FIELD_PLANAR_YUV422; - - case V4L2_PIX_FMT_RGB565: - case V4L2_PIX_FMT_RGB565X: - return buf_interlaced ? CSI_FRAME_RGB565 : CSI_FIELD_RGB565; - - case V4L2_PIX_FMT_JPEG: - return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; - - default: - dev_warn(csi_dev->dev, "Unsupported pixformat: 0x%x\n", pixformat); - break; - } + fwnode_handle_put(handle); - return CSI_FIELD_RAW_8; -} - -static enum csi_input_seq get_csi_input_seq(struct sun6i_csi_device *csi_dev, - u32 mbus_code, u32 pixformat) -{ - /* Input sequence does not apply to non-YUV formats */ - if ((mbus_code & 0xF000) != 0x2000) + if (!IS_ENABLED(CONFIG_VIDEO_SUN6I_ISP)) { + dev_warn(dev, + "ISP link is detected but not enabled in kernel config!"); return 0; - - switch (pixformat) { - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YUV422P: - switch (mbus_code) { - case MEDIA_BUS_FMT_UYVY8_2X8: - case MEDIA_BUS_FMT_UYVY8_1X16: - return CSI_INPUT_SEQ_UYVY; - case MEDIA_BUS_FMT_VYUY8_2X8: - case MEDIA_BUS_FMT_VYUY8_1X16: - return CSI_INPUT_SEQ_VYUY; - case MEDIA_BUS_FMT_YUYV8_2X8: - case MEDIA_BUS_FMT_YUYV8_1X16: - return CSI_INPUT_SEQ_YUYV; - case MEDIA_BUS_FMT_YVYU8_1X16: - case MEDIA_BUS_FMT_YVYU8_2X8: - return CSI_INPUT_SEQ_YVYU; - default: - dev_warn(csi_dev->dev, "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_YVU420: - switch (mbus_code) { - case MEDIA_BUS_FMT_UYVY8_2X8: - case MEDIA_BUS_FMT_UYVY8_1X16: - return CSI_INPUT_SEQ_VYUY; - case MEDIA_BUS_FMT_VYUY8_2X8: - case MEDIA_BUS_FMT_VYUY8_1X16: - return CSI_INPUT_SEQ_UYVY; - case MEDIA_BUS_FMT_YUYV8_2X8: - case MEDIA_BUS_FMT_YUYV8_1X16: - return CSI_INPUT_SEQ_YVYU; - case MEDIA_BUS_FMT_YVYU8_1X16: - case MEDIA_BUS_FMT_YVYU8_2X8: - return CSI_INPUT_SEQ_YUYV; - default: - dev_warn(csi_dev->dev, "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - - case V4L2_PIX_FMT_YUYV: - return CSI_INPUT_SEQ_YUYV; - - default: - dev_warn(csi_dev->dev, "Unsupported pixformat: 0x%x, defaulting to YUYV\n", - pixformat); - break; - } - - return CSI_INPUT_SEQ_YUYV; -} - -static void sun6i_csi_setup_bus(struct sun6i_csi_device *csi_dev) -{ - struct v4l2_fwnode_endpoint *endpoint = &csi_dev->v4l2.v4l2_ep; - struct sun6i_csi_config *config = &csi_dev->config; - unsigned char bus_width; - u32 flags; - u32 cfg; - bool input_interlaced = false; - - if (config->field == V4L2_FIELD_INTERLACED - || config->field == V4L2_FIELD_INTERLACED_TB - || config->field == V4L2_FIELD_INTERLACED_BT) - input_interlaced = true; - - bus_width = endpoint->bus.parallel.bus_width; - - regmap_read(csi_dev->regmap, CSI_IF_CFG_REG, &cfg); - - cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK | - CSI_IF_CFG_IF_DATA_WIDTH_MASK | - CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK | - CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK | - CSI_IF_CFG_SRC_TYPE_MASK); - - if (input_interlaced) - cfg |= CSI_IF_CFG_SRC_TYPE_INTERLACED; - else - cfg |= CSI_IF_CFG_SRC_TYPE_PROGRESSED; - - switch (endpoint->bus_type) { - case V4L2_MBUS_PARALLEL: - cfg |= CSI_IF_CFG_MIPI_IF_CSI; - - flags = endpoint->bus.parallel.flags; - - cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT : - CSI_IF_CFG_CSI_IF_YUV422_INTLV; - - if (flags & V4L2_MBUS_FIELD_EVEN_LOW) - cfg |= CSI_IF_CFG_FIELD_POSITIVE; - - if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) - cfg |= CSI_IF_CFG_VREF_POL_POSITIVE; - if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) - cfg |= CSI_IF_CFG_HREF_POL_POSITIVE; - - if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) - cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE; - break; - case V4L2_MBUS_BT656: - cfg |= CSI_IF_CFG_MIPI_IF_CSI; - - flags = endpoint->bus.parallel.flags; - - cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 : - CSI_IF_CFG_CSI_IF_BT656; - - if (flags & V4L2_MBUS_FIELD_EVEN_LOW) - cfg |= CSI_IF_CFG_FIELD_POSITIVE; - - if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) - cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE; - break; - default: - dev_warn(csi_dev->dev, "Unsupported bus type: %d\n", - endpoint->bus_type); - break; - } - - switch (bus_width) { - case 8: - cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT; - break; - case 10: - cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT; - break; - case 12: - cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT; - break; - case 16: /* No need to configure DATA_WIDTH for 16bit */ - break; - default: - dev_warn(csi_dev->dev, "Unsupported bus width: %u\n", bus_width); - break; - } - - regmap_write(csi_dev->regmap, CSI_IF_CFG_REG, cfg); -} - -static void sun6i_csi_set_format(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_csi_config *config = &csi_dev->config; - u32 cfg; - u32 val; - - regmap_read(csi_dev->regmap, CSI_CH_CFG_REG, &cfg); - - cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK | - CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN | - CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK | - CSI_CH_CFG_INPUT_SEQ_MASK); - - val = get_csi_input_format(csi_dev, config->code, - config->pixelformat); - cfg |= CSI_CH_CFG_INPUT_FMT(val); - - val = get_csi_output_format(csi_dev, config->pixelformat, - config->field); - cfg |= CSI_CH_CFG_OUTPUT_FMT(val); - - val = get_csi_input_seq(csi_dev, config->code, - config->pixelformat); - cfg |= CSI_CH_CFG_INPUT_SEQ(val); - - if (config->field == V4L2_FIELD_TOP) - cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0; - else if (config->field == V4L2_FIELD_BOTTOM) - cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1; - else - cfg |= CSI_CH_CFG_FIELD_SEL_BOTH; - - regmap_write(csi_dev->regmap, CSI_CH_CFG_REG, cfg); -} - -static void sun6i_csi_set_window(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_csi_config *config = &csi_dev->config; - u32 bytesperline_y; - u32 bytesperline_c; - int *planar_offset = csi_dev->planar_offset; - u32 width = config->width; - u32 height = config->height; - u32 hor_len = width; - - switch (config->pixelformat) { - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - dev_dbg(csi_dev->dev, - "Horizontal length should be 2 times of width for packed YUV formats!\n"); - hor_len = width * 2; - break; - default: - break; - } - - regmap_write(csi_dev->regmap, CSI_CH_HSIZE_REG, - CSI_CH_HSIZE_HOR_LEN(hor_len) | - CSI_CH_HSIZE_HOR_START(0)); - regmap_write(csi_dev->regmap, CSI_CH_VSIZE_REG, - CSI_CH_VSIZE_VER_LEN(height) | - CSI_CH_VSIZE_VER_START(0)); - - planar_offset[0] = 0; - switch (config->pixelformat) { - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - bytesperline_y = width; - bytesperline_c = width; - planar_offset[1] = bytesperline_y * height; - planar_offset[2] = -1; - break; - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - bytesperline_y = width; - bytesperline_c = width / 2; - planar_offset[1] = bytesperline_y * height; - planar_offset[2] = planar_offset[1] + - bytesperline_c * height / 2; - break; - case V4L2_PIX_FMT_YUV422P: - bytesperline_y = width; - bytesperline_c = width / 2; - planar_offset[1] = bytesperline_y * height; - planar_offset[2] = planar_offset[1] + - bytesperline_c * height; - break; - default: /* raw */ - dev_dbg(csi_dev->dev, - "Calculating pixelformat(0x%x)'s bytesperline as a packed format\n", - config->pixelformat); - bytesperline_y = (sun6i_csi_get_bpp(config->pixelformat) * - config->width) / 8; - bytesperline_c = 0; - planar_offset[1] = -1; - planar_offset[2] = -1; - break; } - regmap_write(csi_dev->regmap, CSI_CH_BUF_LEN_REG, - CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) | - CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y)); -} - -int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_config *config) -{ - if (!config) - return -EINVAL; - - memcpy(&csi_dev->config, config, sizeof(csi_dev->config)); - - sun6i_csi_setup_bus(csi_dev); - sun6i_csi_set_format(csi_dev); - sun6i_csi_set_window(csi_dev); + csi_dev->isp_available = true; return 0; } -void sun6i_csi_update_buf_addr(struct sun6i_csi_device *csi_dev, - dma_addr_t addr) -{ - regmap_write(csi_dev->regmap, CSI_CH_F0_BUFA_REG, - (addr + csi_dev->planar_offset[0]) >> 2); - if (csi_dev->planar_offset[1] != -1) - regmap_write(csi_dev->regmap, CSI_CH_F1_BUFA_REG, - (addr + csi_dev->planar_offset[1]) >> 2); - if (csi_dev->planar_offset[2] != -1) - regmap_write(csi_dev->regmap, CSI_CH_F2_BUFA_REG, - (addr + csi_dev->planar_offset[2]) >> 2); -} - -void sun6i_csi_set_stream(struct sun6i_csi_device *csi_dev, bool enable) -{ - struct regmap *regmap = csi_dev->regmap; - - if (!enable) { - regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0); - regmap_write(regmap, CSI_CH_INT_EN_REG, 0); - return; - } - - regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF); - regmap_write(regmap, CSI_CH_INT_EN_REG, - CSI_CH_INT_EN_HB_OF_INT_EN | - CSI_CH_INT_EN_FIFO2_OF_INT_EN | - CSI_CH_INT_EN_FIFO1_OF_INT_EN | - CSI_CH_INT_EN_FIFO0_OF_INT_EN | - CSI_CH_INT_EN_FD_INT_EN | - CSI_CH_INT_EN_CD_INT_EN); - - regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, - CSI_CAP_CH0_VCAP_ON); -} - /* Media */ static const struct media_device_ops sun6i_csi_media_ops = { @@ -583,103 +74,11 @@ static const struct media_device_ops sun6i_csi_media_ops = { /* V4L2 */ -static int sun6i_csi_link_entity(struct sun6i_csi_device *csi_dev, - struct media_entity *entity, - struct fwnode_handle *fwnode) -{ - struct media_entity *sink; - struct media_pad *sink_pad; - int src_pad_index; - int ret; - - ret = media_entity_get_fwnode_pad(entity, fwnode, MEDIA_PAD_FL_SOURCE); - if (ret < 0) { - dev_err(csi_dev->dev, - "%s: no source pad in external entity %s\n", __func__, - entity->name); - return -EINVAL; - } - - src_pad_index = ret; - - sink = &csi_dev->video.video_dev.entity; - sink_pad = &csi_dev->video.pad; - - dev_dbg(csi_dev->dev, "creating %s:%u -> %s:%u link\n", - entity->name, src_pad_index, sink->name, sink_pad->index); - ret = media_create_pad_link(entity, src_pad_index, sink, - sink_pad->index, - MEDIA_LNK_FL_ENABLED | - MEDIA_LNK_FL_IMMUTABLE); - if (ret < 0) { - dev_err(csi_dev->dev, "failed to create %s:%u -> %s:%u link\n", - entity->name, src_pad_index, - sink->name, sink_pad->index); - return ret; - } - - return 0; -} - -static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier) -{ - struct sun6i_csi_device *csi_dev = - container_of(notifier, struct sun6i_csi_device, - v4l2.notifier); - struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; - struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; - struct v4l2_subdev *sd; - int ret; - - dev_dbg(csi_dev->dev, "notify complete, all subdevs registered\n"); - - sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list); - if (!sd) - return -EINVAL; - - ret = sun6i_csi_link_entity(csi_dev, &sd->entity, sd->fwnode); - if (ret < 0) - return ret; - - ret = v4l2_device_register_subdev_nodes(v4l2_dev); - if (ret < 0) - return ret; - - return 0; -} - -static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = { - .complete = sun6i_subdev_notify_complete, -}; - -static int sun6i_csi_fwnode_parse(struct device *dev, - struct v4l2_fwnode_endpoint *vep, - struct v4l2_async_subdev *asd) -{ - struct sun6i_csi_device *csi_dev = dev_get_drvdata(dev); - - if (vep->base.port || vep->base.id) { - dev_warn(dev, "Only support a single port with one endpoint\n"); - return -ENOTCONN; - } - - switch (vep->bus_type) { - case V4L2_MBUS_PARALLEL: - case V4L2_MBUS_BT656: - csi_dev->v4l2.v4l2_ep = *vep; - return 0; - default: - dev_err(dev, "Unsupported media bus type\n"); - return -ENOTCONN; - } -} - static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev) { struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; struct media_device *media_dev = &v4l2->media_dev; struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; - struct v4l2_async_notifier *notifier = &v4l2->notifier; struct device *dev = csi_dev->dev; int ret; @@ -709,42 +108,11 @@ static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev) goto error_media; } - /* Video */ - - ret = sun6i_video_setup(csi_dev); - if (ret) - goto error_v4l2_device; - - /* V4L2 Async */ - - v4l2_async_nf_init(notifier); - notifier->ops = &sun6i_csi_async_ops; - - ret = v4l2_async_nf_parse_fwnode_endpoints(dev, notifier, - sizeof(struct - v4l2_async_subdev), - sun6i_csi_fwnode_parse); - if (ret) - goto error_video; - - ret = v4l2_async_nf_register(v4l2_dev, notifier); - if (ret) { - dev_err(dev, "failed to register v4l2 async notifier: %d\n", - ret); - goto error_v4l2_async_notifier; - } + csi_dev->v4l2_dev = v4l2_dev; + csi_dev->media_dev = media_dev; return 0; -error_v4l2_async_notifier: - v4l2_async_nf_cleanup(notifier); - -error_video: - sun6i_video_cleanup(csi_dev); - -error_v4l2_device: - v4l2_device_unregister(&v4l2->v4l2_dev); - error_media: media_device_unregister(media_dev); media_device_cleanup(media_dev); @@ -757,9 +125,6 @@ static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev) struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; media_device_unregister(&v4l2->media_dev); - v4l2_async_nf_unregister(&v4l2->notifier); - v4l2_async_nf_cleanup(&v4l2->notifier); - sun6i_video_cleanup(csi_dev); v4l2_device_unregister(&v4l2->v4l2_dev); media_device_cleanup(&v4l2->media_dev); } @@ -769,29 +134,39 @@ static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev) static irqreturn_t sun6i_csi_interrupt(int irq, void *private) { struct sun6i_csi_device *csi_dev = private; + bool capture_streaming = csi_dev->capture.state.streaming; struct regmap *regmap = csi_dev->regmap; - u32 status; + u32 status = 0, enable = 0; - regmap_read(regmap, CSI_CH_INT_STA_REG, &status); + regmap_read(regmap, SUN6I_CSI_CH_INT_STA_REG, &status); + regmap_read(regmap, SUN6I_CSI_CH_INT_EN_REG, &enable); - if (!(status & 0xFF)) + if (!status) return IRQ_NONE; - - if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) || - (status & CSI_CH_INT_STA_FIFO1_OF_PD) || - (status & CSI_CH_INT_STA_FIFO2_OF_PD) || - (status & CSI_CH_INT_STA_HB_OF_PD)) { - regmap_write(regmap, CSI_CH_INT_STA_REG, status); - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0); - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, - CSI_EN_CSI_EN); + else if (!(status & enable) || !capture_streaming) + goto complete; + + if ((status & SUN6I_CSI_CH_INT_STA_FIFO0_OF) || + (status & SUN6I_CSI_CH_INT_STA_FIFO1_OF) || + (status & SUN6I_CSI_CH_INT_STA_FIFO2_OF) || + (status & SUN6I_CSI_CH_INT_STA_HB_OF)) { + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, status); + + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, + SUN6I_CSI_EN_CSI_EN, 0); + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, + SUN6I_CSI_EN_CSI_EN, SUN6I_CSI_EN_CSI_EN); return IRQ_HANDLED; } - if (status & CSI_CH_INT_STA_FD_PD) - sun6i_video_frame_done(csi_dev); + if (status & SUN6I_CSI_CH_INT_STA_FD) + sun6i_csi_capture_frame_done(csi_dev); + + if (status & SUN6I_CSI_CH_INT_STA_VS) + sun6i_csi_capture_sync(csi_dev); - regmap_write(regmap, CSI_CH_INT_STA_REG, status); +complete: + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, status); return IRQ_HANDLED; } @@ -913,13 +288,12 @@ static int sun6i_csi_resources_setup(struct sun6i_csi_device *csi_dev, irq = platform_get_irq(platform_dev, 0); if (irq < 0) { - dev_err(dev, "failed to get interrupt\n"); ret = -ENXIO; goto error_clock_rate_exclusive; } - ret = devm_request_irq(dev, irq, sun6i_csi_interrupt, 0, SUN6I_CSI_NAME, - csi_dev); + ret = devm_request_irq(dev, irq, sun6i_csi_interrupt, IRQF_SHARED, + SUN6I_CSI_NAME, csi_dev); if (ret) { dev_err(dev, "failed to request interrupt\n"); goto error_clock_rate_exclusive; @@ -960,12 +334,41 @@ static int sun6i_csi_probe(struct platform_device *platform_dev) if (ret) return ret; - ret = sun6i_csi_v4l2_setup(csi_dev); + ret = sun6i_csi_isp_detect(csi_dev); if (ret) goto error_resources; + /* + * Register our own v4l2 and media devices when there is no ISP around. + * Otherwise the ISP will use async subdev registration with our bridge, + * which will provide v4l2 and media devices that are used to register + * the video interface. + */ + if (!csi_dev->isp_available) { + ret = sun6i_csi_v4l2_setup(csi_dev); + if (ret) + goto error_resources; + } + + ret = sun6i_csi_bridge_setup(csi_dev); + if (ret) + goto error_v4l2; + + if (!csi_dev->isp_available) { + ret = sun6i_csi_capture_setup(csi_dev); + if (ret) + goto error_bridge; + } + return 0; +error_bridge: + sun6i_csi_bridge_cleanup(csi_dev); + +error_v4l2: + if (!csi_dev->isp_available) + sun6i_csi_v4l2_cleanup(csi_dev); + error_resources: sun6i_csi_resources_cleanup(csi_dev); @@ -976,7 +379,12 @@ static int sun6i_csi_remove(struct platform_device *pdev) { struct sun6i_csi_device *csi_dev = platform_get_drvdata(pdev); - sun6i_csi_v4l2_cleanup(csi_dev); + sun6i_csi_capture_cleanup(csi_dev); + sun6i_csi_bridge_cleanup(csi_dev); + + if (!csi_dev->isp_available) + sun6i_csi_v4l2_cleanup(csi_dev); + sun6i_csi_resources_cleanup(csi_dev); return 0; @@ -1030,4 +438,5 @@ module_platform_driver(sun6i_csi_platform_driver); MODULE_DESCRIPTION("Allwinner A31 Camera Sensor Interface driver"); MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>"); +MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h index bab705678280..bc3f0dae35df 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h @@ -1,166 +1,63 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> */ -#ifndef __SUN6I_CSI_H__ -#define __SUN6I_CSI_H__ +#ifndef _SUN6I_CSI_H_ +#define _SUN6I_CSI_H_ #include <media/v4l2-device.h> -#include <media/v4l2-fwnode.h> #include <media/videobuf2-v4l2.h> -#include "sun6i_video.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_capture.h" #define SUN6I_CSI_NAME "sun6i-csi" #define SUN6I_CSI_DESCRIPTION "Allwinner A31 CSI Device" +enum sun6i_csi_port { + SUN6I_CSI_PORT_PARALLEL = 0, + SUN6I_CSI_PORT_MIPI_CSI2 = 1, + SUN6I_CSI_PORT_ISP = 2, +}; + struct sun6i_csi_buffer { struct vb2_v4l2_buffer v4l2_buffer; struct list_head list; - - dma_addr_t dma_addr; - bool queued_to_csi; -}; - -/** - * struct sun6i_csi_config - configs for sun6i csi - * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*) - * @code: media bus format code (MEDIA_BUS_FMT_*) - * @field: used interlacing type (enum v4l2_field) - * @width: frame width - * @height: frame height - */ -struct sun6i_csi_config { - u32 pixelformat; - u32 code; - u32 field; - u32 width; - u32 height; }; struct sun6i_csi_v4l2 { struct v4l2_device v4l2_dev; struct media_device media_dev; - - struct v4l2_async_notifier notifier; - /* video port settings */ - struct v4l2_fwnode_endpoint v4l2_ep; }; struct sun6i_csi_device { struct device *dev; + struct v4l2_device *v4l2_dev; + struct media_device *media_dev; - struct sun6i_csi_config config; struct sun6i_csi_v4l2 v4l2; - struct sun6i_video video; + struct sun6i_csi_bridge bridge; + struct sun6i_csi_capture capture; struct regmap *regmap; struct clk *clock_mod; struct clk *clock_ram; struct reset_control *reset; - int planar_offset[3]; + bool isp_available; }; struct sun6i_csi_variant { unsigned long clock_mod_rate; }; -/** - * sun6i_csi_is_format_supported() - check if the format supported by csi - * @csi_dev: pointer to the csi device - * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*) - * @mbus_code: media bus format code (MEDIA_BUS_FMT_*) - * - * Return: true if format is supported, false otherwise. - */ -bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev, - u32 pixformat, u32 mbus_code); - -/** - * sun6i_csi_set_power() - power on/off the csi - * @csi_dev: pointer to the csi device - * @enable: on/off - * - * Return: 0 if successful, error code otherwise. - */ -int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable); - -/** - * sun6i_csi_update_config() - update the csi register settings - * @csi_dev: pointer to the csi device - * @config: see struct sun6i_csi_config - * - * Return: 0 if successful, error code otherwise. - */ -int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_config *config); - -/** - * sun6i_csi_update_buf_addr() - update the csi frame buffer address - * @csi_dev: pointer to the csi device - * @addr: frame buffer's physical address - */ -void sun6i_csi_update_buf_addr(struct sun6i_csi_device *csi_dev, - dma_addr_t addr); - -/** - * sun6i_csi_set_stream() - start/stop csi streaming - * @csi_dev: pointer to the csi device - * @enable: start/stop - */ -void sun6i_csi_set_stream(struct sun6i_csi_device *csi_dev, bool enable); - -/* get bpp form v4l2 pixformat */ -static inline int sun6i_csi_get_bpp(unsigned int pixformat) -{ - switch (pixformat) { - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SGBRG8: - case V4L2_PIX_FMT_SGRBG8: - case V4L2_PIX_FMT_SRGGB8: - case V4L2_PIX_FMT_JPEG: - return 8; - case V4L2_PIX_FMT_SBGGR10: - case V4L2_PIX_FMT_SGBRG10: - case V4L2_PIX_FMT_SGRBG10: - case V4L2_PIX_FMT_SRGGB10: - return 10; - case V4L2_PIX_FMT_SBGGR12: - case V4L2_PIX_FMT_SGBRG12: - case V4L2_PIX_FMT_SGRBG12: - case V4L2_PIX_FMT_SRGGB12: - case V4L2_PIX_FMT_NV12_16L16: - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - return 12; - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_YUV422P: - case V4L2_PIX_FMT_RGB565: - case V4L2_PIX_FMT_RGB565X: - return 16; - case V4L2_PIX_FMT_RGB24: - case V4L2_PIX_FMT_BGR24: - return 24; - case V4L2_PIX_FMT_RGB32: - case V4L2_PIX_FMT_BGR32: - return 32; - default: - WARN(1, "Unsupported pixformat: 0x%x\n", pixformat); - break; - } +/* ISP */ - return 0; -} +int sun6i_csi_isp_complete(struct sun6i_csi_device *csi_dev, + struct v4l2_device *v4l2_dev); -#endif /* __SUN6I_CSI_H__ */ +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c new file mode 100644 index 000000000000..ebfc870d2af5 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c @@ -0,0 +1,868 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#include "sun6i_csi.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_reg.h" + +/* Helpers */ + +void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height) +{ + if (width) + *width = csi_dev->bridge.mbus_format.width; + if (height) + *height = csi_dev->bridge.mbus_format.height; +} + +void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, + u32 *mbus_code, u32 *field) +{ + if (mbus_code) + *mbus_code = csi_dev->bridge.mbus_format.code; + if (field) + *field = csi_dev->bridge.mbus_format.field; +} + +/* Format */ + +static const struct sun6i_csi_bridge_format sun6i_csi_bridge_formats[] = { + /* Bayer */ + { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + /* RGB */ + { + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + /* YUV422 */ + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + }, + /* Compressed */ + { + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, +}; + +const struct sun6i_csi_bridge_format * +sun6i_csi_bridge_format_find(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_bridge_formats); i++) + if (sun6i_csi_bridge_formats[i].mbus_code == mbus_code) + return &sun6i_csi_bridge_formats[i]; + + return NULL; +} + +/* Bridge */ + +static void sun6i_csi_bridge_irq_enable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, + SUN6I_CSI_CH_INT_EN_VS | + SUN6I_CSI_CH_INT_EN_HB_OF | + SUN6I_CSI_CH_INT_EN_FIFO2_OF | + SUN6I_CSI_CH_INT_EN_FIFO1_OF | + SUN6I_CSI_CH_INT_EN_FIFO0_OF | + SUN6I_CSI_CH_INT_EN_FD | + SUN6I_CSI_CH_INT_EN_CD); +} + +static void sun6i_csi_bridge_irq_disable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, 0); +} + +static void sun6i_csi_bridge_irq_clear(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, 0); + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, + SUN6I_CSI_CH_INT_STA_CLEAR); +} + +static void sun6i_csi_bridge_enable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, SUN6I_CSI_EN_CSI_EN, + SUN6I_CSI_EN_CSI_EN); + + regmap_update_bits(regmap, SUN6I_CSI_CAP_REG, SUN6I_CSI_CAP_VCAP_ON, + SUN6I_CSI_CAP_VCAP_ON); +} + +static void sun6i_csi_bridge_disable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_update_bits(regmap, SUN6I_CSI_CAP_REG, SUN6I_CSI_CAP_VCAP_ON, 0); + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, SUN6I_CSI_EN_CSI_EN, 0); +} + +static void +sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) +{ + struct device *dev = csi_dev->dev; + struct regmap *regmap = csi_dev->regmap; + struct v4l2_fwnode_endpoint *endpoint = + &csi_dev->bridge.source_parallel.endpoint; + unsigned char bus_width = endpoint->bus.parallel.bus_width; + unsigned int flags = endpoint->bus.parallel.flags; + u32 field; + u32 value = SUN6I_CSI_IF_CFG_IF_CSI; + + sun6i_csi_bridge_format(csi_dev, NULL, &field); + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED | + SUN6I_CSI_IF_CFG_FIELD_DT_PCLK_SHIFT(1) | + SUN6I_CSI_IF_CFG_FIELD_DT_FIELD_VSYNC; + else + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE; + + switch (endpoint->bus_type) { + case V4L2_MBUS_PARALLEL: + if (bus_width == 16) + value |= SUN6I_CSI_IF_CFG_IF_CSI_YUV_COMBINED; + else + value |= SUN6I_CSI_IF_CFG_IF_CSI_YUV_RAW; + + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + value |= SUN6I_CSI_IF_CFG_FIELD_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_FIELD_POSITIVE; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + value |= SUN6I_CSI_IF_CFG_VREF_POL_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_VREF_POL_POSITIVE; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + value |= SUN6I_CSI_IF_CFG_HREF_POL_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_HREF_POL_POSITIVE; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + value |= SUN6I_CSI_IF_CFG_CLK_POL_RISING; + else + value |= SUN6I_CSI_IF_CFG_CLK_POL_FALLING; + break; + case V4L2_MBUS_BT656: + if (bus_width == 16) + value |= SUN6I_CSI_IF_CFG_IF_CSI_BT1120; + else + value |= SUN6I_CSI_IF_CFG_IF_CSI_BT656; + + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + value |= SUN6I_CSI_IF_CFG_FIELD_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_FIELD_POSITIVE; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + value |= SUN6I_CSI_IF_CFG_CLK_POL_RISING; + else + value |= SUN6I_CSI_IF_CFG_CLK_POL_FALLING; + break; + default: + dev_warn(dev, "unsupported bus type: %d\n", endpoint->bus_type); + break; + } + + switch (bus_width) { + case 8: + /* 16-bit YUV formats use a doubled width in 8-bit mode. */ + case 16: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_8; + break; + case 10: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_10; + break; + case 12: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_12; + break; + default: + dev_warn(dev, "unsupported bus width: %u\n", bus_width); + break; + } + + regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value); +} + +static void +sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + u32 value = SUN6I_CSI_IF_CFG_IF_MIPI; + u32 field; + + sun6i_csi_bridge_format(csi_dev, NULL, &field); + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED; + else + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE; + + regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value); +} + +static void sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + bool capture_streaming = csi_dev->capture.state.streaming; + const struct sun6i_csi_bridge_format *bridge_format; + const struct sun6i_csi_capture_format *capture_format; + u32 mbus_code, field, pixelformat; + u8 input_format, input_yuv_seq, output_format; + u32 value = 0; + + sun6i_csi_bridge_format(csi_dev, &mbus_code, &field); + + bridge_format = sun6i_csi_bridge_format_find(mbus_code); + if (WARN_ON(!bridge_format)) + return; + + input_format = bridge_format->input_format; + input_yuv_seq = bridge_format->input_yuv_seq; + + if (capture_streaming) { + sun6i_csi_capture_format(csi_dev, &pixelformat, NULL); + + capture_format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!capture_format)) + return; + + if (capture_format->input_format_raw) + input_format = SUN6I_CSI_INPUT_FMT_RAW; + + if (capture_format->input_yuv_seq_invert) + input_yuv_seq = bridge_format->input_yuv_seq_invert; + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + output_format = capture_format->output_format_field; + else + output_format = capture_format->output_format_frame; + + value |= SUN6I_CSI_CH_CFG_OUTPUT_FMT(output_format); + } + + value |= SUN6I_CSI_CH_CFG_INPUT_FMT(input_format); + value |= SUN6I_CSI_CH_CFG_INPUT_YUV_SEQ(input_yuv_seq); + + if (field == V4L2_FIELD_TOP) + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD0; + else if (field == V4L2_FIELD_BOTTOM) + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD1; + else + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_EITHER; + + regmap_write(regmap, SUN6I_CSI_CH_CFG_REG, value); +} + +static void sun6i_csi_bridge_configure(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_bridge_source *source) +{ + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + + if (source == &bridge->source_parallel) + sun6i_csi_bridge_configure_parallel(csi_dev); + else + sun6i_csi_bridge_configure_mipi_csi2(csi_dev); + + sun6i_csi_bridge_configure_format(csi_dev); +} + +/* V4L2 Subdev */ + +static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct media_pad *local_pad = &bridge->pads[SUN6I_CSI_BRIDGE_PAD_SINK]; + bool capture_streaming = csi_dev->capture.state.streaming; + struct device *dev = csi_dev->dev; + struct sun6i_csi_bridge_source *source; + struct v4l2_subdev *source_subdev; + struct media_pad *remote_pad; + int ret; + + /* Source */ + + remote_pad = media_pad_remote_pad_unique(local_pad); + if (IS_ERR(remote_pad)) { + dev_err(dev, + "zero or more than a single source connected to the bridge\n"); + return PTR_ERR(remote_pad); + } + + source_subdev = media_entity_to_v4l2_subdev(remote_pad->entity); + + if (source_subdev == bridge->source_parallel.subdev) + source = &bridge->source_parallel; + else + source = &bridge->source_mipi_csi2; + + if (!on) { + v4l2_subdev_call(source_subdev, video, s_stream, 0); + ret = 0; + goto disable; + } + + /* PM */ + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* Clear */ + + sun6i_csi_bridge_irq_clear(csi_dev); + + /* Configure */ + + sun6i_csi_bridge_configure(csi_dev, source); + + if (capture_streaming) + sun6i_csi_capture_configure(csi_dev); + + /* State Update */ + + if (capture_streaming) + sun6i_csi_capture_state_update(csi_dev); + + /* Enable */ + + if (capture_streaming) + sun6i_csi_bridge_irq_enable(csi_dev); + + sun6i_csi_bridge_enable(csi_dev); + + ret = v4l2_subdev_call(source_subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto disable; + + return 0; + +disable: + if (capture_streaming) + sun6i_csi_bridge_irq_disable(csi_dev); + + sun6i_csi_bridge_disable(csi_dev); + + pm_runtime_put(dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops sun6i_csi_bridge_video_ops = { + .s_stream = sun6i_csi_bridge_s_stream, +}; + +static void +sun6i_csi_bridge_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) +{ + if (!sun6i_csi_bridge_format_find(mbus_format->code)) + mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; + + mbus_format->field = V4L2_FIELD_NONE; + mbus_format->colorspace = V4L2_COLORSPACE_RAW; + mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT; + mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_csi_bridge_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + unsigned int pad = SUN6I_CSI_BRIDGE_PAD_SINK; + struct v4l2_mbus_framefmt *mbus_format = + v4l2_subdev_get_try_format(subdev, state, pad); + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; + mbus_format->width = 1280; + mbus_format->height = 720; + + sun6i_csi_bridge_mbus_format_prepare(mbus_format); + + mutex_unlock(lock); + + return 0; +} + +static int +sun6i_csi_bridge_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code_enum) +{ + if (code_enum->index >= ARRAY_SIZE(sun6i_csi_bridge_formats)) + return -EINVAL; + + code_enum->code = sun6i_csi_bridge_formats[code_enum->index].mbus_code; + + return 0; +} + +static int sun6i_csi_bridge_get_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *mbus_format = *v4l2_subdev_get_try_format(subdev, state, + format->pad); + else + *mbus_format = csi_dev->bridge.mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static int sun6i_csi_bridge_set_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + sun6i_csi_bridge_mbus_format_prepare(mbus_format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(subdev, state, format->pad) = + *mbus_format; + else + csi_dev->bridge.mbus_format = *mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops sun6i_csi_bridge_pad_ops = { + .init_cfg = sun6i_csi_bridge_init_cfg, + .enum_mbus_code = sun6i_csi_bridge_enum_mbus_code, + .get_fmt = sun6i_csi_bridge_get_fmt, + .set_fmt = sun6i_csi_bridge_set_fmt, +}; + +static const struct v4l2_subdev_ops sun6i_csi_bridge_subdev_ops = { + .video = &sun6i_csi_bridge_video_ops, + .pad = &sun6i_csi_bridge_pad_ops, +}; + +/* Media Entity */ + +static const struct media_entity_operations sun6i_csi_bridge_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* V4L2 Async */ + +static int sun6i_csi_bridge_link(struct sun6i_csi_device *csi_dev, + int sink_pad_index, + struct v4l2_subdev *remote_subdev, + bool enabled) +{ + struct device *dev = csi_dev->dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + struct media_entity *sink_entity = &subdev->entity; + struct media_entity *source_entity = &remote_subdev->entity; + int source_pad_index; + int ret; + + /* Get the first remote source pad. */ + ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "missing source pad in external entity %s\n", + source_entity->name); + return -EINVAL; + } + + source_pad_index = ret; + + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name, + source_pad_index, sink_entity->name, sink_pad_index); + + ret = media_create_pad_link(source_entity, source_pad_index, + sink_entity, sink_pad_index, + enabled ? MEDIA_LNK_FL_ENABLED : 0); + if (ret < 0) { + dev_err(dev, "failed to create %s:%u -> %s:%u link\n", + source_entity->name, source_pad_index, + sink_entity->name, sink_pad_index); + return ret; + } + + return 0; +} + +static int +sun6i_csi_bridge_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *remote_subdev, + struct v4l2_async_subdev *async_subdev) +{ + struct sun6i_csi_device *csi_dev = + container_of(notifier, struct sun6i_csi_device, + bridge.notifier); + struct sun6i_csi_bridge_async_subdev *bridge_async_subdev = + container_of(async_subdev, struct sun6i_csi_bridge_async_subdev, + async_subdev); + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct sun6i_csi_bridge_source *source = bridge_async_subdev->source; + bool enabled = false; + int ret; + + switch (source->endpoint.base.port) { + case SUN6I_CSI_PORT_PARALLEL: + enabled = true; + break; + case SUN6I_CSI_PORT_MIPI_CSI2: + enabled = !bridge->source_parallel.expected; + break; + default: + break; + } + + source->subdev = remote_subdev; + + if (csi_dev->isp_available) { + /* + * Hook to the first available remote subdev to get v4l2 and + * media devices and register the capture device then. + */ + ret = sun6i_csi_isp_complete(csi_dev, remote_subdev->v4l2_dev); + if (ret) + return ret; + } + + return sun6i_csi_bridge_link(csi_dev, SUN6I_CSI_BRIDGE_PAD_SINK, + remote_subdev, enabled); +} + +static int +sun6i_csi_bridge_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct sun6i_csi_device *csi_dev = + container_of(notifier, struct sun6i_csi_device, + bridge.notifier); + struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; + + if (csi_dev->isp_available) + return 0; + + return v4l2_device_register_subdev_nodes(v4l2_dev); +} + +static const struct v4l2_async_notifier_operations +sun6i_csi_bridge_notifier_ops = { + .bound = sun6i_csi_bridge_notifier_bound, + .complete = sun6i_csi_bridge_notifier_complete, +}; + +/* Bridge */ + +static int sun6i_csi_bridge_source_setup(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_bridge_source *source, + u32 port, + enum v4l2_mbus_type *bus_types) +{ + struct device *dev = csi_dev->dev; + struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier; + struct v4l2_fwnode_endpoint *endpoint = &source->endpoint; + struct sun6i_csi_bridge_async_subdev *bridge_async_subdev; + struct fwnode_handle *handle; + int ret; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), port, 0, 0); + if (!handle) + return -ENODEV; + + ret = v4l2_fwnode_endpoint_parse(handle, endpoint); + if (ret) + goto complete; + + if (bus_types) { + bool valid = false; + unsigned int i; + + for (i = 0; bus_types[i] != V4L2_MBUS_INVALID; i++) { + if (endpoint->bus_type == bus_types[i]) { + valid = true; + break; + } + } + + if (!valid) { + dev_err(dev, "unsupported bus type for port %d\n", + port); + ret = -EINVAL; + goto complete; + } + } + + bridge_async_subdev = + v4l2_async_nf_add_fwnode_remote(notifier, handle, + struct + sun6i_csi_bridge_async_subdev); + if (IS_ERR(bridge_async_subdev)) { + ret = PTR_ERR(bridge_async_subdev); + goto complete; + } + + bridge_async_subdev->source = source; + + source->expected = true; + +complete: + fwnode_handle_put(handle); + + return ret; +} + +int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) +{ + struct device *dev = csi_dev->dev; + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct v4l2_subdev *subdev = &bridge->subdev; + struct v4l2_async_notifier *notifier = &bridge->notifier; + struct media_pad *pads = bridge->pads; + enum v4l2_mbus_type parallel_mbus_types[] = { + V4L2_MBUS_PARALLEL, + V4L2_MBUS_BT656, + V4L2_MBUS_INVALID + }; + int ret; + + mutex_init(&bridge->lock); + + /* V4L2 Subdev */ + + v4l2_subdev_init(subdev, &sun6i_csi_bridge_subdev_ops); + strscpy(subdev->name, SUN6I_CSI_BRIDGE_NAME, sizeof(subdev->name)); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + subdev->owner = THIS_MODULE; + subdev->dev = dev; + + v4l2_set_subdevdata(subdev, csi_dev); + + /* Media Entity */ + + subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + subdev->entity.ops = &sun6i_csi_bridge_entity_ops; + + /* Media Pads */ + + pads[SUN6I_CSI_BRIDGE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[SUN6I_CSI_BRIDGE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&subdev->entity, + SUN6I_CSI_BRIDGE_PAD_COUNT, pads); + if (ret < 0) + return ret; + + /* V4L2 Subdev */ + + if (csi_dev->isp_available) + ret = v4l2_async_register_subdev(subdev); + else + ret = v4l2_device_register_subdev(v4l2_dev, subdev); + + if (ret) { + dev_err(dev, "failed to register v4l2 subdev: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Async */ + + v4l2_async_nf_init(notifier); + notifier->ops = &sun6i_csi_bridge_notifier_ops; + + sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_parallel, + SUN6I_CSI_PORT_PARALLEL, + parallel_mbus_types); + sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_mipi_csi2, + SUN6I_CSI_PORT_MIPI_CSI2, NULL); + + if (csi_dev->isp_available) + ret = v4l2_async_subdev_nf_register(subdev, notifier); + else + ret = v4l2_async_nf_register(v4l2_dev, notifier); + if (ret) { + dev_err(dev, "failed to register v4l2 async notifier: %d\n", + ret); + goto error_v4l2_async_notifier; + } + + return 0; + +error_v4l2_async_notifier: + v4l2_async_nf_cleanup(notifier); + + if (csi_dev->isp_available) + v4l2_async_unregister_subdev(subdev); + else + v4l2_device_unregister_subdev(subdev); + +error_media_entity: + media_entity_cleanup(&subdev->entity); + + return ret; +} + +void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev) +{ + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier; + + v4l2_async_nf_unregister(notifier); + v4l2_async_nf_cleanup(notifier); + + v4l2_device_unregister_subdev(subdev); + + media_entity_cleanup(&subdev->entity); +} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h new file mode 100644 index 000000000000..ee592a14b9c5 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_CSI_BRIDGE_H_ +#define _SUN6I_CSI_BRIDGE_H_ + +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define SUN6I_CSI_BRIDGE_NAME "sun6i-csi-bridge" + +enum sun6i_csi_bridge_pad { + SUN6I_CSI_BRIDGE_PAD_SINK = 0, + SUN6I_CSI_BRIDGE_PAD_SOURCE = 1, + SUN6I_CSI_BRIDGE_PAD_COUNT = 2, +}; + +struct sun6i_csi_device; + +struct sun6i_csi_bridge_format { + u32 mbus_code; + u8 input_format; + u8 input_yuv_seq; + u8 input_yuv_seq_invert; +}; + +struct sun6i_csi_bridge_source { + struct v4l2_subdev *subdev; + struct v4l2_fwnode_endpoint endpoint; + bool expected; +}; + +struct sun6i_csi_bridge_async_subdev { + struct v4l2_async_subdev async_subdev; + struct sun6i_csi_bridge_source *source; +}; + +struct sun6i_csi_bridge { + struct v4l2_subdev subdev; + struct v4l2_async_notifier notifier; + struct media_pad pads[2]; + struct v4l2_mbus_framefmt mbus_format; + struct mutex lock; /* Mbus format lock. */ + + struct sun6i_csi_bridge_source source_parallel; + struct sun6i_csi_bridge_source source_mipi_csi2; +}; + +/* Helpers */ + +void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height); +void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, + u32 *mbus_code, u32 *field); + +/* Format */ + +const struct sun6i_csi_bridge_format * +sun6i_csi_bridge_format_find(u32 mbus_code); + +/* Bridge */ + +int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev); +void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev); + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c new file mode 100644 index 000000000000..6d34f5c0768f --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c @@ -0,0 +1,1102 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/of.h> +#include <linux/regmap.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-v4l2.h> + +#include "sun6i_csi.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_capture.h" +#include "sun6i_csi_reg.h" + +/* Helpers */ + +void sun6i_csi_capture_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height) +{ + if (width) + *width = csi_dev->capture.format.fmt.pix.width; + if (height) + *height = csi_dev->capture.format.fmt.pix.height; +} + +void sun6i_csi_capture_format(struct sun6i_csi_device *csi_dev, + u32 *pixelformat, u32 *field) +{ + if (pixelformat) + *pixelformat = csi_dev->capture.format.fmt.pix.pixelformat; + + if (field) + *field = csi_dev->capture.format.fmt.pix.field; +} + +/* Format */ + +static const struct sun6i_csi_capture_format sun6i_csi_capture_formats[] = { + /* Bayer */ + { + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + /* RGB */ + { + .pixelformat = V4L2_PIX_FMT_RGB565, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565, + }, + { + .pixelformat = V4L2_PIX_FMT_RGB565X, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565, + }, + /* YUV422 */ + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_NV16, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP, + }, + { + .pixelformat = V4L2_PIX_FMT_NV61, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP, + .input_yuv_seq_invert = true, + }, + { + .pixelformat = V4L2_PIX_FMT_YUV422P, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422P, + }, + /* YUV420 */ + { + .pixelformat = V4L2_PIX_FMT_NV12_16L16, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420MB, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420MB, + }, + { + .pixelformat = V4L2_PIX_FMT_NV12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP, + }, + { + .pixelformat = V4L2_PIX_FMT_NV21, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP, + .input_yuv_seq_invert = true, + }, + + { + .pixelformat = V4L2_PIX_FMT_YUV420, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P, + }, + { + .pixelformat = V4L2_PIX_FMT_YVU420, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P, + .input_yuv_seq_invert = true, + }, + /* Compressed */ + { + .pixelformat = V4L2_PIX_FMT_JPEG, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, +}; + +const +struct sun6i_csi_capture_format *sun6i_csi_capture_format_find(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_capture_formats); i++) + if (sun6i_csi_capture_formats[i].pixelformat == pixelformat) + return &sun6i_csi_capture_formats[i]; + + return NULL; +} + +/* RAW formats need an exact match between pixel and mbus formats. */ +static const +struct sun6i_csi_capture_format_match sun6i_csi_capture_format_matches[] = { + /* YUV420 */ + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + }, + /* RGB */ + { + .pixelformat = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + }, + { + .pixelformat = V4L2_PIX_FMT_RGB565X, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE, + }, + /* Bayer */ + { + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG8, + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB8, + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR10, + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG10, + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG10, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB10, + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR12, + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG12, + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG12, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB12, + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + }, + /* Compressed */ + { + .pixelformat = V4L2_PIX_FMT_JPEG, + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + }, +}; + +static bool sun6i_csi_capture_format_match(u32 pixelformat, u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_capture_format_matches); i++) { + const struct sun6i_csi_capture_format_match *match = + &sun6i_csi_capture_format_matches[i]; + + if (match->pixelformat == pixelformat && + match->mbus_code == mbus_code) + return true; + } + + return false; +} + +/* Capture */ + +static void +sun6i_csi_capture_buffer_configure(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_buffer *csi_buffer) +{ + struct regmap *regmap = csi_dev->regmap; + const struct v4l2_format_info *info; + struct vb2_buffer *vb2_buffer; + unsigned int width, height; + dma_addr_t address; + u32 pixelformat; + + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + address = vb2_dma_contig_plane_dma_addr(vb2_buffer, 0); + + regmap_write(regmap, SUN6I_CSI_CH_FIFO0_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + + sun6i_csi_capture_dimensions(csi_dev, &width, &height); + sun6i_csi_capture_format(csi_dev, &pixelformat, NULL); + + info = v4l2_format_info(pixelformat); + /* Unsupported formats are single-plane, so we can stop here. */ + if (!info) + return; + + if (info->comp_planes > 1) { + address += info->bpp[0] * width * height; + + regmap_write(regmap, SUN6I_CSI_CH_FIFO1_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + } + + if (info->comp_planes > 2) { + address += info->bpp[1] * DIV_ROUND_UP(width, info->hdiv) * + DIV_ROUND_UP(height, info->vdiv); + + regmap_write(regmap, SUN6I_CSI_CH_FIFO2_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + } +} + +void sun6i_csi_capture_configure(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + const struct sun6i_csi_capture_format *format; + const struct v4l2_format_info *info; + u32 hsize_len, vsize_len; + u32 luma_line, chroma_line = 0; + u32 pixelformat, field; + u32 width, height; + + sun6i_csi_capture_dimensions(csi_dev, &width, &height); + sun6i_csi_capture_format(csi_dev, &pixelformat, &field); + + format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!format)) + return; + + hsize_len = width; + vsize_len = height; + + /* + * When using 8-bit raw input/output (for packed YUV), we need to adapt + * the width to account for the difference in bpp when it's not 8-bit. + */ + if (format->hsize_len_factor) + hsize_len *= format->hsize_len_factor; + + regmap_write(regmap, SUN6I_CSI_CH_HSIZE_REG, + SUN6I_CSI_CH_HSIZE_LEN(hsize_len) | + SUN6I_CSI_CH_HSIZE_START(0)); + + regmap_write(regmap, SUN6I_CSI_CH_VSIZE_REG, + SUN6I_CSI_CH_VSIZE_LEN(vsize_len) | + SUN6I_CSI_CH_VSIZE_START(0)); + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565X: + luma_line = width * 2; + break; + case V4L2_PIX_FMT_NV12_16L16: + luma_line = width; + chroma_line = width; + break; + case V4L2_PIX_FMT_JPEG: + luma_line = width; + break; + default: + info = v4l2_format_info(pixelformat); + if (WARN_ON(!info)) + return; + + luma_line = width * info->bpp[0]; + + if (info->comp_planes > 1) + chroma_line = width * info->bpp[1] / info->hdiv; + break; + } + + regmap_write(regmap, SUN6I_CSI_CH_BUF_LEN_REG, + SUN6I_CSI_CH_BUF_LEN_CHROMA_LINE(chroma_line) | + SUN6I_CSI_CH_BUF_LEN_LUMA_LINE(luma_line)); +} + +/* State */ + +static void sun6i_csi_capture_state_cleanup(struct sun6i_csi_device *csi_dev, + bool error) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct sun6i_csi_buffer **csi_buffer_states[] = { + &state->pending, &state->current, &state->complete, + }; + struct sun6i_csi_buffer *csi_buffer; + struct vb2_buffer *vb2_buffer; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&state->lock, flags); + + for (i = 0; i < ARRAY_SIZE(csi_buffer_states); i++) { + csi_buffer = *csi_buffer_states[i]; + if (!csi_buffer) + continue; + + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + + *csi_buffer_states[i] = NULL; + } + + list_for_each_entry(csi_buffer, &state->queue, list) { + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + } + + INIT_LIST_HEAD(&state->queue); + + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_state_update(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct sun6i_csi_buffer *csi_buffer; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (list_empty(&state->queue)) + goto complete; + + if (state->pending) + goto complete; + + csi_buffer = list_first_entry(&state->queue, struct sun6i_csi_buffer, + list); + + sun6i_csi_capture_buffer_configure(csi_dev, csi_buffer); + + list_del(&csi_buffer->list); + + state->pending = csi_buffer; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +static void sun6i_csi_capture_state_complete(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (!state->pending) + goto complete; + + state->complete = state->current; + state->current = state->pending; + state->pending = NULL; + + if (state->complete) { + struct sun6i_csi_buffer *csi_buffer = state->complete; + struct vb2_buffer *vb2_buffer = + &csi_buffer->v4l2_buffer.vb2_buf; + + vb2_buffer->timestamp = ktime_get_ns(); + csi_buffer->v4l2_buffer.sequence = state->sequence; + + vb2_buffer_done(vb2_buffer, VB2_BUF_STATE_DONE); + + state->complete = NULL; + } + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_frame_done(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + state->sequence++; + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_sync(struct sun6i_csi_device *csi_dev) +{ + sun6i_csi_capture_state_complete(csi_dev); + sun6i_csi_capture_state_update(csi_dev); +} + +/* Queue */ + +static int sun6i_csi_capture_queue_setup(struct vb2_queue *queue, + unsigned int *buffers_count, + unsigned int *planes_count, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + unsigned int size = csi_dev->capture.format.fmt.pix.sizeimage; + + if (*planes_count) + return sizes[0] < size ? -EINVAL : 0; + + *planes_count = 1; + sizes[0] = size; + + return 0; +} + +static int sun6i_csi_capture_buffer_prepare(struct vb2_buffer *buffer) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); + unsigned long size = capture->format.fmt.pix.sizeimage; + + if (vb2_plane_size(buffer, 0) < size) { + v4l2_err(v4l2_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(buffer, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(buffer, 0, size); + + v4l2_buffer->field = capture->format.fmt.pix.field; + + return 0; +} + +static void sun6i_csi_capture_buffer_queue(struct vb2_buffer *buffer) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); + struct sun6i_csi_buffer *csi_buffer = + container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + list_add_tail(&csi_buffer->list, &state->queue); + spin_unlock_irqrestore(&state->lock, flags); +} + +static int sun6i_csi_capture_start_streaming(struct vb2_queue *queue, + unsigned int count) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct video_device *video_dev = &csi_dev->capture.video_dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + int ret; + + state->sequence = 0; + + ret = video_device_pipeline_alloc_start(video_dev); + if (ret < 0) + goto error_state; + + state->streaming = true; + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto error_streaming; + + return 0; + +error_streaming: + state->streaming = false; + + video_device_pipeline_stop(video_dev); + +error_state: + sun6i_csi_capture_state_cleanup(csi_dev, false); + + return ret; +} + +static void sun6i_csi_capture_stop_streaming(struct vb2_queue *queue) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct video_device *video_dev = &csi_dev->capture.video_dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + + v4l2_subdev_call(subdev, video, s_stream, 0); + + state->streaming = false; + + video_device_pipeline_stop(video_dev); + + sun6i_csi_capture_state_cleanup(csi_dev, true); +} + +static const struct vb2_ops sun6i_csi_capture_queue_ops = { + .queue_setup = sun6i_csi_capture_queue_setup, + .buf_prepare = sun6i_csi_capture_buffer_prepare, + .buf_queue = sun6i_csi_capture_buffer_queue, + .start_streaming = sun6i_csi_capture_start_streaming, + .stop_streaming = sun6i_csi_capture_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* V4L2 Device */ + +static void sun6i_csi_capture_format_prepare(struct v4l2_format *format) +{ + struct v4l2_pix_format *pix_format = &format->fmt.pix; + const struct v4l2_format_info *info; + unsigned int width, height; + + v4l_bound_align_image(&pix_format->width, SUN6I_CSI_CAPTURE_WIDTH_MIN, + SUN6I_CSI_CAPTURE_WIDTH_MAX, 1, + &pix_format->height, SUN6I_CSI_CAPTURE_HEIGHT_MIN, + SUN6I_CSI_CAPTURE_HEIGHT_MAX, 1, 0); + + if (!sun6i_csi_capture_format_find(pix_format->pixelformat)) + pix_format->pixelformat = + sun6i_csi_capture_formats[0].pixelformat; + + width = pix_format->width; + height = pix_format->height; + + info = v4l2_format_info(pix_format->pixelformat); + + switch (pix_format->pixelformat) { + case V4L2_PIX_FMT_NV12_16L16: + pix_format->bytesperline = width * 12 / 8; + pix_format->sizeimage = pix_format->bytesperline * height; + break; + case V4L2_PIX_FMT_JPEG: + pix_format->bytesperline = width; + pix_format->sizeimage = pix_format->bytesperline * height; + break; + default: + v4l2_fill_pixfmt(pix_format, pix_format->pixelformat, + width, height); + break; + } + + if (pix_format->field == V4L2_FIELD_ANY) + pix_format->field = V4L2_FIELD_NONE; + + if (pix_format->pixelformat == V4L2_PIX_FMT_JPEG) + pix_format->colorspace = V4L2_COLORSPACE_JPEG; + else if (info && info->pixel_enc == V4L2_PIXEL_ENC_BAYER) + pix_format->colorspace = V4L2_COLORSPACE_RAW; + else + pix_format->colorspace = V4L2_COLORSPACE_SRGB; + + pix_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pix_format->quantization = V4L2_QUANTIZATION_DEFAULT; + pix_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_csi_capture_querycap(struct file *file, void *private, + struct v4l2_capability *capability) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct video_device *video_dev = &csi_dev->capture.video_dev; + + strscpy(capability->driver, SUN6I_CSI_NAME, sizeof(capability->driver)); + strscpy(capability->card, video_dev->name, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", dev_name(csi_dev->dev)); + + return 0; +} + +static int sun6i_csi_capture_enum_fmt(struct file *file, void *private, + struct v4l2_fmtdesc *fmtdesc) +{ + u32 index = fmtdesc->index; + + if (index >= ARRAY_SIZE(sun6i_csi_capture_formats)) + return -EINVAL; + + fmtdesc->pixelformat = sun6i_csi_capture_formats[index].pixelformat; + + return 0; +} + +static int sun6i_csi_capture_g_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + + *format = csi_dev->capture.format; + + return 0; +} + +static int sun6i_csi_capture_s_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + + if (vb2_is_busy(&capture->queue)) + return -EBUSY; + + sun6i_csi_capture_format_prepare(format); + + csi_dev->capture.format = *format; + + return 0; +} + +static int sun6i_csi_capture_try_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + sun6i_csi_capture_format_prepare(format); + + return 0; +} + +static int sun6i_csi_capture_enum_input(struct file *file, void *private, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, "Camera", sizeof(input->name)); + + return 0; +} + +static int sun6i_csi_capture_g_input(struct file *file, void *private, + unsigned int *index) +{ + *index = 0; + + return 0; +} + +static int sun6i_csi_capture_s_input(struct file *file, void *private, + unsigned int index) +{ + if (index != 0) + return -EINVAL; + + return 0; +} + +static const struct v4l2_ioctl_ops sun6i_csi_capture_ioctl_ops = { + .vidioc_querycap = sun6i_csi_capture_querycap, + + .vidioc_enum_fmt_vid_cap = sun6i_csi_capture_enum_fmt, + .vidioc_g_fmt_vid_cap = sun6i_csi_capture_g_fmt, + .vidioc_s_fmt_vid_cap = sun6i_csi_capture_s_fmt, + .vidioc_try_fmt_vid_cap = sun6i_csi_capture_try_fmt, + + .vidioc_enum_input = sun6i_csi_capture_enum_input, + .vidioc_g_input = sun6i_csi_capture_g_input, + .vidioc_s_input = sun6i_csi_capture_s_input, + + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* V4L2 File */ + +static int sun6i_csi_capture_open(struct file *file) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + int ret = 0; + + if (mutex_lock_interruptible(&capture->lock)) + return -ERESTARTSYS; + + ret = v4l2_pipeline_pm_get(&capture->video_dev.entity); + if (ret < 0) + goto error_lock; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto error_pipeline; + + mutex_unlock(&capture->lock); + + return 0; + +error_pipeline: + v4l2_pipeline_pm_put(&capture->video_dev.entity); + +error_lock: + mutex_unlock(&capture->lock); + + return ret; +} + +static int sun6i_csi_capture_close(struct file *file) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + + mutex_lock(&capture->lock); + + _vb2_fop_release(file, NULL); + v4l2_pipeline_pm_put(&capture->video_dev.entity); + + mutex_unlock(&capture->lock); + + return 0; +} + +static const struct v4l2_file_operations sun6i_csi_capture_fops = { + .owner = THIS_MODULE, + .open = sun6i_csi_capture_open, + .release = sun6i_csi_capture_close, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll +}; + +/* Media Entity */ + +static int sun6i_csi_capture_link_validate(struct media_link *link) +{ + struct video_device *video_dev = + media_entity_to_video_device(link->sink->entity); + struct sun6i_csi_device *csi_dev = video_get_drvdata(video_dev); + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + const struct sun6i_csi_capture_format *capture_format; + const struct sun6i_csi_bridge_format *bridge_format; + unsigned int capture_width, capture_height; + unsigned int bridge_width, bridge_height; + const struct v4l2_format_info *format_info; + u32 pixelformat, capture_field; + u32 mbus_code, bridge_field; + bool match; + + sun6i_csi_capture_dimensions(csi_dev, &capture_width, &capture_height); + + sun6i_csi_capture_format(csi_dev, &pixelformat, &capture_field); + capture_format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!capture_format)) + return -EINVAL; + + sun6i_csi_bridge_dimensions(csi_dev, &bridge_width, &bridge_height); + + sun6i_csi_bridge_format(csi_dev, &mbus_code, &bridge_field); + bridge_format = sun6i_csi_bridge_format_find(mbus_code); + if (WARN_ON(!bridge_format)) + return -EINVAL; + + /* No cropping/scaling is supported. */ + if (capture_width != bridge_width || capture_height != bridge_height) { + v4l2_err(v4l2_dev, + "invalid input/output dimensions: %ux%u/%ux%u\n", + bridge_width, bridge_height, capture_width, + capture_height); + return -EINVAL; + } + + format_info = v4l2_format_info(pixelformat); + /* Some formats are not listed. */ + if (!format_info) + return 0; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_BAYER && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_RAW) + goto invalid; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_RGB && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_RAW) + goto invalid; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_YUV) { + if (bridge_format->input_format != SUN6I_CSI_INPUT_FMT_YUV420 && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_YUV422) + goto invalid; + + /* YUV420 input can't produce YUV422 output. */ + if (bridge_format->input_format == SUN6I_CSI_INPUT_FMT_YUV420 && + format_info->vdiv == 1) + goto invalid; + } + + /* With raw input mode, we need a 1:1 match between input and output. */ + if (bridge_format->input_format == SUN6I_CSI_INPUT_FMT_RAW || + capture_format->input_format_raw) { + match = sun6i_csi_capture_format_match(pixelformat, mbus_code); + if (!match) + goto invalid; + } + + return 0; + +invalid: + v4l2_err(v4l2_dev, "invalid input/output format combination\n"); + return -EINVAL; +} + +static const struct media_entity_operations sun6i_csi_capture_media_ops = { + .link_validate = sun6i_csi_capture_link_validate +}; + +/* Capture */ + +int sun6i_csi_capture_setup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct sun6i_csi_capture_state *state = &capture->state; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct v4l2_subdev *bridge_subdev = &csi_dev->bridge.subdev; + struct video_device *video_dev = &capture->video_dev; + struct vb2_queue *queue = &capture->queue; + struct media_pad *pad = &capture->pad; + struct v4l2_format *format = &csi_dev->capture.format; + struct v4l2_pix_format *pix_format = &format->fmt.pix; + int ret; + + /* This may happen with multiple bridge notifier bound calls. */ + if (state->setup) + return 0; + + /* State */ + + INIT_LIST_HEAD(&state->queue); + spin_lock_init(&state->lock); + + /* Media Entity */ + + video_dev->entity.ops = &sun6i_csi_capture_media_ops; + + /* Media Pad */ + + pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&video_dev->entity, 1, pad); + if (ret < 0) + return ret; + + /* Queue */ + + mutex_init(&capture->lock); + + queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + queue->io_modes = VB2_MMAP | VB2_DMABUF; + queue->buf_struct_size = sizeof(struct sun6i_csi_buffer); + queue->ops = &sun6i_csi_capture_queue_ops; + queue->mem_ops = &vb2_dma_contig_memops; + queue->min_buffers_needed = 2; + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + queue->lock = &capture->lock; + queue->dev = csi_dev->dev; + queue->drv_priv = csi_dev; + + ret = vb2_queue_init(queue); + if (ret) { + v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Format */ + + format->type = queue->type; + pix_format->pixelformat = sun6i_csi_capture_formats[0].pixelformat; + pix_format->width = 1280; + pix_format->height = 720; + pix_format->field = V4L2_FIELD_NONE; + + sun6i_csi_capture_format_prepare(format); + + /* Video Device */ + + strscpy(video_dev->name, SUN6I_CSI_CAPTURE_NAME, + sizeof(video_dev->name)); + video_dev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + video_dev->vfl_dir = VFL_DIR_RX; + video_dev->release = video_device_release_empty; + video_dev->fops = &sun6i_csi_capture_fops; + video_dev->ioctl_ops = &sun6i_csi_capture_ioctl_ops; + video_dev->v4l2_dev = v4l2_dev; + video_dev->queue = queue; + video_dev->lock = &capture->lock; + + video_set_drvdata(video_dev, csi_dev); + + ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to register video device: %d\n", + ret); + goto error_media_entity; + } + + /* Media Pad Link */ + + ret = media_create_pad_link(&bridge_subdev->entity, + SUN6I_CSI_BRIDGE_PAD_SOURCE, + &video_dev->entity, 0, + csi_dev->isp_available ? 0 : + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n", + bridge_subdev->entity.name, + SUN6I_CSI_BRIDGE_PAD_SOURCE, + video_dev->entity.name, 0); + goto error_video_device; + } + + state->setup = true; + + return 0; + +error_video_device: + vb2_video_unregister_device(video_dev); + +error_media_entity: + media_entity_cleanup(&video_dev->entity); + + mutex_destroy(&capture->lock); + + return ret; +} + +void sun6i_csi_capture_cleanup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct video_device *video_dev = &capture->video_dev; + + /* This may happen if async registration failed to complete. */ + if (!capture->state.setup) + return; + + vb2_video_unregister_device(video_dev); + media_entity_cleanup(&video_dev->entity); + mutex_destroy(&capture->lock); + + capture->state.setup = false; +} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h new file mode 100644 index 000000000000..3ee5ccefbd10 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_CAPTURE_H_ +#define _SUN6I_CAPTURE_H_ + +#include <media/v4l2-device.h> + +#define SUN6I_CSI_CAPTURE_NAME "sun6i-csi-capture" + +#define SUN6I_CSI_CAPTURE_WIDTH_MIN 32 +#define SUN6I_CSI_CAPTURE_WIDTH_MAX 4800 +#define SUN6I_CSI_CAPTURE_HEIGHT_MIN 32 +#define SUN6I_CSI_CAPTURE_HEIGHT_MAX 4800 + +struct sun6i_csi_device; + +struct sun6i_csi_capture_format { + u32 pixelformat; + u8 output_format_field; + u8 output_format_frame; + bool input_yuv_seq_invert; + bool input_format_raw; + u32 hsize_len_factor; +}; + +struct sun6i_csi_capture_format_match { + u32 pixelformat; + u32 mbus_code; +}; + +#undef current +struct sun6i_csi_capture_state { + struct list_head queue; + spinlock_t lock; /* Queue and buffers lock. */ + + struct sun6i_csi_buffer *pending; + struct sun6i_csi_buffer *current; + struct sun6i_csi_buffer *complete; + + unsigned int sequence; + bool streaming; + bool setup; +}; + +struct sun6i_csi_capture { + struct sun6i_csi_capture_state state; + + struct video_device video_dev; + struct vb2_queue queue; + struct mutex lock; /* Queue lock. */ + struct media_pad pad; + + struct v4l2_format format; +}; + +/* Helpers */ + +void sun6i_csi_capture_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height); +void sun6i_csi_capture_format(struct sun6i_csi_device *csi_dev, + u32 *pixelformat, u32 *field); + +/* Format */ + +const +struct sun6i_csi_capture_format *sun6i_csi_capture_format_find(u32 pixelformat); + +/* Capture */ + +void sun6i_csi_capture_configure(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_state_update(struct sun6i_csi_device *csi_dev); + +/* State */ + +void sun6i_csi_capture_sync(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_frame_done(struct sun6i_csi_device *csi_dev); + +/* Capture */ + +int sun6i_csi_capture_setup(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_cleanup(struct sun6i_csi_device *csi_dev); + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h index 703fa14bb313..e01c5b9c2d60 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h @@ -1,196 +1,184 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> */ -#ifndef __SUN6I_CSI_REG_H__ -#define __SUN6I_CSI_REG_H__ +#ifndef _SUN6I_CSI_REG_H_ +#define _SUN6I_CSI_REG_H_ #include <linux/kernel.h> -#define CSI_EN_REG 0x0 -#define CSI_EN_VER_EN BIT(30) -#define CSI_EN_CSI_EN BIT(0) - -#define CSI_IF_CFG_REG 0x4 -#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21) -#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK) -#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK) -#define CSI_IF_CFG_FPS_DS_EN BIT(20) -#define CSI_IF_CFG_FIELD_MASK BIT(19) -#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK) -#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK) -#define CSI_IF_CFG_VREF_POL_MASK BIT(18) -#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK) -#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK) -#define CSI_IF_CFG_HREF_POL_MASK BIT(17) -#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK) -#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK) -#define CSI_IF_CFG_CLK_POL_MASK BIT(16) -#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK) -#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK) -#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8) -#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) -#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) -#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) -#define CSI_IF_CFG_MIPI_IF_MASK BIT(7) -#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7) -#define CSI_IF_CFG_MIPI_IF_MIPI BIT(7) -#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0) -#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK) -#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK) -#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK) -#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK) - -#define CSI_CAP_REG 0x8 -#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2) -#define CSI_CAP_CH0_CAP_MASK(count) (((count) << 2) & CSI_CAP_CH0_CAP_MASK_MASK) -#define CSI_CAP_CH0_VCAP_ON BIT(1) -#define CSI_CAP_CH0_SCAP_ON BIT(0) - -#define CSI_SYNC_CNT_REG 0xc -#define CSI_FIFO_THRS_REG 0x10 -#define CSI_BT656_HEAD_CFG_REG 0x14 -#define CSI_PTN_LEN_REG 0x30 -#define CSI_PTN_ADDR_REG 0x34 -#define CSI_VER_REG 0x3c - -#define CSI_CH_CFG_REG 0x44 -#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20) -#define CSI_CH_CFG_INPUT_FMT(fmt) (((fmt) << 20) & CSI_CH_CFG_INPUT_FMT_MASK) -#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16) -#define CSI_CH_CFG_OUTPUT_FMT(fmt) (((fmt) << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK) -#define CSI_CH_CFG_VFLIP_EN BIT(13) -#define CSI_CH_CFG_HFLIP_EN BIT(12) -#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10) -#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) -#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) -#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) -#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8) -#define CSI_CH_CFG_INPUT_SEQ(seq) (((seq) << 8) & CSI_CH_CFG_INPUT_SEQ_MASK) - -#define CSI_CH_SCALE_REG 0x4c -#define CSI_CH_SCALE_QUART_EN BIT(0) - -#define CSI_CH_F0_BUFA_REG 0x50 - -#define CSI_CH_F1_BUFA_REG 0x58 - -#define CSI_CH_F2_BUFA_REG 0x60 - -#define CSI_CH_STA_REG 0x6c -#define CSI_CH_STA_FIELD_STA_MASK BIT(2) -#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK) -#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK) -#define CSI_CH_STA_VCAP_STA BIT(1) -#define CSI_CH_STA_SCAP_STA BIT(0) - -#define CSI_CH_INT_EN_REG 0x70 -#define CSI_CH_INT_EN_VS_INT_EN BIT(7) -#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6) -#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5) -#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4) -#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3) -#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2) -#define CSI_CH_INT_EN_FD_INT_EN BIT(1) -#define CSI_CH_INT_EN_CD_INT_EN BIT(0) - -#define CSI_CH_INT_STA_REG 0x74 -#define CSI_CH_INT_STA_VS_PD BIT(7) -#define CSI_CH_INT_STA_HB_OF_PD BIT(6) -#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5) -#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4) -#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3) -#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2) -#define CSI_CH_INT_STA_FD_PD BIT(1) -#define CSI_CH_INT_STA_CD_PD BIT(0) - -#define CSI_CH_FLD1_VSIZE_REG 0x78 - -#define CSI_CH_HSIZE_REG 0x80 -#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16) -#define CSI_CH_HSIZE_HOR_LEN(len) (((len) << 16) & CSI_CH_HSIZE_HOR_LEN_MASK) -#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0) -#define CSI_CH_HSIZE_HOR_START(start) (((start) << 0) & CSI_CH_HSIZE_HOR_START_MASK) - -#define CSI_CH_VSIZE_REG 0x84 -#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16) -#define CSI_CH_VSIZE_VER_LEN(len) (((len) << 16) & CSI_CH_VSIZE_VER_LEN_MASK) -#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0) -#define CSI_CH_VSIZE_VER_START(start) (((start) << 0) & CSI_CH_VSIZE_VER_START_MASK) - -#define CSI_CH_BUF_LEN_REG 0x88 -#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16) -#define CSI_CH_BUF_LEN_BUF_LEN_C(len) (((len) << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK) -#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0) -#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) (((len) << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK) - -#define CSI_CH_FLIP_SIZE_REG 0x8c -#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16) -#define CSI_CH_FLIP_SIZE_VER_LEN(len) (((len) << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK) -#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0) -#define CSI_CH_FLIP_SIZE_VALID_LEN(len) (((len) << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK) - -#define CSI_CH_FRM_CLK_CNT_REG 0x90 -#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94 -#define CSI_CH_FIFO_STAT_REG 0x98 -#define CSI_CH_PCLK_STAT_REG 0x9c - -/* - * csi input data format - */ -enum csi_input_fmt { - CSI_INPUT_FORMAT_RAW = 0, - CSI_INPUT_FORMAT_YUV422 = 3, - CSI_INPUT_FORMAT_YUV420 = 4, -}; - -/* - * csi output data format - */ -enum csi_output_fmt { - /* only when input format is RAW */ - CSI_FIELD_RAW_8 = 0, - CSI_FIELD_RAW_10 = 1, - CSI_FIELD_RAW_12 = 2, - CSI_FIELD_RGB565 = 4, - CSI_FIELD_RGB888 = 5, - CSI_FIELD_PRGB888 = 6, - CSI_FRAME_RAW_8 = 8, - CSI_FRAME_RAW_10 = 9, - CSI_FRAME_RAW_12 = 10, - CSI_FRAME_RGB565 = 12, - CSI_FRAME_RGB888 = 13, - CSI_FRAME_PRGB888 = 14, - - /* only when input format is YUV422 */ - CSI_FIELD_PLANAR_YUV422 = 0, - CSI_FIELD_PLANAR_YUV420 = 1, - CSI_FRAME_PLANAR_YUV420 = 2, - CSI_FRAME_PLANAR_YUV422 = 3, - CSI_FIELD_UV_CB_YUV422 = 4, - CSI_FIELD_UV_CB_YUV420 = 5, - CSI_FRAME_UV_CB_YUV420 = 6, - CSI_FRAME_UV_CB_YUV422 = 7, - CSI_FIELD_MB_YUV422 = 8, - CSI_FIELD_MB_YUV420 = 9, - CSI_FRAME_MB_YUV420 = 10, - CSI_FRAME_MB_YUV422 = 11, - CSI_FIELD_UV_CB_YUV422_10 = 12, - CSI_FIELD_UV_CB_YUV420_10 = 13, -}; - -/* - * csi YUV input data sequence - */ -enum csi_input_seq { - /* only when input format is YUV422 */ - CSI_INPUT_SEQ_YUYV = 0, - CSI_INPUT_SEQ_YVYU, - CSI_INPUT_SEQ_UYVY, - CSI_INPUT_SEQ_VYUY, -}; - -#endif /* __SUN6I_CSI_REG_H__ */ +#define SUN6I_CSI_ADDR_VALUE(a) ((a) >> 2) + +#define SUN6I_CSI_EN_REG 0x0 +#define SUN6I_CSI_EN_VER_EN BIT(30) +#define SUN6I_CSI_EN_PTN_CYCLE(v) (((v) << 16) & GENMASK(23, 16)) +#define SUN6I_CSI_EN_SRAM_PWDN BIT(8) +#define SUN6I_CSI_EN_PTN_START BIT(4) +#define SUN6I_CSI_EN_CLK_CNT_SPL_VSYNC BIT(3) +#define SUN6I_CSI_EN_CLK_CNT_EN BIT(2) +#define SUN6I_CSI_EN_PTN_GEN_EN BIT(1) +#define SUN6I_CSI_EN_CSI_EN BIT(0) + +/* Note that Allwinner manuals and code invert positive/negative definitions. */ + +#define SUN6I_CSI_IF_CFG_REG 0x4 +#define SUN6I_CSI_IF_CFG_FIELD_DT_PCLK_SHIFT(v) (((v) << 24) & GENMASK(27, 24)) +#define SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE (0 << 21) +#define SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED (1 << 21) +#define SUN6I_CSI_IF_CFG_FPS_DS BIT(20) +#define SUN6I_CSI_IF_CFG_FIELD_POSITIVE (0 << 19) +#define SUN6I_CSI_IF_CFG_FIELD_NEGATIVE (1 << 19) +#define SUN6I_CSI_IF_CFG_VREF_POL_POSITIVE (0 << 18) +#define SUN6I_CSI_IF_CFG_VREF_POL_NEGATIVE (1 << 18) +#define SUN6I_CSI_IF_CFG_HREF_POL_POSITIVE (0 << 17) +#define SUN6I_CSI_IF_CFG_HREF_POL_NEGATIVE (1 << 17) +#define SUN6I_CSI_IF_CFG_CLK_POL_FALLING (0 << 16) +#define SUN6I_CSI_IF_CFG_CLK_POL_RISING (1 << 16) +#define SUN6I_CSI_IF_CFG_FIELD_DT_FIELD_VSYNC (0 << 14) +#define SUN6I_CSI_IF_CFG_FIELD_DT_FIELD (1 << 14) +#define SUN6I_CSI_IF_CFG_FIELD_DT_VSYNC (2 << 14) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_8 (0 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_10 (1 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_12 (2 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_8_PLUS_2 (3 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_2_TIMES_8 (4 << 8) +#define SUN6I_CSI_IF_CFG_IF_CSI (0 << 7) +#define SUN6I_CSI_IF_CFG_IF_MIPI (1 << 7) +#define SUN6I_CSI_IF_CFG_IF_CSI_YUV_RAW (0 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_YUV_COMBINED (1 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_BT656 (4 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_BT1120 (5 << 0) + +#define SUN6I_CSI_CAP_REG 0x8 +#define SUN6I_CSI_CAP_MASK(v) (((v) << 2) & GENMASK(5, 2)) +#define SUN6I_CSI_CAP_VCAP_ON BIT(1) +#define SUN6I_CSI_CAP_SCAP_ON BIT(0) + +#define SUN6I_CSI_SYNC_CNT_REG 0xc +#define SUN6I_CSI_FIFO_THRS_REG 0x10 +#define SUN6I_CSI_BT656_HEAD_CFG_REG 0x14 + +#define SUN6I_CSI_PTN_LEN_REG 0x30 +#define SUN6I_CSI_PTN_ADDR_REG 0x34 +#define SUN6I_CSI_VER_REG 0x3c + +#define SUN6I_CSI_CH_CFG_REG 0x44 +#define SUN6I_CSI_CH_CFG_PAD_VAL(v) (((v) << 24) & GENMASK(31, 24)) +#define SUN6I_CSI_CH_CFG_INPUT_FMT(v) (((v) << 20) & GENMASK(23, 20)) +#define SUN6I_CSI_CH_CFG_OUTPUT_FMT(v) (((v) << 16) & GENMASK(19, 16)) +#define SUN6I_CSI_CH_CFG_VFLIP_EN BIT(13) +#define SUN6I_CSI_CH_CFG_HFLIP_EN BIT(12) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD0 (0 << 10) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD1 (1 << 10) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_EITHER (2 << 10) +#define SUN6I_CSI_CH_CFG_INPUT_YUV_SEQ(v) (((v) << 8) & GENMASK(9, 8)) + +#define SUN6I_CSI_INPUT_FMT_RAW 0 +#define SUN6I_CSI_INPUT_FMT_YUV422 3 +#define SUN6I_CSI_INPUT_FMT_YUV420 4 + +/* Note that Allwinner manuals and code invert frame/field definitions. */ + +/* RAW */ +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8 0 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10 1 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12 2 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565 4 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RGB888 5 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_PRGB888 6 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8 8 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10 9 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12 10 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565 12 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RGB888 13 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_PRGB888 14 + +/* YUV */ +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422P 0 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P 1 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P 2 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422P 3 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP 4 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP 5 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP 6 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP 7 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422MB 8 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420MB 9 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420MB 10 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422MB 11 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP_10 12 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP_10 13 + +/* YUV Planar */ +#define SUN6I_CSI_INPUT_YUV_SEQ_YUYV 0 +#define SUN6I_CSI_INPUT_YUV_SEQ_YVYU 1 +#define SUN6I_CSI_INPUT_YUV_SEQ_UYVY 2 +#define SUN6I_CSI_INPUT_YUV_SEQ_VYUY 3 + +/* YUV Semi-planar */ +#define SUN6I_CSI_INPUT_YUV_SEQ_UV 0 +#define SUN6I_CSI_INPUT_YUV_SEQ_VU 1 + +#define SUN6I_CSI_CH_SCALE_REG 0x4c +#define SUN6I_CSI_CH_SCALE_QUART_EN BIT(0) + +#define SUN6I_CSI_CH_FIFO0_ADDR_REG 0x50 +#define SUN6I_CSI_CH_FIFO1_ADDR_REG 0x58 +#define SUN6I_CSI_CH_FIFO2_ADDR_REG 0x60 + +#define SUN6I_CSI_CH_STA_REG 0x6c +#define SUN6I_CSI_CH_STA_FIELD BIT(2) +#define SUN6I_CSI_CH_STA_VCAP BIT(1) +#define SUN6I_CSI_CH_STA_SCAP BIT(0) + +#define SUN6I_CSI_CH_INT_EN_REG 0x70 +#define SUN6I_CSI_CH_INT_EN_VS BIT(7) +#define SUN6I_CSI_CH_INT_EN_HB_OF BIT(6) +#define SUN6I_CSI_CH_INT_EN_MUL_ERR BIT(5) +#define SUN6I_CSI_CH_INT_EN_FIFO2_OF BIT(4) +#define SUN6I_CSI_CH_INT_EN_FIFO1_OF BIT(3) +#define SUN6I_CSI_CH_INT_EN_FIFO0_OF BIT(2) +#define SUN6I_CSI_CH_INT_EN_FD BIT(1) +#define SUN6I_CSI_CH_INT_EN_CD BIT(0) + +#define SUN6I_CSI_CH_INT_STA_REG 0x74 +#define SUN6I_CSI_CH_INT_STA_CLEAR 0xff +#define SUN6I_CSI_CH_INT_STA_VS BIT(7) +#define SUN6I_CSI_CH_INT_STA_HB_OF BIT(6) +#define SUN6I_CSI_CH_INT_STA_MUL_ERR BIT(5) +#define SUN6I_CSI_CH_INT_STA_FIFO2_OF BIT(4) +#define SUN6I_CSI_CH_INT_STA_FIFO1_OF BIT(3) +#define SUN6I_CSI_CH_INT_STA_FIFO0_OF BIT(2) +#define SUN6I_CSI_CH_INT_STA_FD BIT(1) +#define SUN6I_CSI_CH_INT_STA_CD BIT(0) + +#define SUN6I_CSI_CH_FLD1_VSIZE_REG 0x78 +#define SUN6I_CSI_CH_FLD1_VSIZE_VER_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_FLD1_VSIZE_VER_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_HSIZE_REG 0x80 +#define SUN6I_CSI_CH_HSIZE_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_HSIZE_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_VSIZE_REG 0x84 +#define SUN6I_CSI_CH_VSIZE_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_VSIZE_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_BUF_LEN_REG 0x88 +#define SUN6I_CSI_CH_BUF_LEN_CHROMA_LINE(v) (((v) << 16) & GENMASK(29, 16)) +#define SUN6I_CSI_CH_BUF_LEN_LUMA_LINE(v) ((v) & GENMASK(13, 0)) + +#define SUN6I_CSI_CH_FLIP_SIZE_REG 0x8c +#define SUN6I_CSI_CH_FLIP_SIZE_VER_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_FLIP_SIZE_VALID_LEN(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_FRM_CLK_CNT_REG 0x90 +#define SUN6I_CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94 +#define SUN6I_CSI_CH_FIFO_STAT_REG 0x98 +#define SUN6I_CSI_CH_PCLK_STAT_REG 0x9c + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c deleted file mode 100644 index 791583d23a65..000000000000 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c +++ /dev/null @@ -1,733 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. - * Author: Yong Deng <yong.deng@magewell.com> - */ - -#include <linux/of.h> - -#include <media/v4l2-device.h> -#include <media/v4l2-event.h> -#include <media/v4l2-ioctl.h> -#include <media/v4l2-mc.h> -#include <media/videobuf2-dma-contig.h> -#include <media/videobuf2-v4l2.h> - -#include "sun6i_csi.h" -#include "sun6i_video.h" - -/* This is got from BSP sources. */ -#define MIN_WIDTH (32) -#define MIN_HEIGHT (32) -#define MAX_WIDTH (4800) -#define MAX_HEIGHT (4800) - -/* Helpers */ - -static struct v4l2_subdev * -sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad) -{ - struct media_pad *remote; - - remote = media_pad_remote_pad_first(&video->pad); - - if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) - return NULL; - - if (pad) - *pad = remote->index; - - return media_entity_to_v4l2_subdev(remote->entity); -} - -/* Format */ - -static const u32 sun6i_video_formats[] = { - V4L2_PIX_FMT_SBGGR8, - V4L2_PIX_FMT_SGBRG8, - V4L2_PIX_FMT_SGRBG8, - V4L2_PIX_FMT_SRGGB8, - V4L2_PIX_FMT_SBGGR10, - V4L2_PIX_FMT_SGBRG10, - V4L2_PIX_FMT_SGRBG10, - V4L2_PIX_FMT_SRGGB10, - V4L2_PIX_FMT_SBGGR12, - V4L2_PIX_FMT_SGBRG12, - V4L2_PIX_FMT_SGRBG12, - V4L2_PIX_FMT_SRGGB12, - V4L2_PIX_FMT_YUYV, - V4L2_PIX_FMT_YVYU, - V4L2_PIX_FMT_UYVY, - V4L2_PIX_FMT_VYUY, - V4L2_PIX_FMT_NV12_16L16, - V4L2_PIX_FMT_NV12, - V4L2_PIX_FMT_NV21, - V4L2_PIX_FMT_YUV420, - V4L2_PIX_FMT_YVU420, - V4L2_PIX_FMT_NV16, - V4L2_PIX_FMT_NV61, - V4L2_PIX_FMT_YUV422P, - V4L2_PIX_FMT_RGB565, - V4L2_PIX_FMT_RGB565X, - V4L2_PIX_FMT_JPEG, -}; - -static bool sun6i_video_format_check(u32 format) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(sun6i_video_formats); i++) - if (sun6i_video_formats[i] == format) - return true; - - return false; -} - -/* Video */ - -static void sun6i_video_buffer_configure(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_buffer *csi_buffer) -{ - csi_buffer->queued_to_csi = true; - sun6i_csi_update_buf_addr(csi_dev, csi_buffer->dma_addr); -} - -static void sun6i_video_configure(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct sun6i_csi_config config = { 0 }; - - config.pixelformat = video->format.fmt.pix.pixelformat; - config.code = video->mbus_code; - config.field = video->format.fmt.pix.field; - config.width = video->format.fmt.pix.width; - config.height = video->format.fmt.pix.height; - - sun6i_csi_update_config(csi_dev, &config); -} - -/* Queue */ - -static int sun6i_video_queue_setup(struct vb2_queue *queue, - unsigned int *buffers_count, - unsigned int *planes_count, - unsigned int sizes[], - struct device *alloc_devs[]) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - unsigned int size = video->format.fmt.pix.sizeimage; - - if (*planes_count) - return sizes[0] < size ? -EINVAL : 0; - - *planes_count = 1; - sizes[0] = size; - - return 0; -} - -static int sun6i_video_buffer_prepare(struct vb2_buffer *buffer) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); - struct sun6i_video *video = &csi_dev->video; - struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; - struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); - struct sun6i_csi_buffer *csi_buffer = - container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); - unsigned long size = video->format.fmt.pix.sizeimage; - - if (vb2_plane_size(buffer, 0) < size) { - v4l2_err(v4l2_dev, "buffer too small (%lu < %lu)\n", - vb2_plane_size(buffer, 0), size); - return -EINVAL; - } - - vb2_set_plane_payload(buffer, 0, size); - - csi_buffer->dma_addr = vb2_dma_contig_plane_dma_addr(buffer, 0); - v4l2_buffer->field = video->format.fmt.pix.field; - - return 0; -} - -static void sun6i_video_buffer_queue(struct vb2_buffer *buffer) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); - struct sun6i_video *video = &csi_dev->video; - struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); - struct sun6i_csi_buffer *csi_buffer = - container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); - unsigned long flags; - - spin_lock_irqsave(&video->dma_queue_lock, flags); - csi_buffer->queued_to_csi = false; - list_add_tail(&csi_buffer->list, &video->dma_queue); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); -} - -static int sun6i_video_start_streaming(struct vb2_queue *queue, - unsigned int count) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - struct video_device *video_dev = &video->video_dev; - struct sun6i_csi_buffer *buf; - struct sun6i_csi_buffer *next_buf; - struct v4l2_subdev *subdev; - unsigned long flags; - int ret; - - video->sequence = 0; - - ret = video_device_pipeline_alloc_start(video_dev); - if (ret < 0) - goto error_dma_queue_flush; - - if (video->mbus_code == 0) { - ret = -EINVAL; - goto error_media_pipeline; - } - - subdev = sun6i_video_remote_subdev(video, NULL); - if (!subdev) { - ret = -EINVAL; - goto error_media_pipeline; - } - - sun6i_video_configure(csi_dev); - - spin_lock_irqsave(&video->dma_queue_lock, flags); - - buf = list_first_entry(&video->dma_queue, - struct sun6i_csi_buffer, list); - sun6i_video_buffer_configure(csi_dev, buf); - - sun6i_csi_set_stream(csi_dev, true); - - /* - * CSI will lookup the next dma buffer for next frame before the - * current frame done IRQ triggered. This is not documented - * but reported by OndÅ™ej Jirman. - * The BSP code has workaround for this too. It skip to mark the - * first buffer as frame done for VB2 and pass the second buffer - * to CSI in the first frame done ISR call. Then in second frame - * done ISR call, it mark the first buffer as frame done for VB2 - * and pass the third buffer to CSI. And so on. The bad thing is - * that the first buffer will be written twice and the first frame - * is dropped even the queued buffer is sufficient. - * So, I make some improvement here. Pass the next buffer to CSI - * just follow starting the CSI. In this case, the first frame - * will be stored in first buffer, second frame in second buffer. - * This method is used to avoid dropping the first frame, it - * would also drop frame when lacking of queued buffer. - */ - next_buf = list_next_entry(buf, list); - sun6i_video_buffer_configure(csi_dev, next_buf); - - spin_unlock_irqrestore(&video->dma_queue_lock, flags); - - ret = v4l2_subdev_call(subdev, video, s_stream, 1); - if (ret && ret != -ENOIOCTLCMD) - goto error_stream; - - return 0; - -error_stream: - sun6i_csi_set_stream(csi_dev, false); - -error_media_pipeline: - video_device_pipeline_stop(video_dev); - -error_dma_queue_flush: - spin_lock_irqsave(&video->dma_queue_lock, flags); - list_for_each_entry(buf, &video->dma_queue, list) - vb2_buffer_done(&buf->v4l2_buffer.vb2_buf, - VB2_BUF_STATE_QUEUED); - INIT_LIST_HEAD(&video->dma_queue); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); - - return ret; -} - -static void sun6i_video_stop_streaming(struct vb2_queue *queue) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - struct v4l2_subdev *subdev; - unsigned long flags; - struct sun6i_csi_buffer *buf; - - subdev = sun6i_video_remote_subdev(video, NULL); - if (subdev) - v4l2_subdev_call(subdev, video, s_stream, 0); - - sun6i_csi_set_stream(csi_dev, false); - - video_device_pipeline_stop(&video->video_dev); - - /* Release all active buffers */ - spin_lock_irqsave(&video->dma_queue_lock, flags); - list_for_each_entry(buf, &video->dma_queue, list) - vb2_buffer_done(&buf->v4l2_buffer.vb2_buf, VB2_BUF_STATE_ERROR); - INIT_LIST_HEAD(&video->dma_queue); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); -} - -void sun6i_video_frame_done(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct sun6i_csi_buffer *buf; - struct sun6i_csi_buffer *next_buf; - struct vb2_v4l2_buffer *v4l2_buffer; - - spin_lock(&video->dma_queue_lock); - - buf = list_first_entry(&video->dma_queue, - struct sun6i_csi_buffer, list); - if (list_is_last(&buf->list, &video->dma_queue)) { - dev_dbg(csi_dev->dev, "Frame dropped!\n"); - goto complete; - } - - next_buf = list_next_entry(buf, list); - /* If a new buffer (#next_buf) had not been queued to CSI, the old - * buffer (#buf) is still holding by CSI for storing the next - * frame. So, we queue a new buffer (#next_buf) to CSI then wait - * for next ISR call. - */ - if (!next_buf->queued_to_csi) { - sun6i_video_buffer_configure(csi_dev, next_buf); - dev_dbg(csi_dev->dev, "Frame dropped!\n"); - goto complete; - } - - list_del(&buf->list); - v4l2_buffer = &buf->v4l2_buffer; - v4l2_buffer->vb2_buf.timestamp = ktime_get_ns(); - v4l2_buffer->sequence = video->sequence; - vb2_buffer_done(&v4l2_buffer->vb2_buf, VB2_BUF_STATE_DONE); - - /* Prepare buffer for next frame but one. */ - if (!list_is_last(&next_buf->list, &video->dma_queue)) { - next_buf = list_next_entry(next_buf, list); - sun6i_video_buffer_configure(csi_dev, next_buf); - } else { - dev_dbg(csi_dev->dev, "Next frame will be dropped!\n"); - } - -complete: - video->sequence++; - spin_unlock(&video->dma_queue_lock); -} - -static const struct vb2_ops sun6i_video_queue_ops = { - .queue_setup = sun6i_video_queue_setup, - .buf_prepare = sun6i_video_buffer_prepare, - .buf_queue = sun6i_video_buffer_queue, - .start_streaming = sun6i_video_start_streaming, - .stop_streaming = sun6i_video_stop_streaming, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, -}; - -/* V4L2 Device */ - -static int sun6i_video_querycap(struct file *file, void *private, - struct v4l2_capability *capability) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct video_device *video_dev = &csi_dev->video.video_dev; - - strscpy(capability->driver, SUN6I_CSI_NAME, sizeof(capability->driver)); - strscpy(capability->card, video_dev->name, sizeof(capability->card)); - snprintf(capability->bus_info, sizeof(capability->bus_info), - "platform:%s", dev_name(csi_dev->dev)); - - return 0; -} - -static int sun6i_video_enum_fmt(struct file *file, void *private, - struct v4l2_fmtdesc *fmtdesc) -{ - u32 index = fmtdesc->index; - - if (index >= ARRAY_SIZE(sun6i_video_formats)) - return -EINVAL; - - fmtdesc->pixelformat = sun6i_video_formats[index]; - - return 0; -} - -static int sun6i_video_g_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - *format = video->format; - - return 0; -} - -static int sun6i_video_format_try(struct sun6i_video *video, - struct v4l2_format *format) -{ - struct v4l2_pix_format *pix_format = &format->fmt.pix; - int bpp; - - if (!sun6i_video_format_check(pix_format->pixelformat)) - pix_format->pixelformat = sun6i_video_formats[0]; - - v4l_bound_align_image(&pix_format->width, MIN_WIDTH, MAX_WIDTH, 1, - &pix_format->height, MIN_HEIGHT, MAX_WIDTH, 1, 1); - - bpp = sun6i_csi_get_bpp(pix_format->pixelformat); - pix_format->bytesperline = (pix_format->width * bpp) >> 3; - pix_format->sizeimage = pix_format->bytesperline * pix_format->height; - - if (pix_format->field == V4L2_FIELD_ANY) - pix_format->field = V4L2_FIELD_NONE; - - if (pix_format->pixelformat == V4L2_PIX_FMT_JPEG) - pix_format->colorspace = V4L2_COLORSPACE_JPEG; - else - pix_format->colorspace = V4L2_COLORSPACE_SRGB; - - pix_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; - pix_format->quantization = V4L2_QUANTIZATION_DEFAULT; - pix_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; - - return 0; -} - -static int sun6i_video_format_set(struct sun6i_video *video, - struct v4l2_format *format) -{ - int ret; - - ret = sun6i_video_format_try(video, format); - if (ret) - return ret; - - video->format = *format; - - return 0; -} - -static int sun6i_video_s_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - if (vb2_is_busy(&video->queue)) - return -EBUSY; - - return sun6i_video_format_set(video, format); -} - -static int sun6i_video_try_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - return sun6i_video_format_try(video, format); -} - -static int sun6i_video_enum_input(struct file *file, void *private, - struct v4l2_input *input) -{ - if (input->index != 0) - return -EINVAL; - - input->type = V4L2_INPUT_TYPE_CAMERA; - strscpy(input->name, "Camera", sizeof(input->name)); - - return 0; -} - -static int sun6i_video_g_input(struct file *file, void *private, - unsigned int *index) -{ - *index = 0; - - return 0; -} - -static int sun6i_video_s_input(struct file *file, void *private, - unsigned int index) -{ - if (index != 0) - return -EINVAL; - - return 0; -} - -static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = { - .vidioc_querycap = sun6i_video_querycap, - - .vidioc_enum_fmt_vid_cap = sun6i_video_enum_fmt, - .vidioc_g_fmt_vid_cap = sun6i_video_g_fmt, - .vidioc_s_fmt_vid_cap = sun6i_video_s_fmt, - .vidioc_try_fmt_vid_cap = sun6i_video_try_fmt, - - .vidioc_enum_input = sun6i_video_enum_input, - .vidioc_g_input = sun6i_video_g_input, - .vidioc_s_input = sun6i_video_s_input, - - .vidioc_create_bufs = vb2_ioctl_create_bufs, - .vidioc_prepare_buf = vb2_ioctl_prepare_buf, - .vidioc_reqbufs = vb2_ioctl_reqbufs, - .vidioc_querybuf = vb2_ioctl_querybuf, - .vidioc_expbuf = vb2_ioctl_expbuf, - .vidioc_qbuf = vb2_ioctl_qbuf, - .vidioc_dqbuf = vb2_ioctl_dqbuf, - .vidioc_streamon = vb2_ioctl_streamon, - .vidioc_streamoff = vb2_ioctl_streamoff, -}; - -/* V4L2 File */ - -static int sun6i_video_open(struct file *file) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - int ret = 0; - - if (mutex_lock_interruptible(&video->lock)) - return -ERESTARTSYS; - - ret = v4l2_fh_open(file); - if (ret < 0) - goto error_lock; - - ret = v4l2_pipeline_pm_get(&video->video_dev.entity); - if (ret < 0) - goto error_v4l2_fh; - - /* Power on at first open. */ - if (v4l2_fh_is_singular_file(file)) { - ret = sun6i_csi_set_power(csi_dev, true); - if (ret < 0) - goto error_v4l2_fh; - } - - mutex_unlock(&video->lock); - - return 0; - -error_v4l2_fh: - v4l2_fh_release(file); - -error_lock: - mutex_unlock(&video->lock); - - return ret; -} - -static int sun6i_video_close(struct file *file) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - bool last_close; - - mutex_lock(&video->lock); - - last_close = v4l2_fh_is_singular_file(file); - - _vb2_fop_release(file, NULL); - v4l2_pipeline_pm_put(&video->video_dev.entity); - - /* Power off at last close. */ - if (last_close) - sun6i_csi_set_power(csi_dev, false); - - mutex_unlock(&video->lock); - - return 0; -} - -static const struct v4l2_file_operations sun6i_video_fops = { - .owner = THIS_MODULE, - .open = sun6i_video_open, - .release = sun6i_video_close, - .unlocked_ioctl = video_ioctl2, - .mmap = vb2_fop_mmap, - .poll = vb2_fop_poll -}; - -/* Media Entity */ - -static int sun6i_video_link_validate_get_format(struct media_pad *pad, - struct v4l2_subdev_format *fmt) -{ - if (is_media_entity_v4l2_subdev(pad->entity)) { - struct v4l2_subdev *sd = - media_entity_to_v4l2_subdev(pad->entity); - - fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; - fmt->pad = pad->index; - return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); - } - - return -EINVAL; -} - -static int sun6i_video_link_validate(struct media_link *link) -{ - struct video_device *vdev = container_of(link->sink->entity, - struct video_device, entity); - struct sun6i_csi_device *csi_dev = video_get_drvdata(vdev); - struct sun6i_video *video = &csi_dev->video; - struct v4l2_subdev_format source_fmt; - int ret; - - video->mbus_code = 0; - - if (!media_pad_remote_pad_first(link->sink->entity->pads)) { - dev_info(csi_dev->dev, "video node %s pad not connected\n", - vdev->name); - return -ENOLINK; - } - - ret = sun6i_video_link_validate_get_format(link->source, &source_fmt); - if (ret < 0) - return ret; - - if (!sun6i_csi_is_format_supported(csi_dev, - video->format.fmt.pix.pixelformat, - source_fmt.format.code)) { - dev_err(csi_dev->dev, - "Unsupported pixformat: 0x%x with mbus code: 0x%x!\n", - video->format.fmt.pix.pixelformat, - source_fmt.format.code); - return -EPIPE; - } - - if (source_fmt.format.width != video->format.fmt.pix.width || - source_fmt.format.height != video->format.fmt.pix.height) { - dev_err(csi_dev->dev, - "Wrong width or height %ux%u (%ux%u expected)\n", - video->format.fmt.pix.width, video->format.fmt.pix.height, - source_fmt.format.width, source_fmt.format.height); - return -EPIPE; - } - - video->mbus_code = source_fmt.format.code; - - return 0; -} - -static const struct media_entity_operations sun6i_video_media_ops = { - .link_validate = sun6i_video_link_validate -}; - -/* Video */ - -int sun6i_video_setup(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; - struct video_device *video_dev = &video->video_dev; - struct vb2_queue *queue = &video->queue; - struct media_pad *pad = &video->pad; - struct v4l2_format format = { 0 }; - struct v4l2_pix_format *pix_format = &format.fmt.pix; - int ret; - - /* Media Entity */ - - video_dev->entity.ops = &sun6i_video_media_ops; - - /* Media Pad */ - - pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; - - ret = media_entity_pads_init(&video_dev->entity, 1, pad); - if (ret < 0) - return ret; - - /* DMA queue */ - - INIT_LIST_HEAD(&video->dma_queue); - spin_lock_init(&video->dma_queue_lock); - - video->sequence = 0; - - /* Queue */ - - mutex_init(&video->lock); - - queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - queue->io_modes = VB2_MMAP | VB2_DMABUF; - queue->buf_struct_size = sizeof(struct sun6i_csi_buffer); - queue->ops = &sun6i_video_queue_ops; - queue->mem_ops = &vb2_dma_contig_memops; - queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - queue->lock = &video->lock; - queue->dev = csi_dev->dev; - queue->drv_priv = csi_dev; - - /* Make sure non-dropped frame. */ - queue->min_buffers_needed = 3; - - ret = vb2_queue_init(queue); - if (ret) { - v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); - goto error_media_entity; - } - - /* V4L2 Format */ - - format.type = queue->type; - pix_format->pixelformat = sun6i_video_formats[0]; - pix_format->width = 1280; - pix_format->height = 720; - pix_format->field = V4L2_FIELD_NONE; - - sun6i_video_format_set(video, &format); - - /* Video Device */ - - strscpy(video_dev->name, SUN6I_CSI_NAME, sizeof(video_dev->name)); - video_dev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - video_dev->vfl_dir = VFL_DIR_RX; - video_dev->release = video_device_release_empty; - video_dev->fops = &sun6i_video_fops; - video_dev->ioctl_ops = &sun6i_video_ioctl_ops; - video_dev->v4l2_dev = v4l2_dev; - video_dev->queue = queue; - video_dev->lock = &video->lock; - - video_set_drvdata(video_dev, csi_dev); - - ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); - if (ret < 0) { - v4l2_err(v4l2_dev, "failed to register video device: %d\n", - ret); - goto error_media_entity; - } - - return 0; - -error_media_entity: - media_entity_cleanup(&video_dev->entity); - - mutex_destroy(&video->lock); - - return ret; -} - -void sun6i_video_cleanup(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct video_device *video_dev = &video->video_dev; - - vb2_video_unregister_device(video_dev); - media_entity_cleanup(&video_dev->entity); - mutex_destroy(&video->lock); -} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h deleted file mode 100644 index a917d2da6deb..000000000000 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) - * All rights reserved. - * Author: Yong Deng <yong.deng@magewell.com> - */ - -#ifndef __SUN6I_VIDEO_H__ -#define __SUN6I_VIDEO_H__ - -#include <media/v4l2-dev.h> -#include <media/videobuf2-core.h> - -struct sun6i_csi_device; - -struct sun6i_video { - struct video_device video_dev; - struct vb2_queue queue; - struct mutex lock; /* Queue lock. */ - struct media_pad pad; - - struct list_head dma_queue; - spinlock_t dma_queue_lock; /* DMA queue lock. */ - - struct v4l2_format format; - u32 mbus_code; - unsigned int sequence; -}; - -int sun6i_video_setup(struct sun6i_csi_device *csi_dev); -void sun6i_video_cleanup(struct sun6i_csi_device *csi_dev); - -void sun6i_video_frame_done(struct sun6i_csi_device *csi_dev); - -#endif /* __SUN6I_VIDEO_H__ */ diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c index 30d6c0c5161f..484ac5f054d5 100644 --- a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c @@ -498,6 +498,7 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) struct v4l2_async_notifier *notifier = &bridge->notifier; struct media_pad *pads = bridge->pads; struct device *dev = csi2_dev->dev; + bool notifier_registered = false; int ret; mutex_init(&bridge->lock); @@ -519,8 +520,10 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) /* Media Pads */ - pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; ret = media_entity_pads_init(&subdev->entity, SUN6I_MIPI_CSI2_PAD_COUNT, pads); @@ -533,12 +536,17 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) notifier->ops = &sun6i_mipi_csi2_notifier_ops; ret = sun6i_mipi_csi2_bridge_source_setup(csi2_dev); - if (ret) + if (ret && ret != -ENODEV) goto error_v4l2_notifier_cleanup; - ret = v4l2_async_subdev_nf_register(subdev, notifier); - if (ret < 0) - goto error_v4l2_notifier_cleanup; + /* Only register the notifier when a sensor is connected. */ + if (ret != -ENODEV) { + ret = v4l2_async_subdev_nf_register(subdev, notifier); + if (ret < 0) + goto error_v4l2_notifier_cleanup; + + notifier_registered = true; + } /* V4L2 Subdev */ @@ -549,7 +557,8 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) return 0; error_v4l2_notifier_unregister: - v4l2_async_nf_unregister(notifier); + if (notifier_registered) + v4l2_async_nf_unregister(notifier); error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c index b032ec13a683..d993c09a4820 100644 --- a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c @@ -536,6 +536,7 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) struct v4l2_async_notifier *notifier = &bridge->notifier; struct media_pad *pads = bridge->pads; struct device *dev = csi2_dev->dev; + bool notifier_registered = false; int ret; mutex_init(&bridge->lock); @@ -557,8 +558,10 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) /* Media Pads */ - pads[SUN8I_A83T_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[SUN8I_A83T_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[SUN8I_A83T_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN8I_A83T_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; ret = media_entity_pads_init(&subdev->entity, SUN8I_A83T_MIPI_CSI2_PAD_COUNT, pads); @@ -571,12 +574,17 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) notifier->ops = &sun8i_a83t_mipi_csi2_notifier_ops; ret = sun8i_a83t_mipi_csi2_bridge_source_setup(csi2_dev); - if (ret) + if (ret && ret != -ENODEV) goto error_v4l2_notifier_cleanup; - ret = v4l2_async_subdev_nf_register(subdev, notifier); - if (ret < 0) - goto error_v4l2_notifier_cleanup; + /* Only register the notifier when a sensor is connected. */ + if (ret != -ENODEV) { + ret = v4l2_async_subdev_nf_register(subdev, notifier); + if (ret < 0) + goto error_v4l2_notifier_cleanup; + + notifier_registered = true; + } /* V4L2 Subdev */ @@ -587,7 +595,8 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) return 0; error_v4l2_notifier_unregister: - v4l2_async_nf_unregister(notifier); + if (notifier_registered) + v4l2_async_nf_unregister(notifier); error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); diff --git a/drivers/media/platform/ti/omap3isp/isp.c b/drivers/media/platform/ti/omap3isp/isp.c index 24d2383400b0..1d40bb59ff81 100644 --- a/drivers/media/platform/ti/omap3isp/isp.c +++ b/drivers/media/platform/ti/omap3isp/isp.c @@ -1884,8 +1884,7 @@ static int isp_initialize_modules(struct isp_device *isp) ret = omap3isp_ccp2_init(isp); if (ret < 0) { - if (ret != -EPROBE_DEFER) - dev_err(isp->dev, "CCP2 initialization failed\n"); + dev_err_probe(isp->dev, ret, "CCP2 initialization failed\n"); goto error_ccp2; } diff --git a/drivers/media/platform/xilinx/xilinx-csi2rxss.c b/drivers/media/platform/xilinx/xilinx-csi2rxss.c index 29b53febc2e7..d8a23f18cfbc 100644 --- a/drivers/media/platform/xilinx/xilinx-csi2rxss.c +++ b/drivers/media/platform/xilinx/xilinx-csi2rxss.c @@ -976,11 +976,9 @@ static int xcsi2rxss_probe(struct platform_device *pdev) /* Reset GPIO */ xcsi2rxss->rst_gpio = devm_gpiod_get_optional(dev, "video-reset", GPIOD_OUT_HIGH); - if (IS_ERR(xcsi2rxss->rst_gpio)) { - if (PTR_ERR(xcsi2rxss->rst_gpio) != -EPROBE_DEFER) - dev_err(dev, "Video Reset GPIO not setup in DT"); - return PTR_ERR(xcsi2rxss->rst_gpio); - } + if (IS_ERR(xcsi2rxss->rst_gpio)) + return dev_err_probe(dev, PTR_ERR(xcsi2rxss->rst_gpio), + "Video Reset GPIO not setup in DT\n"); ret = xcsi2rxss_parse_of(xcsi2rxss); if (ret < 0) diff --git a/drivers/media/radio/radio-tea5764.c b/drivers/media/radio/radio-tea5764.c index abda40e81612..2cb74afba49c 100644 --- a/drivers/media/radio/radio-tea5764.c +++ b/drivers/media/radio/radio-tea5764.c @@ -411,8 +411,7 @@ static const struct video_device tea5764_radio_template = { }; /* I2C probe: check if the device exists and register with v4l if it is */ -static int tea5764_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tea5764_i2c_probe(struct i2c_client *client) { struct tea5764_device *radio; struct v4l2_device *v4l2_dev; @@ -512,7 +511,7 @@ static struct i2c_driver tea5764_i2c_driver = { .driver = { .name = "radio-tea5764", }, - .probe = tea5764_i2c_probe, + .probe_new = tea5764_i2c_probe, .remove = tea5764_i2c_remove, .id_table = tea5764_id, }; diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c index 8b8ce2b46a55..621bb8523271 100644 --- a/drivers/media/radio/radio-terratec.c +++ b/drivers/media/radio/radio-terratec.c @@ -82,7 +82,6 @@ static int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) { int i; - int p; int temp; long rest; unsigned char buffer[25]; /* we have to bit shift 25 registers */ @@ -93,7 +92,6 @@ static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) rest = freq * 10 + 10700; /* I once had understood what is going on here */ /* maybe some wise guy (friedhelm?) can comment this stuff */ i = 13; - p = 10; temp = 102400; while (rest != 0) { if (rest % temp == rest) @@ -103,7 +101,6 @@ static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) rest = rest - temp; } i--; - p--; temp = temp / 2; } diff --git a/drivers/media/radio/saa7706h.c b/drivers/media/radio/saa7706h.c index f9e990a9c3ef..3c758a983344 100644 --- a/drivers/media/radio/saa7706h.c +++ b/drivers/media/radio/saa7706h.c @@ -331,8 +331,7 @@ static const struct v4l2_subdev_ops empty_ops = {}; * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int saa7706h_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa7706h_probe(struct i2c_client *client) { struct saa7706h_state *state; struct v4l2_subdev *sd; @@ -406,7 +405,7 @@ static struct i2c_driver saa7706h_driver = { .driver = { .name = DRIVER_NAME, }, - .probe = saa7706h_probe, + .probe_new = saa7706h_probe, .remove = saa7706h_remove, .id_table = saa7706h_id, }; diff --git a/drivers/media/radio/si470x/radio-si470x-usb.c b/drivers/media/radio/si470x/radio-si470x-usb.c index 6b2768623c88..aa7a580dbecc 100644 --- a/drivers/media/radio/si470x/radio-si470x-usb.c +++ b/drivers/media/radio/si470x/radio-si470x-usb.c @@ -727,8 +727,10 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, /* start radio */ retval = si470x_start_usb(radio); - if (retval < 0) + if (retval < 0 && !radio->int_in_running) goto err_buf; + else if (retval < 0) /* in case of radio->int_in_running == 1 */ + goto err_all; /* set initial frequency */ si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */ diff --git a/drivers/media/radio/tef6862.c b/drivers/media/radio/tef6862.c index 7b0870a9785b..d14c97d79e83 100644 --- a/drivers/media/radio/tef6862.c +++ b/drivers/media/radio/tef6862.c @@ -141,8 +141,7 @@ static const struct v4l2_subdev_ops tef6862_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int tef6862_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tef6862_probe(struct i2c_client *client) { struct tef6862_state *state; struct v4l2_subdev *sd; @@ -184,7 +183,7 @@ static struct i2c_driver tef6862_driver = { .driver = { .name = DRIVER_NAME, }, - .probe = tef6862_probe, + .probe_new = tef6862_probe, .remove = tef6862_remove, .id_table = tef6862_id, }; diff --git a/drivers/media/rc/gpio-ir-recv.c b/drivers/media/rc/gpio-ir-recv.c index 22e524b69806..8f1fff7af6c9 100644 --- a/drivers/media/rc/gpio-ir-recv.c +++ b/drivers/media/rc/gpio-ir-recv.c @@ -74,13 +74,9 @@ static int gpio_ir_recv_probe(struct platform_device *pdev) return -ENOMEM; gpio_dev->gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN); - if (IS_ERR(gpio_dev->gpiod)) { - rc = PTR_ERR(gpio_dev->gpiod); - /* Just try again if this happens */ - if (rc != -EPROBE_DEFER) - dev_err(dev, "error getting gpio (%d)\n", rc); - return rc; - } + if (IS_ERR(gpio_dev->gpiod)) + return dev_err_probe(dev, PTR_ERR(gpio_dev->gpiod), + "error getting gpio\n"); gpio_dev->irq = gpiod_to_irq(gpio_dev->gpiod); if (gpio_dev->irq < 0) return gpio_dev->irq; diff --git a/drivers/media/rc/gpio-ir-tx.c b/drivers/media/rc/gpio-ir-tx.c index d3063ddb472e..2b829c146db1 100644 --- a/drivers/media/rc/gpio-ir-tx.c +++ b/drivers/media/rc/gpio-ir-tx.c @@ -174,12 +174,9 @@ static int gpio_ir_tx_probe(struct platform_device *pdev) return -ENOMEM; gpio_ir->gpio = devm_gpiod_get(&pdev->dev, NULL, GPIOD_OUT_LOW); - if (IS_ERR(gpio_ir->gpio)) { - if (PTR_ERR(gpio_ir->gpio) != -EPROBE_DEFER) - dev_err(&pdev->dev, "Failed to get gpio (%ld)\n", - PTR_ERR(gpio_ir->gpio)); - return PTR_ERR(gpio_ir->gpio); - } + if (IS_ERR(gpio_ir->gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(gpio_ir->gpio), + "Failed to get gpio\n"); rcdev->priv = gpio_ir; rcdev->driver_name = DRIVER_NAME; diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c index 5edfd8a9e849..74546f7e3469 100644 --- a/drivers/media/rc/imon.c +++ b/drivers/media/rc/imon.c @@ -646,15 +646,14 @@ static int send_packet(struct imon_context *ictx) pr_err_ratelimited("error submitting urb(%d)\n", retval); } else { /* Wait for transmission to complete (or abort) */ - mutex_unlock(&ictx->lock); retval = wait_for_completion_interruptible( &ictx->tx.finished); if (retval) { usb_kill_urb(ictx->tx_urb); pr_err_ratelimited("task interrupted\n"); } - mutex_lock(&ictx->lock); + ictx->tx.busy = false; retval = ictx->tx.status; if (retval) pr_err_ratelimited("packet tx failed (%d)\n", retval); @@ -953,7 +952,8 @@ static ssize_t vfd_write(struct file *file, const char __user *buf, if (ictx->disconnected) return -ENODEV; - mutex_lock(&ictx->lock); + if (mutex_lock_interruptible(&ictx->lock)) + return -ERESTARTSYS; if (!ictx->dev_present_intf0) { pr_err_ratelimited("no iMON device present\n"); diff --git a/drivers/media/rc/ir-rx51.c b/drivers/media/rc/ir-rx51.c index a3b145183260..85080c3d2053 100644 --- a/drivers/media/rc/ir-rx51.c +++ b/drivers/media/rc/ir-rx51.c @@ -231,13 +231,8 @@ static int ir_rx51_probe(struct platform_device *dev) struct rc_dev *rcdev; pwm = pwm_get(&dev->dev, NULL); - if (IS_ERR(pwm)) { - int err = PTR_ERR(pwm); - - if (err != -EPROBE_DEFER) - dev_err(&dev->dev, "pwm_get failed: %d\n", err); - return err; - } + if (IS_ERR(pwm)) + return dev_err_probe(&dev->dev, PTR_ERR(pwm), "pwm_get failed\n"); /* Use default, in case userspace does not set the carrier */ ir_rx51.freq = DIV_ROUND_CLOSEST_ULL(pwm_get_period(pwm), NSEC_PER_SEC); diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c index 51aa55a84bb5..bbc81bed4f90 100644 --- a/drivers/media/rc/ir-spi.c +++ b/drivers/media/rc/ir-spi.c @@ -158,8 +158,15 @@ static const struct of_device_id ir_spi_of_match[] = { }; MODULE_DEVICE_TABLE(of, ir_spi_of_match); +static const struct spi_device_id ir_spi_ids[] = { + { "ir-spi-led" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, ir_spi_ids); + static struct spi_driver ir_spi_driver = { .probe = ir_spi_probe, + .id_table = ir_spi_ids, .driver = { .name = IR_SPI_DRIVER_NAME, .of_match_table = ir_spi_of_match, diff --git a/drivers/media/test-drivers/Kconfig b/drivers/media/test-drivers/Kconfig index 51cf27834df0..459b433e9fae 100644 --- a/drivers/media/test-drivers/Kconfig +++ b/drivers/media/test-drivers/Kconfig @@ -20,6 +20,7 @@ config VIDEO_VIM2M source "drivers/media/test-drivers/vicodec/Kconfig" source "drivers/media/test-drivers/vimc/Kconfig" source "drivers/media/test-drivers/vivid/Kconfig" +source "drivers/media/test-drivers/visl/Kconfig" endif #V4L_TEST_DRIVERS diff --git a/drivers/media/test-drivers/Makefile b/drivers/media/test-drivers/Makefile index ff390b687189..740714a4584d 100644 --- a/drivers/media/test-drivers/Makefile +++ b/drivers/media/test-drivers/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_VIDEO_VICODEC) += vicodec/ obj-$(CONFIG_VIDEO_VIM2M) += vim2m.o obj-$(CONFIG_VIDEO_VIMC) += vimc/ obj-$(CONFIG_VIDEO_VIVID) += vivid/ +obj-$(CONFIG_VIDEO_VISL) += visl/ diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c index 82620613d56b..dff7265a42ca 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c @@ -459,26 +459,20 @@ fail_dmx_conn: for (j = j - 1; j >= 0; --j) dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->dmx_fe[j]); -fail_dmx_dev: dvb_dmxdev_release(&dvb->dmx_dev); -fail_dmx: +fail_dmx_dev: dvb_dmx_release(&dvb->demux); +fail_dmx: +fail_demod_probe: + for (i = i - 1; i >= 0; --i) { + dvb_unregister_frontend(dvb->fe[i]); fail_fe: - for (j = i; j >= 0; --j) - dvb_unregister_frontend(dvb->fe[j]); + dvb_module_release(dvb->i2c_client_tuner[i]); fail_tuner_probe: - for (j = i; j >= 0; --j) - if (dvb->i2c_client_tuner[j]) - dvb_module_release(dvb->i2c_client_tuner[j]); - -fail_demod_probe: - for (j = i; j >= 0; --j) - if (dvb->i2c_client_demod[j]) - dvb_module_release(dvb->i2c_client_demod[j]); - + dvb_module_release(dvb->i2c_client_demod[i]); + } fail_adapter: dvb_unregister_adapter(&dvb->adapter); - fail_i2c: i2c_del_adapter(&dvb->i2c_adapter); diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c index d60c6d16beea..b878db798686 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_demod.c +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c @@ -412,8 +412,7 @@ static const struct i2c_device_id vidtv_demod_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, vidtv_demod_i2c_id_table); -static int vidtv_demod_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vidtv_demod_i2c_probe(struct i2c_client *client) { struct vidtv_tuner_config *config = client->dev.platform_data; struct vidtv_demod_state *state; @@ -450,7 +449,7 @@ static struct i2c_driver vidtv_demod_i2c_driver = { .name = "dvb_vidtv_demod", .suppress_bind_attrs = true, }, - .probe = vidtv_demod_i2c_probe, + .probe_new = vidtv_demod_i2c_probe, .remove = vidtv_demod_i2c_remove, .id_table = vidtv_demod_i2c_id_table, }; diff --git a/drivers/media/test-drivers/vidtv/vidtv_tuner.c b/drivers/media/test-drivers/vidtv/vidtv_tuner.c index aabc97ed736b..55a4387f3854 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_tuner.c +++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.c @@ -390,8 +390,7 @@ static const struct i2c_device_id vidtv_tuner_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table); -static int vidtv_tuner_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int vidtv_tuner_i2c_probe(struct i2c_client *client) { struct vidtv_tuner_config *config = client->dev.platform_data; struct dvb_frontend *fe = config->fe; @@ -426,7 +425,7 @@ static struct i2c_driver vidtv_tuner_i2c_driver = { .name = "dvb_vidtv_tuner", .suppress_bind_attrs = true, }, - .probe = vidtv_tuner_i2c_probe, + .probe_new = vidtv_tuner_i2c_probe, .remove = vidtv_tuner_i2c_remove, .id_table = vidtv_tuner_i2c_id_table, }; diff --git a/drivers/media/test-drivers/vimc/vimc-core.c b/drivers/media/test-drivers/vimc/vimc-core.c index 2ae7a0f11ebf..e82cfa5ffbf4 100644 --- a/drivers/media/test-drivers/vimc/vimc-core.c +++ b/drivers/media/test-drivers/vimc/vimc-core.c @@ -433,7 +433,7 @@ static int __init vimc_init(void) if (ret) { dev_err(&vimc_pdev.dev, "platform driver registration failed (err=%d)\n", ret); - platform_driver_unregister(&vimc_pdrv); + platform_device_unregister(&vimc_pdev); return ret; } diff --git a/drivers/media/test-drivers/visl/Kconfig b/drivers/media/test-drivers/visl/Kconfig new file mode 100644 index 000000000000..7508b904f196 --- /dev/null +++ b/drivers/media/test-drivers/visl/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0+ +config VIDEO_VISL + tristate "Virtual Stateless Decoder Driver (visl)" + depends on VIDEO_DEV + select FONT_SUPPORT + select FONT_8x16 + select VIDEOBUF2_VMALLOC + select V4L2_MEM2MEM_DEV + select MEDIA_CONTROLLER + select MEDIA_CONTROLLER_REQUEST_API + select VIDEO_V4L2_TPG + help + + A virtual stateless decoder device for uAPI development purposes. + + A userspace implementation can use visl to run a decoding loop even + when no hardware is available or when the kernel uAPI for the codec + has not been upstreamed yet. This can reveal bugs at an early stage. + + When in doubt, say N. + +config VISL_DEBUGFS + bool "Enable debugfs for visl" + depends on VIDEO_VISL + depends on DEBUG_FS + + help + Choose Y to dump the bitstream buffers through debugfs. + When in doubt, say N. diff --git a/drivers/media/test-drivers/visl/Makefile b/drivers/media/test-drivers/visl/Makefile new file mode 100644 index 000000000000..fb4d5ae1b17f --- /dev/null +++ b/drivers/media/test-drivers/visl/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0+ +visl-y := visl-core.o visl-video.o visl-dec.o visl-trace-points.o + +ifeq ($(CONFIG_VISL_DEBUGFS),y) + visl-y += visl-debugfs.o +endif + +obj-$(CONFIG_VIDEO_VISL) += visl.o diff --git a/drivers/media/test-drivers/visl/visl-core.c b/drivers/media/test-drivers/visl/visl-core.c new file mode 100644 index 000000000000..9cb60ab653bf --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-core.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A virtual stateless decoder device for stateless uAPI development purposes. + * + * This tool's objective is to help the development and testing of userspace + * applications that use the V4L2 stateless API to decode media. + * + * A userspace implementation can use visl to run a decoding loop even when no + * hardware is available or when the kernel uAPI for the codec has not been + * upstreamed yet. This can reveal bugs at an early stage. + * + * This driver can also trace the contents of the V4L2 controls submitted to it. + * It can also dump the contents of the vb2 buffers through a debugfs + * interface. This is in many ways similar to the tracing infrastructure + * available for other popular encode/decode APIs out there and can help develop + * a userspace application by using another (working) one as a reference. + * + * Note that no actual decoding of video frames is performed by visl. The V4L2 + * test pattern generator is used to write various debug information to the + * capture buffers instead. + * + * Copyright (C) 2022 Collabora, Ltd. + * + * Based on the vim2m driver, that is: + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, <pawel@osciak.com> + * Marek Szyprowski, <m.szyprowski@samsung.com> + * + * Based on the vicodec driver, that is: + * + * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mem2mem.h> + +#include "visl.h" +#include "visl-dec.h" +#include "visl-debugfs.h" +#include "visl-video.h" + +unsigned int visl_debug; +module_param(visl_debug, uint, 0644); +MODULE_PARM_DESC(visl_debug, " activates debug info"); + +unsigned int visl_transtime_ms; +module_param(visl_transtime_ms, uint, 0644); +MODULE_PARM_DESC(visl_transtime_ms, " simulated process time in milliseconds."); + +/* + * dprintk can be slow through serial. This lets one limit the tracing to a + * particular number of frames + */ +int visl_dprintk_frame_start = -1; +module_param(visl_dprintk_frame_start, int, 0); +MODULE_PARM_DESC(visl_dprintk_frame_start, + " a frame number to start tracing with dprintk"); + +unsigned int visl_dprintk_nframes; +module_param(visl_dprintk_nframes, uint, 0); +MODULE_PARM_DESC(visl_dprintk_nframes, + " the number of frames to trace with dprintk"); + +bool keep_bitstream_buffers; +module_param(keep_bitstream_buffers, bool, false); +MODULE_PARM_DESC(keep_bitstream_buffers, + " keep bitstream buffers in debugfs after streaming is stopped"); + +int bitstream_trace_frame_start = -1; +module_param(bitstream_trace_frame_start, int, 0); +MODULE_PARM_DESC(bitstream_trace_frame_start, + " a frame number to start dumping the bitstream through debugfs"); + +unsigned int bitstream_trace_nframes; +module_param(bitstream_trace_nframes, uint, 0); +MODULE_PARM_DESC(bitstream_trace_nframes, + " the number of frames to dump the bitstream through debugfs"); + +static const struct visl_ctrl_desc visl_fwht_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_FWHT_PARAMS, + }, +}; + +const struct visl_ctrls visl_fwht_ctrls = { + .ctrls = visl_fwht_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_fwht_ctrl_descs) +}; + +static const struct visl_ctrl_desc visl_mpeg2_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_MPEG2_SEQUENCE, + }, + { + .cfg.id = V4L2_CID_STATELESS_MPEG2_PICTURE, + }, + { + .cfg.id = V4L2_CID_STATELESS_MPEG2_QUANTISATION, + }, +}; + +const struct visl_ctrls visl_mpeg2_ctrls = { + .ctrls = visl_mpeg2_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_mpeg2_ctrl_descs), +}; + +static const struct visl_ctrl_desc visl_vp8_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_VP8_FRAME, + }, +}; + +const struct visl_ctrls visl_vp8_ctrls = { + .ctrls = visl_vp8_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_vp8_ctrl_descs), +}; + +static const struct visl_ctrl_desc visl_vp9_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_VP9_FRAME, + }, + { + .cfg.id = V4L2_CID_STATELESS_VP9_COMPRESSED_HDR, + }, +}; + +const struct visl_ctrls visl_vp9_ctrls = { + .ctrls = visl_vp9_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_vp9_ctrl_descs), +}; + +static const struct visl_ctrl_desc visl_h264_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_SPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_PPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_DECODE_MODE, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_START_CODE, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_SLICE_PARAMS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_PRED_WEIGHTS, + }, +}; + +const struct visl_ctrls visl_h264_ctrls = { + .ctrls = visl_h264_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_h264_ctrl_descs), +}; + +static const struct visl_ctrl_desc visl_hevc_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_HEVC_SPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_PPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_SLICE_PARAMS, + /* The absolute maximum for level > 6 */ + .cfg.dims = { 600 }, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_MODE, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_START_CODE, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS, + .cfg.dims = { 256 }, + .cfg.max = 0xffffffff, + .cfg.step = 1, + }, + +}; + +const struct visl_ctrls visl_hevc_ctrls = { + .ctrls = visl_hevc_ctrl_descs, + .num_ctrls = ARRAY_SIZE(visl_hevc_ctrl_descs), +}; + +struct v4l2_ctrl *visl_find_control(struct visl_ctx *ctx, u32 id) +{ + struct v4l2_ctrl_handler *hdl = &ctx->hdl; + + return v4l2_ctrl_find(hdl, id); +} + +void *visl_find_control_data(struct visl_ctx *ctx, u32 id) +{ + struct v4l2_ctrl *ctrl; + + ctrl = visl_find_control(ctx, id); + if (ctrl) + return ctrl->p_cur.p; + + return NULL; +} + +u32 visl_control_num_elems(struct visl_ctx *ctx, u32 id) +{ + struct v4l2_ctrl *ctrl; + + ctrl = visl_find_control(ctx, id); + if (ctrl) + return ctrl->elems; + + return 0; +} + +static void visl_device_release(struct video_device *vdev) +{ + struct visl_dev *dev = container_of(vdev, struct visl_dev, vfd); + + v4l2_device_unregister(&dev->v4l2_dev); + v4l2_m2m_release(dev->m2m_dev); + media_device_cleanup(&dev->mdev); + visl_debugfs_deinit(dev); + kfree(dev); +} + +#define VISL_CONTROLS_COUNT ARRAY_SIZE(visl_controls) + +static int visl_init_ctrls(struct visl_ctx *ctx) +{ + struct visl_dev *dev = ctx->dev; + struct v4l2_ctrl_handler *hdl = &ctx->hdl; + unsigned int ctrl_cnt = 0; + unsigned int i; + unsigned int j; + const struct visl_ctrls *ctrls; + + for (i = 0; i < num_coded_fmts; i++) + ctrl_cnt += visl_coded_fmts[i].ctrls->num_ctrls; + + v4l2_ctrl_handler_init(hdl, ctrl_cnt); + + for (i = 0; i < num_coded_fmts; i++) { + ctrls = visl_coded_fmts[i].ctrls; + for (j = 0; j < ctrls->num_ctrls; j++) + v4l2_ctrl_new_custom(hdl, &ctrls->ctrls[j].cfg, NULL); + } + + if (hdl->error) { + v4l2_err(&dev->v4l2_dev, + "Failed to initialize control handler\n"); + v4l2_ctrl_handler_free(hdl); + return hdl->error; + } + + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + return 0; +} + +static int visl_open(struct file *file) +{ + struct visl_dev *dev = video_drvdata(file); + struct visl_ctx *ctx = NULL; + int rc = 0; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + rc = -ENOMEM; + goto unlock; + } + + ctx->tpg_str_buf = kzalloc(TPG_STR_BUF_SZ, GFP_KERNEL); + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + + rc = visl_init_ctrls(ctx); + if (rc) + goto free_ctx; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &visl_queue_init); + + mutex_init(&ctx->vb_mutex); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc = PTR_ERR(ctx->fh.m2m_ctx); + goto free_hdl; + } + + rc = visl_set_default_format(ctx); + if (rc) + goto free_m2m_ctx; + + v4l2_fh_add(&ctx->fh); + + dprintk(dev, "Created instance: %p, m2m_ctx: %p\n", + ctx, ctx->fh.m2m_ctx); + + mutex_unlock(&dev->dev_mutex); + return rc; + +free_m2m_ctx: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); +free_hdl: + v4l2_ctrl_handler_free(&ctx->hdl); + v4l2_fh_exit(&ctx->fh); +free_ctx: + kfree(ctx->tpg_str_buf); + kfree(ctx); +unlock: + mutex_unlock(&dev->dev_mutex); + return rc; +} + +static int visl_release(struct file *file) +{ + struct visl_dev *dev = video_drvdata(file); + struct visl_ctx *ctx = visl_file_to_ctx(file); + + dprintk(dev, "Releasing instance %p\n", ctx); + + tpg_free(&ctx->tpg); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(&dev->dev_mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&dev->dev_mutex); + + kfree(ctx->tpg_str_buf); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations visl_fops = { + .owner = THIS_MODULE, + .open = visl_open, + .release = visl_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device visl_videodev = { + .name = VISL_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &visl_fops, + .ioctl_ops = &visl_ioctl_ops, + .minor = -1, + .release = visl_device_release, + .device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops visl_m2m_ops = { + .device_run = visl_device_run, +}; + +static const struct media_device_ops visl_m2m_media_ops = { + .req_validate = visl_request_validate, + .req_queue = v4l2_m2m_request_queue, +}; + +static int visl_probe(struct platform_device *pdev) +{ + struct visl_dev *dev; + struct video_device *vfd; + int ret; + int rc; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto error_visl_dev; + + mutex_init(&dev->dev_mutex); + + dev->vfd = visl_videodev; + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + video_set_drvdata(vfd, dev); + + platform_set_drvdata(pdev, dev); + + dev->m2m_dev = v4l2_m2m_init(&visl_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + dev->m2m_dev = NULL; + goto error_dev; + } + + dev->mdev.dev = &pdev->dev; + strscpy(dev->mdev.model, "visl", sizeof(dev->mdev.model)); + strscpy(dev->mdev.bus_info, "platform:visl", + sizeof(dev->mdev.bus_info)); + media_device_init(&dev->mdev); + dev->mdev.ops = &visl_m2m_media_ops; + dev->v4l2_dev.mdev = &dev->mdev; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto error_m2m; + } + + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, + MEDIA_ENT_F_PROC_VIDEO_DECODER); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); + goto error_v4l2; + } + + ret = media_device_register(&dev->mdev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register mem2mem media device\n"); + goto error_m2m_mc; + } + + rc = visl_debugfs_init(dev); + if (rc) + dprintk(dev, "visl_debugfs_init failed: %d\n" + "Continuing without debugfs support\n", rc); + + return 0; + +error_m2m_mc: + v4l2_m2m_unregister_media_controller(dev->m2m_dev); +error_v4l2: + video_unregister_device(&dev->vfd); + /* visl_device_release called by video_unregister_device to release various objects */ + return ret; +error_m2m: + v4l2_m2m_release(dev->m2m_dev); +error_dev: + v4l2_device_unregister(&dev->v4l2_dev); +error_visl_dev: + kfree(dev); + + return ret; +} + +static int visl_remove(struct platform_device *pdev) +{ + struct visl_dev *dev = platform_get_drvdata(pdev); + + v4l2_info(&dev->v4l2_dev, "Removing " VISL_NAME); + +#ifdef CONFIG_MEDIA_CONTROLLER + if (media_devnode_is_registered(dev->mdev.devnode)) { + media_device_unregister(&dev->mdev); + v4l2_m2m_unregister_media_controller(dev->m2m_dev); + } +#endif + video_unregister_device(&dev->vfd); + + return 0; +} + +static struct platform_driver visl_pdrv = { + .probe = visl_probe, + .remove = visl_remove, + .driver = { + .name = VISL_NAME, + }, +}; + +static void visl_dev_release(struct device *dev) {} + +static struct platform_device visl_pdev = { + .name = VISL_NAME, + .dev.release = visl_dev_release, +}; + +static void __exit visl_exit(void) +{ + platform_driver_unregister(&visl_pdrv); + platform_device_unregister(&visl_pdev); +} + +static int __init visl_init(void) +{ + int ret; + + ret = platform_device_register(&visl_pdev); + if (ret) + return ret; + + ret = platform_driver_register(&visl_pdrv); + if (ret) + platform_device_unregister(&visl_pdev); + + return ret; +} + +MODULE_DESCRIPTION("Virtual stateless decoder device"); +MODULE_AUTHOR("Daniel Almeida <daniel.almeida@collabora.com>"); +MODULE_LICENSE("GPL"); + +module_init(visl_init); +module_exit(visl_exit); diff --git a/drivers/media/test-drivers/visl/visl-debugfs.c b/drivers/media/test-drivers/visl/visl-debugfs.c new file mode 100644 index 000000000000..45f2a8268014 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-debugfs.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Debugfs tracing for bitstream buffers. This is similar to VA-API's + * LIBVA_TRACE_BUFDATA in that the raw bitstream can be dumped as a debugging + * aid. + * + * Produces one file per OUTPUT buffer. Files are automatically cleared on + * STREAMOFF unless the module parameter "keep_bitstream_buffers" is set. + */ + +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <media/v4l2-mem2mem.h> + +#include "visl-debugfs.h" + +int visl_debugfs_init(struct visl_dev *dev) +{ + dev->debugfs_root = debugfs_create_dir("visl", NULL); + INIT_LIST_HEAD(&dev->bitstream_blobs); + mutex_init(&dev->bitstream_lock); + + if (IS_ERR(dev->debugfs_root)) + return PTR_ERR(dev->debugfs_root); + + return visl_debugfs_bitstream_init(dev); +} + +int visl_debugfs_bitstream_init(struct visl_dev *dev) +{ + dev->bitstream_debugfs = debugfs_create_dir("bitstream", + dev->debugfs_root); + if (IS_ERR(dev->bitstream_debugfs)) + return PTR_ERR(dev->bitstream_debugfs); + + return 0; +} + +void visl_trace_bitstream(struct visl_ctx *ctx, struct visl_run *run) +{ + u8 *vaddr = vb2_plane_vaddr(&run->src->vb2_buf, 0); + struct visl_blob *blob; + size_t data_sz = vb2_get_plane_payload(&run->src->vb2_buf, 0); + struct dentry *dentry; + char name[32]; + + blob = kzalloc(sizeof(*blob), GFP_KERNEL); + if (!blob) + return; + + blob->blob.data = vzalloc(data_sz); + if (!blob->blob.data) + goto err_vmalloc; + + blob->blob.size = data_sz; + snprintf(name, 32, "bitstream%d", run->src->sequence); + + memcpy(blob->blob.data, vaddr, data_sz); + + dentry = debugfs_create_blob(name, 0444, ctx->dev->bitstream_debugfs, + &blob->blob); + if (IS_ERR(dentry)) + goto err_debugfs; + + blob->dentry = dentry; + + mutex_lock(&ctx->dev->bitstream_lock); + list_add_tail(&blob->list, &ctx->dev->bitstream_blobs); + mutex_unlock(&ctx->dev->bitstream_lock); + + return; + +err_debugfs: + vfree(blob->blob.data); +err_vmalloc: + kfree(blob); +} + +void visl_debugfs_clear_bitstream(struct visl_dev *dev) +{ + struct visl_blob *blob; + struct visl_blob *tmp; + + mutex_lock(&dev->bitstream_lock); + if (list_empty(&dev->bitstream_blobs)) + goto unlock; + + list_for_each_entry_safe(blob, tmp, &dev->bitstream_blobs, list) { + list_del(&blob->list); + debugfs_remove(blob->dentry); + vfree(blob->blob.data); + kfree(blob); + } + +unlock: + mutex_unlock(&dev->bitstream_lock); +} + +void visl_debugfs_bitstream_deinit(struct visl_dev *dev) +{ + visl_debugfs_clear_bitstream(dev); + debugfs_remove_recursive(dev->bitstream_debugfs); + dev->bitstream_debugfs = NULL; +} + +void visl_debugfs_deinit(struct visl_dev *dev) +{ + visl_debugfs_bitstream_deinit(dev); + debugfs_remove_recursive(dev->debugfs_root); + dev->debugfs_root = NULL; +} diff --git a/drivers/media/test-drivers/visl/visl-debugfs.h b/drivers/media/test-drivers/visl/visl-debugfs.h new file mode 100644 index 000000000000..81508f611918 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-debugfs.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Debugfs tracing for bitstream buffers. This is similar to VA-API's + * LIBVA_TRACE_BUFDATA in that the raw bitstream can be dumped as a debugging + * aid. + * + * Produces one file per OUTPUT buffer. Files are automatically cleared on + * STREAMOFF unless the module parameter "keep_bitstream_buffers" is set. + */ + +#include "visl.h" +#include "visl-dec.h" + +#ifdef CONFIG_VISL_DEBUGFS + +int visl_debugfs_init(struct visl_dev *dev); +int visl_debugfs_bitstream_init(struct visl_dev *dev); +void visl_trace_bitstream(struct visl_ctx *ctx, struct visl_run *run); +void visl_debugfs_clear_bitstream(struct visl_dev *dev); +void visl_debugfs_bitstream_deinit(struct visl_dev *dev); +void visl_debugfs_deinit(struct visl_dev *dev); + +#else + +static inline int visl_debugfs_init(struct visl_dev *dev) +{ + return 0; +} + +static inline int visl_debugfs_bitstream_init(struct visl_dev *dev) +{ + return 0; +} + +static inline void visl_trace_bitstream(struct visl_ctx *ctx, struct visl_run *run) {} +static inline void visl_debugfs_clear_bitstream(struct visl_dev *dev) {} +static inline void visl_debugfs_bitstream_deinit(struct visl_dev *dev) {} +static inline void visl_debugfs_deinit(struct visl_dev *dev) {} + +#endif diff --git a/drivers/media/test-drivers/visl/visl-dec.c b/drivers/media/test-drivers/visl/visl-dec.c new file mode 100644 index 000000000000..318d675e5668 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-dec.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Contains the virtual decoder logic. The functions here control the + * tracing/TPG on a per-frame basis + */ + +#include "visl.h" +#include "visl-debugfs.h" +#include "visl-dec.h" +#include "visl-trace-fwht.h" +#include "visl-trace-mpeg2.h" +#include "visl-trace-vp8.h" +#include "visl-trace-vp9.h" +#include "visl-trace-h264.h" +#include "visl-trace-hevc.h" + +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <media/v4l2-mem2mem.h> +#include <media/tpg/v4l2-tpg.h> + +static void *plane_vaddr(struct tpg_data *tpg, struct vb2_buffer *buf, + u32 p, u32 bpl[TPG_MAX_PLANES], u32 h) +{ + u32 i; + void *vbuf; + + if (p == 0 || tpg_g_buffers(tpg) > 1) + return vb2_plane_vaddr(buf, p); + vbuf = vb2_plane_vaddr(buf, 0); + for (i = 0; i < p; i++) + vbuf += bpl[i] * h / tpg->vdownsampling[i]; + return vbuf; +} + +static void visl_get_ref_frames(struct visl_ctx *ctx, u8 *buf, + __kernel_size_t buflen, struct visl_run *run) +{ + struct vb2_queue *cap_q = &ctx->fh.m2m_ctx->cap_q_ctx.q; + char header[] = "Reference frames:\n"; + u32 i; + u32 len; + + len = scnprintf(buf, buflen, header); + buf += len; + buflen -= len; + + switch (ctx->current_codec) { + case VISL_CODEC_NONE: + break; + + case VISL_CODEC_FWHT: { + struct vb2_buffer *vb2_buf; + + vb2_buf = vb2_find_buffer(cap_q, run->fwht.params->backward_ref_ts); + + scnprintf(buf, buflen, "backwards_ref_ts: %lld, vb2_idx: %d", + run->fwht.params->backward_ref_ts, + vb2_buf ? vb2_buf->index : -1); + break; + } + + case VISL_CODEC_MPEG2: { + struct vb2_buffer *b_ref; + struct vb2_buffer *f_ref; + + b_ref = vb2_find_buffer(cap_q, run->mpeg2.pic->backward_ref_ts); + f_ref = vb2_find_buffer(cap_q, run->mpeg2.pic->forward_ref_ts); + + scnprintf(buf, buflen, + "backward_ref_ts: %llu, vb2_idx: %d\n" + "forward_ref_ts: %llu, vb2_idx: %d\n", + run->mpeg2.pic->backward_ref_ts, + b_ref ? b_ref->index : -1, + run->mpeg2.pic->forward_ref_ts, + f_ref ? f_ref->index : -1); + break; + } + + case VISL_CODEC_VP8: { + struct vb2_buffer *last; + struct vb2_buffer *golden; + struct vb2_buffer *alt; + + last = vb2_find_buffer(cap_q, run->vp8.frame->last_frame_ts); + golden = vb2_find_buffer(cap_q, run->vp8.frame->golden_frame_ts); + alt = vb2_find_buffer(cap_q, run->vp8.frame->alt_frame_ts); + + scnprintf(buf, buflen, + "last_ref_ts: %llu, vb2_idx: %d\n" + "golden_ref_ts: %llu, vb2_idx: %d\n" + "alt_ref_ts: %llu, vb2_idx: %d\n", + run->vp8.frame->last_frame_ts, + last ? last->index : -1, + run->vp8.frame->golden_frame_ts, + golden ? golden->index : -1, + run->vp8.frame->alt_frame_ts, + alt ? alt->index : -1); + break; + } + + case VISL_CODEC_VP9: { + struct vb2_buffer *last; + struct vb2_buffer *golden; + struct vb2_buffer *alt; + + last = vb2_find_buffer(cap_q, run->vp9.frame->last_frame_ts); + golden = vb2_find_buffer(cap_q, run->vp9.frame->golden_frame_ts); + alt = vb2_find_buffer(cap_q, run->vp9.frame->alt_frame_ts); + + scnprintf(buf, buflen, + "last_ref_ts: %llu, vb2_idx: %d\n" + "golden_ref_ts: %llu, vb2_idx: %d\n" + "alt_ref_ts: %llu, vb2_idx: %d\n", + run->vp9.frame->last_frame_ts, + last ? last->index : -1, + run->vp9.frame->golden_frame_ts, + golden ? golden->index : -1, + run->vp9.frame->alt_frame_ts, + alt ? alt->index : -1); + break; + } + + case VISL_CODEC_H264: { + char entry[] = "dpb[%d]:%u, vb2_index: %d\n"; + struct vb2_buffer *vb2_buf; + + for (i = 0; i < ARRAY_SIZE(run->h264.dpram->dpb); i++) { + vb2_buf = vb2_find_buffer(cap_q, run->h264.dpram->dpb[i].reference_ts); + len = scnprintf(buf, buflen, entry, i, + run->h264.dpram->dpb[i].reference_ts, + vb2_buf ? vb2_buf->index : -1); + buf += len; + buflen -= len; + } + + break; + } + + case VISL_CODEC_HEVC: { + char entry[] = "dpb[%d]:%u, vb2_index: %d\n"; + struct vb2_buffer *vb2_buf; + + for (i = 0; i < ARRAY_SIZE(run->hevc.dpram->dpb); i++) { + vb2_buf = vb2_find_buffer(cap_q, run->hevc.dpram->dpb[i].timestamp); + len = scnprintf(buf, buflen, entry, i, + run->hevc.dpram->dpb[i].timestamp, + vb2_buf ? vb2_buf->index : -1); + buf += len; + buflen -= len; + } + + break; + } + } +} + +static char *visl_get_vb2_state(enum vb2_buffer_state state) +{ + switch (state) { + case VB2_BUF_STATE_DEQUEUED: + return "Dequeued"; + case VB2_BUF_STATE_IN_REQUEST: + return "In request"; + case VB2_BUF_STATE_PREPARING: + return "Preparing"; + case VB2_BUF_STATE_QUEUED: + return "Queued"; + case VB2_BUF_STATE_ACTIVE: + return "Active"; + case VB2_BUF_STATE_DONE: + return "Done"; + case VB2_BUF_STATE_ERROR: + return "Error"; + default: + return ""; + } +} + +static int visl_fill_bytesused(struct vb2_v4l2_buffer *v4l2_vb2_buf, char *buf, size_t bufsz) +{ + int len = 0; + u32 i; + + for (i = 0; i < v4l2_vb2_buf->vb2_buf.num_planes; i++) + len += scnprintf(buf, bufsz, + "bytesused[%u]: %u length[%u]: %u data_offset[%u]: %u", + i, v4l2_vb2_buf->planes[i].bytesused, + i, v4l2_vb2_buf->planes[i].length, + i, v4l2_vb2_buf->planes[i].data_offset); + + return len; +} + +static void visl_tpg_fill_sequence(struct visl_ctx *ctx, + struct visl_run *run, char buf[], size_t bufsz) +{ + u32 stream_ms; + + stream_ms = jiffies_to_msecs(get_jiffies_64() - ctx->capture_streamon_jiffies); + + scnprintf(buf, bufsz, + "stream time: %02d:%02d:%02d:%03d sequence:%u timestamp:%lld field:%s", + (stream_ms / (60 * 60 * 1000)) % 24, + (stream_ms / (60 * 1000)) % 60, + (stream_ms / 1000) % 60, + stream_ms % 1000, + run->dst->sequence, + run->dst->vb2_buf.timestamp, + (run->dst->field == V4L2_FIELD_ALTERNATE) ? + (run->dst->field == V4L2_FIELD_TOP ? + " top" : " bottom") : "none"); +} + +static void visl_tpg_fill(struct visl_ctx *ctx, struct visl_run *run) +{ + u8 *basep[TPG_MAX_PLANES][2]; + char *buf = ctx->tpg_str_buf; + char *tmp = buf; + char *line_str; + u32 line = 1; + const u32 line_height = 16; + u32 len; + struct vb2_queue *out_q = &ctx->fh.m2m_ctx->out_q_ctx.q; + struct vb2_queue *cap_q = &ctx->fh.m2m_ctx->cap_q_ctx.q; + struct v4l2_pix_format_mplane *coded_fmt = &ctx->coded_fmt.fmt.pix_mp; + struct v4l2_pix_format_mplane *decoded_fmt = &ctx->decoded_fmt.fmt.pix_mp; + u32 p; + u32 i; + + for (p = 0; p < tpg_g_planes(&ctx->tpg); p++) { + void *vbuf = plane_vaddr(&ctx->tpg, + &run->dst->vb2_buf, p, + ctx->tpg.bytesperline, + ctx->tpg.buf_height); + + tpg_calc_text_basep(&ctx->tpg, basep, p, vbuf); + tpg_fill_plane_buffer(&ctx->tpg, 0, p, vbuf); + } + + visl_tpg_fill_sequence(ctx, run, buf, TPG_STR_BUF_SZ); + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + frame_dprintk(ctx->dev, run->dst->sequence, ""); + line++; + + visl_get_ref_frames(ctx, buf, TPG_STR_BUF_SZ, run); + + while ((line_str = strsep(&tmp, "\n")) && strlen(line_str)) { + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, line_str); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", line_str); + } + + frame_dprintk(ctx->dev, run->dst->sequence, ""); + line++; + + scnprintf(buf, + TPG_STR_BUF_SZ, + "OUTPUT pixelformat: %c%c%c%c, resolution: %dx%d, num_planes: %d", + coded_fmt->pixelformat, + (coded_fmt->pixelformat >> 8) & 0xff, + (coded_fmt->pixelformat >> 16) & 0xff, + (coded_fmt->pixelformat >> 24) & 0xff, + coded_fmt->width, + coded_fmt->height, + coded_fmt->num_planes); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + + for (i = 0; i < coded_fmt->num_planes; i++) { + scnprintf(buf, + TPG_STR_BUF_SZ, + "plane[%d]: bytesperline: %d, sizeimage: %d", + i, + coded_fmt->plane_fmt[i].bytesperline, + coded_fmt->plane_fmt[i].sizeimage); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + } + + line++; + frame_dprintk(ctx->dev, run->dst->sequence, ""); + scnprintf(buf, TPG_STR_BUF_SZ, "Output queue status:"); + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + + len = 0; + for (i = 0; i < out_q->num_buffers; i++) { + char entry[] = "index: %u, state: %s, request_fd: %d, "; + u32 old_len = len; + char *q_status = visl_get_vb2_state(out_q->bufs[i]->state); + + len += scnprintf(&buf[len], TPG_STR_BUF_SZ - len, + entry, i, q_status, + to_vb2_v4l2_buffer(out_q->bufs[i])->request_fd); + + len += visl_fill_bytesused(to_vb2_v4l2_buffer(out_q->bufs[i]), + &buf[len], + TPG_STR_BUF_SZ - len); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, &buf[old_len]); + frame_dprintk(ctx->dev, run->dst->sequence, "%s", &buf[old_len]); + } + + line++; + frame_dprintk(ctx->dev, run->dst->sequence, ""); + + scnprintf(buf, + TPG_STR_BUF_SZ, + "CAPTURE pixelformat: %c%c%c%c, resolution: %dx%d, num_planes: %d", + decoded_fmt->pixelformat, + (decoded_fmt->pixelformat >> 8) & 0xff, + (decoded_fmt->pixelformat >> 16) & 0xff, + (decoded_fmt->pixelformat >> 24) & 0xff, + decoded_fmt->width, + decoded_fmt->height, + decoded_fmt->num_planes); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + + for (i = 0; i < decoded_fmt->num_planes; i++) { + scnprintf(buf, + TPG_STR_BUF_SZ, + "plane[%d]: bytesperline: %d, sizeimage: %d", + i, + decoded_fmt->plane_fmt[i].bytesperline, + decoded_fmt->plane_fmt[i].sizeimage); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + } + + line++; + frame_dprintk(ctx->dev, run->dst->sequence, ""); + scnprintf(buf, TPG_STR_BUF_SZ, "Capture queue status:"); + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, buf); + frame_dprintk(ctx->dev, run->dst->sequence, "%s\n", buf); + + len = 0; + for (i = 0; i < cap_q->num_buffers; i++) { + u32 old_len = len; + char *q_status = visl_get_vb2_state(cap_q->bufs[i]->state); + + len += scnprintf(&buf[len], TPG_STR_BUF_SZ - len, + "index: %u, status: %s, timestamp: %llu, is_held: %d", + cap_q->bufs[i]->index, q_status, + cap_q->bufs[i]->timestamp, + to_vb2_v4l2_buffer(cap_q->bufs[i])->is_held); + + tpg_gen_text(&ctx->tpg, basep, line++ * line_height, 16, &buf[old_len]); + frame_dprintk(ctx->dev, run->dst->sequence, "%s", &buf[old_len]); + } +} + +static void visl_trace_ctrls(struct visl_ctx *ctx, struct visl_run *run) +{ + int i; + + switch (ctx->current_codec) { + default: + case VISL_CODEC_NONE: + break; + case VISL_CODEC_FWHT: + trace_v4l2_ctrl_fwht_params(run->fwht.params); + break; + case VISL_CODEC_MPEG2: + trace_v4l2_ctrl_mpeg2_sequence(run->mpeg2.seq); + trace_v4l2_ctrl_mpeg2_picture(run->mpeg2.pic); + trace_v4l2_ctrl_mpeg2_quantisation(run->mpeg2.quant); + break; + case VISL_CODEC_VP8: + trace_v4l2_ctrl_vp8_frame(run->vp8.frame); + trace_v4l2_ctrl_vp8_entropy(run->vp8.frame); + break; + case VISL_CODEC_VP9: + trace_v4l2_ctrl_vp9_frame(run->vp9.frame); + trace_v4l2_ctrl_vp9_compressed_hdr(run->vp9.probs); + trace_v4l2_ctrl_vp9_compressed_coeff(run->vp9.probs); + trace_v4l2_vp9_mv_probs(&run->vp9.probs->mv); + break; + case VISL_CODEC_H264: + trace_v4l2_ctrl_h264_sps(run->h264.sps); + trace_v4l2_ctrl_h264_pps(run->h264.pps); + trace_v4l2_ctrl_h264_scaling_matrix(run->h264.sm); + trace_v4l2_ctrl_h264_slice_params(run->h264.spram); + + for (i = 0; i < ARRAY_SIZE(run->h264.spram->ref_pic_list0); i++) + trace_v4l2_h264_ref_pic_list0(&run->h264.spram->ref_pic_list0[i], i); + for (i = 0; i < ARRAY_SIZE(run->h264.spram->ref_pic_list0); i++) + trace_v4l2_h264_ref_pic_list1(&run->h264.spram->ref_pic_list1[i], i); + + trace_v4l2_ctrl_h264_decode_params(run->h264.dpram); + + for (i = 0; i < ARRAY_SIZE(run->h264.dpram->dpb); i++) + trace_v4l2_h264_dpb_entry(&run->h264.dpram->dpb[i], i); + + trace_v4l2_ctrl_h264_pred_weights(run->h264.pwht); + break; + case VISL_CODEC_HEVC: + trace_v4l2_ctrl_hevc_sps(run->hevc.sps); + trace_v4l2_ctrl_hevc_pps(run->hevc.pps); + trace_v4l2_ctrl_hevc_slice_params(run->hevc.spram); + trace_v4l2_ctrl_hevc_scaling_matrix(run->hevc.sm); + trace_v4l2_ctrl_hevc_decode_params(run->hevc.dpram); + + for (i = 0; i < ARRAY_SIZE(run->hevc.dpram->dpb); i++) + trace_v4l2_hevc_dpb_entry(&run->hevc.dpram->dpb[i]); + + trace_v4l2_hevc_pred_weight_table(&run->hevc.spram->pred_weight_table); + break; + } +} + +void visl_device_run(void *priv) +{ + struct visl_ctx *ctx = priv; + struct visl_run run = {}; + struct media_request *src_req; + + run.src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + run.dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + /* Apply request(s) controls if needed. */ + src_req = run.src->vb2_buf.req_obj.req; + + if (src_req) + v4l2_ctrl_request_setup(src_req, &ctx->hdl); + + v4l2_m2m_buf_copy_metadata(run.src, run.dst, true); + run.dst->sequence = ctx->q_data[V4L2_M2M_DST].sequence++; + run.src->sequence = ctx->q_data[V4L2_M2M_SRC].sequence++; + run.dst->field = ctx->decoded_fmt.fmt.pix.field; + + switch (ctx->current_codec) { + default: + case VISL_CODEC_NONE: + break; + case VISL_CODEC_FWHT: + run.fwht.params = visl_find_control_data(ctx, V4L2_CID_STATELESS_FWHT_PARAMS); + break; + case VISL_CODEC_MPEG2: + run.mpeg2.seq = visl_find_control_data(ctx, V4L2_CID_STATELESS_MPEG2_SEQUENCE); + run.mpeg2.pic = visl_find_control_data(ctx, V4L2_CID_STATELESS_MPEG2_PICTURE); + run.mpeg2.quant = visl_find_control_data(ctx, + V4L2_CID_STATELESS_MPEG2_QUANTISATION); + break; + case VISL_CODEC_VP8: + run.vp8.frame = visl_find_control_data(ctx, V4L2_CID_STATELESS_VP8_FRAME); + break; + case VISL_CODEC_VP9: + run.vp9.frame = visl_find_control_data(ctx, V4L2_CID_STATELESS_VP9_FRAME); + run.vp9.probs = visl_find_control_data(ctx, V4L2_CID_STATELESS_VP9_COMPRESSED_HDR); + break; + case VISL_CODEC_H264: + run.h264.sps = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_SPS); + run.h264.pps = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_PPS); + run.h264.sm = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_SCALING_MATRIX); + run.h264.spram = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_SLICE_PARAMS); + run.h264.dpram = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_DECODE_PARAMS); + run.h264.pwht = visl_find_control_data(ctx, V4L2_CID_STATELESS_H264_PRED_WEIGHTS); + break; + case VISL_CODEC_HEVC: + run.hevc.sps = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS); + run.hevc.pps = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_PPS); + run.hevc.spram = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SLICE_PARAMS); + run.hevc.sm = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); + run.hevc.dpram = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_DECODE_PARAMS); + break; + } + + frame_dprintk(ctx->dev, run.dst->sequence, + "Got OUTPUT buffer sequence %d, timestamp %llu\n", + run.src->sequence, run.src->vb2_buf.timestamp); + + frame_dprintk(ctx->dev, run.dst->sequence, + "Got CAPTURE buffer sequence %d, timestamp %llu\n", + run.dst->sequence, run.dst->vb2_buf.timestamp); + + visl_tpg_fill(ctx, &run); + visl_trace_ctrls(ctx, &run); + + if (bitstream_trace_frame_start > -1 && + run.dst->sequence >= bitstream_trace_frame_start && + run.dst->sequence < bitstream_trace_frame_start + bitstream_trace_nframes) + visl_trace_bitstream(ctx, &run); + + /* Complete request(s) controls if needed. */ + if (src_req) + v4l2_ctrl_request_complete(src_req, &ctx->hdl); + + if (visl_transtime_ms) + usleep_range(visl_transtime_ms * 1000, 2 * visl_transtime_ms * 1000); + + v4l2_m2m_buf_done_and_job_finish(ctx->dev->m2m_dev, + ctx->fh.m2m_ctx, VB2_BUF_STATE_DONE); +} diff --git a/drivers/media/test-drivers/visl/visl-dec.h b/drivers/media/test-drivers/visl/visl-dec.h new file mode 100644 index 000000000000..4a706a9de02e --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-dec.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Contains the virtual decoder logic. The functions here control the + * tracing/TPG on a per-frame basis + */ + +#ifndef _VISL_DEC_H_ +#define _VISL_DEC_H_ + +#include "visl.h" + +struct visl_fwht_run { + const struct v4l2_ctrl_fwht_params *params; +}; + +struct visl_mpeg2_run { + const struct v4l2_ctrl_mpeg2_sequence *seq; + const struct v4l2_ctrl_mpeg2_picture *pic; + const struct v4l2_ctrl_mpeg2_quantisation *quant; +}; + +struct visl_vp8_run { + const struct v4l2_ctrl_vp8_frame *frame; +}; + +struct visl_vp9_run { + const struct v4l2_ctrl_vp9_frame *frame; + const struct v4l2_ctrl_vp9_compressed_hdr *probs; +}; + +struct visl_h264_run { + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; + const struct v4l2_ctrl_h264_scaling_matrix *sm; + const struct v4l2_ctrl_h264_slice_params *spram; + const struct v4l2_ctrl_h264_decode_params *dpram; + const struct v4l2_ctrl_h264_pred_weights *pwht; +}; + +struct visl_hevc_run { + const struct v4l2_ctrl_hevc_sps *sps; + const struct v4l2_ctrl_hevc_pps *pps; + const struct v4l2_ctrl_hevc_slice_params *spram; + const struct v4l2_ctrl_hevc_scaling_matrix *sm; + const struct v4l2_ctrl_hevc_decode_params *dpram; +}; + +struct visl_run { + struct vb2_v4l2_buffer *src; + struct vb2_v4l2_buffer *dst; + + union { + struct visl_fwht_run fwht; + struct visl_mpeg2_run mpeg2; + struct visl_vp8_run vp8; + struct visl_vp9_run vp9; + struct visl_h264_run h264; + struct visl_hevc_run hevc; + }; +}; + +int visl_dec_start(struct visl_ctx *ctx); +int visl_dec_stop(struct visl_ctx *ctx); +int visl_job_ready(void *priv); +void visl_device_run(void *priv); + +#endif /* _VISL_DEC_H_ */ diff --git a/drivers/media/test-drivers/visl/visl-trace-fwht.h b/drivers/media/test-drivers/visl/visl-trace-fwht.h new file mode 100644 index 000000000000..54b119359ff5 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-fwht.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_FWHT_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_FWHT_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_fwht_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_fwht_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_fwht_params *p), + TP_ARGS(p), + TP_STRUCT__entry( + __field(u64, backward_ref_ts) + __field(u32, version) + __field(u32, width) + __field(u32, height) + __field(u32, flags) + __field(u32, colorspace) + __field(u32, xfer_func) + __field(u32, ycbcr_enc) + __field(u32, quantization) + ), + TP_fast_assign( + __entry->backward_ref_ts = p->backward_ref_ts; + __entry->version = p->version; + __entry->width = p->width; + __entry->height = p->height; + __entry->flags = p->flags; + __entry->colorspace = p->colorspace; + __entry->xfer_func = p->xfer_func; + __entry->ycbcr_enc = p->ycbcr_enc; + __entry->quantization = p->quantization; + ), + TP_printk("backward_ref_ts %llu version %u width %u height %u flags %s colorspace %u xfer_func %u ycbcr_enc %u quantization %u", + __entry->backward_ref_ts, __entry->version, __entry->width, __entry->height, + __print_flags(__entry->flags, "|", + {V4L2_FWHT_FL_IS_INTERLACED, "IS_INTERLACED"}, + {V4L2_FWHT_FL_IS_BOTTOM_FIRST, "IS_BOTTOM_FIRST"}, + {V4L2_FWHT_FL_IS_ALTERNATE, "IS_ALTERNATE"}, + {V4L2_FWHT_FL_IS_BOTTOM_FIELD, "IS_BOTTOM_FIELD"}, + {V4L2_FWHT_FL_LUMA_IS_UNCOMPRESSED, "LUMA_IS_UNCOMPRESSED"}, + {V4L2_FWHT_FL_CB_IS_UNCOMPRESSED, "CB_IS_UNCOMPRESSED"}, + {V4L2_FWHT_FL_CR_IS_UNCOMPRESSED, "CR_IS_UNCOMPRESSED"}, + {V4L2_FWHT_FL_ALPHA_IS_UNCOMPRESSED, "ALPHA_IS_UNCOMPRESSED"}, + {V4L2_FWHT_FL_I_FRAME, "I_FRAME"}, + {V4L2_FWHT_FL_PIXENC_HSV, "PIXENC_HSV"}, + {V4L2_FWHT_FL_PIXENC_RGB, "PIXENC_RGB"}, + {V4L2_FWHT_FL_PIXENC_YUV, "PIXENC_YUV"}), + __entry->colorspace, __entry->xfer_func, __entry->ycbcr_enc, + __entry->quantization) +); + +DEFINE_EVENT(v4l2_ctrl_fwht_params_tmpl, v4l2_ctrl_fwht_params, + TP_PROTO(const struct v4l2_ctrl_fwht_params *p), + TP_ARGS(p) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-fwht +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-h264.h b/drivers/media/test-drivers/visl/visl-trace-h264.h new file mode 100644 index 000000000000..d84296a01deb --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-h264.h @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_H264_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_H264_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_h264_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_sps_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_sps *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_sps, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nprofile_idc %u\n" + "constraint_set_flags %s\n" + "level_idc %u\n" + "seq_parameter_set_id %u\n" + "chroma_format_idc %u\n" + "bit_depth_luma_minus8 %u\n" + "bit_depth_chroma_minus8 %u\n" + "log2_max_frame_num_minus4 %u\n" + "pic_order_cnt_type %u\n" + "log2_max_pic_order_cnt_lsb_minus4 %u\n" + "max_num_ref_frames %u\n" + "num_ref_frames_in_pic_order_cnt_cycle %u\n" + "offset_for_ref_frame %s\n" + "offset_for_non_ref_pic %d\n" + "offset_for_top_to_bottom_field %d\n" + "pic_width_in_mbs_minus1 %u\n" + "pic_height_in_map_units_minus1 %u\n" + "flags %s", + __entry->s.profile_idc, + __print_flags(__entry->s.constraint_set_flags, "|", + {V4L2_H264_SPS_CONSTRAINT_SET0_FLAG, "CONSTRAINT_SET0_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET1_FLAG, "CONSTRAINT_SET1_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET2_FLAG, "CONSTRAINT_SET2_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET3_FLAG, "CONSTRAINT_SET3_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET4_FLAG, "CONSTRAINT_SET4_FLAG"}, + {V4L2_H264_SPS_CONSTRAINT_SET5_FLAG, "CONSTRAINT_SET5_FLAG"}), + __entry->s.level_idc, + __entry->s.seq_parameter_set_id, + __entry->s.chroma_format_idc, + __entry->s.bit_depth_luma_minus8, + __entry->s.bit_depth_chroma_minus8, + __entry->s.log2_max_frame_num_minus4, + __entry->s.pic_order_cnt_type, + __entry->s.log2_max_pic_order_cnt_lsb_minus4, + __entry->s.max_num_ref_frames, + __entry->s.num_ref_frames_in_pic_order_cnt_cycle, + __print_array(__entry->s.offset_for_ref_frame, + ARRAY_SIZE(__entry->s.offset_for_ref_frame), + sizeof(__entry->s.offset_for_ref_frame[0])), + __entry->s.offset_for_non_ref_pic, + __entry->s.offset_for_top_to_bottom_field, + __entry->s.pic_width_in_mbs_minus1, + __entry->s.pic_height_in_map_units_minus1, + __print_flags(__entry->s.flags, "|", + {V4L2_H264_SPS_FLAG_SEPARATE_COLOUR_PLANE, "SEPARATE_COLOUR_PLANE"}, + {V4L2_H264_SPS_FLAG_QPPRIME_Y_ZERO_TRANSFORM_BYPASS, "QPPRIME_Y_ZERO_TRANSFORM_BYPASS"}, + {V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO, "DELTA_PIC_ORDER_ALWAYS_ZERO"}, + {V4L2_H264_SPS_FLAG_GAPS_IN_FRAME_NUM_VALUE_ALLOWED, "GAPS_IN_FRAME_NUM_VALUE_ALLOWED"}, + {V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY, "FRAME_MBS_ONLY"}, + {V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD, "MB_ADAPTIVE_FRAME_FIELD"}, + {V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE, "DIRECT_8X8_INFERENCE"} + )) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_pps_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_pps *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_pps, p)), + TP_fast_assign(__entry->p = *p), + TP_printk("\npic_parameter_set_id %u\n" + "seq_parameter_set_id %u\n" + "num_slice_groups_minus1 %u\n" + "num_ref_idx_l0_default_active_minus1 %u\n" + "num_ref_idx_l1_default_active_minus1 %u\n" + "weighted_bipred_idc %u\n" + "pic_init_qp_minus26 %d\n" + "pic_init_qs_minus26 %d\n" + "chroma_qp_index_offset %d\n" + "second_chroma_qp_index_offset %d\n" + "flags %s", + __entry->p.pic_parameter_set_id, + __entry->p.seq_parameter_set_id, + __entry->p.num_slice_groups_minus1, + __entry->p.num_ref_idx_l0_default_active_minus1, + __entry->p.num_ref_idx_l1_default_active_minus1, + __entry->p.weighted_bipred_idc, + __entry->p.pic_init_qp_minus26, + __entry->p.pic_init_qs_minus26, + __entry->p.chroma_qp_index_offset, + __entry->p.second_chroma_qp_index_offset, + __print_flags(__entry->p.flags, "|", + {V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE, "ENTROPY_CODING_MODE"}, + {V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT, "BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT"}, + {V4L2_H264_PPS_FLAG_WEIGHTED_PRED, "WEIGHTED_PRED"}, + {V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT, "DEBLOCKING_FILTER_CONTROL_PRESENT"}, + {V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED, "CONSTRAINED_INTRA_PRED"}, + {V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT, "REDUNDANT_PIC_CNT_PRESENT"}, + {V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE, "TRANSFORM_8X8_MODE"}, + {V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT, "SCALING_MATRIX_PRESENT"} + )) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_scaling_matrix_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_scaling_matrix *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_scaling_matrix, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nscaling_list_4x4 {%s}\nscaling_list_8x8 {%s}", + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_4x4, + sizeof(__entry->s.scaling_list_4x4), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_8x8, + sizeof(__entry->s.scaling_list_8x8), + false) + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_pred_weights_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_pred_weights *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_pred_weights, p)), + TP_fast_assign(__entry->p = *p), + TP_printk("\nluma_log2_weight_denom %u\n" + "chroma_log2_weight_denom %u\n" + "weight_factor[0].luma_weight %s\n" + "weight_factor[0].luma_offset %s\n" + "weight_factor[0].chroma_weight {%s}\n" + "weight_factor[0].chroma_offset {%s}\n" + "weight_factor[1].luma_weight %s\n" + "weight_factor[1].luma_offset %s\n" + "weight_factor[1].chroma_weight {%s}\n" + "weight_factor[1].chroma_offset {%s}\n", + __entry->p.luma_log2_weight_denom, + __entry->p.chroma_log2_weight_denom, + __print_array(__entry->p.weight_factors[0].luma_weight, + ARRAY_SIZE(__entry->p.weight_factors[0].luma_weight), + sizeof(__entry->p.weight_factors[0].luma_weight[0])), + __print_array(__entry->p.weight_factors[0].luma_offset, + ARRAY_SIZE(__entry->p.weight_factors[0].luma_offset), + sizeof(__entry->p.weight_factors[0].luma_offset[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.weight_factors[0].chroma_weight, + sizeof(__entry->p.weight_factors[0].chroma_weight), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.weight_factors[0].chroma_offset, + sizeof(__entry->p.weight_factors[0].chroma_offset), + false), + __print_array(__entry->p.weight_factors[1].luma_weight, + ARRAY_SIZE(__entry->p.weight_factors[1].luma_weight), + sizeof(__entry->p.weight_factors[1].luma_weight[0])), + __print_array(__entry->p.weight_factors[1].luma_offset, + ARRAY_SIZE(__entry->p.weight_factors[1].luma_offset), + sizeof(__entry->p.weight_factors[1].luma_offset[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.weight_factors[1].chroma_weight, + sizeof(__entry->p.weight_factors[1].chroma_weight), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.weight_factors[1].chroma_offset, + sizeof(__entry->p.weight_factors[1].chroma_offset), + false) + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_slice_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_slice_params *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_slice_params, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nheader_bit_size %u\n" + "first_mb_in_slice %u\n" + "slice_type %s\n" + "colour_plane_id %u\n" + "redundant_pic_cnt %u\n" + "cabac_init_idc %u\n" + "slice_qp_delta %d\n" + "slice_qs_delta %d\n" + "disable_deblocking_filter_idc %u\n" + "slice_alpha_c0_offset_div2 %u\n" + "slice_beta_offset_div2 %u\n" + "num_ref_idx_l0_active_minus1 %u\n" + "num_ref_idx_l1_active_minus1 %u\n" + "flags %s", + __entry->s.header_bit_size, + __entry->s.first_mb_in_slice, + __print_symbolic(__entry->s.slice_type, + {V4L2_H264_SLICE_TYPE_P, "P"}, + {V4L2_H264_SLICE_TYPE_B, "B"}, + {V4L2_H264_SLICE_TYPE_I, "I"}, + {V4L2_H264_SLICE_TYPE_SP, "SP"}, + {V4L2_H264_SLICE_TYPE_SI, "SI"}), + __entry->s.colour_plane_id, + __entry->s.redundant_pic_cnt, + __entry->s.cabac_init_idc, + __entry->s.slice_qp_delta, + __entry->s.slice_qs_delta, + __entry->s.disable_deblocking_filter_idc, + __entry->s.slice_alpha_c0_offset_div2, + __entry->s.slice_beta_offset_div2, + __entry->s.num_ref_idx_l0_active_minus1, + __entry->s.num_ref_idx_l1_active_minus1, + __print_flags(__entry->s.flags, "|", + {V4L2_H264_SLICE_FLAG_DIRECT_SPATIAL_MV_PRED, "DIRECT_SPATIAL_MV_PRED"}, + {V4L2_H264_SLICE_FLAG_SP_FOR_SWITCH, "SP_FOR_SWITCH"}) + ) +); + +DECLARE_EVENT_CLASS(v4l2_h264_reference_tmpl, + TP_PROTO(const struct v4l2_h264_reference *r, int i), + TP_ARGS(r, i), + TP_STRUCT__entry(__field_struct(struct v4l2_h264_reference, r) + __field(int, i)), + TP_fast_assign(__entry->r = *r; __entry->i = i;), + TP_printk("[%d]: fields %s index %u", + __entry->i, + __print_flags(__entry->r.fields, "|", + {V4L2_H264_TOP_FIELD_REF, "TOP_FIELD_REF"}, + {V4L2_H264_BOTTOM_FIELD_REF, "BOTTOM_FIELD_REF"}, + {V4L2_H264_FRAME_REF, "FRAME_REF"}), + __entry->r.index + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_h264_decode_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_h264_decode_params *d), + TP_ARGS(d), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_h264_decode_params, d)), + TP_fast_assign(__entry->d = *d), + TP_printk("\nnal_ref_idc %u\n" + "frame_num %u\n" + "top_field_order_cnt %d\n" + "bottom_field_order_cnt %d\n" + "idr_pic_id %u\n" + "pic_order_cnt_lsb %u\n" + "delta_pic_order_cnt_bottom %d\n" + "delta_pic_order_cnt0 %d\n" + "delta_pic_order_cnt1 %d\n" + "dec_ref_pic_marking_bit_size %u\n" + "pic_order_cnt_bit_size %u\n" + "slice_group_change_cycle %u\n" + "flags %s\n", + __entry->d.nal_ref_idc, + __entry->d.frame_num, + __entry->d.top_field_order_cnt, + __entry->d.bottom_field_order_cnt, + __entry->d.idr_pic_id, + __entry->d.pic_order_cnt_lsb, + __entry->d.delta_pic_order_cnt_bottom, + __entry->d.delta_pic_order_cnt0, + __entry->d.delta_pic_order_cnt1, + __entry->d.dec_ref_pic_marking_bit_size, + __entry->d.pic_order_cnt_bit_size, + __entry->d.slice_group_change_cycle, + __print_flags(__entry->d.flags, "|", + {V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC, "IDR_PIC"}, + {V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC, "FIELD_PIC"}, + {V4L2_H264_DECODE_PARAM_FLAG_BOTTOM_FIELD, "BOTTOM_FIELD"}, + {V4L2_H264_DECODE_PARAM_FLAG_PFRAME, "PFRAME"}, + {V4L2_H264_DECODE_PARAM_FLAG_BFRAME, "BFRAME"}) + ) +); + +DECLARE_EVENT_CLASS(v4l2_h264_dpb_entry_tmpl, + TP_PROTO(const struct v4l2_h264_dpb_entry *e, int i), + TP_ARGS(e, i), + TP_STRUCT__entry(__field_struct(struct v4l2_h264_dpb_entry, e) + __field(int, i)), + TP_fast_assign(__entry->e = *e; __entry->i = i;), + TP_printk("[%d]: reference_ts %llu, pic_num %u frame_num %u fields %s " + "top_field_order_cnt %d bottom_field_order_cnt %d flags %s", + __entry->i, + __entry->e.reference_ts, + __entry->e.pic_num, + __entry->e.frame_num, + __print_flags(__entry->e.fields, "|", + {V4L2_H264_TOP_FIELD_REF, "TOP_FIELD_REF"}, + {V4L2_H264_BOTTOM_FIELD_REF, "BOTTOM_FIELD_REF"}, + {V4L2_H264_FRAME_REF, "FRAME_REF"}), + __entry->e.top_field_order_cnt, + __entry->e.bottom_field_order_cnt, + __print_flags(__entry->e.flags, "|", + {V4L2_H264_DPB_ENTRY_FLAG_VALID, "VALID"}, + {V4L2_H264_DPB_ENTRY_FLAG_ACTIVE, "ACTIVE"}, + {V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM, "LONG_TERM"}, + {V4L2_H264_DPB_ENTRY_FLAG_FIELD, "FIELD"}) + + ) +); + +DEFINE_EVENT(v4l2_ctrl_h264_sps_tmpl, v4l2_ctrl_h264_sps, + TP_PROTO(const struct v4l2_ctrl_h264_sps *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_h264_pps_tmpl, v4l2_ctrl_h264_pps, + TP_PROTO(const struct v4l2_ctrl_h264_pps *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_h264_scaling_matrix_tmpl, v4l2_ctrl_h264_scaling_matrix, + TP_PROTO(const struct v4l2_ctrl_h264_scaling_matrix *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_h264_pred_weights_tmpl, v4l2_ctrl_h264_pred_weights, + TP_PROTO(const struct v4l2_ctrl_h264_pred_weights *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_h264_slice_params_tmpl, v4l2_ctrl_h264_slice_params, + TP_PROTO(const struct v4l2_ctrl_h264_slice_params *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_h264_reference_tmpl, v4l2_h264_ref_pic_list0, + TP_PROTO(const struct v4l2_h264_reference *r, int i), + TP_ARGS(r, i) +); + +DEFINE_EVENT(v4l2_h264_reference_tmpl, v4l2_h264_ref_pic_list1, + TP_PROTO(const struct v4l2_h264_reference *r, int i), + TP_ARGS(r, i) +); + +DEFINE_EVENT(v4l2_ctrl_h264_decode_params_tmpl, v4l2_ctrl_h264_decode_params, + TP_PROTO(const struct v4l2_ctrl_h264_decode_params *d), + TP_ARGS(d) +); + +DEFINE_EVENT(v4l2_h264_dpb_entry_tmpl, v4l2_h264_dpb_entry, + TP_PROTO(const struct v4l2_h264_dpb_entry *e, int i), + TP_ARGS(e, i) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-h264 +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-hevc.h b/drivers/media/test-drivers/visl/visl-trace-hevc.h new file mode 100644 index 000000000000..837b8ec12e97 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-hevc.h @@ -0,0 +1,405 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#if !defined(_VISL_TRACE_HEVC_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_HEVC_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_hevc_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_sps_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_sps *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_sps, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nvideo_parameter_set_id %u\n" + "seq_parameter_set_id %u\n" + "pic_width_in_luma_samples %u\n" + "pic_height_in_luma_samples %u\n" + "bit_depth_luma_minus8 %u\n" + "bit_depth_chroma_minus8 %u\n" + "log2_max_pic_order_cnt_lsb_minus4 %u\n" + "sps_max_dec_pic_buffering_minus1 %u\n" + "sps_max_num_reorder_pics %u\n" + "sps_max_latency_increase_plus1 %u\n" + "log2_min_luma_coding_block_size_minus3 %u\n" + "log2_diff_max_min_luma_coding_block_size %u\n" + "log2_min_luma_transform_block_size_minus2 %u\n" + "log2_diff_max_min_luma_transform_block_size %u\n" + "max_transform_hierarchy_depth_inter %u\n" + "max_transform_hierarchy_depth_intra %u\n" + "pcm_sample_bit_depth_luma_minus1 %u\n" + "pcm_sample_bit_depth_chroma_minus1 %u\n" + "log2_min_pcm_luma_coding_block_size_minus3 %u\n" + "log2_diff_max_min_pcm_luma_coding_block_size %u\n" + "num_short_term_ref_pic_sets %u\n" + "num_long_term_ref_pics_sps %u\n" + "chroma_format_idc %u\n" + "sps_max_sub_layers_minus1 %u\n" + "flags %s", + __entry->s.video_parameter_set_id, + __entry->s.seq_parameter_set_id, + __entry->s.pic_width_in_luma_samples, + __entry->s.pic_height_in_luma_samples, + __entry->s.bit_depth_luma_minus8, + __entry->s.bit_depth_chroma_minus8, + __entry->s.log2_max_pic_order_cnt_lsb_minus4, + __entry->s.sps_max_dec_pic_buffering_minus1, + __entry->s.sps_max_num_reorder_pics, + __entry->s.sps_max_latency_increase_plus1, + __entry->s.log2_min_luma_coding_block_size_minus3, + __entry->s.log2_diff_max_min_luma_coding_block_size, + __entry->s.log2_min_luma_transform_block_size_minus2, + __entry->s.log2_diff_max_min_luma_transform_block_size, + __entry->s.max_transform_hierarchy_depth_inter, + __entry->s.max_transform_hierarchy_depth_intra, + __entry->s.pcm_sample_bit_depth_luma_minus1, + __entry->s.pcm_sample_bit_depth_chroma_minus1, + __entry->s.log2_min_pcm_luma_coding_block_size_minus3, + __entry->s.log2_diff_max_min_pcm_luma_coding_block_size, + __entry->s.num_short_term_ref_pic_sets, + __entry->s.num_long_term_ref_pics_sps, + __entry->s.chroma_format_idc, + __entry->s.sps_max_sub_layers_minus1, + __print_flags(__entry->s.flags, "|", + {V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE, "SEPARATE_COLOUR_PLANE"}, + {V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED, "SCALING_LIST_ENABLED"}, + {V4L2_HEVC_SPS_FLAG_AMP_ENABLED, "AMP_ENABLED"}, + {V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET, "SAMPLE_ADAPTIVE_OFFSET"}, + {V4L2_HEVC_SPS_FLAG_PCM_ENABLED, "PCM_ENABLED"}, + {V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED, "V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED"}, + {V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT, "LONG_TERM_REF_PICS_PRESENT"}, + {V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED, "TEMPORAL_MVP_ENABLED"}, + {V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED, "STRONG_INTRA_SMOOTHING_ENABLED"} + )) + +); + + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_pps_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_pps *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_pps, p)), + TP_fast_assign(__entry->p = *p), + TP_printk("\npic_parameter_set_id %u\n" + "num_extra_slice_header_bits %u\n" + "num_ref_idx_l0_default_active_minus1 %u\n" + "num_ref_idx_l1_default_active_minus1 %u\n" + "init_qp_minus26 %d\n" + "diff_cu_qp_delta_depth %u\n" + "pps_cb_qp_offset %d\n" + "pps_cr_qp_offset %d\n" + "num_tile_columns_minus1 %d\n" + "num_tile_rows_minus1 %d\n" + "column_width_minus1 %s\n" + "row_height_minus1 %s\n" + "pps_beta_offset_div2 %d\n" + "pps_tc_offset_div2 %d\n" + "log2_parallel_merge_level_minus2 %u\n" + "flags %s", + __entry->p.pic_parameter_set_id, + __entry->p.num_extra_slice_header_bits, + __entry->p.num_ref_idx_l0_default_active_minus1, + __entry->p.num_ref_idx_l1_default_active_minus1, + __entry->p.init_qp_minus26, + __entry->p.diff_cu_qp_delta_depth, + __entry->p.pps_cb_qp_offset, + __entry->p.pps_cr_qp_offset, + __entry->p.num_tile_columns_minus1, + __entry->p.num_tile_rows_minus1, + __print_array(__entry->p.column_width_minus1, + ARRAY_SIZE(__entry->p.column_width_minus1), + sizeof(__entry->p.column_width_minus1[0])), + __print_array(__entry->p.row_height_minus1, + ARRAY_SIZE(__entry->p.row_height_minus1), + sizeof(__entry->p.row_height_minus1[0])), + __entry->p.pps_beta_offset_div2, + __entry->p.pps_tc_offset_div2, + __entry->p.log2_parallel_merge_level_minus2, + __print_flags(__entry->p.flags, "|", + {V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED, "DEPENDENT_SLICE_SEGMENT_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT, "OUTPUT_FLAG_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED, "SIGN_DATA_HIDING_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT, "CABAC_INIT_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED, "CONSTRAINED_INTRA_PRED"}, + {V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED, "CU_QP_DELTA_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT, "PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED, "WEIGHTED_PRED"}, + {V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED, "WEIGHTED_BIPRED"}, + {V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED, "TRANSQUANT_BYPASS_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_TILES_ENABLED, "TILES_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED, "ENTROPY_CODING_SYNC_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED, "LOOP_FILTER_ACROSS_TILES_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED, "PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED, "DEBLOCKING_FILTER_OVERRIDE_ENABLED"}, + {V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER, "DISABLE_DEBLOCKING_FILTER"}, + {V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT, "LISTS_MODIFICATION_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT, "SLICE_SEGMENT_HEADER_EXTENSION_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT, "DEBLOCKING_FILTER_CONTROL_PRESENT"}, + {V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING, "UNIFORM_SPACING"} + )) + +); + + + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_slice_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_slice_params *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_slice_params, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nbit_size %u\n" + "data_byte_offset %u\n" + "num_entry_point_offsets %u\n" + "nal_unit_type %u\n" + "nuh_temporal_id_plus1 %u\n" + "slice_type %u\n" + "colour_plane_id %u\n" + "slice_pic_order_cnt %d\n" + "num_ref_idx_l0_active_minus1 %u\n" + "num_ref_idx_l1_active_minus1 %u\n" + "collocated_ref_idx %u\n" + "five_minus_max_num_merge_cand %u\n" + "slice_qp_delta %d\n" + "slice_cb_qp_offset %d\n" + "slice_cr_qp_offset %d\n" + "slice_act_y_qp_offset %d\n" + "slice_act_cb_qp_offset %d\n" + "slice_act_cr_qp_offset %d\n" + "slice_beta_offset_div2 %d\n" + "slice_tc_offset_div2 %d\n" + "pic_struct %u\n" + "slice_segment_addr %u\n" + "ref_idx_l0 %s\n" + "ref_idx_l1 %s\n" + "short_term_ref_pic_set_size %u\n" + "long_term_ref_pic_set_size %u\n" + "flags %s", + __entry->s.bit_size, + __entry->s.data_byte_offset, + __entry->s.num_entry_point_offsets, + __entry->s.nal_unit_type, + __entry->s.nuh_temporal_id_plus1, + __entry->s.slice_type, + __entry->s.colour_plane_id, + __entry->s.slice_pic_order_cnt, + __entry->s.num_ref_idx_l0_active_minus1, + __entry->s.num_ref_idx_l1_active_minus1, + __entry->s.collocated_ref_idx, + __entry->s.five_minus_max_num_merge_cand, + __entry->s.slice_qp_delta, + __entry->s.slice_cb_qp_offset, + __entry->s.slice_cr_qp_offset, + __entry->s.slice_act_y_qp_offset, + __entry->s.slice_act_cb_qp_offset, + __entry->s.slice_act_cr_qp_offset, + __entry->s.slice_beta_offset_div2, + __entry->s.slice_tc_offset_div2, + __entry->s.pic_struct, + __entry->s.slice_segment_addr, + __print_array(__entry->s.ref_idx_l0, + ARRAY_SIZE(__entry->s.ref_idx_l0), + sizeof(__entry->s.ref_idx_l0[0])), + __print_array(__entry->s.ref_idx_l1, + ARRAY_SIZE(__entry->s.ref_idx_l1), + sizeof(__entry->s.ref_idx_l1[0])), + __entry->s.short_term_ref_pic_set_size, + __entry->s.long_term_ref_pic_set_size, + __print_flags(__entry->s.flags, "|", + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA, "SLICE_SAO_LUMA"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_CHROMA, "SLICE_SAO_CHROMA"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED, "SLICE_TEMPORAL_MVP_ENABLED"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO, "MVD_L1_ZERO"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT, "CABAC_INIT"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0, "COLLOCATED_FROM_L0"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_USE_INTEGER_MV, "USE_INTEGER_MV"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED, "SLICE_DEBLOCKING_FILTER_DISABLED"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED, "SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED"}, + {V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT, "DEPENDENT_SLICE_SEGMENT"} + + )) +); + +DECLARE_EVENT_CLASS(v4l2_hevc_pred_weight_table_tmpl, + TP_PROTO(const struct v4l2_hevc_pred_weight_table *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_hevc_pred_weight_table, p)), + TP_fast_assign(__entry->p = *p), + TP_printk("\ndelta_luma_weight_l0 %s\n" + "luma_offset_l0 %s\n" + "delta_chroma_weight_l0 {%s}\n" + "chroma_offset_l0 {%s}\n" + "delta_luma_weight_l1 %s\n" + "luma_offset_l1 %s\n" + "delta_chroma_weight_l1 {%s}\n" + "chroma_offset_l1 {%s}\n" + "luma_log2_weight_denom %d\n" + "delta_chroma_log2_weight_denom %d\n", + __print_array(__entry->p.delta_luma_weight_l0, + ARRAY_SIZE(__entry->p.delta_luma_weight_l0), + sizeof(__entry->p.delta_luma_weight_l0[0])), + __print_array(__entry->p.luma_offset_l0, + ARRAY_SIZE(__entry->p.luma_offset_l0), + sizeof(__entry->p.luma_offset_l0[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.delta_chroma_weight_l0, + sizeof(__entry->p.delta_chroma_weight_l0), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.chroma_offset_l0, + sizeof(__entry->p.chroma_offset_l0), + false), + __print_array(__entry->p.delta_luma_weight_l1, + ARRAY_SIZE(__entry->p.delta_luma_weight_l1), + sizeof(__entry->p.delta_luma_weight_l1[0])), + __print_array(__entry->p.luma_offset_l1, + ARRAY_SIZE(__entry->p.luma_offset_l1), + sizeof(__entry->p.luma_offset_l1[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.delta_chroma_weight_l1, + sizeof(__entry->p.delta_chroma_weight_l1), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.chroma_offset_l1, + sizeof(__entry->p.chroma_offset_l1), + false), + __entry->p.luma_log2_weight_denom, + __entry->p.delta_chroma_log2_weight_denom + + )) + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_scaling_matrix_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_scaling_matrix *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_scaling_matrix, s)), + TP_fast_assign(__entry->s = *s), + TP_printk("\nscaling_list_4x4 {%s}\n" + "scaling_list_8x8 {%s}\n" + "scaling_list_16x16 {%s}\n" + "scaling_list_32x32 {%s}\n" + "scaling_list_dc_coef_16x16 %s\n" + "scaling_list_dc_coef_32x32 %s\n", + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_4x4, + sizeof(__entry->s.scaling_list_4x4), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_8x8, + sizeof(__entry->s.scaling_list_8x8), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_16x16, + sizeof(__entry->s.scaling_list_16x16), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->s.scaling_list_32x32, + sizeof(__entry->s.scaling_list_32x32), + false), + __print_array(__entry->s.scaling_list_dc_coef_16x16, + ARRAY_SIZE(__entry->s.scaling_list_dc_coef_16x16), + sizeof(__entry->s.scaling_list_dc_coef_16x16[0])), + __print_array(__entry->s.scaling_list_dc_coef_32x32, + ARRAY_SIZE(__entry->s.scaling_list_dc_coef_32x32), + sizeof(__entry->s.scaling_list_dc_coef_32x32[0])) + )) + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_decode_params_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_decode_params *d), + TP_ARGS(d), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_decode_params, d)), + TP_fast_assign(__entry->d = *d), + TP_printk("\npic_order_cnt_val %d\n" + "short_term_ref_pic_set_size %u\n" + "long_term_ref_pic_set_size %u\n" + "num_active_dpb_entries %u\n" + "num_poc_st_curr_before %u\n" + "num_poc_st_curr_after %u\n" + "num_poc_lt_curr %u\n" + "poc_st_curr_before %s\n" + "poc_st_curr_after %s\n" + "poc_lt_curr %s\n" + "flags %s", + __entry->d.pic_order_cnt_val, + __entry->d.short_term_ref_pic_set_size, + __entry->d.long_term_ref_pic_set_size, + __entry->d.num_active_dpb_entries, + __entry->d.num_poc_st_curr_before, + __entry->d.num_poc_st_curr_after, + __entry->d.num_poc_lt_curr, + __print_array(__entry->d.poc_st_curr_before, + ARRAY_SIZE(__entry->d.poc_st_curr_before), + sizeof(__entry->d.poc_st_curr_before[0])), + __print_array(__entry->d.poc_st_curr_after, + ARRAY_SIZE(__entry->d.poc_st_curr_after), + sizeof(__entry->d.poc_st_curr_after[0])), + __print_array(__entry->d.poc_lt_curr, + ARRAY_SIZE(__entry->d.poc_lt_curr), + sizeof(__entry->d.poc_lt_curr[0])), + __print_flags(__entry->d.flags, "|", + {V4L2_HEVC_DECODE_PARAM_FLAG_IRAP_PIC, "IRAP_PIC"}, + {V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC, "IDR_PIC"}, + {V4L2_HEVC_DECODE_PARAM_FLAG_NO_OUTPUT_OF_PRIOR, "NO_OUTPUT_OF_PRIOR"} + )) +); + + +DECLARE_EVENT_CLASS(v4l2_hevc_dpb_entry_tmpl, + TP_PROTO(const struct v4l2_hevc_dpb_entry *e), + TP_ARGS(e), + TP_STRUCT__entry(__field_struct(struct v4l2_hevc_dpb_entry, e)), + TP_fast_assign(__entry->e = *e), + TP_printk("\ntimestamp %llu\n" + "flags %s\n" + "field_pic %u\n" + "pic_order_cnt_val %d\n", + __entry->e.timestamp, + __print_flags(__entry->e.flags, "|", + {V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE, "LONG_TERM_REFERENCE"} + ), + __entry->e.field_pic, + __entry->e.pic_order_cnt_val + )) + +DEFINE_EVENT(v4l2_ctrl_hevc_sps_tmpl, v4l2_ctrl_hevc_sps, + TP_PROTO(const struct v4l2_ctrl_hevc_sps *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_pps_tmpl, v4l2_ctrl_hevc_pps, + TP_PROTO(const struct v4l2_ctrl_hevc_pps *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_slice_params_tmpl, v4l2_ctrl_hevc_slice_params, + TP_PROTO(const struct v4l2_ctrl_hevc_slice_params *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_hevc_pred_weight_table_tmpl, v4l2_hevc_pred_weight_table, + TP_PROTO(const struct v4l2_hevc_pred_weight_table *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_scaling_matrix_tmpl, v4l2_ctrl_hevc_scaling_matrix, + TP_PROTO(const struct v4l2_ctrl_hevc_scaling_matrix *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_decode_params_tmpl, v4l2_ctrl_hevc_decode_params, + TP_PROTO(const struct v4l2_ctrl_hevc_decode_params *d), + TP_ARGS(d) +); + +DEFINE_EVENT(v4l2_hevc_dpb_entry_tmpl, v4l2_hevc_dpb_entry, + TP_PROTO(const struct v4l2_hevc_dpb_entry *e), + TP_ARGS(e) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-hevc +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-mpeg2.h b/drivers/media/test-drivers/visl/visl-trace-mpeg2.h new file mode 100644 index 000000000000..ba6c65481194 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-mpeg2.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_MPEG2_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_MPEG2_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_mpeg2_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_mpeg2_seq_tmpl, + TP_PROTO(const struct v4l2_ctrl_mpeg2_sequence *s), + TP_ARGS(s), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_mpeg2_sequence, s)), + TP_fast_assign(__entry->s = *s;), + TP_printk("\nhorizontal_size %u\nvertical_size %u\nvbv_buffer_size %u\n" + "profile_and_level_indication %u\nchroma_format %u\nflags %s\n", + __entry->s.horizontal_size, + __entry->s.vertical_size, + __entry->s.vbv_buffer_size, + __entry->s.profile_and_level_indication, + __entry->s.chroma_format, + __print_flags(__entry->s.flags, "|", + {V4L2_MPEG2_SEQ_FLAG_PROGRESSIVE, "PROGRESSIVE"}) + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_mpeg2_pic_tmpl, + TP_PROTO(const struct v4l2_ctrl_mpeg2_picture *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_mpeg2_picture, p)), + TP_fast_assign(__entry->p = *p;), + TP_printk("\nbackward_ref_ts %llu\nforward_ref_ts %llu\nflags %s\nf_code {%s}\n" + "picture_coding_type: %u\npicture_structure %u\nintra_dc_precision %u\n", + __entry->p.backward_ref_ts, + __entry->p.forward_ref_ts, + __print_flags(__entry->p.flags, "|", + {V4L2_MPEG2_PIC_FLAG_TOP_FIELD_FIRST, "TOP_FIELD_FIRST"}, + {V4L2_MPEG2_PIC_FLAG_FRAME_PRED_DCT, "FRAME_PRED_DCT"}, + {V4L2_MPEG2_PIC_FLAG_CONCEALMENT_MV, "CONCEALMENT_MV"}, + {V4L2_MPEG2_PIC_FLAG_Q_SCALE_TYPE, "Q_SCALE_TYPE"}, + {V4L2_MPEG2_PIC_FLAG_INTRA_VLC, "INTA_VLC"}, + {V4L2_MPEG2_PIC_FLAG_ALT_SCAN, "ALT_SCAN"}, + {V4L2_MPEG2_PIC_FLAG_REPEAT_FIRST, "REPEAT_FIRST"}, + {V4L2_MPEG2_PIC_FLAG_PROGRESSIVE, "PROGRESSIVE"}), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.f_code, + sizeof(__entry->p.f_code), + false), + __entry->p.picture_coding_type, + __entry->p.picture_structure, + __entry->p.intra_dc_precision + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_mpeg2_quant_tmpl, + TP_PROTO(const struct v4l2_ctrl_mpeg2_quantisation *q), + TP_ARGS(q), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_mpeg2_quantisation, q)), + TP_fast_assign(__entry->q = *q;), + TP_printk("\nintra_quantiser_matrix %s\nnon_intra_quantiser_matrix %s\n" + "chroma_intra_quantiser_matrix %s\nchroma_non_intra_quantiser_matrix %s\n", + __print_array(__entry->q.intra_quantiser_matrix, + ARRAY_SIZE(__entry->q.intra_quantiser_matrix), + sizeof(__entry->q.intra_quantiser_matrix[0])), + __print_array(__entry->q.non_intra_quantiser_matrix, + ARRAY_SIZE(__entry->q.non_intra_quantiser_matrix), + sizeof(__entry->q.non_intra_quantiser_matrix[0])), + __print_array(__entry->q.chroma_intra_quantiser_matrix, + ARRAY_SIZE(__entry->q.chroma_intra_quantiser_matrix), + sizeof(__entry->q.chroma_intra_quantiser_matrix[0])), + __print_array(__entry->q.chroma_non_intra_quantiser_matrix, + ARRAY_SIZE(__entry->q.chroma_non_intra_quantiser_matrix), + sizeof(__entry->q.chroma_non_intra_quantiser_matrix[0])) + ) +) + +DEFINE_EVENT(v4l2_ctrl_mpeg2_seq_tmpl, v4l2_ctrl_mpeg2_sequence, + TP_PROTO(const struct v4l2_ctrl_mpeg2_sequence *s), + TP_ARGS(s) +); + +DEFINE_EVENT(v4l2_ctrl_mpeg2_pic_tmpl, v4l2_ctrl_mpeg2_picture, + TP_PROTO(const struct v4l2_ctrl_mpeg2_picture *p), + TP_ARGS(p) +); + +DEFINE_EVENT(v4l2_ctrl_mpeg2_quant_tmpl, v4l2_ctrl_mpeg2_quantisation, + TP_PROTO(const struct v4l2_ctrl_mpeg2_quantisation *q), + TP_ARGS(q) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-mpeg2 +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-points.c b/drivers/media/test-drivers/visl/visl-trace-points.c new file mode 100644 index 000000000000..f7b866534f1e --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-points.c @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "visl.h" + +#define CREATE_TRACE_POINTS +#include "visl-trace-fwht.h" +#include "visl-trace-mpeg2.h" +#include "visl-trace-vp8.h" +#include "visl-trace-vp9.h" +#include "visl-trace-h264.h" +#include "visl-trace-hevc.h" diff --git a/drivers/media/test-drivers/visl/visl-trace-vp8.h b/drivers/media/test-drivers/visl/visl-trace-vp8.h new file mode 100644 index 000000000000..dabe17d69ddc --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-vp8.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_VP8_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_VP8_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_vp8_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp8_entropy_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp8_frame *f), + TP_ARGS(f), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp8_frame, f)), + TP_fast_assign(__entry->f = *f;), + TP_printk("\nentropy.coeff_probs {%s}\n" + "entropy.y_mode_probs %s\n" + "entropy.uv_mode_probs %s\n" + "entropy.mv_probs {%s}", + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->f.entropy.coeff_probs, + sizeof(__entry->f.entropy.coeff_probs), + false), + __print_array(__entry->f.entropy.y_mode_probs, + ARRAY_SIZE(__entry->f.entropy.y_mode_probs), + sizeof(__entry->f.entropy.y_mode_probs[0])), + __print_array(__entry->f.entropy.uv_mode_probs, + ARRAY_SIZE(__entry->f.entropy.uv_mode_probs), + sizeof(__entry->f.entropy.uv_mode_probs[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->f.entropy.mv_probs, + sizeof(__entry->f.entropy.mv_probs), + false) + ) +) + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp8_frame_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp8_frame *f), + TP_ARGS(f), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp8_frame, f)), + TP_fast_assign(__entry->f = *f;), + TP_printk("\nsegment.quant_update %s\n" + "segment.lf_update %s\n" + "segment.segment_probs %s\n" + "segment.flags %s\n" + "lf.ref_frm_delta %s\n" + "lf.mb_mode_delta %s\n" + "lf.sharpness_level %u\n" + "lf.level %u\n" + "lf.flags %s\n" + "quant.y_ac_qi %u\n" + "quant.y_dc_delta %d\n" + "quant.y2_dc_delta %d\n" + "quant.y2_ac_delta %d\n" + "quant.uv_dc_delta %d\n" + "quant.uv_ac_delta %d\n" + "coder_state.range %u\n" + "coder_state.value %u\n" + "coder_state.bit_count %u\n" + "width %u\n" + "height %u\n" + "horizontal_scale %u\n" + "vertical_scale %u\n" + "version %u\n" + "prob_skip_false %u\n" + "prob_intra %u\n" + "prob_last %u\n" + "prob_gf %u\n" + "num_dct_parts %u\n" + "first_part_size %u\n" + "first_part_header_bits %u\n" + "dct_part_sizes %s\n" + "last_frame_ts %llu\n" + "golden_frame_ts %llu\n" + "alt_frame_ts %llu\n" + "flags %s", + __print_array(__entry->f.segment.quant_update, + ARRAY_SIZE(__entry->f.segment.quant_update), + sizeof(__entry->f.segment.quant_update[0])), + __print_array(__entry->f.segment.lf_update, + ARRAY_SIZE(__entry->f.segment.lf_update), + sizeof(__entry->f.segment.lf_update[0])), + __print_array(__entry->f.segment.segment_probs, + ARRAY_SIZE(__entry->f.segment.segment_probs), + sizeof(__entry->f.segment.segment_probs[0])), + __print_flags(__entry->f.segment.flags, "|", + {V4L2_VP8_SEGMENT_FLAG_ENABLED, "SEGMENT_ENABLED"}, + {V4L2_VP8_SEGMENT_FLAG_UPDATE_MAP, "SEGMENT_UPDATE_MAP"}, + {V4L2_VP8_SEGMENT_FLAG_UPDATE_FEATURE_DATA, "SEGMENT_UPDATE_FEATURE_DATA"}, + {V4L2_VP8_SEGMENT_FLAG_DELTA_VALUE_MODE, "SEGMENT_DELTA_VALUE_MODE"}), + __print_array(__entry->f.lf.ref_frm_delta, + ARRAY_SIZE(__entry->f.lf.ref_frm_delta), + sizeof(__entry->f.lf.ref_frm_delta[0])), + __print_array(__entry->f.lf.mb_mode_delta, + ARRAY_SIZE(__entry->f.lf.mb_mode_delta), + sizeof(__entry->f.lf.mb_mode_delta[0])), + __entry->f.lf.sharpness_level, + __entry->f.lf.level, + __print_flags(__entry->f.lf.flags, "|", + {V4L2_VP8_LF_ADJ_ENABLE, "LF_ADJ_ENABLED"}, + {V4L2_VP8_LF_DELTA_UPDATE, "LF_DELTA_UPDATE"}, + {V4L2_VP8_LF_FILTER_TYPE_SIMPLE, "LF_FILTER_TYPE_SIMPLE"}), + __entry->f.quant.y_ac_qi, + __entry->f.quant.y_dc_delta, + __entry->f.quant.y2_dc_delta, + __entry->f.quant.y2_ac_delta, + __entry->f.quant.uv_dc_delta, + __entry->f.quant.uv_ac_delta, + __entry->f.coder_state.range, + __entry->f.coder_state.value, + __entry->f.coder_state.bit_count, + __entry->f.width, + __entry->f.height, + __entry->f.horizontal_scale, + __entry->f.vertical_scale, + __entry->f.version, + __entry->f.prob_skip_false, + __entry->f.prob_intra, + __entry->f.prob_last, + __entry->f.prob_gf, + __entry->f.num_dct_parts, + __entry->f.first_part_size, + __entry->f.first_part_header_bits, + __print_array(__entry->f.dct_part_sizes, + ARRAY_SIZE(__entry->f.dct_part_sizes), + sizeof(__entry->f.dct_part_sizes[0])), + __entry->f.last_frame_ts, + __entry->f.golden_frame_ts, + __entry->f.alt_frame_ts, + __print_flags(__entry->f.flags, "|", + {V4L2_VP8_FRAME_FLAG_KEY_FRAME, "KEY_FRAME"}, + {V4L2_VP8_FRAME_FLAG_EXPERIMENTAL, "EXPERIMENTAL"}, + {V4L2_VP8_FRAME_FLAG_SHOW_FRAME, "SHOW_FRAME"}, + {V4L2_VP8_FRAME_FLAG_MB_NO_SKIP_COEFF, "MB_NO_SKIP_COEFF"}, + {V4L2_VP8_FRAME_FLAG_SIGN_BIAS_GOLDEN, "SIGN_BIAS_GOLDEN"}, + {V4L2_VP8_FRAME_FLAG_SIGN_BIAS_ALT, "SIGN_BIAS_ALT"}) + ) +); + +DEFINE_EVENT(v4l2_ctrl_vp8_frame_tmpl, v4l2_ctrl_vp8_frame, + TP_PROTO(const struct v4l2_ctrl_vp8_frame *f), + TP_ARGS(f) +); + +DEFINE_EVENT(v4l2_ctrl_vp8_entropy_tmpl, v4l2_ctrl_vp8_entropy, + TP_PROTO(const struct v4l2_ctrl_vp8_frame *f), + TP_ARGS(f) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-vp8 +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-trace-vp9.h b/drivers/media/test-drivers/visl/visl-trace-vp9.h new file mode 100644 index 000000000000..362b92b07f93 --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-trace-vp9.h @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(_VISL_TRACE_VP9_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _VISL_TRACE_VP9_H_ + +#include <linux/tracepoint.h> +#include "visl.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM visl_vp9_controls + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp9_frame_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp9_frame *f), + TP_ARGS(f), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp9_frame, f)), + TP_fast_assign(__entry->f = *f;), + TP_printk("\nlf.ref_deltas %s\n" + "lf.mode_deltas %s\n" + "lf.level %u\n" + "lf.sharpness %u\n" + "lf.flags %s\n" + "quant.base_q_idx %u\n" + "quant.delta_q_y_dc %d\n" + "quant.delta_q_uv_dc %d\n" + "quant.delta_q_uv_ac %d\n" + "seg.feature_data {%s}\n" + "seg.feature_enabled %s\n" + "seg.tree_probs %s\n" + "seg.pred_probs %s\n" + "seg.flags %s\n" + "flags %s\n" + "compressed_header_size %u\n" + "uncompressed_header_size %u\n" + "frame_width_minus_1 %u\n" + "frame_height_minus_1 %u\n" + "render_width_minus_1 %u\n" + "render_height_minus_1 %u\n" + "last_frame_ts %llu\n" + "golden_frame_ts %llu\n" + "alt_frame_ts %llu\n" + "ref_frame_sign_bias %s\n" + "reset_frame_context %s\n" + "frame_context_idx %u\n" + "profile %u\n" + "bit_depth %u\n" + "interpolation_filter %s\n" + "tile_cols_log2 %u\n" + "tile_rows_log_2 %u\n" + "reference_mode %s\n", + __print_array(__entry->f.lf.ref_deltas, + ARRAY_SIZE(__entry->f.lf.ref_deltas), + sizeof(__entry->f.lf.ref_deltas[0])), + __print_array(__entry->f.lf.mode_deltas, + ARRAY_SIZE(__entry->f.lf.mode_deltas), + sizeof(__entry->f.lf.mode_deltas[0])), + __entry->f.lf.level, + __entry->f.lf.sharpness, + __print_flags(__entry->f.lf.flags, "|", + {V4L2_VP9_LOOP_FILTER_FLAG_DELTA_ENABLED, "DELTA_ENABLED"}, + {V4L2_VP9_LOOP_FILTER_FLAG_DELTA_UPDATE, "DELTA_UPDATE"}), + __entry->f.quant.base_q_idx, + __entry->f.quant.delta_q_y_dc, + __entry->f.quant.delta_q_uv_dc, + __entry->f.quant.delta_q_uv_ac, + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->f.seg.feature_data, + sizeof(__entry->f.seg.feature_data), + false), + __print_array(__entry->f.seg.feature_enabled, + ARRAY_SIZE(__entry->f.seg.feature_enabled), + sizeof(__entry->f.seg.feature_enabled[0])), + __print_array(__entry->f.seg.tree_probs, + ARRAY_SIZE(__entry->f.seg.tree_probs), + sizeof(__entry->f.seg.tree_probs[0])), + __print_array(__entry->f.seg.pred_probs, + ARRAY_SIZE(__entry->f.seg.pred_probs), + sizeof(__entry->f.seg.pred_probs[0])), + __print_flags(__entry->f.seg.flags, "|", + {V4L2_VP9_SEGMENTATION_FLAG_ENABLED, "ENABLED"}, + {V4L2_VP9_SEGMENTATION_FLAG_UPDATE_MAP, "UPDATE_MAP"}, + {V4L2_VP9_SEGMENTATION_FLAG_TEMPORAL_UPDATE, "TEMPORAL_UPDATE"}, + {V4L2_VP9_SEGMENTATION_FLAG_UPDATE_DATA, "UPDATE_DATA"}, + {V4L2_VP9_SEGMENTATION_FLAG_ABS_OR_DELTA_UPDATE, "ABS_OR_DELTA_UPDATE"}), + __print_flags(__entry->f.flags, "|", + {V4L2_VP9_FRAME_FLAG_KEY_FRAME, "KEY_FRAME"}, + {V4L2_VP9_FRAME_FLAG_SHOW_FRAME, "SHOW_FRAME"}, + {V4L2_VP9_FRAME_FLAG_ERROR_RESILIENT, "ERROR_RESILIENT"}, + {V4L2_VP9_FRAME_FLAG_INTRA_ONLY, "INTRA_ONLY"}, + {V4L2_VP9_FRAME_FLAG_ALLOW_HIGH_PREC_MV, "ALLOW_HIGH_PREC_MV"}, + {V4L2_VP9_FRAME_FLAG_REFRESH_FRAME_CTX, "REFRESH_FRAME_CTX"}, + {V4L2_VP9_FRAME_FLAG_PARALLEL_DEC_MODE, "PARALLEL_DEC_MODE"}, + {V4L2_VP9_FRAME_FLAG_X_SUBSAMPLING, "X_SUBSAMPLING"}, + {V4L2_VP9_FRAME_FLAG_Y_SUBSAMPLING, "Y_SUBSAMPLING"}, + {V4L2_VP9_FRAME_FLAG_COLOR_RANGE_FULL_SWING, "COLOR_RANGE_FULL_SWING"}), + __entry->f.compressed_header_size, + __entry->f.uncompressed_header_size, + __entry->f.frame_width_minus_1, + __entry->f.frame_height_minus_1, + __entry->f.render_width_minus_1, + __entry->f.render_height_minus_1, + __entry->f.last_frame_ts, + __entry->f.golden_frame_ts, + __entry->f.alt_frame_ts, + __print_symbolic(__entry->f.ref_frame_sign_bias, + {V4L2_VP9_SIGN_BIAS_LAST, "SIGN_BIAS_LAST"}, + {V4L2_VP9_SIGN_BIAS_GOLDEN, "SIGN_BIAS_GOLDEN"}, + {V4L2_VP9_SIGN_BIAS_ALT, "SIGN_BIAS_ALT"}), + __print_symbolic(__entry->f.reset_frame_context, + {V4L2_VP9_RESET_FRAME_CTX_NONE, "RESET_FRAME_CTX_NONE"}, + {V4L2_VP9_RESET_FRAME_CTX_SPEC, "RESET_FRAME_CTX_SPEC"}, + {V4L2_VP9_RESET_FRAME_CTX_ALL, "RESET_FRAME_CTX_ALL"}), + __entry->f.frame_context_idx, + __entry->f.profile, + __entry->f.bit_depth, + __print_symbolic(__entry->f.interpolation_filter, + {V4L2_VP9_INTERP_FILTER_EIGHTTAP, "INTERP_FILTER_EIGHTTAP"}, + {V4L2_VP9_INTERP_FILTER_EIGHTTAP_SMOOTH, "INTERP_FILTER_EIGHTTAP_SMOOTH"}, + {V4L2_VP9_INTERP_FILTER_EIGHTTAP_SHARP, "INTERP_FILTER_EIGHTTAP_SHARP"}, + {V4L2_VP9_INTERP_FILTER_BILINEAR, "INTERP_FILTER_BILINEAR"}, + {V4L2_VP9_INTERP_FILTER_SWITCHABLE, "INTERP_FILTER_SWITCHABLE"}), + __entry->f.tile_cols_log2, + __entry->f.tile_rows_log2, + __print_symbolic(__entry->f.reference_mode, + {V4L2_VP9_REFERENCE_MODE_SINGLE_REFERENCE, "REFERENCE_MODE_SINGLE_REFERENCE"}, + {V4L2_VP9_REFERENCE_MODE_COMPOUND_REFERENCE, "REFERENCE_MODE_COMPOUND_REFERENCE"}, + {V4L2_VP9_REFERENCE_MODE_SELECT, "REFERENCE_MODE_SELECT"})) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp9_compressed_hdr_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp9_compressed_hdr *h), + TP_ARGS(h), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp9_compressed_hdr, h)), + TP_fast_assign(__entry->h = *h;), + TP_printk("\ntx_mode %s\n" + "tx8 {%s}\n" + "tx16 {%s}\n" + "tx32 {%s}\n" + "skip %s\n" + "inter_mode {%s}\n" + "interp_filter {%s}\n" + "is_inter %s\n" + "comp_mode %s\n" + "single_ref {%s}\n" + "comp_ref %s\n" + "y_mode {%s}\n" + "uv_mode {%s}\n" + "partition {%s}\n", + __print_symbolic(__entry->h.tx_mode, + {V4L2_VP9_TX_MODE_ONLY_4X4, "TX_MODE_ONLY_4X4"}, + {V4L2_VP9_TX_MODE_ALLOW_8X8, "TX_MODE_ALLOW_8X8"}, + {V4L2_VP9_TX_MODE_ALLOW_16X16, "TX_MODE_ALLOW_16X16"}, + {V4L2_VP9_TX_MODE_ALLOW_32X32, "TX_MODE_ALLOW_32X32"}, + {V4L2_VP9_TX_MODE_SELECT, "TX_MODE_SELECT"}), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.tx8, + sizeof(__entry->h.tx8), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.tx16, + sizeof(__entry->h.tx16), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.tx32, + sizeof(__entry->h.tx32), + false), + __print_array(__entry->h.skip, + ARRAY_SIZE(__entry->h.skip), + sizeof(__entry->h.skip[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.inter_mode, + sizeof(__entry->h.inter_mode), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.interp_filter, + sizeof(__entry->h.interp_filter), + false), + __print_array(__entry->h.is_inter, + ARRAY_SIZE(__entry->h.is_inter), + sizeof(__entry->h.is_inter[0])), + __print_array(__entry->h.comp_mode, + ARRAY_SIZE(__entry->h.comp_mode), + sizeof(__entry->h.comp_mode[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.single_ref, + sizeof(__entry->h.single_ref), + false), + __print_array(__entry->h.comp_ref, + ARRAY_SIZE(__entry->h.comp_ref), + sizeof(__entry->h.comp_ref[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.y_mode, + sizeof(__entry->h.y_mode), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.uv_mode, + sizeof(__entry->h.uv_mode), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.partition, + sizeof(__entry->h.partition), + false) + ) +); + +DECLARE_EVENT_CLASS(v4l2_ctrl_vp9_compressed_coef_tmpl, + TP_PROTO(const struct v4l2_ctrl_vp9_compressed_hdr *h), + TP_ARGS(h), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_vp9_compressed_hdr, h)), + TP_fast_assign(__entry->h = *h;), + TP_printk("\n coef {%s}", + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->h.coef, + sizeof(__entry->h.coef), + false) + ) +); + +DECLARE_EVENT_CLASS(v4l2_vp9_mv_probs_tmpl, + TP_PROTO(const struct v4l2_vp9_mv_probs *p), + TP_ARGS(p), + TP_STRUCT__entry(__field_struct(struct v4l2_vp9_mv_probs, p)), + TP_fast_assign(__entry->p = *p;), + TP_printk("\n joint %s\n" + "sign %s\n" + "classes {%s}\n" + "class0_bit %s\n" + "bits {%s}\n" + "class0_fr {%s}\n" + "fr {%s}\n" + "class0_hp %s\n" + "hp %s\n", + __print_array(__entry->p.joint, + ARRAY_SIZE(__entry->p.joint), + sizeof(__entry->p.joint[0])), + __print_array(__entry->p.sign, + ARRAY_SIZE(__entry->p.sign), + sizeof(__entry->p.sign[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.classes, + sizeof(__entry->p.classes), + false), + __print_array(__entry->p.class0_bit, + ARRAY_SIZE(__entry->p.class0_bit), + sizeof(__entry->p.class0_bit[0])), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.bits, + sizeof(__entry->p.bits), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.class0_fr, + sizeof(__entry->p.class0_fr), + false), + __print_hex_dump("", DUMP_PREFIX_NONE, 32, 1, + __entry->p.fr, + sizeof(__entry->p.fr), + false), + __print_array(__entry->p.class0_hp, + ARRAY_SIZE(__entry->p.class0_hp), + sizeof(__entry->p.class0_hp[0])), + __print_array(__entry->p.hp, + ARRAY_SIZE(__entry->p.hp), + sizeof(__entry->p.hp[0])) + ) +); + +DEFINE_EVENT(v4l2_ctrl_vp9_frame_tmpl, v4l2_ctrl_vp9_frame, + TP_PROTO(const struct v4l2_ctrl_vp9_frame *f), + TP_ARGS(f) +); + +DEFINE_EVENT(v4l2_ctrl_vp9_compressed_hdr_tmpl, v4l2_ctrl_vp9_compressed_hdr, + TP_PROTO(const struct v4l2_ctrl_vp9_compressed_hdr *h), + TP_ARGS(h) +); + +DEFINE_EVENT(v4l2_ctrl_vp9_compressed_coef_tmpl, v4l2_ctrl_vp9_compressed_coeff, + TP_PROTO(const struct v4l2_ctrl_vp9_compressed_hdr *h), + TP_ARGS(h) +); + + +DEFINE_EVENT(v4l2_vp9_mv_probs_tmpl, v4l2_vp9_mv_probs, + TP_PROTO(const struct v4l2_vp9_mv_probs *p), + TP_ARGS(p) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/media/test-drivers/visl +#define TRACE_INCLUDE_FILE visl-trace-vp9 +#include <trace/define_trace.h> diff --git a/drivers/media/test-drivers/visl/visl-video.c b/drivers/media/test-drivers/visl/visl-video.c new file mode 100644 index 000000000000..b08664dfbe5f --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-video.c @@ -0,0 +1,767 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Contains the driver implementation for the V4L2 stateless interface. + */ + +#include <linux/debugfs.h> +#include <linux/font.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-vmalloc.h> +#include <media/videobuf2-v4l2.h> + +#include "visl-video.h" + +#include "visl.h" +#include "visl-debugfs.h" + +#define MIN_CODED_SZ (1024U * 256U) + +static void visl_set_current_codec(struct visl_ctx *ctx) +{ + u32 fourcc = ctx->coded_fmt.fmt.pix_mp.pixelformat; + + switch (fourcc) { + case V4L2_PIX_FMT_FWHT_STATELESS: + ctx->current_codec = VISL_CODEC_FWHT; + break; + case V4L2_PIX_FMT_MPEG2_SLICE: + ctx->current_codec = VISL_CODEC_MPEG2; + break; + case V4L2_PIX_FMT_VP8_FRAME: + ctx->current_codec = VISL_CODEC_VP8; + break; + case V4L2_PIX_FMT_VP9_FRAME: + ctx->current_codec = VISL_CODEC_VP9; + break; + case V4L2_PIX_FMT_H264_SLICE: + ctx->current_codec = VISL_CODEC_H264; + break; + case V4L2_PIX_FMT_HEVC_SLICE: + ctx->current_codec = VISL_CODEC_HEVC; + break; + default: + dprintk(ctx->dev, "Warning: unsupported fourcc: %d\n", fourcc); + ctx->current_codec = VISL_CODEC_NONE; + break; + } +} + +static void visl_print_fmt(struct visl_ctx *ctx, const struct v4l2_format *f) +{ + const struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + u32 i; + + dprintk(ctx->dev, "width: %d\n", pix_mp->width); + dprintk(ctx->dev, "height: %d\n", pix_mp->height); + dprintk(ctx->dev, "pixelformat: %c%c%c%c\n", + pix_mp->pixelformat, + (pix_mp->pixelformat >> 8) & 0xff, + (pix_mp->pixelformat >> 16) & 0xff, + (pix_mp->pixelformat >> 24) & 0xff); + + dprintk(ctx->dev, "field: %d\n", pix_mp->field); + dprintk(ctx->dev, "colorspace: %d\n", pix_mp->colorspace); + dprintk(ctx->dev, "num_planes: %d\n", pix_mp->num_planes); + dprintk(ctx->dev, "flags: %d\n", pix_mp->flags); + dprintk(ctx->dev, "quantization: %d\n", pix_mp->quantization); + dprintk(ctx->dev, "xfer_func: %d\n", pix_mp->xfer_func); + + for (i = 0; i < pix_mp->num_planes; i++) { + dprintk(ctx->dev, + "plane[%d]: sizeimage: %d\n", i, pix_mp->plane_fmt[i].sizeimage); + dprintk(ctx->dev, + "plane[%d]: bytesperline: %d\n", i, pix_mp->plane_fmt[i].bytesperline); + } +} + +static int visl_tpg_init(struct visl_ctx *ctx) +{ + const struct font_desc *font; + const char *font_name = "VGA8x16"; + int ret; + u32 width = ctx->decoded_fmt.fmt.pix_mp.width; + u32 height = ctx->decoded_fmt.fmt.pix_mp.height; + struct v4l2_pix_format_mplane *f = &ctx->decoded_fmt.fmt.pix_mp; + + tpg_free(&ctx->tpg); + + font = find_font(font_name); + if (font) { + tpg_init(&ctx->tpg, width, height); + + ret = tpg_alloc(&ctx->tpg, width); + if (ret) + goto err_alloc; + + tpg_set_font(font->data); + ret = tpg_s_fourcc(&ctx->tpg, + f->pixelformat); + + if (!ret) + goto err_fourcc; + + tpg_reset_source(&ctx->tpg, width, height, f->field); + + tpg_s_pattern(&ctx->tpg, TPG_PAT_75_COLORBAR); + + tpg_s_field(&ctx->tpg, f->field, false); + tpg_s_colorspace(&ctx->tpg, f->colorspace); + tpg_s_ycbcr_enc(&ctx->tpg, f->ycbcr_enc); + tpg_s_quantization(&ctx->tpg, f->quantization); + tpg_s_xfer_func(&ctx->tpg, f->xfer_func); + } else { + v4l2_err(&ctx->dev->v4l2_dev, + "Font %s not found\n", font_name); + + return -EINVAL; + } + + dprintk(ctx->dev, "Initialized the V4L2 test pattern generator, w=%d, h=%d, max_w=%d\n", + width, height, width); + + return 0; +err_alloc: + return ret; +err_fourcc: + tpg_free(&ctx->tpg); + return ret; +} + +static const u32 visl_decoded_fmts[] = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, +}; + +const struct visl_coded_format_desc visl_coded_fmts[] = { + { + .pixelformat = V4L2_PIX_FMT_FWHT_STATELESS, + .frmsize = { + .min_width = 640, + .max_width = 4096, + .step_width = 1, + .min_height = 360, + .max_height = 2160, + .step_height = 1, + }, + .ctrls = &visl_fwht_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_MPEG2_SLICE, + .frmsize = { + .min_width = 16, + .max_width = 1920, + .step_width = 1, + .min_height = 16, + .max_height = 1152, + .step_height = 1, + }, + .ctrls = &visl_mpeg2_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_VP8_FRAME, + .frmsize = { + .min_width = 64, + .max_width = 16383, + .step_width = 1, + .min_height = 64, + .max_height = 16383, + .step_height = 1, + }, + .ctrls = &visl_vp8_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_VP9_FRAME, + .frmsize = { + .min_width = 64, + .max_width = 8192, + .step_width = 1, + .min_height = 64, + .max_height = 4352, + .step_height = 1, + }, + .ctrls = &visl_vp9_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_H264_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 4096, + .step_width = 1, + .min_height = 64, + .max_height = 2304, + .step_height = 1, + }, + .ctrls = &visl_h264_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, + { + .pixelformat = V4L2_PIX_FMT_HEVC_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 4096, + .step_width = 1, + .min_height = 64, + .max_height = 2304, + .step_height = 1, + }, + .ctrls = &visl_hevc_ctrls, + .num_decoded_fmts = ARRAY_SIZE(visl_decoded_fmts), + .decoded_fmts = visl_decoded_fmts, + }, +}; + +const size_t num_coded_fmts = ARRAY_SIZE(visl_coded_fmts); + +static const struct visl_coded_format_desc* +visl_find_coded_fmt_desc(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(visl_coded_fmts); i++) { + if (visl_coded_fmts[i].pixelformat == fourcc) + return &visl_coded_fmts[i]; + } + + return NULL; +} + +static void visl_init_fmt(struct v4l2_format *f, u32 fourcc) +{ memset(f, 0, sizeof(*f)); + f->fmt.pix_mp.pixelformat = fourcc; + f->fmt.pix_mp.field = V4L2_FIELD_NONE; + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; + f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_DEFAULT; + f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static void visl_reset_coded_fmt(struct visl_ctx *ctx) +{ + struct v4l2_format *f = &ctx->coded_fmt; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + + ctx->coded_format_desc = &visl_coded_fmts[0]; + visl_init_fmt(f, ctx->coded_format_desc->pixelformat); + + f->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + f->fmt.pix_mp.width = ctx->coded_format_desc->frmsize.min_width; + f->fmt.pix_mp.height = ctx->coded_format_desc->frmsize.min_height; + + pix_mp->num_planes = 1; + pix_mp->plane_fmt[0].sizeimage = pix_mp->width * pix_mp->height * 8; + + dprintk(ctx->dev, "OUTPUT format was set to:\n"); + visl_print_fmt(ctx, &ctx->coded_fmt); + + visl_set_current_codec(ctx); +} + +static int visl_reset_decoded_fmt(struct visl_ctx *ctx) +{ + struct v4l2_format *f = &ctx->decoded_fmt; + u32 decoded_fmt = ctx->coded_format_desc[0].decoded_fmts[0]; + + visl_init_fmt(f, decoded_fmt); + + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + v4l2_fill_pixfmt_mp(&f->fmt.pix_mp, + ctx->coded_format_desc->decoded_fmts[0], + ctx->coded_fmt.fmt.pix_mp.width, + ctx->coded_fmt.fmt.pix_mp.height); + + dprintk(ctx->dev, "CAPTURE format was set to:\n"); + visl_print_fmt(ctx, &ctx->decoded_fmt); + + return visl_tpg_init(ctx); +} + +int visl_set_default_format(struct visl_ctx *ctx) +{ + visl_reset_coded_fmt(ctx); + return visl_reset_decoded_fmt(ctx); +} + +static struct visl_q_data *get_q_data(struct visl_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return &ctx->q_data[V4L2_M2M_SRC]; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + return &ctx->q_data[V4L2_M2M_DST]; + default: + break; + } + return NULL; +} + +static int visl_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, VISL_NAME, sizeof(cap->driver)); + strscpy(cap->card, VISL_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", VISL_NAME); + + return 0; +} + +static int visl_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + + if (f->index >= ctx->coded_format_desc->num_decoded_fmts) + return -EINVAL; + + f->pixelformat = ctx->coded_format_desc->decoded_fmts[f->index]; + return 0; +} + +static int visl_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(visl_coded_fmts)) + return -EINVAL; + + f->pixelformat = visl_coded_fmts[f->index].pixelformat; + return 0; +} + +static int visl_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + *f = ctx->decoded_fmt; + + return 0; +} + +static int visl_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + + *f = ctx->coded_fmt; + return 0; +} + +static int visl_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct visl_ctx *ctx = visl_file_to_ctx(file); + const struct visl_coded_format_desc *coded_desc; + unsigned int i; + + coded_desc = ctx->coded_format_desc; + + for (i = 0; i < coded_desc->num_decoded_fmts; i++) { + if (coded_desc->decoded_fmts[i] == pix_mp->pixelformat) + break; + } + + if (i == coded_desc->num_decoded_fmts) + pix_mp->pixelformat = coded_desc->decoded_fmts[0]; + + v4l2_apply_frmsize_constraints(&pix_mp->width, + &pix_mp->height, + &coded_desc->frmsize); + + v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, + pix_mp->width, pix_mp->height); + + pix_mp->field = V4L2_FIELD_NONE; + + return 0; +} + +static int visl_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct visl_coded_format_desc *coded_desc; + + coded_desc = visl_find_coded_fmt_desc(pix_mp->pixelformat); + if (!coded_desc) { + pix_mp->pixelformat = visl_coded_fmts[0].pixelformat; + coded_desc = &visl_coded_fmts[0]; + } + + v4l2_apply_frmsize_constraints(&pix_mp->width, + &pix_mp->height, + &coded_desc->frmsize); + + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->num_planes = 1; + + if (pix_mp->plane_fmt[0].sizeimage == 0) + pix_mp->plane_fmt[0].sizeimage = max(MIN_CODED_SZ, + pix_mp->width * pix_mp->height * 3); + + return 0; +} + +static int visl_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + const struct visl_coded_format_desc *desc; + struct vb2_queue *peer_vq; + int ret; + + peer_vq = v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (vb2_is_busy(peer_vq)) + return -EBUSY; + + dprintk(ctx->dev, "Trying to set the OUTPUT format to:\n"); + visl_print_fmt(ctx, f); + + ret = visl_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + desc = visl_find_coded_fmt_desc(f->fmt.pix_mp.pixelformat); + ctx->coded_format_desc = desc; + ctx->coded_fmt = *f; + + ret = visl_reset_decoded_fmt(ctx); + if (ret) + return ret; + + ctx->decoded_fmt.fmt.pix_mp.colorspace = f->fmt.pix_mp.colorspace; + ctx->decoded_fmt.fmt.pix_mp.xfer_func = f->fmt.pix_mp.xfer_func; + ctx->decoded_fmt.fmt.pix_mp.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; + ctx->decoded_fmt.fmt.pix_mp.quantization = f->fmt.pix_mp.quantization; + + dprintk(ctx->dev, "OUTPUT format was set to:\n"); + visl_print_fmt(ctx, &ctx->coded_fmt); + + visl_set_current_codec(ctx); + return 0; +} + +static int visl_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct visl_ctx *ctx = visl_file_to_ctx(file); + int ret; + + dprintk(ctx->dev, "Trying to set the CAPTURE format to:\n"); + visl_print_fmt(ctx, f); + + ret = visl_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ctx->decoded_fmt = *f; + + dprintk(ctx->dev, "CAPTURE format was set to:\n"); + visl_print_fmt(ctx, &ctx->decoded_fmt); + + visl_tpg_init(ctx); + return 0; +} + +static int visl_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + const struct visl_coded_format_desc *fmt; + struct visl_ctx *ctx = visl_file_to_ctx(file); + + if (fsize->index != 0) + return -EINVAL; + + fmt = visl_find_coded_fmt_desc(fsize->pixel_format); + if (!fmt) { + dprintk(ctx->dev, + "Unsupported format for the OUTPUT queue: %d\n", + fsize->pixel_format); + + return -EINVAL; + } + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise = fmt->frmsize; + return 0; +} + +const struct v4l2_ioctl_ops visl_ioctl_ops = { + .vidioc_querycap = visl_querycap, + .vidioc_enum_framesizes = visl_enum_framesizes, + + .vidioc_enum_fmt_vid_cap = visl_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane = visl_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane = visl_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = visl_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = visl_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out_mplane = visl_g_fmt_vid_out, + .vidioc_try_fmt_vid_out_mplane = visl_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane = visl_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int visl_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_format *f; + u32 i; + char *qname; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + f = &ctx->coded_fmt; + qname = "Output"; + } else { + f = &ctx->decoded_fmt; + qname = "Capture"; + } + + if (*num_planes) { + if (*num_planes != f->fmt.pix_mp.num_planes) + return -EINVAL; + + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) { + if (sizes[i] < f->fmt.pix_mp.plane_fmt[i].sizeimage) + return -EINVAL; + } + } else { + *num_planes = f->fmt.pix_mp.num_planes; + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) + sizes[i] = f->fmt.pix_mp.plane_fmt[i].sizeimage; + } + + dprintk(ctx->dev, "%s: %d buffer(s) requested, num_planes=%d.\n", + qname, *nbuffers, *num_planes); + + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) + dprintk(ctx->dev, "plane[%d].sizeimage=%d\n", + i, f->fmt.pix_mp.plane_fmt[i].sizeimage); + + return 0; +} + +static void visl_queue_cleanup(struct vb2_queue *vq, u32 state) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf; + + dprintk(ctx->dev, "Cleaning up queues\n"); + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (!vbuf) + break; + + v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, + &ctx->hdl); + dprintk(ctx->dev, "Marked request %p as complete\n", + vbuf->vb2_buf.req_obj.req); + + v4l2_m2m_buf_done(vbuf, state); + dprintk(ctx->dev, + "Marked buffer %llu as done, state is %d\n", + vbuf->vb2_buf.timestamp, + state); + } +} + +static int visl_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static int visl_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + u32 plane_sz = vb2_plane_size(vb, 0); + struct v4l2_pix_format *pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + pix_fmt = &ctx->coded_fmt.fmt.pix; + } else { + pix_fmt = &ctx->decoded_fmt.fmt.pix; + vb2_set_plane_payload(vb, 0, pix_fmt->sizeimage); + } + + if (plane_sz < pix_fmt->sizeimage) { + v4l2_err(&ctx->dev->v4l2_dev, "plane[0] size is %d, sizeimage is %d\n", + plane_sz, pix_fmt->sizeimage); + return -EINVAL; + } + + return 0; +} + +static int visl_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + struct visl_q_data *q_data = get_q_data(ctx, vq->type); + int rc = 0; + + if (!q_data) { + rc = -EINVAL; + goto err; + } + + q_data->sequence = 0; + + if (V4L2_TYPE_IS_CAPTURE(vq->type)) { + ctx->capture_streamon_jiffies = get_jiffies_64(); + return 0; + } + + if (WARN_ON(!ctx->coded_format_desc)) { + rc = -EINVAL; + goto err; + } + + return 0; + +err: + visl_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); + return rc; +} + +static void visl_stop_streaming(struct vb2_queue *vq) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vq); + + dprintk(ctx->dev, "Stop streaming\n"); + visl_queue_cleanup(vq, VB2_BUF_STATE_ERROR); + + if (!keep_bitstream_buffers) + visl_debugfs_clear_bitstream(ctx->dev); +} + +static void visl_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct visl_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void visl_buf_request_complete(struct vb2_buffer *vb) +{ + struct visl_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl); +} + +const struct vb2_ops visl_qops = { + .queue_setup = visl_queue_setup, + .buf_out_validate = visl_buf_out_validate, + .buf_prepare = visl_buf_prepare, + .buf_queue = visl_buf_queue, + .start_streaming = visl_start_streaming, + .stop_streaming = visl_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_request_complete = visl_buf_request_complete, +}; + +int visl_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct visl_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &visl_qops; + src_vq->mem_ops = &vb2_vmalloc_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->vb_mutex; + src_vq->supports_requests = true; + src_vq->subsystem_flags |= VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &visl_qops; + dst_vq->mem_ops = &vb2_vmalloc_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->vb_mutex; + + return vb2_queue_init(dst_vq); +} + +int visl_request_validate(struct media_request *req) +{ + struct media_request_object *obj; + struct visl_ctx *ctx = NULL; + unsigned int count; + + list_for_each_entry(obj, &req->objects, list) { + struct vb2_buffer *vb; + + if (vb2_request_object_is_buffer(obj)) { + vb = container_of(obj, struct vb2_buffer, req_obj); + ctx = vb2_get_drv_priv(vb->vb2_queue); + + break; + } + } + + if (!ctx) + return -ENOENT; + + count = vb2_request_buffer_cnt(req); + if (!count) { + v4l2_err(&ctx->dev->v4l2_dev, + "No buffer was provided with the request\n"); + return -ENOENT; + } else if (count > 1) { + v4l2_err(&ctx->dev->v4l2_dev, + "More than one buffer was provided with the request\n"); + return -EINVAL; + } + + return vb2_request_validate(req); +} diff --git a/drivers/media/test-drivers/visl/visl-video.h b/drivers/media/test-drivers/visl/visl-video.h new file mode 100644 index 000000000000..27ad70a558db --- /dev/null +++ b/drivers/media/test-drivers/visl/visl-video.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Contains the driver implementation for the V4L2 stateless interface. + */ + +#ifndef _VISL_VIDEO_H_ +#define _VISL_VIDEO_H_ +#include <media/v4l2-mem2mem.h> + +#include "visl.h" + +extern const struct v4l2_ioctl_ops visl_ioctl_ops; + +extern const struct visl_ctrls visl_fwht_ctrls; +extern const struct visl_ctrls visl_mpeg2_ctrls; +extern const struct visl_ctrls visl_vp8_ctrls; +extern const struct visl_ctrls visl_vp9_ctrls; +extern const struct visl_ctrls visl_h264_ctrls; +extern const struct visl_ctrls visl_hevc_ctrls; + +int visl_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); + +int visl_set_default_format(struct visl_ctx *ctx); +int visl_request_validate(struct media_request *req); + +#endif /* _VISL_VIDEO_H_ */ diff --git a/drivers/media/test-drivers/visl/visl.h b/drivers/media/test-drivers/visl/visl.h new file mode 100644 index 000000000000..31639f2e593d --- /dev/null +++ b/drivers/media/test-drivers/visl/visl.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * A virtual stateless device for stateless uAPI development purposes. + * + * This tool's objective is to help the development and testing of userspace + * applications that use the V4L2 stateless API to decode media. + * + * A userspace implementation can use visl to run a decoding loop even when no + * hardware is available or when the kernel uAPI for the codec has not been + * upstreamed yet. This can reveal bugs at an early stage. + * + * This driver can also trace the contents of the V4L2 controls submitted to it. + * It can also dump the contents of the vb2 buffers through a debugfs + * interface. This is in many ways similar to the tracing infrastructure + * available for other popular encode/decode APIs out there and can help develop + * a userspace application by using another (working) one as a reference. + * + * Note that no actual decoding of video frames is performed by visl. The V4L2 + * test pattern generator is used to write various debug information to the + * capture buffers instead. + * + * Copyright (C) 2022 Collabora, Ltd. + * + * Based on the vim2m driver, that is: + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, <pawel@osciak.com> + * Marek Szyprowski, <m.szyprowski@samsung.com> + * + * Based on the vicodec driver, that is: + * + * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#ifndef _VISL_H_ +#define _VISL_H_ + +#include <linux/debugfs.h> +#include <linux/list.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/tpg/v4l2-tpg.h> + +#define VISL_NAME "visl" +#define VISL_M2M_NQUEUES 2 + +#define TPG_STR_BUF_SZ 2048 + +extern unsigned int visl_transtime_ms; + +struct visl_ctrls { + const struct visl_ctrl_desc *ctrls; + unsigned int num_ctrls; +}; + +struct visl_coded_format_desc { + u32 pixelformat; + struct v4l2_frmsize_stepwise frmsize; + const struct visl_ctrls *ctrls; + unsigned int num_decoded_fmts; + const u32 *decoded_fmts; +}; + +extern const struct visl_coded_format_desc visl_coded_fmts[]; +extern const size_t num_coded_fmts; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +extern unsigned int visl_debug; +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, visl_debug, &(dev)->v4l2_dev, "%s: " fmt, __func__, ## arg) + +extern int visl_dprintk_frame_start; +extern unsigned int visl_dprintk_nframes; +extern bool keep_bitstream_buffers; +extern int bitstream_trace_frame_start; +extern unsigned int bitstream_trace_nframes; + +#define frame_dprintk(dev, current, fmt, arg...) \ + do { \ + if (visl_dprintk_frame_start > -1 && \ + (current) >= visl_dprintk_frame_start && \ + (current) < visl_dprintk_frame_start + visl_dprintk_nframes) \ + dprintk(dev, fmt, ## arg); \ + } while (0) \ + +struct visl_q_data { + unsigned int sequence; +}; + +struct visl_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device mdev; +#endif + + struct mutex dev_mutex; + + struct v4l2_m2m_dev *m2m_dev; + +#ifdef CONFIG_VISL_DEBUGFS + struct dentry *debugfs_root; + struct dentry *bitstream_debugfs; + struct list_head bitstream_blobs; + + /* Protects the "blob" list */ + struct mutex bitstream_lock; +#endif +}; + +enum visl_codec { + VISL_CODEC_NONE, + VISL_CODEC_FWHT, + VISL_CODEC_MPEG2, + VISL_CODEC_VP8, + VISL_CODEC_VP9, + VISL_CODEC_H264, + VISL_CODEC_HEVC, +}; + +struct visl_blob { + struct list_head list; + struct dentry *dentry; + struct debugfs_blob_wrapper blob; +}; + +struct visl_ctx { + struct v4l2_fh fh; + struct visl_dev *dev; + struct v4l2_ctrl_handler hdl; + + struct mutex vb_mutex; + + struct visl_q_data q_data[VISL_M2M_NQUEUES]; + enum visl_codec current_codec; + + const struct visl_coded_format_desc *coded_format_desc; + + struct v4l2_format coded_fmt; + struct v4l2_format decoded_fmt; + + struct tpg_data tpg; + u64 capture_streamon_jiffies; + char *tpg_str_buf; +}; + +struct visl_ctrl_desc { + struct v4l2_ctrl_config cfg; +}; + +static inline struct visl_ctx *visl_file_to_ctx(struct file *file) +{ + return container_of(file->private_data, struct visl_ctx, fh); +} + +static inline struct visl_ctx *visl_v4l2fh_to_ctx(struct v4l2_fh *v4l2_fh) +{ + return container_of(v4l2_fh, struct visl_ctx, fh); +} + +void *visl_find_control_data(struct visl_ctx *ctx, u32 id); +struct v4l2_ctrl *visl_find_control(struct visl_ctx *ctx, u32 id); +u32 visl_control_num_elems(struct visl_ctx *ctx, u32 id); + +#endif /* _VISL_H_ */ diff --git a/drivers/media/test-drivers/vivid/vivid-ctrls.c b/drivers/media/test-drivers/vivid/vivid-ctrls.c index 92b1a7598470..f2b20e25a7a4 100644 --- a/drivers/media/test-drivers/vivid/vivid-ctrls.c +++ b/drivers/media/test-drivers/vivid/vivid-ctrls.c @@ -36,6 +36,8 @@ #define VIVID_CID_RO_INTEGER (VIVID_CID_CUSTOM_BASE + 12) #define VIVID_CID_U32_DYN_ARRAY (VIVID_CID_CUSTOM_BASE + 13) #define VIVID_CID_U8_PIXEL_ARRAY (VIVID_CID_CUSTOM_BASE + 14) +#define VIVID_CID_S32_ARRAY (VIVID_CID_CUSTOM_BASE + 15) +#define VIVID_CID_S64_ARRAY (VIVID_CID_CUSTOM_BASE + 16) #define VIVID_CID_VIVID_BASE (0x00f00000 | 0xf000) #define VIVID_CID_VIVID_CLASS (0x00f00000 | 1) @@ -241,6 +243,30 @@ static const struct v4l2_ctrl_config vivid_ctrl_u8_pixel_array = { .dims = { 640 / PIXEL_ARRAY_DIV, 360 / PIXEL_ARRAY_DIV }, }; +static const struct v4l2_ctrl_config vivid_ctrl_s32_array = { + .ops = &vivid_user_gen_ctrl_ops, + .id = VIVID_CID_S32_ARRAY, + .name = "S32 2 Element Array", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = 2, + .min = -10, + .max = 10, + .step = 1, + .dims = { 2 }, +}; + +static const struct v4l2_ctrl_config vivid_ctrl_s64_array = { + .ops = &vivid_user_gen_ctrl_ops, + .id = VIVID_CID_S64_ARRAY, + .name = "S64 5 Element Array", + .type = V4L2_CTRL_TYPE_INTEGER64, + .def = 4, + .min = -10, + .max = 10, + .step = 1, + .dims = { 5 }, +}; + static const char * const vivid_ctrl_menu_strings[] = { "Menu Item 0 (Skipped)", "Menu Item 1", @@ -1656,6 +1682,8 @@ int vivid_create_controls(struct vivid_dev *dev, bool show_ccs_cap, v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_u16_matrix, NULL); v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_u8_4d_array, NULL); dev->pixel_array = v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_u8_pixel_array, NULL); + v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_s32_array, NULL); + v4l2_ctrl_new_custom(hdl_user_gen, &vivid_ctrl_s64_array, NULL); if (dev->has_vid_cap) { /* Image Processing Controls */ diff --git a/drivers/media/test-drivers/vivid/vivid-vbi-gen.c b/drivers/media/test-drivers/vivid/vivid-vbi-gen.c index a141369a7a63..70a4024d461e 100644 --- a/drivers/media/test-drivers/vivid/vivid-vbi-gen.c +++ b/drivers/media/test-drivers/vivid/vivid-vbi-gen.c @@ -194,7 +194,6 @@ static void vivid_vbi_gen_set_time_of_day(u8 *packet) for (checksum = i = 0; i <= 8; i++) checksum += packet[i] & 0x7f; packet[9] = calc_parity(0x100 - checksum); - checksum = 0; packet[10] = calc_parity(0x07); packet[11] = calc_parity(0x04); if (sys_tz.tz_minuteswest >= 0) diff --git a/drivers/media/test-drivers/vivid/vivid-vid-cap.c b/drivers/media/test-drivers/vivid/vivid-vid-cap.c index 11620eaf941e..c0999581c599 100644 --- a/drivers/media/test-drivers/vivid/vivid-vid-cap.c +++ b/drivers/media/test-drivers/vivid/vivid-vid-cap.c @@ -973,6 +973,7 @@ int vivid_vid_cap_s_selection(struct file *file, void *fh, struct v4l2_selection if (dev->has_compose_cap) { v4l2_rect_set_min_size(compose, &min_rect); v4l2_rect_set_max_size(compose, &max_rect); + v4l2_rect_map_inside(compose, &fmt); } dev->fmt_cap_rect = fmt; tpg_s_buf_height(&dev->tpg, fmt.height); diff --git a/drivers/media/tuners/e4000.c b/drivers/media/tuners/e4000.c index 61ae884ea59a..7c269f3159ef 100644 --- a/drivers/media/tuners/e4000.c +++ b/drivers/media/tuners/e4000.c @@ -609,8 +609,7 @@ static const struct dvb_tuner_ops e4000_dvb_tuner_ops = { .get_if_frequency = e4000_dvb_get_if_frequency, }; -static int e4000_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int e4000_probe(struct i2c_client *client) { struct e4000_dev *dev; struct e4000_config *cfg = client->dev.platform_data; @@ -730,7 +729,7 @@ static struct i2c_driver e4000_driver = { .name = "e4000", .suppress_bind_attrs = true, }, - .probe = e4000_probe, + .probe_new = e4000_probe, .remove = e4000_remove, .id_table = e4000_id_table, }; diff --git a/drivers/media/tuners/fc2580.c b/drivers/media/tuners/fc2580.c index f30932e1a0f3..3cd8279f4f2e 100644 --- a/drivers/media/tuners/fc2580.c +++ b/drivers/media/tuners/fc2580.c @@ -506,8 +506,7 @@ static struct v4l2_subdev *fc2580_get_v4l2_subdev(struct i2c_client *client) return NULL; } -static int fc2580_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int fc2580_probe(struct i2c_client *client) { struct fc2580_dev *dev; struct fc2580_platform_data *pdata = client->dev.platform_data; @@ -611,7 +610,7 @@ static struct i2c_driver fc2580_driver = { .name = "fc2580", .suppress_bind_attrs = true, }, - .probe = fc2580_probe, + .probe_new = fc2580_probe, .remove = fc2580_remove, .id_table = fc2580_id_table, }; diff --git a/drivers/media/tuners/m88rs6000t.c b/drivers/media/tuners/m88rs6000t.c index e32e3e9daa15..7d172a5a66d9 100644 --- a/drivers/media/tuners/m88rs6000t.c +++ b/drivers/media/tuners/m88rs6000t.c @@ -573,8 +573,7 @@ static const struct dvb_tuner_ops m88rs6000t_tuner_ops = { .get_rf_strength = m88rs6000t_get_rf_strength, }; -static int m88rs6000t_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int m88rs6000t_probe(struct i2c_client *client) { struct m88rs6000t_config *cfg = client->dev.platform_data; struct dvb_frontend *fe = cfg->fe; @@ -719,7 +718,7 @@ static struct i2c_driver m88rs6000t_driver = { .driver = { .name = "m88rs6000t", }, - .probe = m88rs6000t_probe, + .probe_new = m88rs6000t_probe, .remove = m88rs6000t_remove, .id_table = m88rs6000t_id, }; diff --git a/drivers/media/tuners/mt2060.c b/drivers/media/tuners/mt2060.c index 322c806228a5..e5d86874adb3 100644 --- a/drivers/media/tuners/mt2060.c +++ b/drivers/media/tuners/mt2060.c @@ -442,8 +442,7 @@ struct dvb_frontend * mt2060_attach(struct dvb_frontend *fe, struct i2c_adapter } EXPORT_SYMBOL(mt2060_attach); -static int mt2060_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mt2060_probe(struct i2c_client *client) { struct mt2060_platform_data *pdata = client->dev.platform_data; struct dvb_frontend *fe; @@ -525,7 +524,7 @@ static struct i2c_driver mt2060_driver = { .name = "mt2060", .suppress_bind_attrs = true, }, - .probe = mt2060_probe, + .probe_new = mt2060_probe, .remove = mt2060_remove, .id_table = mt2060_id_table, }; diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c index 6422056185a9..c35442a77ae5 100644 --- a/drivers/media/tuners/mxl301rf.c +++ b/drivers/media/tuners/mxl301rf.c @@ -283,8 +283,7 @@ static const struct dvb_tuner_ops mxl301rf_ops = { }; -static int mxl301rf_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mxl301rf_probe(struct i2c_client *client) { struct mxl301rf_state *state; struct mxl301rf_config *cfg; @@ -327,7 +326,7 @@ static struct i2c_driver mxl301rf_driver = { .driver = { .name = "mxl301rf", }, - .probe = mxl301rf_probe, + .probe_new = mxl301rf_probe, .remove = mxl301rf_remove, .id_table = mxl301rf_id, }; diff --git a/drivers/media/tuners/mxl5005s.c b/drivers/media/tuners/mxl5005s.c index ab4c43df9d18..3a509038c8df 100644 --- a/drivers/media/tuners/mxl5005s.c +++ b/drivers/media/tuners/mxl5005s.c @@ -3637,7 +3637,7 @@ static u16 MXL_GetCHRegister_ZeroIF(struct dvb_frontend *fe, u8 *RegNum, u16 status = 0; int i; - u8 RegAddr[] = {43, 136}; + static const u8 RegAddr[] = {43, 136}; *count = ARRAY_SIZE(RegAddr); diff --git a/drivers/media/tuners/qm1d1b0004.c b/drivers/media/tuners/qm1d1b0004.c index 9cba0893207c..0b6f750c54ad 100644 --- a/drivers/media/tuners/qm1d1b0004.c +++ b/drivers/media/tuners/qm1d1b0004.c @@ -197,7 +197,7 @@ static const struct dvb_tuner_ops qm1d1b0004_ops = { }; static int -qm1d1b0004_probe(struct i2c_client *client, const struct i2c_device_id *id) +qm1d1b0004_probe(struct i2c_client *client) { struct dvb_frontend *fe; struct qm1d1b0004_config *cfg; @@ -253,7 +253,7 @@ static struct i2c_driver qm1d1b0004_driver = { .driver = { .name = "qm1d1b0004", }, - .probe = qm1d1b0004_probe, + .probe_new = qm1d1b0004_probe, .remove = qm1d1b0004_remove, .id_table = qm1d1b0004_id, }; diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c index 2d60bf501fb5..f9be7a721d2c 100644 --- a/drivers/media/tuners/qm1d1c0042.c +++ b/drivers/media/tuners/qm1d1c0042.c @@ -401,8 +401,7 @@ static const struct dvb_tuner_ops qm1d1c0042_ops = { }; -static int qm1d1c0042_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int qm1d1c0042_probe(struct i2c_client *client) { struct qm1d1c0042_state *state; struct qm1d1c0042_config *cfg; @@ -444,7 +443,7 @@ static struct i2c_driver qm1d1c0042_driver = { .driver = { .name = "qm1d1c0042", }, - .probe = qm1d1c0042_probe, + .probe_new = qm1d1c0042_probe, .remove = qm1d1c0042_remove, .id_table = qm1d1c0042_id, }; diff --git a/drivers/media/tuners/tda18212.c b/drivers/media/tuners/tda18212.c index eb97711c9c68..5fdf05a97415 100644 --- a/drivers/media/tuners/tda18212.c +++ b/drivers/media/tuners/tda18212.c @@ -173,8 +173,7 @@ static const struct dvb_tuner_ops tda18212_tuner_ops = { .get_if_frequency = tda18212_get_if_frequency, }; -static int tda18212_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda18212_probe(struct i2c_client *client) { struct tda18212_config *cfg = client->dev.platform_data; struct dvb_frontend *fe = cfg->fe; @@ -264,7 +263,7 @@ static struct i2c_driver tda18212_driver = { .driver = { .name = "tda18212", }, - .probe = tda18212_probe, + .probe_new = tda18212_probe, .remove = tda18212_remove, .id_table = tda18212_id, }; diff --git a/drivers/media/tuners/tda18250.c b/drivers/media/tuners/tda18250.c index e404a5afad4c..66ff2d035de7 100644 --- a/drivers/media/tuners/tda18250.c +++ b/drivers/media/tuners/tda18250.c @@ -741,8 +741,7 @@ static const struct dvb_tuner_ops tda18250_ops = { .sleep = tda18250_sleep, }; -static int tda18250_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda18250_probe(struct i2c_client *client) { struct tda18250_config *cfg = client->dev.platform_data; struct dvb_frontend *fe = cfg->fe; @@ -878,7 +877,7 @@ static struct i2c_driver tda18250_driver = { .driver = { .name = "tda18250", }, - .probe = tda18250_probe, + .probe_new = tda18250_probe, .remove = tda18250_remove, .id_table = tda18250_id_table, }; diff --git a/drivers/media/tuners/tua9001.c b/drivers/media/tuners/tua9001.c index d141d000b819..ac38afd3441a 100644 --- a/drivers/media/tuners/tua9001.c +++ b/drivers/media/tuners/tua9001.c @@ -167,8 +167,7 @@ static const struct dvb_tuner_ops tua9001_tuner_ops = { .get_if_frequency = tua9001_get_if_frequency, }; -static int tua9001_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tua9001_probe(struct i2c_client *client) { struct tua9001_dev *dev; struct tua9001_platform_data *pdata = client->dev.platform_data; @@ -256,7 +255,7 @@ static struct i2c_driver tua9001_driver = { .name = "tua9001", .suppress_bind_attrs = true, }, - .probe = tua9001_probe, + .probe_new = tua9001_probe, .remove = tua9001_remove, .id_table = tua9001_id_table, }; diff --git a/drivers/media/usb/au0828/au0828-vbi.c b/drivers/media/usb/au0828/au0828-vbi.c index 97f5e8733c2a..b0333637b747 100644 --- a/drivers/media/usb/au0828/au0828-vbi.c +++ b/drivers/media/usb/au0828/au0828-vbi.c @@ -14,6 +14,7 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> +#include <media/v4l2-mc.h> /* ------------------------------------------------------------------ */ @@ -70,6 +71,7 @@ const struct vb2_ops au0828_vbi_qops = { .queue_setup = vbi_queue_setup, .buf_prepare = vbi_buffer_prepare, .buf_queue = vbi_buffer_queue, + .prepare_streaming = v4l_vb2q_enable_media_source, .start_streaming = au0828_start_analog_streaming, .stop_streaming = au0828_stop_vbi_streaming, .wait_prepare = vb2_ops_wait_prepare, diff --git a/drivers/media/usb/au0828/au0828-video.c b/drivers/media/usb/au0828/au0828-video.c index eb303e94cceb..fd9fc43d47e0 100644 --- a/drivers/media/usb/au0828/au0828-video.c +++ b/drivers/media/usb/au0828/au0828-video.c @@ -915,6 +915,7 @@ static const struct vb2_ops au0828_video_qops = { .queue_setup = queue_setup, .buf_prepare = buffer_prepare, .buf_queue = buffer_queue, + .prepare_streaming = v4l_vb2q_enable_media_source, .start_streaming = au0828_start_analog_streaming, .stop_streaming = au0828_stop_streaming, .wait_prepare = vb2_ops_wait_prepare, diff --git a/drivers/media/usb/dvb-usb/az6027.c b/drivers/media/usb/dvb-usb/az6027.c index cf15988dfb51..7d78ee09be5e 100644 --- a/drivers/media/usb/dvb-usb/az6027.c +++ b/drivers/media/usb/dvb-usb/az6027.c @@ -975,6 +975,10 @@ static int az6027_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], int n if (msg[i].addr == 0x99) { req = 0xBE; index = 0; + if (msg[i].len < 1) { + i = -EOPNOTSUPP; + break; + } value = msg[i].buf[0] & 0x00ff; length = 1; az6027_usb_out_op(d, req, value, index, data, length); diff --git a/drivers/media/usb/dvb-usb/dib0700.h b/drivers/media/usb/dvb-usb/dib0700.h index 2defbd8b6fc1..ac06a9ab2641 100644 --- a/drivers/media/usb/dvb-usb/dib0700.h +++ b/drivers/media/usb/dvb-usb/dib0700.h @@ -72,7 +72,6 @@ int dib0700_set_i2c_speed(struct dvb_usb_device *d, u16 scl_kHz); extern struct i2c_algorithm dib0700_i2c_algo; extern int dib0700_device_count; -extern int dvb_usb_dib0700_ir_proto; extern struct dvb_usb_device_properties dib0700_devices[]; extern struct usb_device_id dib0700_usb_id_table[]; diff --git a/drivers/media/usb/dvb-usb/dib0700_devices.c b/drivers/media/usb/dvb-usb/dib0700_devices.c index 7f8bebfa3e8e..3af594134a6d 100644 --- a/drivers/media/usb/dvb-usb/dib0700_devices.c +++ b/drivers/media/usb/dvb-usb/dib0700_devices.c @@ -2024,13 +2024,6 @@ static struct dib0090_config tfe8096p_dib0090_config = { .force_cband_input = 0, }; -struct dibx090p_adc { - u32 freq; /* RF freq MHz */ - u32 timf; /* New Timf */ - u32 pll_loopdiv; /* New prediv */ - u32 pll_prediv; /* New loopdiv */ -}; - struct dibx090p_best_adc { u32 timf; u32 pll_loopdiv; diff --git a/drivers/media/usb/dvb-usb/dvb-usb-init.c b/drivers/media/usb/dvb-usb/dvb-usb-init.c index 61439c8f33ca..fbf58012becd 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb-init.c +++ b/drivers/media/usb/dvb-usb/dvb-usb-init.c @@ -81,7 +81,7 @@ static int dvb_usb_adapter_init(struct dvb_usb_device *d, short *adapter_nrs) ret = dvb_usb_adapter_stream_init(adap); if (ret) - return ret; + goto stream_init_err; ret = dvb_usb_adapter_dvb_init(adap, adapter_nrs); if (ret) @@ -92,7 +92,7 @@ static int dvb_usb_adapter_init(struct dvb_usb_device *d, short *adapter_nrs) goto frontend_init_err; /* use exclusive FE lock if there is multiple shared FEs */ - if (adap->fe_adap[1].fe) + if (adap->fe_adap[1].fe && adap->dvb_adap.mfe_shared < 1) adap->dvb_adap.mfe_shared = 1; d->num_adapters_initialized++; @@ -114,6 +114,8 @@ frontend_init_err: dvb_usb_adapter_dvb_exit(adap); dvb_init_err: dvb_usb_adapter_stream_exit(adap); +stream_init_err: + kfree(adap->priv); return ret; } diff --git a/drivers/media/usb/dvb-usb/m920x.c b/drivers/media/usb/dvb-usb/m920x.c index 548199cd86f6..fea5bcf72a31 100644 --- a/drivers/media/usb/dvb-usb/m920x.c +++ b/drivers/media/usb/dvb-usb/m920x.c @@ -485,14 +485,14 @@ static int m920x_identify_state(struct usb_device *udev, static int m920x_mt352_demod_init(struct dvb_frontend *fe) { int ret; - u8 config[] = { CONFIG, 0x3d }; - u8 clock[] = { CLOCK_CTL, 0x30 }; - u8 reset[] = { RESET, 0x80 }; - u8 adc_ctl[] = { ADC_CTL_1, 0x40 }; - u8 agc[] = { AGC_TARGET, 0x1c, 0x20 }; - u8 sec_agc[] = { 0x69, 0x00, 0xff, 0xff, 0x40, 0xff, 0x00, 0x40, 0x40 }; - u8 unk1[] = { 0x93, 0x1a }; - u8 unk2[] = { 0xb5, 0x7a }; + static const u8 config[] = { CONFIG, 0x3d }; + static const u8 clock[] = { CLOCK_CTL, 0x30 }; + static const u8 reset[] = { RESET, 0x80 }; + static const u8 adc_ctl[] = { ADC_CTL_1, 0x40 }; + static const u8 agc[] = { AGC_TARGET, 0x1c, 0x20 }; + static const u8 sec_agc[] = { 0x69, 0x00, 0xff, 0xff, 0x40, 0xff, 0x00, 0x40, 0x40 }; + static const u8 unk1[] = { 0x93, 0x1a }; + static const u8 unk2[] = { 0xb5, 0x7a }; deb("Demod init!\n"); diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c index 185e89c18d68..9fce59979e3b 100644 --- a/drivers/media/usb/em28xx/em28xx-dvb.c +++ b/drivers/media/usb/em28xx/em28xx-dvb.c @@ -1204,6 +1204,12 @@ static int em28178_dvb_init_pctv_461e(struct em28xx *dev) /* attach SEC */ a8293_pdata.dvb_frontend = dvb->fe[0]; + /* + * 461e has a tendency to have vIN undervoltage troubles. + * Slew mitigates this. + */ + a8293_pdata.volt_slew_nanos_per_mv = 20; + dvb->i2c_client_sec = dvb_module_probe("a8293", NULL, &dev->i2c_adap[dev->def_i2c_bus], 0x08, &a8293_pdata); diff --git a/drivers/media/usb/go7007/s2250-board.c b/drivers/media/usb/go7007/s2250-board.c index 2f45188bf9d4..29dfcc6d0b0a 100644 --- a/drivers/media/usb/go7007/s2250-board.c +++ b/drivers/media/usb/go7007/s2250-board.c @@ -494,8 +494,7 @@ static const struct v4l2_subdev_ops s2250_ops = { /* --------------------------------------------------------------------------*/ -static int s2250_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int s2250_probe(struct i2c_client *client) { struct i2c_client *audio; struct i2c_adapter *adapter = client->adapter; @@ -621,7 +620,7 @@ static struct i2c_driver s2250_driver = { .driver = { .name = "s2250", }, - .probe = s2250_probe, + .probe_new = s2250_probe, .remove = s2250_remove, .id_table = s2250_id, }; diff --git a/drivers/media/usb/pwc/pwc-uncompress.c b/drivers/media/usb/pwc/pwc-uncompress.c index faf44cdeb268..cf2591a9675c 100644 --- a/drivers/media/usb/pwc/pwc-uncompress.c +++ b/drivers/media/usb/pwc/pwc-uncompress.c @@ -39,7 +39,7 @@ int pwc_decompress(struct pwc_device *pdev, struct pwc_frame_buf *fbuf) * first 3 bytes is filled (Nala case). We can * determine this using the type of the webcam */ memcpy(raw_frame->cmd, pdev->cmd_buf, 4); - memcpy(raw_frame+1, yuv, pdev->frame_size); + memcpy(raw_frame->rawframe, yuv, pdev->frame_size); vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, struct_size(raw_frame, rawframe, pdev->frame_size)); return 0; diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 215fb483efb0..e4bcb5011360 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1266,12 +1266,9 @@ static int uvc_gpio_parse(struct uvc_device *dev) return PTR_ERR_OR_ZERO(gpio_privacy); irq = gpiod_to_irq(gpio_privacy); - if (irq < 0) { - if (irq != EPROBE_DEFER) - dev_err(&dev->udev->dev, - "No IRQ for privacy GPIO (%d)\n", irq); - return irq; - } + if (irq < 0) + return dev_err_probe(&dev->udev->dev, irq, + "No IRQ for privacy GPIO\n"); unit = uvc_alloc_entity(UVC_EXT_GPIO_UNIT, UVC_EXT_GPIO_UNIT_ID, 0, 1); if (!unit) diff --git a/drivers/media/v4l2-core/tuner-core.c b/drivers/media/v4l2-core/tuner-core.c index 33162dc1daf6..1c0d23c52203 100644 --- a/drivers/media/v4l2-core/tuner-core.c +++ b/drivers/media/v4l2-core/tuner-core.c @@ -614,7 +614,6 @@ static void tuner_lookup(struct i2c_adapter *adap, *tuner_probe - Probes the existing tuners on an I2C bus * * @client: i2c_client descriptor - * @id: not used * * This routine probes for tuners at the expected I2C addresses. On most * cases, if a device answers to a given I2C address, it assumes that the @@ -625,8 +624,7 @@ static void tuner_lookup(struct i2c_adapter *adap, * During client attach, set_type is called by adapter's attach_inform callback. * set_type must then be completed by tuner_probe. */ -static int tuner_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tuner_probe(struct i2c_client *client) { struct tuner *t; struct tuner *radio; @@ -1413,7 +1411,7 @@ static struct i2c_driver tuner_driver = { .name = "tuner", .pm = &tuner_pm_ops, }, - .probe = tuner_probe, + .probe_new = tuner_probe, .remove = tuner_remove, .command = tuner_command, .id_table = tuner_id, diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 0dab1d7b90f0..29169170880a 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -1827,7 +1827,7 @@ struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, else if (type == V4L2_CTRL_TYPE_INTEGER_MENU) qmenu_int = v4l2_ctrl_get_int_menu(id, &qmenu_int_len); - if ((!qmenu && !qmenu_int) || (qmenu_int && max > qmenu_int_len)) { + if ((!qmenu && !qmenu_int) || (qmenu_int && max >= qmenu_int_len)) { handler_set_err(hdl, -EINVAL); return NULL; } diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index e22921e7ea61..564fedee2c88 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1043,6 +1043,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_UNIT_CELL_SIZE: return "Unit Cell Size"; case V4L2_CID_CAMERA_ORIENTATION: return "Camera Orientation"; case V4L2_CID_CAMERA_SENSOR_ROTATION: return "Camera Sensor Rotation"; + case V4L2_CID_HDR_SENSOR_MODE: return "HDR Sensor Mode"; /* FM Radio Modulator controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ @@ -1370,6 +1371,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_STATELESS_H264_START_CODE: case V4L2_CID_CAMERA_ORIENTATION: case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE: + case V4L2_CID_HDR_SENSOR_MODE: *type = V4L2_CTRL_TYPE_MENU; break; case V4L2_CID_LINK_FREQ: diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index 3d85a8600f57..3d9533c1b202 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -298,10 +298,25 @@ v4l2_fwnode_endpoint_parse_parallel_bus(struct fwnode_handle *fwnode, if (!fwnode_property_read_u32(fwnode, "pclk-sample", &v)) { flags &= ~(V4L2_MBUS_PCLK_SAMPLE_RISING | - V4L2_MBUS_PCLK_SAMPLE_FALLING); - flags |= v ? V4L2_MBUS_PCLK_SAMPLE_RISING : - V4L2_MBUS_PCLK_SAMPLE_FALLING; - pr_debug("pclk-sample %s\n", v ? "high" : "low"); + V4L2_MBUS_PCLK_SAMPLE_FALLING | + V4L2_MBUS_PCLK_SAMPLE_DUALEDGE); + switch (v) { + case 0: + flags |= V4L2_MBUS_PCLK_SAMPLE_FALLING; + pr_debug("pclk-sample low\n"); + break; + case 1: + flags |= V4L2_MBUS_PCLK_SAMPLE_RISING; + pr_debug("pclk-sample high\n"); + break; + case 2: + flags |= V4L2_MBUS_PCLK_SAMPLE_DUALEDGE; + pr_debug("pclk-sample dual edge\n"); + break; + default: + pr_warn("invalid argument for pclk-sample"); + break; + } } if (!fwnode_property_read_u32(fwnode, "data-active", &v)) { diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index fddba75d9074..8e0a0ff62a70 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -31,12 +31,6 @@ #include <trace/events/v4l2.h> -/* Zero out the end of the struct pointed to by p. Everything after, but - * not including, the specified field is cleared. */ -#define CLEAR_AFTER_FIELD(p, field) \ - memset((u8 *)(p) + offsetof(typeof(*(p)), field) + sizeof((p)->field), \ - 0, sizeof(*(p)) - offsetof(typeof(*(p)), field) - sizeof((p)->field)) - #define is_valid_ioctl(vfd, cmd) test_bit(_IOC_NR(cmd), (vfd)->valid_ioctls) struct std_descr { @@ -1347,23 +1341,23 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_YUV420: descr = "Planar YUV 4:2:0"; break; case V4L2_PIX_FMT_HI240: descr = "8-bit Dithered RGB (BTTV)"; break; case V4L2_PIX_FMT_M420: descr = "YUV 4:2:0 (M420)"; break; - case V4L2_PIX_FMT_NV12: descr = "Y/CbCr 4:2:0"; break; - case V4L2_PIX_FMT_NV21: descr = "Y/CrCb 4:2:0"; break; - case V4L2_PIX_FMT_NV16: descr = "Y/CbCr 4:2:2"; break; - case V4L2_PIX_FMT_NV61: descr = "Y/CrCb 4:2:2"; break; - case V4L2_PIX_FMT_NV24: descr = "Y/CbCr 4:4:4"; break; - case V4L2_PIX_FMT_NV42: descr = "Y/CrCb 4:4:4"; break; - case V4L2_PIX_FMT_P010: descr = "10-bit Y/CbCr 4:2:0"; break; - case V4L2_PIX_FMT_NV12_4L4: descr = "Y/CbCr 4:2:0 (4x4 Linear)"; break; - case V4L2_PIX_FMT_NV12_16L16: descr = "Y/CbCr 4:2:0 (16x16 Linear)"; break; - case V4L2_PIX_FMT_NV12_32L32: descr = "Y/CbCr 4:2:0 (32x32 Linear)"; break; - case V4L2_PIX_FMT_P010_4L4: descr = "10-bit Y/CbCr 4:2:0 (4x4 Linear)"; break; - case V4L2_PIX_FMT_NV12M: descr = "Y/CbCr 4:2:0 (N-C)"; break; - case V4L2_PIX_FMT_NV21M: descr = "Y/CrCb 4:2:0 (N-C)"; break; - case V4L2_PIX_FMT_NV16M: descr = "Y/CbCr 4:2:2 (N-C)"; break; - case V4L2_PIX_FMT_NV61M: descr = "Y/CrCb 4:2:2 (N-C)"; break; - case V4L2_PIX_FMT_NV12MT: descr = "Y/CbCr 4:2:0 (64x32 MB, N-C)"; break; - case V4L2_PIX_FMT_NV12MT_16X16: descr = "Y/CbCr 4:2:0 (16x16 MB, N-C)"; break; + case V4L2_PIX_FMT_NV12: descr = "Y/UV 4:2:0"; break; + case V4L2_PIX_FMT_NV21: descr = "Y/VU 4:2:0"; break; + case V4L2_PIX_FMT_NV16: descr = "Y/UV 4:2:2"; break; + case V4L2_PIX_FMT_NV61: descr = "Y/VU 4:2:2"; break; + case V4L2_PIX_FMT_NV24: descr = "Y/UV 4:4:4"; break; + case V4L2_PIX_FMT_NV42: descr = "Y/VU 4:4:4"; break; + case V4L2_PIX_FMT_P010: descr = "10-bit Y/UV 4:2:0"; break; + case V4L2_PIX_FMT_NV12_4L4: descr = "Y/UV 4:2:0 (4x4 Linear)"; break; + case V4L2_PIX_FMT_NV12_16L16: descr = "Y/UV 4:2:0 (16x16 Linear)"; break; + case V4L2_PIX_FMT_NV12_32L32: descr = "Y/UV 4:2:0 (32x32 Linear)"; break; + case V4L2_PIX_FMT_P010_4L4: descr = "10-bit Y/UV 4:2:0 (4x4 Linear)"; break; + case V4L2_PIX_FMT_NV12M: descr = "Y/UV 4:2:0 (N-C)"; break; + case V4L2_PIX_FMT_NV21M: descr = "Y/VU 4:2:0 (N-C)"; break; + case V4L2_PIX_FMT_NV16M: descr = "Y/UV 4:2:2 (N-C)"; break; + case V4L2_PIX_FMT_NV61M: descr = "Y/VU 4:2:2 (N-C)"; break; + case V4L2_PIX_FMT_NV12MT: descr = "Y/UV 4:2:0 (64x32 MB, N-C)"; break; + case V4L2_PIX_FMT_NV12MT_16X16: descr = "Y/UV 4:2:0 (16x16 MB, N-C)"; break; case V4L2_PIX_FMT_YUV420M: descr = "Planar YUV 4:2:0 (N-C)"; break; case V4L2_PIX_FMT_YVU420M: descr = "Planar YVU 4:2:0 (N-C)"; break; case V4L2_PIX_FMT_YUV422M: descr = "Planar YUV 4:2:2 (N-C)"; break; @@ -1444,7 +1438,9 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_VIVID: descr = "Vivid Metadata"; break; case V4L2_META_FMT_RK_ISP1_PARAMS: descr = "Rockchip ISP1 3A Parameters"; break; case V4L2_META_FMT_RK_ISP1_STAT_3A: descr = "Rockchip ISP1 3A Statistics"; break; + case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; + case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; case V4L2_PIX_FMT_NV12M_10BE_8L128: descr = "10-bit NV12M (8x128 Linear, BE)"; break; default: @@ -1497,6 +1493,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_MT21C: descr = "Mediatek Compressed Format"; break; case V4L2_PIX_FMT_QC08C: descr = "QCOM Compressed 8-bit Format"; break; case V4L2_PIX_FMT_QC10C: descr = "QCOM Compressed 10-bit Format"; break; + case V4L2_PIX_FMT_AJPG: descr = "Aspeed JPEG"; break; default: if (fmt->description[0]) return; @@ -1530,7 +1527,7 @@ static int v4l_enum_fmt(const struct v4l2_ioctl_ops *ops, p->mbus_code = 0; mbus_code = p->mbus_code; - CLEAR_AFTER_FIELD(p, type); + memset_after(p, 0, type); p->mbus_code = mbus_code; switch (p->type) { @@ -1705,7 +1702,7 @@ static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_vid_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.pix); + memset_after(p, 0, fmt.pix); ret = ops->vidioc_s_fmt_vid_cap(file, fh, arg); /* just in case the driver zeroed it again */ p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; @@ -1715,30 +1712,30 @@ static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: if (unlikely(!ops->vidioc_s_fmt_vid_cap_mplane)) break; - CLEAR_AFTER_FIELD(p, fmt.pix_mp.xfer_func); + memset_after(p, 0, fmt.pix_mp.xfer_func); for (i = 0; i < p->fmt.pix_mp.num_planes; i++) - CLEAR_AFTER_FIELD(&p->fmt.pix_mp.plane_fmt[i], - bytesperline); + memset_after(&p->fmt.pix_mp.plane_fmt[i], + 0, bytesperline); return ops->vidioc_s_fmt_vid_cap_mplane(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OVERLAY: if (unlikely(!ops->vidioc_s_fmt_vid_overlay)) break; - CLEAR_AFTER_FIELD(p, fmt.win); + memset_after(p, 0, fmt.win); return ops->vidioc_s_fmt_vid_overlay(file, fh, arg); case V4L2_BUF_TYPE_VBI_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_vbi_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.vbi.flags); + memset_after(p, 0, fmt.vbi.flags); return ops->vidioc_s_fmt_vbi_cap(file, fh, arg); case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_sliced_vbi_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.sliced.io_size); + memset_after(p, 0, fmt.sliced.io_size); return ops->vidioc_s_fmt_sliced_vbi_cap(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_vid_out)) break; - CLEAR_AFTER_FIELD(p, fmt.pix); + memset_after(p, 0, fmt.pix); ret = ops->vidioc_s_fmt_vid_out(file, fh, arg); /* just in case the driver zeroed it again */ p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; @@ -1746,45 +1743,45 @@ static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: if (unlikely(!ops->vidioc_s_fmt_vid_out_mplane)) break; - CLEAR_AFTER_FIELD(p, fmt.pix_mp.xfer_func); + memset_after(p, 0, fmt.pix_mp.xfer_func); for (i = 0; i < p->fmt.pix_mp.num_planes; i++) - CLEAR_AFTER_FIELD(&p->fmt.pix_mp.plane_fmt[i], - bytesperline); + memset_after(&p->fmt.pix_mp.plane_fmt[i], + 0, bytesperline); return ops->vidioc_s_fmt_vid_out_mplane(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: if (unlikely(!ops->vidioc_s_fmt_vid_out_overlay)) break; - CLEAR_AFTER_FIELD(p, fmt.win); + memset_after(p, 0, fmt.win); return ops->vidioc_s_fmt_vid_out_overlay(file, fh, arg); case V4L2_BUF_TYPE_VBI_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_vbi_out)) break; - CLEAR_AFTER_FIELD(p, fmt.vbi.flags); + memset_after(p, 0, fmt.vbi.flags); return ops->vidioc_s_fmt_vbi_out(file, fh, arg); case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_sliced_vbi_out)) break; - CLEAR_AFTER_FIELD(p, fmt.sliced.io_size); + memset_after(p, 0, fmt.sliced.io_size); return ops->vidioc_s_fmt_sliced_vbi_out(file, fh, arg); case V4L2_BUF_TYPE_SDR_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_sdr_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.sdr.buffersize); + memset_after(p, 0, fmt.sdr.buffersize); return ops->vidioc_s_fmt_sdr_cap(file, fh, arg); case V4L2_BUF_TYPE_SDR_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_sdr_out)) break; - CLEAR_AFTER_FIELD(p, fmt.sdr.buffersize); + memset_after(p, 0, fmt.sdr.buffersize); return ops->vidioc_s_fmt_sdr_out(file, fh, arg); case V4L2_BUF_TYPE_META_CAPTURE: if (unlikely(!ops->vidioc_s_fmt_meta_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.meta); + memset_after(p, 0, fmt.meta); return ops->vidioc_s_fmt_meta_cap(file, fh, arg); case V4L2_BUF_TYPE_META_OUTPUT: if (unlikely(!ops->vidioc_s_fmt_meta_out)) break; - CLEAR_AFTER_FIELD(p, fmt.meta); + memset_after(p, 0, fmt.meta); return ops->vidioc_s_fmt_meta_out(file, fh, arg); } return -EINVAL; @@ -1807,7 +1804,7 @@ static int v4l_try_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_vid_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.pix); + memset_after(p, 0, fmt.pix); ret = ops->vidioc_try_fmt_vid_cap(file, fh, arg); /* just in case the driver zeroed it again */ p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; @@ -1817,30 +1814,30 @@ static int v4l_try_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: if (unlikely(!ops->vidioc_try_fmt_vid_cap_mplane)) break; - CLEAR_AFTER_FIELD(p, fmt.pix_mp.xfer_func); + memset_after(p, 0, fmt.pix_mp.xfer_func); for (i = 0; i < p->fmt.pix_mp.num_planes; i++) - CLEAR_AFTER_FIELD(&p->fmt.pix_mp.plane_fmt[i], - bytesperline); + memset_after(&p->fmt.pix_mp.plane_fmt[i], + 0, bytesperline); return ops->vidioc_try_fmt_vid_cap_mplane(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OVERLAY: if (unlikely(!ops->vidioc_try_fmt_vid_overlay)) break; - CLEAR_AFTER_FIELD(p, fmt.win); + memset_after(p, 0, fmt.win); return ops->vidioc_try_fmt_vid_overlay(file, fh, arg); case V4L2_BUF_TYPE_VBI_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_vbi_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.vbi.flags); + memset_after(p, 0, fmt.vbi.flags); return ops->vidioc_try_fmt_vbi_cap(file, fh, arg); case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_sliced_vbi_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.sliced.io_size); + memset_after(p, 0, fmt.sliced.io_size); return ops->vidioc_try_fmt_sliced_vbi_cap(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_vid_out)) break; - CLEAR_AFTER_FIELD(p, fmt.pix); + memset_after(p, 0, fmt.pix); ret = ops->vidioc_try_fmt_vid_out(file, fh, arg); /* just in case the driver zeroed it again */ p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; @@ -1848,45 +1845,45 @@ static int v4l_try_fmt(const struct v4l2_ioctl_ops *ops, case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: if (unlikely(!ops->vidioc_try_fmt_vid_out_mplane)) break; - CLEAR_AFTER_FIELD(p, fmt.pix_mp.xfer_func); + memset_after(p, 0, fmt.pix_mp.xfer_func); for (i = 0; i < p->fmt.pix_mp.num_planes; i++) - CLEAR_AFTER_FIELD(&p->fmt.pix_mp.plane_fmt[i], - bytesperline); + memset_after(&p->fmt.pix_mp.plane_fmt[i], + 0, bytesperline); return ops->vidioc_try_fmt_vid_out_mplane(file, fh, arg); case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: if (unlikely(!ops->vidioc_try_fmt_vid_out_overlay)) break; - CLEAR_AFTER_FIELD(p, fmt.win); + memset_after(p, 0, fmt.win); return ops->vidioc_try_fmt_vid_out_overlay(file, fh, arg); case V4L2_BUF_TYPE_VBI_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_vbi_out)) break; - CLEAR_AFTER_FIELD(p, fmt.vbi.flags); + memset_after(p, 0, fmt.vbi.flags); return ops->vidioc_try_fmt_vbi_out(file, fh, arg); case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_sliced_vbi_out)) break; - CLEAR_AFTER_FIELD(p, fmt.sliced.io_size); + memset_after(p, 0, fmt.sliced.io_size); return ops->vidioc_try_fmt_sliced_vbi_out(file, fh, arg); case V4L2_BUF_TYPE_SDR_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_sdr_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.sdr.buffersize); + memset_after(p, 0, fmt.sdr.buffersize); return ops->vidioc_try_fmt_sdr_cap(file, fh, arg); case V4L2_BUF_TYPE_SDR_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_sdr_out)) break; - CLEAR_AFTER_FIELD(p, fmt.sdr.buffersize); + memset_after(p, 0, fmt.sdr.buffersize); return ops->vidioc_try_fmt_sdr_out(file, fh, arg); case V4L2_BUF_TYPE_META_CAPTURE: if (unlikely(!ops->vidioc_try_fmt_meta_cap)) break; - CLEAR_AFTER_FIELD(p, fmt.meta); + memset_after(p, 0, fmt.meta); return ops->vidioc_try_fmt_meta_cap(file, fh, arg); case V4L2_BUF_TYPE_META_OUTPUT: if (unlikely(!ops->vidioc_try_fmt_meta_out)) break; - CLEAR_AFTER_FIELD(p, fmt.meta); + memset_after(p, 0, fmt.meta); return ops->vidioc_try_fmt_meta_out(file, fh, arg); } return -EINVAL; @@ -2085,7 +2082,7 @@ static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops, if (ret) return ret; - CLEAR_AFTER_FIELD(p, flags); + memset_after(p, 0, flags); return ops->vidioc_reqbufs(file, fh, p); } @@ -2126,7 +2123,7 @@ static int v4l_create_bufs(const struct v4l2_ioctl_ops *ops, if (ret) return ret; - CLEAR_AFTER_FIELD(create, flags); + memset_after(create, 0, flags); v4l_sanitize_format(&create->format); diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 5c27bac772ea..4988a25bd8f4 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -318,6 +318,20 @@ static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, sd->ops->pad->get_mbus_config(sd, pad, config); } +static int call_s_stream(struct v4l2_subdev *sd, int enable) +{ + int ret; + + ret = sd->ops->video->s_stream(sd, enable); + + if (!enable && ret < 0) { + dev_warn(sd->dev, "disabling streaming failed (%d)\n", ret); + return 0; + } + + return ret; +} + #ifdef CONFIG_MEDIA_CONTROLLER /* * Create state-management wrapper for pad ops dealing with subdev state. The @@ -377,6 +391,7 @@ static const struct v4l2_subdev_pad_ops v4l2_subdev_call_pad_wrappers = { static const struct v4l2_subdev_video_ops v4l2_subdev_call_video_wrappers = { .g_frame_interval = call_g_frame_interval, .s_frame_interval = call_s_frame_interval, + .s_stream = call_s_stream, }; const struct v4l2_subdev_ops v4l2_subdev_call_wrappers = { @@ -845,7 +860,7 @@ int v4l2_subdev_get_fwnode_pad_1_to_1(struct media_entity *entity, fwnode = fwnode_graph_get_port_parent(endpoint->local_fwnode); fwnode_handle_put(fwnode); - if (dev_fwnode(sd->dev) == fwnode) + if (device_match_fwnode(sd->dev, fwnode)) return endpoint->port; return -ENXIO; |