diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2023-02-26 11:47:26 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2023-02-26 11:47:26 -0800 |
commit | 4b8c673b761e74add4fd185d806ac16c9b40158f (patch) | |
tree | b7589b0844fc3306e8886228d4290eaff26456c6 /drivers/media | |
parent | d4563201f33a022fc0353033d9dfeb1606a88330 (diff) | |
parent | 3e62aba8284de0994a669d07983299242e68fe72 (diff) |
Merge tag 'media/v6.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
Pull media updates from Mauro Carvalho Chehab:
- Removal of several VB1-only deprecated drivers: cpia2, fsl-viu, meye,
stkwebcam, tm6000, vpfe_capture and zr364xx
- saa7146 recovered from staging/deprecated. We opted to give ti a
chance, and, instead of deprecating it, the intention is to write
patches migrating it from VB1 to VB2.
- av7110 returned from staging/deprecated/ to staging/ as we're not
planning on dropping it any time soon
- media controller API has gained experimental support for G_ROUTING
and streams API. No drivers use it right now. We're planning to add
one after -rc1, giving some time to experience the API and eventually
have changes during the next development cycle
- New sensor drivers: imx296, imx415, ov8858
- Atomisp had lots of changes, specially on its sensor's interface,
making atomisp sensor drivers closer to normal sensor drivers
- media controller kAPI has gained some helpers to traverse pipelines
- uvcvideo now better support power line control
- lots of bug fixes, cleanups and driver improvements
* tag 'media/v6.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (296 commits)
media: imx-mipi-csis: Check csis_fmt validity before use
media: v4l2-subdev.c: clear stream field
media: v4l2-ctrls-api.c: move ctrl->is_new = 1 to the correct line
media: Revert "media: saa7146: deprecate hexium_gemini/orion, mxb and ttpci"
media: Revert "media: av7110: move to staging/media/deprecated/saa7146"
media: imx-pxp: convert to regmap
media: imx-pxp: Use non-threaded IRQ
media: imx-pxp: Introduce pxp_read() and pxp_write() wrappers
media: imx-pxp: Implement frame size enumeration
media: imx-pxp: Pass pixel format value to find_format()
media: imx-pxp: Add media controller support
media: imx-pxp: Don't set bus_info manually in .querycap()
media: imx-pxp: Sort headers alphabetically
media: imx-pxp: add support for i.MX7D
media: imx-pxp: make data_path_ctrl0 platform dependent
media: imx-pxp: disable LUT block
media: imx-pxp: explicitly disable unused blocks
media: imx-pxp: extract helper function to setup data path
media: imx-pxp: detect PXP version
media: dt-bindings: media: fsl-pxp: convert to yaml
...
Diffstat (limited to 'drivers/media')
120 files changed, 19220 insertions, 1594 deletions
diff --git a/drivers/media/common/Kconfig b/drivers/media/common/Kconfig index b1bc58da27fc..adcb6655385a 100644 --- a/drivers/media/common/Kconfig +++ b/drivers/media/common/Kconfig @@ -25,6 +25,7 @@ config VIDEO_TVEEPROM depends on I2C source "drivers/media/common/b2c2/Kconfig" +source "drivers/media/common/saa7146/Kconfig" source "drivers/media/common/siano/Kconfig" source "drivers/media/common/v4l2-tpg/Kconfig" source "drivers/media/common/videobuf2/Kconfig" diff --git a/drivers/media/common/Makefile b/drivers/media/common/Makefile index 3f17d696feb2..c5ab905e7c20 100644 --- a/drivers/media/common/Makefile +++ b/drivers/media/common/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-y += b2c2/ siano/ v4l2-tpg/ videobuf2/ +obj-y += b2c2/ saa7146/ siano/ v4l2-tpg/ videobuf2/ # Please keep it alphabetically sorted by Kconfig name # (e. g. LC_ALL=C sort Makefile) diff --git a/drivers/media/common/saa7146/Kconfig b/drivers/media/common/saa7146/Kconfig new file mode 100644 index 000000000000..a0aa155e5d85 --- /dev/null +++ b/drivers/media/common/saa7146/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SAA7146 + tristate + depends on I2C && PCI + +config VIDEO_SAA7146_VV + tristate + depends on VIDEO_DEV + select VIDEOBUF_DMA_SG + select VIDEO_SAA7146 diff --git a/drivers/media/common/saa7146/Makefile b/drivers/media/common/saa7146/Makefile new file mode 100644 index 000000000000..2a6337feaec8 --- /dev/null +++ b/drivers/media/common/saa7146/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +saa7146-objs := saa7146_i2c.o saa7146_core.o +saa7146_vv-objs := saa7146_fops.o saa7146_video.o saa7146_hlp.o saa7146_vbi.o + +obj-$(CONFIG_VIDEO_SAA7146) += saa7146.o +obj-$(CONFIG_VIDEO_SAA7146_VV) += saa7146_vv.o diff --git a/drivers/media/common/saa7146/saa7146_core.c b/drivers/media/common/saa7146/saa7146_core.c new file mode 100644 index 000000000000..e50fa0ff7c5d --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_core.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + saa7146.o - driver for generic saa7146-based hardware + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <media/drv-intf/saa7146.h> +#include <linux/module.h> + +static int saa7146_num; + +unsigned int saa7146_debug; + +module_param(saa7146_debug, uint, 0644); +MODULE_PARM_DESC(saa7146_debug, "debug level (default: 0)"); + +#if 0 +static void dump_registers(struct saa7146_dev* dev) +{ + int i = 0; + + pr_info(" @ %li jiffies:\n", jiffies); + for (i = 0; i <= 0x148; i += 4) + pr_info("0x%03x: 0x%08x\n", i, saa7146_read(dev, i)); +} +#endif + +/**************************************************************************** + * gpio and debi helper functions + ****************************************************************************/ + +void saa7146_setgpio(struct saa7146_dev *dev, int port, u32 data) +{ + u32 value = 0; + + BUG_ON(port > 3); + + value = saa7146_read(dev, GPIO_CTRL); + value &= ~(0xff << (8*port)); + value |= (data << (8*port)); + saa7146_write(dev, GPIO_CTRL, value); +} + +/* This DEBI code is based on the saa7146 Stradis driver by Nathan Laredo */ +static inline int saa7146_wait_for_debi_done_sleep(struct saa7146_dev *dev, + unsigned long us1, unsigned long us2) +{ + unsigned long timeout; + int err; + + /* wait for registers to be programmed */ + timeout = jiffies + usecs_to_jiffies(us1); + while (1) { + err = time_after(jiffies, timeout); + if (saa7146_read(dev, MC2) & 2) + break; + if (err) { + pr_debug("%s: %s timed out while waiting for registers getting programmed\n", + dev->name, __func__); + return -ETIMEDOUT; + } + msleep(1); + } + + /* wait for transfer to complete */ + timeout = jiffies + usecs_to_jiffies(us2); + while (1) { + err = time_after(jiffies, timeout); + if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) + break; + saa7146_read(dev, MC2); + if (err) { + DEB_S("%s: %s timed out while waiting for transfer completion\n", + dev->name, __func__); + return -ETIMEDOUT; + } + msleep(1); + } + + return 0; +} + +static inline int saa7146_wait_for_debi_done_busyloop(struct saa7146_dev *dev, + unsigned long us1, unsigned long us2) +{ + unsigned long loops; + + /* wait for registers to be programmed */ + loops = us1; + while (1) { + if (saa7146_read(dev, MC2) & 2) + break; + if (!loops--) { + pr_err("%s: %s timed out while waiting for registers getting programmed\n", + dev->name, __func__); + return -ETIMEDOUT; + } + udelay(1); + } + + /* wait for transfer to complete */ + loops = us2 / 5; + while (1) { + if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) + break; + saa7146_read(dev, MC2); + if (!loops--) { + DEB_S("%s: %s timed out while waiting for transfer completion\n", + dev->name, __func__); + return -ETIMEDOUT; + } + udelay(5); + } + + return 0; +} + +int saa7146_wait_for_debi_done(struct saa7146_dev *dev, int nobusyloop) +{ + if (nobusyloop) + return saa7146_wait_for_debi_done_sleep(dev, 50000, 250000); + else + return saa7146_wait_for_debi_done_busyloop(dev, 50000, 250000); +} + +/**************************************************************************** + * general helper functions + ****************************************************************************/ + +/* this is videobuf_vmalloc_to_sg() from videobuf-dma-sg.c + make sure virt has been allocated with vmalloc_32(), otherwise the BUG() + may be triggered on highmem machines */ +static struct scatterlist* vmalloc_to_sg(unsigned char *virt, int nr_pages) +{ + struct scatterlist *sglist; + struct page *pg; + int i; + + sglist = kmalloc_array(nr_pages, sizeof(struct scatterlist), GFP_KERNEL); + if (NULL == sglist) + return NULL; + sg_init_table(sglist, nr_pages); + for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) { + pg = vmalloc_to_page(virt); + if (NULL == pg) + goto err; + BUG_ON(PageHighMem(pg)); + sg_set_page(&sglist[i], pg, PAGE_SIZE, 0); + } + return sglist; + + err: + kfree(sglist); + return NULL; +} + +/********************************************************************************/ +/* common page table functions */ + +void *saa7146_vmalloc_build_pgtable(struct pci_dev *pci, long length, struct saa7146_pgtable *pt) +{ + int pages = (length+PAGE_SIZE-1)/PAGE_SIZE; + void *mem = vmalloc_32(length); + int slen = 0; + + if (NULL == mem) + goto err_null; + + if (!(pt->slist = vmalloc_to_sg(mem, pages))) + goto err_free_mem; + + if (saa7146_pgtable_alloc(pci, pt)) + goto err_free_slist; + + pt->nents = pages; + slen = dma_map_sg(&pci->dev, pt->slist, pt->nents, DMA_FROM_DEVICE); + if (0 == slen) + goto err_free_pgtable; + + if (0 != saa7146_pgtable_build_single(pci, pt, pt->slist, slen)) + goto err_unmap_sg; + + return mem; + +err_unmap_sg: + dma_unmap_sg(&pci->dev, pt->slist, pt->nents, DMA_FROM_DEVICE); +err_free_pgtable: + saa7146_pgtable_free(pci, pt); +err_free_slist: + kfree(pt->slist); + pt->slist = NULL; +err_free_mem: + vfree(mem); +err_null: + return NULL; +} + +void saa7146_vfree_destroy_pgtable(struct pci_dev *pci, void *mem, struct saa7146_pgtable *pt) +{ + dma_unmap_sg(&pci->dev, pt->slist, pt->nents, DMA_FROM_DEVICE); + saa7146_pgtable_free(pci, pt); + kfree(pt->slist); + pt->slist = NULL; + vfree(mem); +} + +void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt) +{ + if (NULL == pt->cpu) + return; + dma_free_coherent(&pci->dev, pt->size, pt->cpu, pt->dma); + pt->cpu = NULL; +} + +int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt) +{ + __le32 *cpu; + dma_addr_t dma_addr = 0; + + cpu = dma_alloc_coherent(&pci->dev, PAGE_SIZE, &dma_addr, GFP_KERNEL); + if (NULL == cpu) { + return -ENOMEM; + } + pt->size = PAGE_SIZE; + pt->cpu = cpu; + pt->dma = dma_addr; + + return 0; +} + +int saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, + struct scatterlist *list, int sglen ) +{ + __le32 *ptr, fill; + int nr_pages = 0; + int i,p; + + BUG_ON(0 == sglen); + BUG_ON(list->offset > PAGE_SIZE); + + /* if we have a user buffer, the first page may not be + aligned to a page boundary. */ + pt->offset = list->offset; + + ptr = pt->cpu; + for (i = 0; i < sglen; i++, list++) { +/* + pr_debug("i:%d, adr:0x%08x, len:%d, offset:%d\n", + i, sg_dma_address(list), sg_dma_len(list), + list->offset); +*/ + for (p = 0; p * 4096 < sg_dma_len(list); p++, ptr++) { + *ptr = cpu_to_le32(sg_dma_address(list) + p * 4096); + nr_pages++; + } + } + + + /* safety; fill the page table up with the last valid page */ + fill = *(ptr-1); + for(i=nr_pages;i<1024;i++) { + *ptr++ = fill; + } + +/* + ptr = pt->cpu; + pr_debug("offset: %d\n", pt->offset); + for(i=0;i<5;i++) { + pr_debug("ptr1 %d: 0x%08x\n", i, ptr[i]); + } +*/ + return 0; +} + +/********************************************************************************/ +/* interrupt handler */ +static irqreturn_t interrupt_hw(int irq, void *dev_id) +{ + struct saa7146_dev *dev = dev_id; + u32 isr; + u32 ack_isr; + + /* read out the interrupt status register */ + ack_isr = isr = saa7146_read(dev, ISR); + + /* is this our interrupt? */ + if ( 0 == isr ) { + /* nope, some other device */ + return IRQ_NONE; + } + + if (dev->ext) { + if (dev->ext->irq_mask & isr) { + if (dev->ext->irq_func) + dev->ext->irq_func(dev, &isr); + isr &= ~dev->ext->irq_mask; + } + } + if (0 != (isr & (MASK_27))) { + DEB_INT("irq: RPS0 (0x%08x)\n", isr); + if (dev->vv_data && dev->vv_callback) + dev->vv_callback(dev,isr); + isr &= ~MASK_27; + } + if (0 != (isr & (MASK_28))) { + if (dev->vv_data && dev->vv_callback) + dev->vv_callback(dev,isr); + isr &= ~MASK_28; + } + if (0 != (isr & (MASK_16|MASK_17))) { + SAA7146_IER_DISABLE(dev, MASK_16|MASK_17); + /* only wake up if we expect something */ + if (0 != dev->i2c_op) { + dev->i2c_op = 0; + wake_up(&dev->i2c_wq); + } else { + u32 psr = saa7146_read(dev, PSR); + u32 ssr = saa7146_read(dev, SSR); + pr_warn("%s: unexpected i2c irq: isr %08x psr %08x ssr %08x\n", + dev->name, isr, psr, ssr); + } + isr &= ~(MASK_16|MASK_17); + } + if( 0 != isr ) { + ERR("warning: interrupt enabled, but not handled properly.(0x%08x)\n", + isr); + ERR("disabling interrupt source(s)!\n"); + SAA7146_IER_DISABLE(dev,isr); + } + saa7146_write(dev, ISR, ack_isr); + return IRQ_HANDLED; +} + +/*********************************************************************************/ +/* configuration-functions */ + +static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent) +{ + struct saa7146_pci_extension_data *pci_ext = (struct saa7146_pci_extension_data *)ent->driver_data; + struct saa7146_extension *ext = pci_ext->ext; + struct saa7146_dev *dev; + int err = -ENOMEM; + + /* clear out mem for sure */ + dev = kzalloc(sizeof(struct saa7146_dev), GFP_KERNEL); + if (!dev) { + ERR("out of memory\n"); + goto out; + } + + /* create a nice device name */ + sprintf(dev->name, "saa7146 (%d)", saa7146_num); + + DEB_EE("pci:%p\n", pci); + + err = pci_enable_device(pci); + if (err < 0) { + ERR("pci_enable_device() failed\n"); + goto err_free; + } + + /* enable bus-mastering */ + pci_set_master(pci); + + dev->pci = pci; + + /* get chip-revision; this is needed to enable bug-fixes */ + dev->revision = pci->revision; + + /* remap the memory from virtual to physical address */ + + err = pci_request_region(pci, 0, "saa7146"); + if (err < 0) + goto err_disable; + + dev->mem = ioremap(pci_resource_start(pci, 0), + pci_resource_len(pci, 0)); + if (!dev->mem) { + ERR("ioremap() failed\n"); + err = -ENODEV; + goto err_release; + } + + /* we don't do a master reset here anymore, it screws up + some boards that don't have an i2c-eeprom for configuration + values */ +/* + saa7146_write(dev, MC1, MASK_31); +*/ + + /* disable all irqs */ + saa7146_write(dev, IER, 0); + + /* shut down all dma transfers and rps tasks */ + saa7146_write(dev, MC1, 0x30ff0000); + + /* clear out any rps-signals pending */ + saa7146_write(dev, MC2, 0xf8000000); + + /* request an interrupt for the saa7146 */ + err = request_irq(pci->irq, interrupt_hw, IRQF_SHARED, + dev->name, dev); + if (err < 0) { + ERR("request_irq() failed\n"); + goto err_unmap; + } + + err = -ENOMEM; + + /* get memory for various stuff */ + dev->d_rps0.cpu_addr = dma_alloc_coherent(&pci->dev, SAA7146_RPS_MEM, + &dev->d_rps0.dma_handle, + GFP_KERNEL); + if (!dev->d_rps0.cpu_addr) + goto err_free_irq; + + dev->d_rps1.cpu_addr = dma_alloc_coherent(&pci->dev, SAA7146_RPS_MEM, + &dev->d_rps1.dma_handle, + GFP_KERNEL); + if (!dev->d_rps1.cpu_addr) + goto err_free_rps0; + + dev->d_i2c.cpu_addr = dma_alloc_coherent(&pci->dev, SAA7146_RPS_MEM, + &dev->d_i2c.dma_handle, GFP_KERNEL); + if (!dev->d_i2c.cpu_addr) + goto err_free_rps1; + + /* the rest + print status message */ + + pr_info("found saa7146 @ mem %p (revision %d, irq %d) (0x%04x,0x%04x)\n", + dev->mem, dev->revision, pci->irq, + pci->subsystem_vendor, pci->subsystem_device); + dev->ext = ext; + + mutex_init(&dev->v4l2_lock); + spin_lock_init(&dev->int_slock); + spin_lock_init(&dev->slock); + + mutex_init(&dev->i2c_lock); + + dev->module = THIS_MODULE; + init_waitqueue_head(&dev->i2c_wq); + + /* set some sane pci arbitrition values */ + saa7146_write(dev, PCI_BT_V1, 0x1c00101f); + + /* TODO: use the status code of the callback */ + + err = -ENODEV; + + if (ext->probe && ext->probe(dev)) { + DEB_D("ext->probe() failed for %p. skipping device.\n", dev); + goto err_free_i2c; + } + + if (ext->attach(dev, pci_ext)) { + DEB_D("ext->attach() failed for %p. skipping device.\n", dev); + goto err_free_i2c; + } + /* V4L extensions will set the pci drvdata to the v4l2_device in the + attach() above. So for those cards that do not use V4L we have to + set it explicitly. */ + pci_set_drvdata(pci, &dev->v4l2_dev); + + saa7146_num++; + + err = 0; +out: + return err; + +err_free_i2c: + dma_free_coherent(&pci->dev, SAA7146_RPS_MEM, dev->d_i2c.cpu_addr, + dev->d_i2c.dma_handle); +err_free_rps1: + dma_free_coherent(&pci->dev, SAA7146_RPS_MEM, dev->d_rps1.cpu_addr, + dev->d_rps1.dma_handle); +err_free_rps0: + dma_free_coherent(&pci->dev, SAA7146_RPS_MEM, dev->d_rps0.cpu_addr, + dev->d_rps0.dma_handle); +err_free_irq: + free_irq(pci->irq, (void *)dev); +err_unmap: + iounmap(dev->mem); +err_release: + pci_release_region(pci, 0); +err_disable: + pci_disable_device(pci); +err_free: + kfree(dev); + goto out; +} + +static void saa7146_remove_one(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); + struct saa7146_dev *dev = to_saa7146_dev(v4l2_dev); + struct { + void *addr; + dma_addr_t dma; + } dev_map[] = { + { dev->d_i2c.cpu_addr, dev->d_i2c.dma_handle }, + { dev->d_rps1.cpu_addr, dev->d_rps1.dma_handle }, + { dev->d_rps0.cpu_addr, dev->d_rps0.dma_handle }, + { NULL, 0 } + }, *p; + + DEB_EE("dev:%p\n", dev); + + dev->ext->detach(dev); + + /* shut down all video dma transfers */ + saa7146_write(dev, MC1, 0x00ff0000); + + /* disable all irqs, release irq-routine */ + saa7146_write(dev, IER, 0); + + free_irq(pdev->irq, dev); + + for (p = dev_map; p->addr; p++) + dma_free_coherent(&pdev->dev, SAA7146_RPS_MEM, p->addr, + p->dma); + + iounmap(dev->mem); + pci_release_region(pdev, 0); + pci_disable_device(pdev); + kfree(dev); + + saa7146_num--; +} + +/*********************************************************************************/ +/* extension handling functions */ + +int saa7146_register_extension(struct saa7146_extension* ext) +{ + DEB_EE("ext:%p\n", ext); + + ext->driver.name = ext->name; + ext->driver.id_table = ext->pci_tbl; + ext->driver.probe = saa7146_init_one; + ext->driver.remove = saa7146_remove_one; + + pr_info("register extension '%s'\n", ext->name); + return pci_register_driver(&ext->driver); +} + +int saa7146_unregister_extension(struct saa7146_extension* ext) +{ + DEB_EE("ext:%p\n", ext); + pr_info("unregister extension '%s'\n", ext->name); + pci_unregister_driver(&ext->driver); + return 0; +} + +EXPORT_SYMBOL_GPL(saa7146_register_extension); +EXPORT_SYMBOL_GPL(saa7146_unregister_extension); + +/* misc functions used by extension modules */ +EXPORT_SYMBOL_GPL(saa7146_pgtable_alloc); +EXPORT_SYMBOL_GPL(saa7146_pgtable_free); +EXPORT_SYMBOL_GPL(saa7146_pgtable_build_single); +EXPORT_SYMBOL_GPL(saa7146_vmalloc_build_pgtable); +EXPORT_SYMBOL_GPL(saa7146_vfree_destroy_pgtable); +EXPORT_SYMBOL_GPL(saa7146_wait_for_debi_done); + +EXPORT_SYMBOL_GPL(saa7146_setgpio); + +EXPORT_SYMBOL_GPL(saa7146_i2c_adapter_prepare); + +EXPORT_SYMBOL_GPL(saa7146_debug); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("driver for generic saa7146-based hardware"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/saa7146/saa7146_fops.c b/drivers/media/common/saa7146/saa7146_fops.c new file mode 100644 index 000000000000..e9a15de6126e --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_fops.c @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <media/drv-intf/saa7146_vv.h> +#include <linux/module.h> + +/****************************************************************************/ +/* resource management functions, shamelessly stolen from saa7134 driver */ + +int saa7146_res_get(struct saa7146_fh *fh, unsigned int bit) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + + if (fh->resources & bit) { + DEB_D("already allocated! want: 0x%02x, cur:0x%02x\n", + bit, vv->resources); + /* have it already allocated */ + return 1; + } + + /* is it free? */ + if (vv->resources & bit) { + DEB_D("locked! vv->resources:0x%02x, we want:0x%02x\n", + vv->resources, bit); + /* no, someone else uses it */ + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + vv->resources |= bit; + DEB_D("res: get 0x%02x, cur:0x%02x\n", bit, vv->resources); + return 1; +} + +void saa7146_res_free(struct saa7146_fh *fh, unsigned int bits) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + + BUG_ON((fh->resources & bits) != bits); + + fh->resources &= ~bits; + vv->resources &= ~bits; + DEB_D("res: put 0x%02x, cur:0x%02x\n", bits, vv->resources); +} + + +/********************************************************************************/ +/* common dma functions */ + +void saa7146_dma_free(struct saa7146_dev *dev,struct videobuf_queue *q, + struct saa7146_buf *buf) +{ + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + DEB_EE("dev:%p, buf:%p\n", dev, buf); + + videobuf_waiton(q, &buf->vb, 0, 0); + videobuf_dma_unmap(q->dev, dma); + videobuf_dma_free(dma); + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + + +/********************************************************************************/ +/* common buffer functions */ + +int saa7146_buffer_queue(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, + struct saa7146_buf *buf) +{ + assert_spin_locked(&dev->slock); + DEB_EE("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf); + + BUG_ON(!q); + + if (NULL == q->curr) { + q->curr = buf; + DEB_D("immediately activating buffer %p\n", buf); + buf->activate(dev,buf,NULL); + } else { + list_add_tail(&buf->vb.queue,&q->queue); + buf->vb.state = VIDEOBUF_QUEUED; + DEB_D("adding buffer %p to queue. (active buffer present)\n", + buf); + } + return 0; +} + +void saa7146_buffer_finish(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, + int state) +{ + assert_spin_locked(&dev->slock); + DEB_EE("dev:%p, dmaq:%p, state:%d\n", dev, q, state); + DEB_EE("q->curr:%p\n", q->curr); + + /* finish current buffer */ + if (NULL == q->curr) { + DEB_D("aiii. no current buffer\n"); + return; + } + + q->curr->vb.state = state; + q->curr->vb.ts = ktime_get_ns(); + wake_up(&q->curr->vb.done); + + q->curr = NULL; +} + +void saa7146_buffer_next(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, int vbi) +{ + struct saa7146_buf *buf,*next = NULL; + + BUG_ON(!q); + + DEB_INT("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi); + + assert_spin_locked(&dev->slock); + if (!list_empty(&q->queue)) { + /* activate next one from queue */ + buf = list_entry(q->queue.next,struct saa7146_buf,vb.queue); + list_del(&buf->vb.queue); + if (!list_empty(&q->queue)) + next = list_entry(q->queue.next,struct saa7146_buf, vb.queue); + q->curr = buf; + DEB_INT("next buffer: buf:%p, prev:%p, next:%p\n", + buf, q->queue.prev, q->queue.next); + buf->activate(dev,buf,next); + } else { + DEB_INT("no next buffer. stopping.\n"); + if( 0 != vbi ) { + /* turn off video-dma3 */ + saa7146_write(dev,MC1, MASK_20); + } else { + /* nothing to do -- just prevent next video-dma1 transfer + by lowering the protection address */ + + // fixme: fix this for vflip != 0 + + saa7146_write(dev, PROT_ADDR1, 0); + saa7146_write(dev, MC2, (MASK_02|MASK_18)); + + /* write the address of the rps-program */ + saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); + /* turn on rps */ + saa7146_write(dev, MC1, (MASK_12 | MASK_28)); + +/* + printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); + printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); + printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); + printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); + printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1)); + printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); +*/ + } + del_timer(&q->timeout); + } +} + +void saa7146_buffer_timeout(struct timer_list *t) +{ + struct saa7146_dmaqueue *q = from_timer(q, t, timeout); + struct saa7146_dev *dev = q->dev; + unsigned long flags; + + DEB_EE("dev:%p, dmaq:%p\n", dev, q); + + spin_lock_irqsave(&dev->slock,flags); + if (q->curr) { + DEB_D("timeout on %p\n", q->curr); + saa7146_buffer_finish(dev,q,VIDEOBUF_ERROR); + } + + /* we don't restart the transfer here like other drivers do. when + a streaming capture is disabled, the timeout function will be + called for the current buffer. if we activate the next buffer now, + we mess up our capture logic. if a timeout occurs on another buffer, + then something is seriously broken before, so no need to buffer the + next capture IMHO... */ +/* + saa7146_buffer_next(dev,q); +*/ + spin_unlock_irqrestore(&dev->slock,flags); +} + +/********************************************************************************/ +/* file operations */ + +static int fops_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_dev *dev = video_drvdata(file); + struct saa7146_fh *fh = NULL; + int result = 0; + + DEB_EE("file:%p, dev:%s\n", file, video_device_node_name(vdev)); + + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + + DEB_D("using: %p\n", dev); + + /* check if an extension is registered */ + if( NULL == dev->ext ) { + DEB_S("no extension registered for this device\n"); + result = -ENODEV; + goto out; + } + + /* allocate per open data */ + fh = kzalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) { + DEB_S("cannot allocate memory for per open data\n"); + result = -ENOMEM; + goto out; + } + + v4l2_fh_init(&fh->fh, vdev); + + file->private_data = &fh->fh; + fh->dev = dev; + + if (vdev->vfl_type == VFL_TYPE_VBI) { + DEB_S("initializing vbi...\n"); + if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) + result = saa7146_vbi_uops.open(dev,file); + if (dev->ext_vv_data->vbi_fops.open) + dev->ext_vv_data->vbi_fops.open(file); + } else { + DEB_S("initializing video...\n"); + result = saa7146_video_uops.open(dev,file); + } + + if (0 != result) { + goto out; + } + + if( 0 == try_module_get(dev->ext->module)) { + result = -EINVAL; + goto out; + } + + result = 0; + v4l2_fh_add(&fh->fh); +out: + if (fh && result != 0) { + kfree(fh); + file->private_data = NULL; + } + mutex_unlock(vdev->lock); + return result; +} + +static int fops_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + + DEB_EE("file:%p\n", file); + + mutex_lock(vdev->lock); + + if (vdev->vfl_type == VFL_TYPE_VBI) { + if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) + saa7146_vbi_uops.release(dev,file); + if (dev->ext_vv_data->vbi_fops.release) + dev->ext_vv_data->vbi_fops.release(file); + } else { + saa7146_video_uops.release(dev,file); + } + + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + module_put(dev->ext->module); + file->private_data = NULL; + kfree(fh); + + mutex_unlock(vdev->lock); + + return 0; +} + +static int fops_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + struct videobuf_queue *q; + int res; + + switch (vdev->vfl_type) { + case VFL_TYPE_VIDEO: { + DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, vma:%p\n", + file, vma); + q = &fh->video_q; + break; + } + case VFL_TYPE_VBI: { + DEB_EE("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, vma:%p\n", + file, vma); + if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_SLICED_VBI_OUTPUT) + return -ENODEV; + q = &fh->vbi_q; + break; + } + default: + BUG(); + } + + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + res = videobuf_mmap_mapper(q, vma); + mutex_unlock(vdev->lock); + return res; +} + +static __poll_t __fops_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + struct videobuf_buffer *buf = NULL; + struct videobuf_queue *q; + __poll_t res = v4l2_ctrl_poll(file, wait); + + DEB_EE("file:%p, poll:%p\n", file, wait); + + if (vdev->vfl_type == VFL_TYPE_VBI) { + if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_SLICED_VBI_OUTPUT) + return res | EPOLLOUT | EPOLLWRNORM; + if( 0 == fh->vbi_q.streaming ) + return res | videobuf_poll_stream(file, &fh->vbi_q, wait); + q = &fh->vbi_q; + } else { + DEB_D("using video queue\n"); + q = &fh->video_q; + } + + if (!list_empty(&q->stream)) + buf = list_entry(q->stream.next, struct videobuf_buffer, stream); + + if (!buf) { + DEB_D("buf == NULL!\n"); + return res | EPOLLERR; + } + + poll_wait(file, &buf->done, wait); + if (buf->state == VIDEOBUF_DONE || buf->state == VIDEOBUF_ERROR) { + DEB_D("poll succeeded!\n"); + return res | EPOLLIN | EPOLLRDNORM; + } + + DEB_D("nothing to poll for, buf->state:%d\n", buf->state); + return res; +} + +static __poll_t fops_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_device *vdev = video_devdata(file); + __poll_t res; + + mutex_lock(vdev->lock); + res = __fops_poll(file, wait); + mutex_unlock(vdev->lock); + return res; +} + +static ssize_t fops_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + int ret; + + switch (vdev->vfl_type) { + case VFL_TYPE_VIDEO: +/* + DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, data:%p, count:%lun", + file, data, (unsigned long)count); +*/ + return saa7146_video_uops.read(file,data,count,ppos); + case VFL_TYPE_VBI: +/* + DEB_EE("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, data:%p, count:%lu\n", + file, data, (unsigned long)count); +*/ + if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) { + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + ret = saa7146_vbi_uops.read(file, data, count, ppos); + mutex_unlock(vdev->lock); + return ret; + } + return -EINVAL; + default: + BUG(); + } +} + +static ssize_t fops_write(struct file *file, const char __user *data, size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + int ret; + + switch (vdev->vfl_type) { + case VFL_TYPE_VIDEO: + return -EINVAL; + case VFL_TYPE_VBI: + if (fh->dev->ext_vv_data->vbi_fops.write) { + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + ret = fh->dev->ext_vv_data->vbi_fops.write(file, data, count, ppos); + mutex_unlock(vdev->lock); + return ret; + } + return -EINVAL; + default: + BUG(); + } +} + +static const struct v4l2_file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = fops_open, + .release = fops_release, + .read = fops_read, + .write = fops_write, + .poll = fops_poll, + .mmap = fops_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static void vv_callback(struct saa7146_dev *dev, unsigned long status) +{ + u32 isr = status; + + DEB_INT("dev:%p, isr:0x%08x\n", dev, (u32)status); + + if (0 != (isr & (MASK_27))) { + DEB_INT("irq: RPS0 (0x%08x)\n", isr); + saa7146_video_uops.irq_done(dev,isr); + } + + if (0 != (isr & (MASK_28))) { + u32 mc2 = saa7146_read(dev, MC2); + if( 0 != (mc2 & MASK_15)) { + DEB_INT("irq: RPS1 vbi workaround (0x%08x)\n", isr); + wake_up(&dev->vv_data->vbi_wq); + saa7146_write(dev,MC2, MASK_31); + return; + } + DEB_INT("irq: RPS1 (0x%08x)\n", isr); + saa7146_vbi_uops.irq_done(dev,isr); + } +} + +static const struct v4l2_ctrl_ops saa7146_ctrl_ops = { + .s_ctrl = saa7146_s_ctrl, +}; + +int saa7146_vv_init(struct saa7146_dev* dev, struct saa7146_ext_vv *ext_vv) +{ + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + struct v4l2_pix_format *fmt; + struct v4l2_vbi_format *vbi; + struct saa7146_vv *vv; + int err; + + err = v4l2_device_register(&dev->pci->dev, &dev->v4l2_dev); + if (err) + return err; + + v4l2_ctrl_handler_init(hdl, 6); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (hdl->error) { + err = hdl->error; + v4l2_ctrl_handler_free(hdl); + v4l2_device_unregister(&dev->v4l2_dev); + return err; + } + dev->v4l2_dev.ctrl_handler = hdl; + + vv = kzalloc(sizeof(struct saa7146_vv), GFP_KERNEL); + if (vv == NULL) { + ERR("out of memory. aborting.\n"); + v4l2_ctrl_handler_free(hdl); + v4l2_device_unregister(&dev->v4l2_dev); + return -ENOMEM; + } + ext_vv->vid_ops = saa7146_video_ioctl_ops; + ext_vv->vbi_ops = saa7146_vbi_ioctl_ops; + ext_vv->core_ops = &saa7146_video_ioctl_ops; + + DEB_EE("dev:%p\n", dev); + + /* set default values for video parts of the saa7146 */ + saa7146_write(dev, BCS_CTRL, 0x80400040); + + /* enable video-port pins */ + saa7146_write(dev, MC1, (MASK_10 | MASK_26)); + + /* save per-device extension data (one extension can + handle different devices that might need different + configuration data) */ + dev->ext_vv_data = ext_vv; + + vv->d_clipping.cpu_addr = + dma_alloc_coherent(&dev->pci->dev, SAA7146_CLIPPING_MEM, + &vv->d_clipping.dma_handle, GFP_KERNEL); + if( NULL == vv->d_clipping.cpu_addr ) { + ERR("out of memory. aborting.\n"); + kfree(vv); + v4l2_ctrl_handler_free(hdl); + v4l2_device_unregister(&dev->v4l2_dev); + return -ENOMEM; + } + + saa7146_video_uops.init(dev,vv); + if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) + saa7146_vbi_uops.init(dev,vv); + + vv->ov_fb.fmt.width = vv->standard->h_max_out; + vv->ov_fb.fmt.height = vv->standard->v_max_out; + vv->ov_fb.fmt.pixelformat = V4L2_PIX_FMT_RGB565; + vv->ov_fb.fmt.bytesperline = 2 * vv->ov_fb.fmt.width; + vv->ov_fb.fmt.sizeimage = vv->ov_fb.fmt.bytesperline * vv->ov_fb.fmt.height; + vv->ov_fb.fmt.colorspace = V4L2_COLORSPACE_SRGB; + + fmt = &vv->video_fmt; + fmt->width = 384; + fmt->height = 288; + fmt->pixelformat = V4L2_PIX_FMT_BGR24; + fmt->field = V4L2_FIELD_ANY; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->bytesperline = 3 * fmt->width; + fmt->sizeimage = fmt->bytesperline * fmt->height; + + vbi = &vv->vbi_fmt; + vbi->sampling_rate = 27000000; + vbi->offset = 248; /* todo */ + vbi->samples_per_line = 720 * 2; + vbi->sample_format = V4L2_PIX_FMT_GREY; + + /* fixme: this only works for PAL */ + vbi->start[0] = 5; + vbi->count[0] = 16; + vbi->start[1] = 312; + vbi->count[1] = 16; + + timer_setup(&vv->vbi_read_timeout, NULL, 0); + + vv->ov_fb.capability = V4L2_FBUF_CAP_LIST_CLIPPING; + vv->ov_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + dev->vv_data = vv; + dev->vv_callback = &vv_callback; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_vv_init); + +int saa7146_vv_release(struct saa7146_dev* dev) +{ + struct saa7146_vv *vv = dev->vv_data; + + DEB_EE("dev:%p\n", dev); + + v4l2_device_unregister(&dev->v4l2_dev); + dma_free_coherent(&dev->pci->dev, SAA7146_CLIPPING_MEM, + vv->d_clipping.cpu_addr, vv->d_clipping.dma_handle); + v4l2_ctrl_handler_free(&dev->ctrl_handler); + kfree(vv); + dev->vv_data = NULL; + dev->vv_callback = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_vv_release); + +int saa7146_register_device(struct video_device *vfd, struct saa7146_dev *dev, + char *name, int type) +{ + int err; + int i; + + DEB_EE("dev:%p, name:'%s', type:%d\n", dev, name, type); + + vfd->fops = &video_fops; + if (type == VFL_TYPE_VIDEO) + vfd->ioctl_ops = &dev->ext_vv_data->vid_ops; + else + vfd->ioctl_ops = &dev->ext_vv_data->vbi_ops; + vfd->release = video_device_release_empty; + vfd->lock = &dev->v4l2_lock; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->tvnorms = 0; + for (i = 0; i < dev->ext_vv_data->num_stds; i++) + vfd->tvnorms |= dev->ext_vv_data->stds[i].id; + strscpy(vfd->name, name, sizeof(vfd->name)); + vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + vfd->device_caps |= dev->ext_vv_data->capabilities; + if (type == VFL_TYPE_VIDEO) + vfd->device_caps &= + ~(V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_OUTPUT); + else + vfd->device_caps &= + ~(V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_AUDIO); + video_set_drvdata(vfd, dev); + + err = video_register_device(vfd, type, -1); + if (err < 0) { + ERR("cannot register v4l2 device. skipping.\n"); + return err; + } + + pr_info("%s: registered device %s [v4l2]\n", + dev->name, video_device_node_name(vfd)); + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_register_device); + +int saa7146_unregister_device(struct video_device *vfd, struct saa7146_dev *dev) +{ + DEB_EE("dev:%p\n", dev); + + video_unregister_device(vfd); + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_unregister_device); + +static int __init saa7146_vv_init_module(void) +{ + return 0; +} + + +static void __exit saa7146_vv_cleanup_module(void) +{ +} + +module_init(saa7146_vv_init_module); +module_exit(saa7146_vv_cleanup_module); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/saa7146/saa7146_hlp.c b/drivers/media/common/saa7146/saa7146_hlp.c new file mode 100644 index 000000000000..6c9946a402ee --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_hlp.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/export.h> +#include <media/drv-intf/saa7146_vv.h> + +static void calculate_output_format_register(struct saa7146_dev* saa, u32 palette, u32* clip_format) +{ + /* clear out the necessary bits */ + *clip_format &= 0x0000ffff; + /* set these bits new */ + *clip_format |= (( ((palette&0xf00)>>8) << 30) | ((palette&0x00f) << 24) | (((palette&0x0f0)>>4) << 16)); +} + +static void calculate_hps_source_and_sync(struct saa7146_dev *dev, int source, int sync, u32* hps_ctrl) +{ + *hps_ctrl &= ~(MASK_30 | MASK_31 | MASK_28); + *hps_ctrl |= (source << 30) | (sync << 28); +} + +static void calculate_hxo_and_hyo(struct saa7146_vv *vv, u32* hps_h_scale, u32* hps_ctrl) +{ + int hyo = 0, hxo = 0; + + hyo = vv->standard->v_offset; + hxo = vv->standard->h_offset; + + *hps_h_scale &= ~(MASK_B0 | 0xf00); + *hps_h_scale |= (hxo << 0); + + *hps_ctrl &= ~(MASK_W0 | MASK_B2); + *hps_ctrl |= (hyo << 12); +} + +/* helper functions for the calculation of the horizontal- and vertical + scaling registers, clip-format-register etc ... + these functions take pointers to the (most-likely read-out + original-values) and manipulate them according to the requested + changes. +*/ + +/* hps_coeff used for CXY and CXUV; scale 1/1 -> scale 1/64 */ +static struct { + u16 hps_coeff; + u16 weight_sum; +} hps_h_coeff_tab [] = { + {0x00, 2}, {0x02, 4}, {0x00, 4}, {0x06, 8}, {0x02, 8}, + {0x08, 8}, {0x00, 8}, {0x1E, 16}, {0x0E, 8}, {0x26, 8}, + {0x06, 8}, {0x42, 8}, {0x02, 8}, {0x80, 8}, {0x00, 8}, + {0xFE, 16}, {0xFE, 8}, {0x7E, 8}, {0x7E, 8}, {0x3E, 8}, + {0x3E, 8}, {0x1E, 8}, {0x1E, 8}, {0x0E, 8}, {0x0E, 8}, + {0x06, 8}, {0x06, 8}, {0x02, 8}, {0x02, 8}, {0x00, 8}, + {0x00, 8}, {0xFE, 16}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, + {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, + {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, + {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0x7E, 8}, + {0x7E, 8}, {0x3E, 8}, {0x3E, 8}, {0x1E, 8}, {0x1E, 8}, + {0x0E, 8}, {0x0E, 8}, {0x06, 8}, {0x06, 8}, {0x02, 8}, + {0x02, 8}, {0x00, 8}, {0x00, 8}, {0xFE, 16} +}; + +/* table of attenuation values for horizontal scaling */ +static u8 h_attenuation[] = { 1, 2, 4, 8, 2, 4, 8, 16, 0}; + +/* calculate horizontal scale registers */ +static int calculate_h_scale_registers(struct saa7146_dev *dev, + int in_x, int out_x, int flip_lr, + u32* hps_ctrl, u32* hps_v_gain, u32* hps_h_prescale, u32* hps_h_scale) +{ + /* horizontal prescaler */ + u32 dcgx = 0, xpsc = 0, xacm = 0, cxy = 0, cxuv = 0; + /* horizontal scaler */ + u32 xim = 0, xp = 0, xsci =0; + /* vertical scale & gain */ + u32 pfuv = 0; + + /* helper variables */ + u32 h_atten = 0, i = 0; + + if ( 0 == out_x ) { + return -EINVAL; + } + + /* mask out vanity-bit */ + *hps_ctrl &= ~MASK_29; + + /* calculate prescale-(xspc)-value: [n .. 1/2) : 1 + [1/2 .. 1/3) : 2 + [1/3 .. 1/4) : 3 + ... */ + if (in_x > out_x) { + xpsc = in_x / out_x; + } + else { + /* zooming */ + xpsc = 1; + } + + /* if flip_lr-bit is set, number of pixels after + horizontal prescaling must be < 384 */ + if ( 0 != flip_lr ) { + + /* set vanity bit */ + *hps_ctrl |= MASK_29; + + while (in_x / xpsc >= 384 ) + xpsc++; + } + /* if zooming is wanted, number of pixels after + horizontal prescaling must be < 768 */ + else { + while ( in_x / xpsc >= 768 ) + xpsc++; + } + + /* maximum prescale is 64 (p.69) */ + if ( xpsc > 64 ) + xpsc = 64; + + /* keep xacm clear*/ + xacm = 0; + + /* set horizontal filter parameters (CXY = CXUV) */ + cxy = hps_h_coeff_tab[( (xpsc - 1) < 63 ? (xpsc - 1) : 63 )].hps_coeff; + cxuv = cxy; + + /* calculate and set horizontal fine scale (xsci) */ + + /* bypass the horizontal scaler ? */ + if ( (in_x == out_x) && ( 1 == xpsc ) ) + xsci = 0x400; + else + xsci = ( (1024 * in_x) / (out_x * xpsc) ) + xpsc; + + /* set start phase for horizontal fine scale (xp) to 0 */ + xp = 0; + + /* set xim, if we bypass the horizontal scaler */ + if ( 0x400 == xsci ) + xim = 1; + else + xim = 0; + + /* if the prescaler is bypassed, enable horizontal + accumulation mode (xacm) and clear dcgx */ + if( 1 == xpsc ) { + xacm = 1; + dcgx = 0; + } else { + xacm = 0; + /* get best match in the table of attenuations + for horizontal scaling */ + h_atten = hps_h_coeff_tab[( (xpsc - 1) < 63 ? (xpsc - 1) : 63 )].weight_sum; + + for (i = 0; h_attenuation[i] != 0; i++) { + if (h_attenuation[i] >= h_atten) + break; + } + + dcgx = i; + } + + /* the horizontal scaling increment controls the UV filter + to reduce the bandwidth to improve the display quality, + so set it ... */ + if ( xsci == 0x400) + pfuv = 0x00; + else if ( xsci < 0x600) + pfuv = 0x01; + else if ( xsci < 0x680) + pfuv = 0x11; + else if ( xsci < 0x700) + pfuv = 0x22; + else + pfuv = 0x33; + + + *hps_v_gain &= MASK_W0|MASK_B2; + *hps_v_gain |= (pfuv << 24); + + *hps_h_scale &= ~(MASK_W1 | 0xf000); + *hps_h_scale |= (xim << 31) | (xp << 24) | (xsci << 12); + + *hps_h_prescale |= (dcgx << 27) | ((xpsc-1) << 18) | (xacm << 17) | (cxy << 8) | (cxuv << 0); + + return 0; +} + +static struct { + u16 hps_coeff; + u16 weight_sum; +} hps_v_coeff_tab [] = { + {0x0100, 2}, {0x0102, 4}, {0x0300, 4}, {0x0106, 8}, {0x0502, 8}, + {0x0708, 8}, {0x0F00, 8}, {0x011E, 16}, {0x110E, 16}, {0x1926, 16}, + {0x3906, 16}, {0x3D42, 16}, {0x7D02, 16}, {0x7F80, 16}, {0xFF00, 16}, + {0x01FE, 32}, {0x01FE, 32}, {0x817E, 32}, {0x817E, 32}, {0xC13E, 32}, + {0xC13E, 32}, {0xE11E, 32}, {0xE11E, 32}, {0xF10E, 32}, {0xF10E, 32}, + {0xF906, 32}, {0xF906, 32}, {0xFD02, 32}, {0xFD02, 32}, {0xFF00, 32}, + {0xFF00, 32}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, + {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, + {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, + {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x817E, 64}, + {0x817E, 64}, {0xC13E, 64}, {0xC13E, 64}, {0xE11E, 64}, {0xE11E, 64}, + {0xF10E, 64}, {0xF10E, 64}, {0xF906, 64}, {0xF906, 64}, {0xFD02, 64}, + {0xFD02, 64}, {0xFF00, 64}, {0xFF00, 64}, {0x01FE, 128} +}; + +/* table of attenuation values for vertical scaling */ +static u16 v_attenuation[] = { 2, 4, 8, 16, 32, 64, 128, 256, 0}; + +/* calculate vertical scale registers */ +static int calculate_v_scale_registers(struct saa7146_dev *dev, enum v4l2_field field, + int in_y, int out_y, u32* hps_v_scale, u32* hps_v_gain) +{ + int lpi = 0; + + /* vertical scaling */ + u32 yacm = 0, ysci = 0, yacl = 0, ypo = 0, ype = 0; + /* vertical scale & gain */ + u32 dcgy = 0, cya_cyb = 0; + + /* helper variables */ + u32 v_atten = 0, i = 0; + + /* error, if vertical zooming */ + if ( in_y < out_y ) { + return -EINVAL; + } + + /* linear phase interpolation may be used + if scaling is between 1 and 1/2 (both fields used) + or scaling is between 1/2 and 1/4 (if only one field is used) */ + + if (V4L2_FIELD_HAS_BOTH(field)) { + if( 2*out_y >= in_y) { + lpi = 1; + } + } else if (field == V4L2_FIELD_TOP + || field == V4L2_FIELD_ALTERNATE + || field == V4L2_FIELD_BOTTOM) { + if( 4*out_y >= in_y ) { + lpi = 1; + } + out_y *= 2; + } + if( 0 != lpi ) { + + yacm = 0; + yacl = 0; + cya_cyb = 0x00ff; + + /* calculate scaling increment */ + if ( in_y > out_y ) + ysci = ((1024 * in_y) / (out_y + 1)) - 1024; + else + ysci = 0; + + dcgy = 0; + + /* calculate ype and ypo */ + ype = ysci / 16; + ypo = ype + (ysci / 64); + + } else { + yacm = 1; + + /* calculate scaling increment */ + ysci = (((10 * 1024 * (in_y - out_y - 1)) / in_y) + 9) / 10; + + /* calculate ype and ypo */ + ypo = ype = ((ysci + 15) / 16); + + /* the sequence length interval (yacl) has to be set according + to the prescale value, e.g. [n .. 1/2) : 0 + [1/2 .. 1/3) : 1 + [1/3 .. 1/4) : 2 + ... */ + if ( ysci < 512) { + yacl = 0; + } else { + yacl = ( ysci / (1024 - ysci) ); + } + + /* get filter coefficients for cya, cyb from table hps_v_coeff_tab */ + cya_cyb = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].hps_coeff; + + /* get best match in the table of attenuations for vertical scaling */ + v_atten = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].weight_sum; + + for (i = 0; v_attenuation[i] != 0; i++) { + if (v_attenuation[i] >= v_atten) + break; + } + + dcgy = i; + } + + /* ypo and ype swapped in spec ? */ + *hps_v_scale |= (yacm << 31) | (ysci << 21) | (yacl << 15) | (ypo << 8 ) | (ype << 1); + + *hps_v_gain &= ~(MASK_W0|MASK_B2); + *hps_v_gain |= (dcgy << 16) | (cya_cyb << 0); + + return 0; +} + +/* simple bubble-sort algorithm with duplicate elimination */ +static int sort_and_eliminate(u32* values, int* count) +{ + int low = 0, high = 0, top = 0; + int cur = 0, next = 0; + + /* sanity checks */ + if( (0 > *count) || (NULL == values) ) { + return -EINVAL; + } + + /* bubble sort the first @count items of the array @values */ + for( top = *count; top > 0; top--) { + for( low = 0, high = 1; high < top; low++, high++) { + if( values[low] > values[high] ) + swap(values[low], values[high]); + } + } + + /* remove duplicate items */ + for( cur = 0, next = 1; next < *count; next++) { + if( values[cur] != values[next]) + values[++cur] = values[next]; + } + + *count = cur + 1; + + return 0; +} + +static void calculate_clipping_registers_rect(struct saa7146_dev *dev, struct saa7146_fh *fh, + struct saa7146_video_dma *vdma2, u32* clip_format, u32* arbtr_ctrl, enum v4l2_field field) +{ + struct saa7146_vv *vv = dev->vv_data; + __le32 *clipping = vv->d_clipping.cpu_addr; + + int width = vv->ov.win.w.width; + int height = vv->ov.win.w.height; + int clipcount = vv->ov.nclips; + + u32 line_list[32]; + u32 pixel_list[32]; + int numdwords = 0; + + int i = 0, j = 0; + int cnt_line = 0, cnt_pixel = 0; + + int x[32], y[32], w[32], h[32]; + + /* clear out memory */ + memset(&line_list[0], 0x00, sizeof(u32)*32); + memset(&pixel_list[0], 0x00, sizeof(u32)*32); + memset(clipping, 0x00, SAA7146_CLIPPING_MEM); + + /* fill the line and pixel-lists */ + for(i = 0; i < clipcount; i++) { + int l = 0, r = 0, t = 0, b = 0; + + x[i] = vv->ov.clips[i].c.left; + y[i] = vv->ov.clips[i].c.top; + w[i] = vv->ov.clips[i].c.width; + h[i] = vv->ov.clips[i].c.height; + + if( w[i] < 0) { + x[i] += w[i]; w[i] = -w[i]; + } + if( h[i] < 0) { + y[i] += h[i]; h[i] = -h[i]; + } + if( x[i] < 0) { + w[i] += x[i]; x[i] = 0; + } + if( y[i] < 0) { + h[i] += y[i]; y[i] = 0; + } + if( 0 != vv->vflip ) { + y[i] = height - y[i] - h[i]; + } + + l = x[i]; + r = x[i]+w[i]; + t = y[i]; + b = y[i]+h[i]; + + /* insert left/right coordinates */ + pixel_list[ 2*i ] = min_t(int, l, width); + pixel_list[(2*i)+1] = min_t(int, r, width); + /* insert top/bottom coordinates */ + line_list[ 2*i ] = min_t(int, t, height); + line_list[(2*i)+1] = min_t(int, b, height); + } + + /* sort and eliminate lists */ + cnt_line = cnt_pixel = 2*clipcount; + sort_and_eliminate( &pixel_list[0], &cnt_pixel ); + sort_and_eliminate( &line_list[0], &cnt_line ); + + /* calculate the number of used u32s */ + numdwords = max_t(int, (cnt_line+1), (cnt_pixel+1))*2; + numdwords = max_t(int, 4, numdwords); + numdwords = min_t(int, 64, numdwords); + + /* fill up cliptable */ + for(i = 0; i < cnt_pixel; i++) { + clipping[2*i] |= cpu_to_le32(pixel_list[i] << 16); + } + for(i = 0; i < cnt_line; i++) { + clipping[(2*i)+1] |= cpu_to_le32(line_list[i] << 16); + } + + /* fill up cliptable with the display infos */ + for(j = 0; j < clipcount; j++) { + + for(i = 0; i < cnt_pixel; i++) { + + if( x[j] < 0) + x[j] = 0; + + if( pixel_list[i] < (x[j] + w[j])) { + + if ( pixel_list[i] >= x[j] ) { + clipping[2*i] |= cpu_to_le32(1 << j); + } + } + } + for(i = 0; i < cnt_line; i++) { + + if( y[j] < 0) + y[j] = 0; + + if( line_list[i] < (y[j] + h[j]) ) { + + if( line_list[i] >= y[j] ) { + clipping[(2*i)+1] |= cpu_to_le32(1 << j); + } + } + } + } + + /* adjust arbitration control register */ + *arbtr_ctrl &= 0xffff00ff; + *arbtr_ctrl |= 0x00001c00; + + vdma2->base_even = vv->d_clipping.dma_handle; + vdma2->base_odd = vv->d_clipping.dma_handle; + vdma2->prot_addr = vv->d_clipping.dma_handle+((sizeof(u32))*(numdwords)); + vdma2->base_page = 0x04; + vdma2->pitch = 0x00; + vdma2->num_line_byte = (0 << 16 | (sizeof(u32))*(numdwords-1) ); + + /* set clipping-mode. this depends on the field(s) used */ + *clip_format &= 0xfffffff7; + if (V4L2_FIELD_HAS_BOTH(field)) { + *clip_format |= 0x00000008; + } else { + *clip_format |= 0x00000000; + } +} + +/* disable clipping */ +static void saa7146_disable_clipping(struct saa7146_dev *dev) +{ + u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); + + /* mask out relevant bits (=lower word)*/ + clip_format &= MASK_W1; + + /* upload clipping-registers*/ + saa7146_write(dev, CLIP_FORMAT_CTRL,clip_format); + saa7146_write(dev, MC2, (MASK_05 | MASK_21)); + + /* disable video dma2 */ + saa7146_write(dev, MC1, MASK_21); +} + +static void saa7146_set_clipping_rect(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + enum v4l2_field field = vv->ov.win.field; + struct saa7146_video_dma vdma2; + u32 clip_format; + u32 arbtr_ctrl; + + /* check clipcount, disable clipping if clipcount == 0*/ + if (vv->ov.nclips == 0) { + saa7146_disable_clipping(dev); + return; + } + + clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); + arbtr_ctrl = saa7146_read(dev, PCI_BT_V1); + + calculate_clipping_registers_rect(dev, fh, &vdma2, &clip_format, &arbtr_ctrl, field); + + /* set clipping format */ + clip_format &= 0xffff0008; + clip_format |= (SAA7146_CLIPPING_RECT << 4); + + /* prepare video dma2 */ + saa7146_write(dev, BASE_EVEN2, vdma2.base_even); + saa7146_write(dev, BASE_ODD2, vdma2.base_odd); + saa7146_write(dev, PROT_ADDR2, vdma2.prot_addr); + saa7146_write(dev, BASE_PAGE2, vdma2.base_page); + saa7146_write(dev, PITCH2, vdma2.pitch); + saa7146_write(dev, NUM_LINE_BYTE2, vdma2.num_line_byte); + + /* prepare the rest */ + saa7146_write(dev, CLIP_FORMAT_CTRL,clip_format); + saa7146_write(dev, PCI_BT_V1, arbtr_ctrl); + + /* upload clip_control-register, clipping-registers, enable video dma2 */ + saa7146_write(dev, MC2, (MASK_05 | MASK_21 | MASK_03 | MASK_19)); + saa7146_write(dev, MC1, (MASK_05 | MASK_21)); +} + +static void saa7146_set_window(struct saa7146_dev *dev, int width, int height, enum v4l2_field field) +{ + struct saa7146_vv *vv = dev->vv_data; + + int source = vv->current_hps_source; + int sync = vv->current_hps_sync; + + u32 hps_v_scale = 0, hps_v_gain = 0, hps_ctrl = 0, hps_h_prescale = 0, hps_h_scale = 0; + + /* set vertical scale */ + hps_v_scale = 0; /* all bits get set by the function-call */ + hps_v_gain = 0; /* fixme: saa7146_read(dev, HPS_V_GAIN);*/ + calculate_v_scale_registers(dev, field, vv->standard->v_field*2, height, &hps_v_scale, &hps_v_gain); + + /* set horizontal scale */ + hps_ctrl = 0; + hps_h_prescale = 0; /* all bits get set in the function */ + hps_h_scale = 0; + calculate_h_scale_registers(dev, vv->standard->h_pixels, width, vv->hflip, &hps_ctrl, &hps_v_gain, &hps_h_prescale, &hps_h_scale); + + /* set hyo and hxo */ + calculate_hxo_and_hyo(vv, &hps_h_scale, &hps_ctrl); + calculate_hps_source_and_sync(dev, source, sync, &hps_ctrl); + + /* write out new register contents */ + saa7146_write(dev, HPS_V_SCALE, hps_v_scale); + saa7146_write(dev, HPS_V_GAIN, hps_v_gain); + saa7146_write(dev, HPS_CTRL, hps_ctrl); + saa7146_write(dev, HPS_H_PRESCALE,hps_h_prescale); + saa7146_write(dev, HPS_H_SCALE, hps_h_scale); + + /* upload shadow-ram registers */ + saa7146_write(dev, MC2, (MASK_05 | MASK_06 | MASK_21 | MASK_22) ); +} + +/* calculate the new memory offsets for a desired position */ +static void saa7146_set_position(struct saa7146_dev *dev, int w_x, int w_y, int w_height, enum v4l2_field field, u32 pixelformat) +{ + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev, pixelformat); + + int b_depth = vv->ov_fmt->depth; + int b_bpl = vv->ov_fb.fmt.bytesperline; + /* The unsigned long cast is to remove a 64-bit compile warning since + it looks like a 64-bit address is cast to a 32-bit value, even + though the base pointer is really a 32-bit physical address that + goes into a 32-bit DMA register. + FIXME: might not work on some 64-bit platforms, but see the FIXME + in struct v4l2_framebuffer (videodev2.h) for that. + */ + u32 base = (u32)(unsigned long)vv->ov_fb.base; + + struct saa7146_video_dma vdma1; + + /* calculate memory offsets for picture, look if we shall top-down-flip */ + vdma1.pitch = 2*b_bpl; + if ( 0 == vv->vflip ) { + vdma1.base_even = base + (w_y * (vdma1.pitch/2)) + (w_x * (b_depth / 8)); + vdma1.base_odd = vdma1.base_even + (vdma1.pitch / 2); + vdma1.prot_addr = vdma1.base_even + (w_height * (vdma1.pitch / 2)); + } + else { + vdma1.base_even = base + ((w_y+w_height) * (vdma1.pitch/2)) + (w_x * (b_depth / 8)); + vdma1.base_odd = vdma1.base_even - (vdma1.pitch / 2); + vdma1.prot_addr = vdma1.base_odd - (w_height * (vdma1.pitch / 2)); + } + + if (V4L2_FIELD_HAS_BOTH(field)) { + } else if (field == V4L2_FIELD_ALTERNATE) { + /* fixme */ + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + } else if (field == V4L2_FIELD_TOP) { + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + } else if (field == V4L2_FIELD_BOTTOM) { + vdma1.base_odd = vdma1.base_even; + vdma1.base_even = vdma1.prot_addr; + vdma1.pitch /= 2; + } + + if ( 0 != vv->vflip ) { + vdma1.pitch *= -1; + } + + vdma1.base_page = sfmt->swap; + vdma1.num_line_byte = (vv->standard->v_field<<16)+vv->standard->h_pixels; + + saa7146_write_out_dma(dev, 1, &vdma1); +} + +static void saa7146_set_output_format(struct saa7146_dev *dev, unsigned long palette) +{ + u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); + + /* call helper function */ + calculate_output_format_register(dev,palette,&clip_format); + + /* update the hps registers */ + saa7146_write(dev, CLIP_FORMAT_CTRL, clip_format); + saa7146_write(dev, MC2, (MASK_05 | MASK_21)); +} + +/* select input-source */ +void saa7146_set_hps_source_and_sync(struct saa7146_dev *dev, int source, int sync) +{ + struct saa7146_vv *vv = dev->vv_data; + u32 hps_ctrl = 0; + + /* read old state */ + hps_ctrl = saa7146_read(dev, HPS_CTRL); + + hps_ctrl &= ~( MASK_31 | MASK_30 | MASK_28 ); + hps_ctrl |= (source << 30) | (sync << 28); + + /* write back & upload register */ + saa7146_write(dev, HPS_CTRL, hps_ctrl); + saa7146_write(dev, MC2, (MASK_05 | MASK_21)); + + vv->current_hps_source = source; + vv->current_hps_sync = sync; +} +EXPORT_SYMBOL_GPL(saa7146_set_hps_source_and_sync); + +int saa7146_enable_overlay(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + + saa7146_set_window(dev, vv->ov.win.w.width, vv->ov.win.w.height, vv->ov.win.field); + saa7146_set_position(dev, vv->ov.win.w.left, vv->ov.win.w.top, vv->ov.win.w.height, vv->ov.win.field, vv->ov_fmt->pixelformat); + saa7146_set_output_format(dev, vv->ov_fmt->trans); + saa7146_set_clipping_rect(fh); + + /* enable video dma1 */ + saa7146_write(dev, MC1, (MASK_06 | MASK_22)); + return 0; +} + +void saa7146_disable_overlay(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + + /* disable clipping + video dma1 */ + saa7146_disable_clipping(dev); + saa7146_write(dev, MC1, MASK_22); +} + +void saa7146_write_out_dma(struct saa7146_dev* dev, int which, struct saa7146_video_dma* vdma) +{ + int where = 0; + + if( which < 1 || which > 3) { + return; + } + + /* calculate starting address */ + where = (which-1)*0x18; + + saa7146_write(dev, where, vdma->base_odd); + saa7146_write(dev, where+0x04, vdma->base_even); + saa7146_write(dev, where+0x08, vdma->prot_addr); + saa7146_write(dev, where+0x0c, vdma->pitch); + saa7146_write(dev, where+0x10, vdma->base_page); + saa7146_write(dev, where+0x14, vdma->num_line_byte); + + /* upload */ + saa7146_write(dev, MC2, (MASK_02<<(which-1))|(MASK_18<<(which-1))); +/* + printk("vdma%d.base_even: 0x%08x\n", which,vdma->base_even); + printk("vdma%d.base_odd: 0x%08x\n", which,vdma->base_odd); + printk("vdma%d.prot_addr: 0x%08x\n", which,vdma->prot_addr); + printk("vdma%d.base_page: 0x%08x\n", which,vdma->base_page); + printk("vdma%d.pitch: 0x%08x\n", which,vdma->pitch); + printk("vdma%d.num_line_byte: 0x%08x\n", which,vdma->num_line_byte); +*/ +} + +static int calculate_video_dma_grab_packed(struct saa7146_dev* dev, struct saa7146_buf *buf) +{ + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_video_dma vdma1; + + struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + + int width = buf->fmt->width; + int height = buf->fmt->height; + int bytesperline = buf->fmt->bytesperline; + enum v4l2_field field = buf->fmt->field; + + int depth = sfmt->depth; + + DEB_CAP("[size=%dx%d,fields=%s]\n", + width, height, v4l2_field_names[field]); + + if( bytesperline != 0) { + vdma1.pitch = bytesperline*2; + } else { + vdma1.pitch = (width*depth*2)/8; + } + vdma1.num_line_byte = ((vv->standard->v_field<<16) + vv->standard->h_pixels); + vdma1.base_page = buf->pt[0].dma | ME1 | sfmt->swap; + + if( 0 != vv->vflip ) { + vdma1.prot_addr = buf->pt[0].offset; + vdma1.base_even = buf->pt[0].offset+(vdma1.pitch/2)*height; + vdma1.base_odd = vdma1.base_even - (vdma1.pitch/2); + } else { + vdma1.base_even = buf->pt[0].offset; + vdma1.base_odd = vdma1.base_even + (vdma1.pitch/2); + vdma1.prot_addr = buf->pt[0].offset+(vdma1.pitch/2)*height; + } + + if (V4L2_FIELD_HAS_BOTH(field)) { + } else if (field == V4L2_FIELD_ALTERNATE) { + /* fixme */ + if ( vv->last_field == V4L2_FIELD_TOP ) { + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + } else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { + vdma1.base_odd = vdma1.base_even; + vdma1.base_even = vdma1.prot_addr; + vdma1.pitch /= 2; + } + } else if (field == V4L2_FIELD_TOP) { + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + } else if (field == V4L2_FIELD_BOTTOM) { + vdma1.base_odd = vdma1.base_even; + vdma1.base_even = vdma1.prot_addr; + vdma1.pitch /= 2; + } + + if( 0 != vv->vflip ) { + vdma1.pitch *= -1; + } + + saa7146_write_out_dma(dev, 1, &vdma1); + return 0; +} + +static int calc_planar_422(struct saa7146_vv *vv, struct saa7146_buf *buf, struct saa7146_video_dma *vdma2, struct saa7146_video_dma *vdma3) +{ + int height = buf->fmt->height; + int width = buf->fmt->width; + + vdma2->pitch = width; + vdma3->pitch = width; + + /* fixme: look at bytesperline! */ + + if( 0 != vv->vflip ) { + vdma2->prot_addr = buf->pt[1].offset; + vdma2->base_even = ((vdma2->pitch/2)*height)+buf->pt[1].offset; + vdma2->base_odd = vdma2->base_even - (vdma2->pitch/2); + + vdma3->prot_addr = buf->pt[2].offset; + vdma3->base_even = ((vdma3->pitch/2)*height)+buf->pt[2].offset; + vdma3->base_odd = vdma3->base_even - (vdma3->pitch/2); + } else { + vdma3->base_even = buf->pt[2].offset; + vdma3->base_odd = vdma3->base_even + (vdma3->pitch/2); + vdma3->prot_addr = (vdma3->pitch/2)*height+buf->pt[2].offset; + + vdma2->base_even = buf->pt[1].offset; + vdma2->base_odd = vdma2->base_even + (vdma2->pitch/2); + vdma2->prot_addr = (vdma2->pitch/2)*height+buf->pt[1].offset; + } + + return 0; +} + +static int calc_planar_420(struct saa7146_vv *vv, struct saa7146_buf *buf, struct saa7146_video_dma *vdma2, struct saa7146_video_dma *vdma3) +{ + int height = buf->fmt->height; + int width = buf->fmt->width; + + vdma2->pitch = width/2; + vdma3->pitch = width/2; + + if( 0 != vv->vflip ) { + vdma2->prot_addr = buf->pt[2].offset; + vdma2->base_even = ((vdma2->pitch/2)*height)+buf->pt[2].offset; + vdma2->base_odd = vdma2->base_even - (vdma2->pitch/2); + + vdma3->prot_addr = buf->pt[1].offset; + vdma3->base_even = ((vdma3->pitch/2)*height)+buf->pt[1].offset; + vdma3->base_odd = vdma3->base_even - (vdma3->pitch/2); + + } else { + vdma3->base_even = buf->pt[2].offset; + vdma3->base_odd = vdma3->base_even + (vdma3->pitch); + vdma3->prot_addr = (vdma3->pitch/2)*height+buf->pt[2].offset; + + vdma2->base_even = buf->pt[1].offset; + vdma2->base_odd = vdma2->base_even + (vdma2->pitch); + vdma2->prot_addr = (vdma2->pitch/2)*height+buf->pt[1].offset; + } + return 0; +} + +static int calculate_video_dma_grab_planar(struct saa7146_dev* dev, struct saa7146_buf *buf) +{ + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_video_dma vdma1; + struct saa7146_video_dma vdma2; + struct saa7146_video_dma vdma3; + + struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + + int width = buf->fmt->width; + int height = buf->fmt->height; + enum v4l2_field field = buf->fmt->field; + + BUG_ON(0 == buf->pt[0].dma); + BUG_ON(0 == buf->pt[1].dma); + BUG_ON(0 == buf->pt[2].dma); + + DEB_CAP("[size=%dx%d,fields=%s]\n", + width, height, v4l2_field_names[field]); + + /* fixme: look at bytesperline! */ + + /* fixme: what happens for user space buffers here?. The offsets are + most likely wrong, this version here only works for page-aligned + buffers, modifications to the pagetable-functions are necessary...*/ + + vdma1.pitch = width*2; + vdma1.num_line_byte = ((vv->standard->v_field<<16) + vv->standard->h_pixels); + vdma1.base_page = buf->pt[0].dma | ME1; + + if( 0 != vv->vflip ) { + vdma1.prot_addr = buf->pt[0].offset; + vdma1.base_even = ((vdma1.pitch/2)*height)+buf->pt[0].offset; + vdma1.base_odd = vdma1.base_even - (vdma1.pitch/2); + } else { + vdma1.base_even = buf->pt[0].offset; + vdma1.base_odd = vdma1.base_even + (vdma1.pitch/2); + vdma1.prot_addr = (vdma1.pitch/2)*height+buf->pt[0].offset; + } + + vdma2.num_line_byte = 0; /* unused */ + vdma2.base_page = buf->pt[1].dma | ME1; + + vdma3.num_line_byte = 0; /* unused */ + vdma3.base_page = buf->pt[2].dma | ME1; + + switch( sfmt->depth ) { + case 12: { + calc_planar_420(vv,buf,&vdma2,&vdma3); + break; + } + case 16: { + calc_planar_422(vv,buf,&vdma2,&vdma3); + break; + } + default: { + return -1; + } + } + + if (V4L2_FIELD_HAS_BOTH(field)) { + } else if (field == V4L2_FIELD_ALTERNATE) { + /* fixme */ + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + vdma2.base_odd = vdma2.prot_addr; + vdma2.pitch /= 2; + vdma3.base_odd = vdma3.prot_addr; + vdma3.pitch /= 2; + } else if (field == V4L2_FIELD_TOP) { + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + vdma2.base_odd = vdma2.prot_addr; + vdma2.pitch /= 2; + vdma3.base_odd = vdma3.prot_addr; + vdma3.pitch /= 2; + } else if (field == V4L2_FIELD_BOTTOM) { + vdma1.base_odd = vdma1.base_even; + vdma1.base_even = vdma1.prot_addr; + vdma1.pitch /= 2; + vdma2.base_odd = vdma2.base_even; + vdma2.base_even = vdma2.prot_addr; + vdma2.pitch /= 2; + vdma3.base_odd = vdma3.base_even; + vdma3.base_even = vdma3.prot_addr; + vdma3.pitch /= 2; + } + + if( 0 != vv->vflip ) { + vdma1.pitch *= -1; + vdma2.pitch *= -1; + vdma3.pitch *= -1; + } + + saa7146_write_out_dma(dev, 1, &vdma1); + if( (sfmt->flags & FORMAT_BYTE_SWAP) != 0 ) { + saa7146_write_out_dma(dev, 3, &vdma2); + saa7146_write_out_dma(dev, 2, &vdma3); + } else { + saa7146_write_out_dma(dev, 2, &vdma2); + saa7146_write_out_dma(dev, 3, &vdma3); + } + return 0; +} + +static void program_capture_engine(struct saa7146_dev *dev, int planar) +{ + struct saa7146_vv *vv = dev->vv_data; + int count = 0; + + unsigned long e_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B; + unsigned long o_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B; + + /* wait for o_fid_a/b / e_fid_a/b toggle only if rps register 0 is not set*/ + WRITE_RPS0(CMD_PAUSE | CMD_OAN | CMD_SIG0 | o_wait); + WRITE_RPS0(CMD_PAUSE | CMD_OAN | CMD_SIG0 | e_wait); + + /* set rps register 0 */ + WRITE_RPS0(CMD_WR_REG | (1 << 8) | (MC2/4)); + WRITE_RPS0(MASK_27 | MASK_11); + + /* turn on video-dma1 */ + WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); + WRITE_RPS0(MASK_06 | MASK_22); /* => mask */ + WRITE_RPS0(MASK_06 | MASK_22); /* => values */ + if( 0 != planar ) { + /* turn on video-dma2 */ + WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); + WRITE_RPS0(MASK_05 | MASK_21); /* => mask */ + WRITE_RPS0(MASK_05 | MASK_21); /* => values */ + + /* turn on video-dma3 */ + WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); + WRITE_RPS0(MASK_04 | MASK_20); /* => mask */ + WRITE_RPS0(MASK_04 | MASK_20); /* => values */ + } + + /* wait for o_fid_a/b / e_fid_a/b toggle */ + if ( vv->last_field == V4L2_FIELD_INTERLACED ) { + WRITE_RPS0(CMD_PAUSE | o_wait); + WRITE_RPS0(CMD_PAUSE | e_wait); + } else if ( vv->last_field == V4L2_FIELD_TOP ) { + WRITE_RPS0(CMD_PAUSE | (vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? MASK_10 : MASK_09)); + WRITE_RPS0(CMD_PAUSE | o_wait); + } else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { + WRITE_RPS0(CMD_PAUSE | (vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? MASK_10 : MASK_09)); + WRITE_RPS0(CMD_PAUSE | e_wait); + } + + /* turn off video-dma1 */ + WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); + WRITE_RPS0(MASK_22 | MASK_06); /* => mask */ + WRITE_RPS0(MASK_22); /* => values */ + if( 0 != planar ) { + /* turn off video-dma2 */ + WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); + WRITE_RPS0(MASK_05 | MASK_21); /* => mask */ + WRITE_RPS0(MASK_21); /* => values */ + + /* turn off video-dma3 */ + WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); + WRITE_RPS0(MASK_04 | MASK_20); /* => mask */ + WRITE_RPS0(MASK_20); /* => values */ + } + + /* generate interrupt */ + WRITE_RPS0(CMD_INTERRUPT); + + /* stop */ + WRITE_RPS0(CMD_STOP); +} + +void saa7146_set_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next) +{ + struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + struct saa7146_vv *vv = dev->vv_data; + u32 vdma1_prot_addr; + + DEB_CAP("buf:%p, next:%p\n", buf, next); + + vdma1_prot_addr = saa7146_read(dev, PROT_ADDR1); + if( 0 == vdma1_prot_addr ) { + /* clear out beginning of streaming bit (rps register 0)*/ + DEB_CAP("forcing sync to new frame\n"); + saa7146_write(dev, MC2, MASK_27 ); + } + + saa7146_set_window(dev, buf->fmt->width, buf->fmt->height, buf->fmt->field); + saa7146_set_output_format(dev, sfmt->trans); + saa7146_disable_clipping(dev); + + if ( vv->last_field == V4L2_FIELD_INTERLACED ) { + } else if ( vv->last_field == V4L2_FIELD_TOP ) { + vv->last_field = V4L2_FIELD_BOTTOM; + } else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { + vv->last_field = V4L2_FIELD_TOP; + } + + if( 0 != IS_PLANAR(sfmt->trans)) { + calculate_video_dma_grab_planar(dev, buf); + program_capture_engine(dev,1); + } else { + calculate_video_dma_grab_packed(dev, buf); + program_capture_engine(dev,0); + } + +/* + printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); + printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); + printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); + printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); + printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1)); + printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); + printk("vdma%d => vptr : 0x%08x\n", 1,saa7146_read(dev,PCI_VDP1)); +*/ + + /* write the address of the rps-program */ + saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); + + /* turn on rps */ + saa7146_write(dev, MC1, (MASK_12 | MASK_28)); +} diff --git a/drivers/media/common/saa7146/saa7146_i2c.c b/drivers/media/common/saa7146/saa7146_i2c.c new file mode 100644 index 000000000000..df9ebe2a168c --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_i2c.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <media/drv-intf/saa7146_vv.h> + +static u32 saa7146_i2c_func(struct i2c_adapter *adapter) +{ + /* DEB_I2C("'%s'\n", adapter->name); */ + + return I2C_FUNC_I2C + | I2C_FUNC_SMBUS_QUICK + | I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE + | I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE_DATA; +} + +/* this function returns the status-register of our i2c-device */ +static inline u32 saa7146_i2c_status(struct saa7146_dev *dev) +{ + u32 iicsta = saa7146_read(dev, I2C_STATUS); + /* DEB_I2C("status: 0x%08x\n", iicsta); */ + return iicsta; +} + +/* this function runs through the i2c-messages and prepares the data to be + sent through the saa7146. have a look at the specifications p. 122 ff + to understand this. it returns the number of u32s to send, or -1 + in case of an error. */ +static int saa7146_i2c_msg_prepare(const struct i2c_msg *m, int num, __le32 *op) +{ + int h1, h2; + int i, j, addr; + int mem = 0, op_count = 0; + + /* first determine size of needed memory */ + for(i = 0; i < num; i++) { + mem += m[i].len + 1; + } + + /* worst case: we need one u32 for three bytes to be send + plus one extra byte to address the device */ + mem = 1 + ((mem-1) / 3); + + /* we assume that op points to a memory of at least + * SAA7146_I2C_MEM bytes size. if we exceed this limit... + */ + if ((4 * mem) > SAA7146_I2C_MEM) { + /* DEB_I2C("cannot prepare i2c-message\n"); */ + return -ENOMEM; + } + + /* be careful: clear out the i2c-mem first */ + memset(op,0,sizeof(__le32)*mem); + + /* loop through all messages */ + for(i = 0; i < num; i++) { + + addr = i2c_8bit_addr_from_msg(&m[i]); + h1 = op_count/3; h2 = op_count%3; + op[h1] |= cpu_to_le32( (u8)addr << ((3-h2)*8)); + op[h1] |= cpu_to_le32(SAA7146_I2C_START << ((3-h2)*2)); + op_count++; + + /* loop through all bytes of message i */ + for(j = 0; j < m[i].len; j++) { + /* insert the data bytes */ + h1 = op_count/3; h2 = op_count%3; + op[h1] |= cpu_to_le32( (u32)((u8)m[i].buf[j]) << ((3-h2)*8)); + op[h1] |= cpu_to_le32( SAA7146_I2C_CONT << ((3-h2)*2)); + op_count++; + } + + } + + /* have a look at the last byte inserted: + if it was: ...CONT change it to ...STOP */ + h1 = (op_count-1)/3; h2 = (op_count-1)%3; + if ( SAA7146_I2C_CONT == (0x3 & (le32_to_cpu(op[h1]) >> ((3-h2)*2))) ) { + op[h1] &= ~cpu_to_le32(0x2 << ((3-h2)*2)); + op[h1] |= cpu_to_le32(SAA7146_I2C_STOP << ((3-h2)*2)); + } + + /* return the number of u32s to send */ + return mem; +} + +/* this functions loops through all i2c-messages. normally, it should determine + which bytes were read through the adapter and write them back to the corresponding + i2c-message. but instead, we simply write back all bytes. + fixme: this could be improved. */ +static int saa7146_i2c_msg_cleanup(const struct i2c_msg *m, int num, __le32 *op) +{ + int i, j; + int op_count = 0; + + /* loop through all messages */ + for(i = 0; i < num; i++) { + + op_count++; + + /* loop through all bytes of message i */ + for(j = 0; j < m[i].len; j++) { + /* write back all bytes that could have been read */ + m[i].buf[j] = (le32_to_cpu(op[op_count/3]) >> ((3-(op_count%3))*8)); + op_count++; + } + } + + return 0; +} + +/* this functions resets the i2c-device and returns 0 if everything was fine, otherwise -1 */ +static int saa7146_i2c_reset(struct saa7146_dev *dev) +{ + /* get current status */ + u32 status = saa7146_i2c_status(dev); + + /* clear registers for sure */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, I2C_TRANSFER, 0); + + /* check if any operation is still in progress */ + if ( 0 != ( status & SAA7146_I2C_BUSY) ) { + + /* yes, kill ongoing operation */ + DEB_I2C("busy_state detected\n"); + + /* set "ABORT-OPERATION"-bit (bit 7)*/ + saa7146_write(dev, I2C_STATUS, (dev->i2c_bitrate | MASK_07)); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + msleep(SAA7146_I2C_DELAY); + + /* clear all error-bits pending; this is needed because p.123, note 1 */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + msleep(SAA7146_I2C_DELAY); + } + + /* check if any error is (still) present. (this can be necessary because p.123, note 1) */ + status = saa7146_i2c_status(dev); + + if ( dev->i2c_bitrate != status ) { + + DEB_I2C("error_state detected. status:0x%08x\n", status); + + /* Repeat the abort operation. This seems to be necessary + after serious protocol errors caused by e.g. the SAA7740 */ + saa7146_write(dev, I2C_STATUS, (dev->i2c_bitrate | MASK_07)); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + msleep(SAA7146_I2C_DELAY); + + /* clear all error-bits pending */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + msleep(SAA7146_I2C_DELAY); + + /* the data sheet says it might be necessary to clear the status + twice after an abort */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + msleep(SAA7146_I2C_DELAY); + } + + /* if any error is still present, a fatal error has occurred ... */ + status = saa7146_i2c_status(dev); + if ( dev->i2c_bitrate != status ) { + DEB_I2C("fatal error. status:0x%08x\n", status); + return -1; + } + + return 0; +} + +/* this functions writes out the data-byte 'dword' to the i2c-device. + it returns 0 if ok, -1 if the transfer failed, -2 if the transfer + failed badly (e.g. address error) */ +static int saa7146_i2c_writeout(struct saa7146_dev *dev, __le32 *dword, int short_delay) +{ + u32 status = 0, mc2 = 0; + int trial = 0; + unsigned long timeout; + + /* write out i2c-command */ + DEB_I2C("before: 0x%08x (status: 0x%08x), %d\n", + *dword, saa7146_read(dev, I2C_STATUS), dev->i2c_op); + + if( 0 != (SAA7146_USE_I2C_IRQ & dev->ext->flags)) { + + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, I2C_TRANSFER, le32_to_cpu(*dword)); + + dev->i2c_op = 1; + SAA7146_ISR_CLEAR(dev, MASK_16|MASK_17); + SAA7146_IER_ENABLE(dev, MASK_16|MASK_17); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + + timeout = HZ/100 + 1; /* 10ms */ + timeout = wait_event_interruptible_timeout(dev->i2c_wq, dev->i2c_op == 0, timeout); + if (timeout == -ERESTARTSYS || dev->i2c_op) { + SAA7146_IER_DISABLE(dev, MASK_16|MASK_17); + SAA7146_ISR_CLEAR(dev, MASK_16|MASK_17); + if (timeout == -ERESTARTSYS) + /* a signal arrived */ + return -ERESTARTSYS; + + pr_warn("%s %s [irq]: timed out waiting for end of xfer\n", + dev->name, __func__); + return -EIO; + } + status = saa7146_read(dev, I2C_STATUS); + } else { + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, I2C_TRANSFER, le32_to_cpu(*dword)); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + + /* do not poll for i2c-status before upload is complete */ + timeout = jiffies + HZ/100 + 1; /* 10ms */ + while(1) { + mc2 = (saa7146_read(dev, MC2) & 0x1); + if( 0 != mc2 ) { + break; + } + if (time_after(jiffies,timeout)) { + pr_warn("%s %s: timed out waiting for MC2\n", + dev->name, __func__); + return -EIO; + } + } + /* wait until we get a transfer done or error */ + timeout = jiffies + HZ/100 + 1; /* 10ms */ + /* first read usually delivers bogus results... */ + saa7146_i2c_status(dev); + while(1) { + status = saa7146_i2c_status(dev); + if ((status & 0x3) != 1) + break; + if (time_after(jiffies,timeout)) { + /* this is normal when probing the bus + * (no answer from nonexisistant device...) + */ + pr_warn("%s %s [poll]: timed out waiting for end of xfer\n", + dev->name, __func__); + return -EIO; + } + if (++trial < 50 && short_delay) + udelay(10); + else + msleep(1); + } + } + + /* give a detailed status report */ + if ( 0 != (status & (SAA7146_I2C_SPERR | SAA7146_I2C_APERR | + SAA7146_I2C_DTERR | SAA7146_I2C_DRERR | + SAA7146_I2C_AL | SAA7146_I2C_ERR | + SAA7146_I2C_BUSY)) ) { + + if ( 0 == (status & SAA7146_I2C_ERR) || + 0 == (status & SAA7146_I2C_BUSY) ) { + /* it may take some time until ERR goes high - ignore */ + DEB_I2C("unexpected i2c status %04x\n", status); + } + if( 0 != (status & SAA7146_I2C_SPERR) ) { + DEB_I2C("error due to invalid start/stop condition\n"); + } + if( 0 != (status & SAA7146_I2C_DTERR) ) { + DEB_I2C("error in data transmission\n"); + } + if( 0 != (status & SAA7146_I2C_DRERR) ) { + DEB_I2C("error when receiving data\n"); + } + if( 0 != (status & SAA7146_I2C_AL) ) { + DEB_I2C("error because arbitration lost\n"); + } + + /* we handle address-errors here */ + if( 0 != (status & SAA7146_I2C_APERR) ) { + DEB_I2C("error in address phase\n"); + return -EREMOTEIO; + } + + return -EIO; + } + + /* read back data, just in case we were reading ... */ + *dword = cpu_to_le32(saa7146_read(dev, I2C_TRANSFER)); + + DEB_I2C("after: 0x%08x\n", *dword); + return 0; +} + +static int saa7146_i2c_transfer(struct saa7146_dev *dev, const struct i2c_msg *msgs, int num, int retries) +{ + int i = 0, count = 0; + __le32 *buffer = dev->d_i2c.cpu_addr; + int err = 0; + int short_delay = 0; + + if (mutex_lock_interruptible(&dev->i2c_lock)) + return -ERESTARTSYS; + + for(i=0;i<num;i++) { + DEB_I2C("msg:%d/%d\n", i+1, num); + } + + /* prepare the message(s), get number of u32s to transfer */ + count = saa7146_i2c_msg_prepare(msgs, num, buffer); + if ( 0 > count ) { + err = -EIO; + goto out; + } + + if ( count > 3 || 0 != (SAA7146_I2C_SHORT_DELAY & dev->ext->flags) ) + short_delay = 1; + + do { + /* reset the i2c-device if necessary */ + err = saa7146_i2c_reset(dev); + if ( 0 > err ) { + DEB_I2C("could not reset i2c-device\n"); + goto out; + } + + /* write out the u32s one after another */ + for(i = 0; i < count; i++) { + err = saa7146_i2c_writeout(dev, &buffer[i], short_delay); + if ( 0 != err) { + /* this one is unsatisfying: some i2c slaves on some + dvb cards don't acknowledge correctly, so the saa7146 + thinks that an address error occurred. in that case, the + transaction should be retrying, even if an address error + occurred. analog saa7146 based cards extensively rely on + i2c address probing, however, and address errors indicate that a + device is really *not* there. retrying in that case + increases the time the device needs to probe greatly, so + it should be avoided. So we bail out in irq mode after an + address error and trust the saa7146 address error detection. */ + if (-EREMOTEIO == err && 0 != (SAA7146_USE_I2C_IRQ & dev->ext->flags)) + goto out; + DEB_I2C("error while sending message(s). starting again\n"); + break; + } + } + if( 0 == err ) { + err = num; + break; + } + + /* delay a bit before retrying */ + msleep(10); + + } while (err != num && retries--); + + /* quit if any error occurred */ + if (err != num) + goto out; + + /* if any things had to be read, get the results */ + if ( 0 != saa7146_i2c_msg_cleanup(msgs, num, buffer)) { + DEB_I2C("could not cleanup i2c-message\n"); + err = -EIO; + goto out; + } + + /* return the number of delivered messages */ + DEB_I2C("transmission successful. (msg:%d)\n", err); +out: + /* another bug in revision 0: the i2c-registers get uploaded randomly by other + uploads, so we better clear them out before continuing */ + if( 0 == dev->revision ) { + __le32 zero = 0; + saa7146_i2c_reset(dev); + if( 0 != saa7146_i2c_writeout(dev, &zero, short_delay)) { + pr_info("revision 0 error. this should never happen\n"); + } + } + + mutex_unlock(&dev->i2c_lock); + return err; +} + +/* utility functions */ +static int saa7146_i2c_xfer(struct i2c_adapter* adapter, struct i2c_msg *msg, int num) +{ + struct v4l2_device *v4l2_dev = i2c_get_adapdata(adapter); + struct saa7146_dev *dev = to_saa7146_dev(v4l2_dev); + + /* use helper function to transfer data */ + return saa7146_i2c_transfer(dev, msg, num, adapter->retries); +} + + +/*****************************************************************************/ +/* i2c-adapter helper functions */ + +/* exported algorithm data */ +static const struct i2c_algorithm saa7146_algo = { + .master_xfer = saa7146_i2c_xfer, + .functionality = saa7146_i2c_func, +}; + +int saa7146_i2c_adapter_prepare(struct saa7146_dev *dev, struct i2c_adapter *i2c_adapter, u32 bitrate) +{ + DEB_EE("bitrate: 0x%08x\n", bitrate); + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24)); + + dev->i2c_bitrate = bitrate; + saa7146_i2c_reset(dev); + + if (i2c_adapter) { + i2c_set_adapdata(i2c_adapter, &dev->v4l2_dev); + i2c_adapter->dev.parent = &dev->pci->dev; + i2c_adapter->algo = &saa7146_algo; + i2c_adapter->algo_data = NULL; + i2c_adapter->timeout = SAA7146_I2C_TIMEOUT; + i2c_adapter->retries = SAA7146_I2C_RETRIES; + } + + return 0; +} diff --git a/drivers/media/common/saa7146/saa7146_vbi.c b/drivers/media/common/saa7146/saa7146_vbi.c new file mode 100644 index 000000000000..bd442b984423 --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_vbi.c @@ -0,0 +1,498 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <media/drv-intf/saa7146_vv.h> + +static int vbi_pixel_to_capture = 720 * 2; + +static int vbi_workaround(struct saa7146_dev *dev) +{ + struct saa7146_vv *vv = dev->vv_data; + + u32 *cpu; + dma_addr_t dma_addr; + + int count = 0; + int i; + + DECLARE_WAITQUEUE(wait, current); + + DEB_VBI("dev:%p\n", dev); + + /* once again, a bug in the saa7146: the brs acquisition + is buggy and especially the BXO-counter does not work + as specified. there is this workaround, but please + don't let me explain it. ;-) */ + + cpu = dma_alloc_coherent(&dev->pci->dev, 4096, &dma_addr, GFP_KERNEL); + if (NULL == cpu) + return -ENOMEM; + + /* setup some basic programming, just for the workaround */ + saa7146_write(dev, BASE_EVEN3, dma_addr); + saa7146_write(dev, BASE_ODD3, dma_addr+vbi_pixel_to_capture); + saa7146_write(dev, PROT_ADDR3, dma_addr+4096); + saa7146_write(dev, PITCH3, vbi_pixel_to_capture); + saa7146_write(dev, BASE_PAGE3, 0x0); + saa7146_write(dev, NUM_LINE_BYTE3, (2<<16)|((vbi_pixel_to_capture)<<0)); + saa7146_write(dev, MC2, MASK_04|MASK_20); + + /* load brs-control register */ + WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); + /* BXO = 1h, BRS to outbound */ + WRITE_RPS1(0xc000008c); + /* wait for vbi_a or vbi_b*/ + if ( 0 != (SAA7146_USE_PORT_B_FOR_VBI & dev->ext_vv_data->flags)) { + DEB_D("...using port b\n"); + WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | CMD_E_FID_B); + WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | CMD_O_FID_B); +/* + WRITE_RPS1(CMD_PAUSE | MASK_09); +*/ + } else { + DEB_D("...using port a\n"); + WRITE_RPS1(CMD_PAUSE | MASK_10); + } + /* upload brs */ + WRITE_RPS1(CMD_UPLOAD | MASK_08); + /* load brs-control register */ + WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); + /* BYO = 1, BXO = NQBIL (=1728 for PAL, for NTSC this is 858*2) - NumByte3 (=1440) = 288 */ + WRITE_RPS1(((1728-(vbi_pixel_to_capture)) << 7) | MASK_19); + /* wait for brs_done */ + WRITE_RPS1(CMD_PAUSE | MASK_08); + /* upload brs */ + WRITE_RPS1(CMD_UPLOAD | MASK_08); + /* load video-dma3 NumLines3 and NumBytes3 */ + WRITE_RPS1(CMD_WR_REG | (1 << 8) | (NUM_LINE_BYTE3/4)); + /* dev->vbi_count*2 lines, 720 pixel (= 1440 Bytes) */ + WRITE_RPS1((2 << 16) | (vbi_pixel_to_capture)); + /* load brs-control register */ + WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); + /* Set BRS right: note: this is an experimental value for BXO (=> PAL!) */ + WRITE_RPS1((540 << 7) | (5 << 19)); // 5 == vbi_start + /* wait for brs_done */ + WRITE_RPS1(CMD_PAUSE | MASK_08); + /* upload brs and video-dma3*/ + WRITE_RPS1(CMD_UPLOAD | MASK_08 | MASK_04); + /* load mc2 register: enable dma3 */ + WRITE_RPS1(CMD_WR_REG | (1 << 8) | (MC1/4)); + WRITE_RPS1(MASK_20 | MASK_04); + /* generate interrupt */ + WRITE_RPS1(CMD_INTERRUPT); + /* stop rps1 */ + WRITE_RPS1(CMD_STOP); + + /* we have to do the workaround twice to be sure that + everything is ok */ + for(i = 0; i < 2; i++) { + + /* indicate to the irq handler that we do the workaround */ + saa7146_write(dev, MC2, MASK_31|MASK_15); + + saa7146_write(dev, NUM_LINE_BYTE3, (1<<16)|(2<<0)); + saa7146_write(dev, MC2, MASK_04|MASK_20); + + /* enable rps1 irqs */ + SAA7146_IER_ENABLE(dev,MASK_28); + + /* prepare to wait to be woken up by the irq-handler */ + add_wait_queue(&vv->vbi_wq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + /* start rps1 to enable workaround */ + saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); + saa7146_write(dev, MC1, (MASK_13 | MASK_29)); + + schedule(); + + DEB_VBI("brs bug workaround %d/1\n", i); + + remove_wait_queue(&vv->vbi_wq, &wait); + __set_current_state(TASK_RUNNING); + + /* disable rps1 irqs */ + SAA7146_IER_DISABLE(dev,MASK_28); + + /* stop video-dma3 */ + saa7146_write(dev, MC1, MASK_20); + + if(signal_pending(current)) { + + DEB_VBI("aborted (rps:0x%08x)\n", + saa7146_read(dev, RPS_ADDR1)); + + /* stop rps1 for sure */ + saa7146_write(dev, MC1, MASK_29); + + dma_free_coherent(&dev->pci->dev, 4096, cpu, dma_addr); + return -EINTR; + } + } + + dma_free_coherent(&dev->pci->dev, 4096, cpu, dma_addr); + return 0; +} + +static void saa7146_set_vbi_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next) +{ + struct saa7146_vv *vv = dev->vv_data; + + struct saa7146_video_dma vdma3; + + int count = 0; + unsigned long e_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B; + unsigned long o_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B; + +/* + vdma3.base_even = 0xc8000000+2560*70; + vdma3.base_odd = 0xc8000000; + vdma3.prot_addr = 0xc8000000+2560*164; + vdma3.pitch = 2560; + vdma3.base_page = 0; + vdma3.num_line_byte = (64<<16)|((vbi_pixel_to_capture)<<0); // set above! +*/ + vdma3.base_even = buf->pt[2].offset; + vdma3.base_odd = buf->pt[2].offset + 16 * vbi_pixel_to_capture; + vdma3.prot_addr = buf->pt[2].offset + 16 * 2 * vbi_pixel_to_capture; + vdma3.pitch = vbi_pixel_to_capture; + vdma3.base_page = buf->pt[2].dma | ME1; + vdma3.num_line_byte = (16 << 16) | vbi_pixel_to_capture; + + saa7146_write_out_dma(dev, 3, &vdma3); + + /* write beginning of rps-program */ + count = 0; + + /* wait for o_fid_a/b / e_fid_a/b toggle only if bit 1 is not set */ + + /* we don't wait here for the first field anymore. this is different from the video + capture and might cause that the first buffer is only half filled (with only + one field). but since this is some sort of streaming data, this is not that negative. + but by doing this, we can use the whole engine from videobuf-dma-sg.c... */ + +/* + WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | e_wait); + WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | o_wait); +*/ + /* set bit 1 */ + WRITE_RPS1(CMD_WR_REG | (1 << 8) | (MC2/4)); + WRITE_RPS1(MASK_28 | MASK_12); + + /* turn on video-dma3 */ + WRITE_RPS1(CMD_WR_REG_MASK | (MC1/4)); + WRITE_RPS1(MASK_04 | MASK_20); /* => mask */ + WRITE_RPS1(MASK_04 | MASK_20); /* => values */ + + /* wait for o_fid_a/b / e_fid_a/b toggle */ + WRITE_RPS1(CMD_PAUSE | o_wait); + WRITE_RPS1(CMD_PAUSE | e_wait); + + /* generate interrupt */ + WRITE_RPS1(CMD_INTERRUPT); + + /* stop */ + WRITE_RPS1(CMD_STOP); + + /* enable rps1 irqs */ + SAA7146_IER_ENABLE(dev, MASK_28); + + /* write the address of the rps-program */ + saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); + + /* turn on rps */ + saa7146_write(dev, MC1, (MASK_13 | MASK_29)); +} + +static int buffer_activate(struct saa7146_dev *dev, + struct saa7146_buf *buf, + struct saa7146_buf *next) +{ + struct saa7146_vv *vv = dev->vv_data; + buf->vb.state = VIDEOBUF_ACTIVE; + + DEB_VBI("dev:%p, buf:%p, next:%p\n", dev, buf, next); + saa7146_set_vbi_capture(dev,buf,next); + + mod_timer(&vv->vbi_dmaq.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb,enum v4l2_field field) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + int err = 0; + int lines, llength, size; + + lines = 16 * 2 ; /* 2 fields */ + llength = vbi_pixel_to_capture; + size = lines * llength; + + DEB_VBI("vb:%p\n", vb); + + if (0 != buf->vb.baddr && buf->vb.bsize < size) { + DEB_VBI("size mismatch\n"); + return -EINVAL; + } + + if (buf->vb.size != size) + saa7146_dma_free(dev,q,buf); + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + + buf->vb.width = llength; + buf->vb.height = lines; + buf->vb.size = size; + buf->vb.field = field; // FIXME: check this + + saa7146_pgtable_free(dev->pci, &buf->pt[2]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); + + err = videobuf_iolock(q,&buf->vb, NULL); + if (err) + goto oops; + err = saa7146_pgtable_build_single(dev->pci, &buf->pt[2], + dma->sglist, dma->sglen); + if (0 != err) + return err; + } + buf->vb.state = VIDEOBUF_PREPARED; + buf->activate = buffer_activate; + + return 0; + + oops: + DEB_VBI("error out\n"); + saa7146_dma_free(dev,q,buf); + + return err; +} + +static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + int llength,lines; + + lines = 16 * 2 ; /* 2 fields */ + llength = vbi_pixel_to_capture; + + *size = lines * llength; + *count = 2; + + DEB_VBI("count:%d, size:%d\n", *count, *size); + + return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_VBI("vb:%p\n", vb); + saa7146_buffer_queue(dev, &vv->vbi_dmaq, buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_VBI("vb:%p\n", vb); + saa7146_dma_free(dev,q,buf); +} + +static const struct videobuf_queue_ops vbi_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +static void vbi_stop(struct saa7146_fh *fh, struct file *file) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + unsigned long flags; + DEB_VBI("dev:%p, fh:%p\n", dev, fh); + + spin_lock_irqsave(&dev->slock,flags); + + /* disable rps1 */ + saa7146_write(dev, MC1, MASK_29); + + /* disable rps1 irqs */ + SAA7146_IER_DISABLE(dev, MASK_28); + + /* shut down dma 3 transfers */ + saa7146_write(dev, MC1, MASK_20); + + if (vv->vbi_dmaq.curr) + saa7146_buffer_finish(dev, &vv->vbi_dmaq, VIDEOBUF_DONE); + + videobuf_queue_cancel(&fh->vbi_q); + + vv->vbi_streaming = NULL; + + del_timer(&vv->vbi_dmaq.timeout); + del_timer(&vv->vbi_read_timeout); + + spin_unlock_irqrestore(&dev->slock, flags); +} + +static void vbi_read_timeout(struct timer_list *t) +{ + struct saa7146_vv *vv = from_timer(vv, t, vbi_read_timeout); + struct file *file = vv->vbi_read_timeout_file; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + + DEB_VBI("dev:%p, fh:%p\n", dev, fh); + + vbi_stop(fh, file); +} + +static void vbi_init(struct saa7146_dev *dev, struct saa7146_vv *vv) +{ + DEB_VBI("dev:%p\n", dev); + + INIT_LIST_HEAD(&vv->vbi_dmaq.queue); + + timer_setup(&vv->vbi_dmaq.timeout, saa7146_buffer_timeout, 0); + vv->vbi_dmaq.dev = dev; + + init_waitqueue_head(&vv->vbi_wq); +} + +static int vbi_open(struct saa7146_dev *dev, struct file *file) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_vv *vv = fh->dev->vv_data; + + u32 arbtr_ctrl = saa7146_read(dev, PCI_BT_V1); + int ret = 0; + + DEB_VBI("dev:%p, fh:%p\n", dev, fh); + + ret = saa7146_res_get(fh, RESOURCE_DMA3_BRS); + if (0 == ret) { + DEB_S("cannot get vbi RESOURCE_DMA3_BRS resource\n"); + return -EBUSY; + } + + /* adjust arbitrition control for video dma 3 */ + arbtr_ctrl &= ~0x1f0000; + arbtr_ctrl |= 0x1d0000; + saa7146_write(dev, PCI_BT_V1, arbtr_ctrl); + saa7146_write(dev, MC2, (MASK_04|MASK_20)); + + videobuf_queue_sg_init(&fh->vbi_q, &vbi_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, // FIXME: does this really work? + sizeof(struct saa7146_buf), + file, &dev->v4l2_lock); + + vv->vbi_read_timeout.function = vbi_read_timeout; + vv->vbi_read_timeout_file = file; + + /* initialize the brs */ + if ( 0 != (SAA7146_USE_PORT_B_FOR_VBI & dev->ext_vv_data->flags)) { + saa7146_write(dev, BRS_CTRL, MASK_30|MASK_29 | (7 << 19)); + } else { + saa7146_write(dev, BRS_CTRL, 0x00000001); + + if (0 != (ret = vbi_workaround(dev))) { + DEB_VBI("vbi workaround failed!\n"); + /* return ret;*/ + } + } + + /* upload brs register */ + saa7146_write(dev, MC2, (MASK_08|MASK_24)); + return 0; +} + +static void vbi_close(struct saa7146_dev *dev, struct file *file) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_vv *vv = dev->vv_data; + DEB_VBI("dev:%p, fh:%p\n", dev, fh); + + if( fh == vv->vbi_streaming ) { + vbi_stop(fh, file); + } + saa7146_res_free(fh, RESOURCE_DMA3_BRS); +} + +static void vbi_irq_done(struct saa7146_dev *dev, unsigned long status) +{ + struct saa7146_vv *vv = dev->vv_data; + spin_lock(&dev->slock); + + if (vv->vbi_dmaq.curr) { + DEB_VBI("dev:%p, curr:%p\n", dev, vv->vbi_dmaq.curr); + /* this must be += 2, one count for each field */ + vv->vbi_fieldcount+=2; + vv->vbi_dmaq.curr->vb.field_count = vv->vbi_fieldcount; + saa7146_buffer_finish(dev, &vv->vbi_dmaq, VIDEOBUF_DONE); + } else { + DEB_VBI("dev:%p\n", dev); + } + saa7146_buffer_next(dev, &vv->vbi_dmaq, 1); + + spin_unlock(&dev->slock); +} + +static ssize_t vbi_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + ssize_t ret = 0; + + DEB_VBI("dev:%p, fh:%p\n", dev, fh); + + if( NULL == vv->vbi_streaming ) { + // fixme: check if dma3 is available + // fixme: activate vbi engine here if necessary. (really?) + vv->vbi_streaming = fh; + } + + if( fh != vv->vbi_streaming ) { + DEB_VBI("open %p is already using vbi capture\n", + vv->vbi_streaming); + return -EBUSY; + } + + mod_timer(&vv->vbi_read_timeout, jiffies+BUFFER_TIMEOUT); + ret = videobuf_read_stream(&fh->vbi_q, data, count, ppos, 1, + file->f_flags & O_NONBLOCK); +/* + printk("BASE_ODD3: 0x%08x\n", saa7146_read(dev, BASE_ODD3)); + printk("BASE_EVEN3: 0x%08x\n", saa7146_read(dev, BASE_EVEN3)); + printk("PROT_ADDR3: 0x%08x\n", saa7146_read(dev, PROT_ADDR3)); + printk("PITCH3: 0x%08x\n", saa7146_read(dev, PITCH3)); + printk("BASE_PAGE3: 0x%08x\n", saa7146_read(dev, BASE_PAGE3)); + printk("NUM_LINE_BYTE3: 0x%08x\n", saa7146_read(dev, NUM_LINE_BYTE3)); + printk("BRS_CTRL: 0x%08x\n", saa7146_read(dev, BRS_CTRL)); +*/ + return ret; +} + +const struct saa7146_use_ops saa7146_vbi_uops = { + .init = vbi_init, + .open = vbi_open, + .release = vbi_close, + .irq_done = vbi_irq_done, + .read = vbi_read, +}; diff --git a/drivers/media/common/saa7146/saa7146_video.c b/drivers/media/common/saa7146/saa7146_video.c new file mode 100644 index 000000000000..2296765079a4 --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_video.c @@ -0,0 +1,1286 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <media/drv-intf/saa7146_vv.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> +#include <linux/module.h> +#include <linux/kernel.h> + +static int max_memory = 32; + +module_param(max_memory, int, 0644); +MODULE_PARM_DESC(max_memory, "maximum memory usage for capture buffers (default: 32Mb)"); + +#define IS_CAPTURE_ACTIVE(fh) \ + (((vv->video_status & STATUS_CAPTURE) != 0) && (vv->video_fh == fh)) + +#define IS_OVERLAY_ACTIVE(fh) \ + (((vv->video_status & STATUS_OVERLAY) != 0) && (vv->video_fh == fh)) + +/* format descriptions for capture and preview */ +static struct saa7146_format formats[] = { + { + .pixelformat = V4L2_PIX_FMT_RGB332, + .trans = RGB08_COMPOSED, + .depth = 8, + .flags = 0, + }, { + .pixelformat = V4L2_PIX_FMT_RGB565, + .trans = RGB16_COMPOSED, + .depth = 16, + .flags = 0, + }, { + .pixelformat = V4L2_PIX_FMT_BGR24, + .trans = RGB24_COMPOSED, + .depth = 24, + .flags = 0, + }, { + .pixelformat = V4L2_PIX_FMT_BGR32, + .trans = RGB32_COMPOSED, + .depth = 32, + .flags = 0, + }, { + .pixelformat = V4L2_PIX_FMT_RGB32, + .trans = RGB32_COMPOSED, + .depth = 32, + .flags = 0, + .swap = 0x2, + }, { + .pixelformat = V4L2_PIX_FMT_GREY, + .trans = Y8, + .depth = 8, + .flags = 0, + }, { + .pixelformat = V4L2_PIX_FMT_YUV422P, + .trans = YUV422_DECOMPOSED, + .depth = 16, + .flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, + }, { + .pixelformat = V4L2_PIX_FMT_YVU420, + .trans = YUV420_DECOMPOSED, + .depth = 12, + .flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, + }, { + .pixelformat = V4L2_PIX_FMT_YUV420, + .trans = YUV420_DECOMPOSED, + .depth = 12, + .flags = FORMAT_IS_PLANAR, + }, { + .pixelformat = V4L2_PIX_FMT_UYVY, + .trans = YUV422_COMPOSED, + .depth = 16, + .flags = 0, + } +}; + +/* unfortunately, the saa7146 contains a bug which prevents it from doing on-the-fly byte swaps. + due to this, it's impossible to provide additional *packed* formats, which are simply byte swapped + (like V4L2_PIX_FMT_YUYV) ... 8-( */ + +struct saa7146_format* saa7146_format_by_fourcc(struct saa7146_dev *dev, int fourcc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixelformat == fourcc) { + return formats+i; + } + } + + DEB_D("unknown pixelformat:'%4.4s'\n", (char *)&fourcc); + return NULL; +} + +static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f); + +int saa7146_start_preview(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct v4l2_format fmt; + int ret = 0, err = 0; + + DEB_EE("dev:%p, fh:%p\n", dev, fh); + + /* check if we have overlay information */ + if (vv->ov.fh == NULL) { + DEB_D("no overlay data available. try S_FMT first.\n"); + return -EAGAIN; + } + + /* check if streaming capture is running */ + if (IS_CAPTURE_ACTIVE(fh) != 0) { + DEB_D("streaming capture is active\n"); + return -EBUSY; + } + + /* check if overlay is running */ + if (IS_OVERLAY_ACTIVE(fh) != 0) { + if (vv->video_fh == fh) { + DEB_D("overlay is already active\n"); + return 0; + } + DEB_D("overlay is already active in another open\n"); + return -EBUSY; + } + + if (0 == saa7146_res_get(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP)) { + DEB_D("cannot get necessary overlay resources\n"); + return -EBUSY; + } + + fmt.fmt.win = vv->ov.win; + err = vidioc_try_fmt_vid_overlay(NULL, fh, &fmt); + if (0 != err) { + saa7146_res_free(vv->video_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); + return -EBUSY; + } + vv->ov.win = fmt.fmt.win; + + DEB_D("%dx%d+%d+%d 0x%08x field=%s\n", + vv->ov.win.w.width, vv->ov.win.w.height, + vv->ov.win.w.left, vv->ov.win.w.top, + vv->ov_fmt->pixelformat, v4l2_field_names[vv->ov.win.field]); + + if (0 != (ret = saa7146_enable_overlay(fh))) { + DEB_D("enabling overlay failed: %d\n", ret); + saa7146_res_free(vv->video_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); + return ret; + } + + vv->video_status = STATUS_OVERLAY; + vv->video_fh = fh; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_start_preview); + +int saa7146_stop_preview(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + + DEB_EE("dev:%p, fh:%p\n", dev, fh); + + /* check if streaming capture is running */ + if (IS_CAPTURE_ACTIVE(fh) != 0) { + DEB_D("streaming capture is active\n"); + return -EBUSY; + } + + /* check if overlay is running at all */ + if ((vv->video_status & STATUS_OVERLAY) == 0) { + DEB_D("no active overlay\n"); + return 0; + } + + if (vv->video_fh != fh) { + DEB_D("overlay is active, but in another open\n"); + return -EBUSY; + } + + vv->video_status = 0; + vv->video_fh = NULL; + + saa7146_disable_overlay(fh); + + saa7146_res_free(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_stop_preview); + +/********************************************************************************/ +/* common pagetable functions */ + +static int saa7146_pgtable_build(struct saa7146_dev *dev, struct saa7146_buf *buf) +{ + struct pci_dev *pci = dev->pci; + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + struct scatterlist *list = dma->sglist; + int length = dma->sglen; + struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + + DEB_EE("dev:%p, buf:%p, sg_len:%d\n", dev, buf, length); + + if( 0 != IS_PLANAR(sfmt->trans)) { + struct saa7146_pgtable *pt1 = &buf->pt[0]; + struct saa7146_pgtable *pt2 = &buf->pt[1]; + struct saa7146_pgtable *pt3 = &buf->pt[2]; + __le32 *ptr1, *ptr2, *ptr3; + __le32 fill; + + int size = buf->fmt->width*buf->fmt->height; + int i,p,m1,m2,m3,o1,o2; + + switch( sfmt->depth ) { + case 12: { + /* create some offsets inside the page table */ + m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; + m2 = ((size+(size/4)+PAGE_SIZE)/PAGE_SIZE)-1; + m3 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; + o1 = size%PAGE_SIZE; + o2 = (size+(size/4))%PAGE_SIZE; + DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", + size, m1, m2, m3, o1, o2); + break; + } + case 16: { + /* create some offsets inside the page table */ + m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; + m2 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; + m3 = ((2*size+PAGE_SIZE)/PAGE_SIZE)-1; + o1 = size%PAGE_SIZE; + o2 = (size+(size/2))%PAGE_SIZE; + DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", + size, m1, m2, m3, o1, o2); + break; + } + default: { + return -1; + } + } + + ptr1 = pt1->cpu; + ptr2 = pt2->cpu; + ptr3 = pt3->cpu; + + /* walk all pages, copy all page addresses to ptr1 */ + for (i = 0; i < length; i++, list++) { + for (p = 0; p * 4096 < sg_dma_len(list); p++, ptr1++) + *ptr1 = cpu_to_le32(sg_dma_address(list) - list->offset); + } +/* + ptr1 = pt1->cpu; + for(j=0;j<40;j++) { + printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); + } +*/ + + /* if we have a user buffer, the first page may not be + aligned to a page boundary. */ + pt1->offset = dma->sglist->offset; + pt2->offset = pt1->offset+o1; + pt3->offset = pt1->offset+o2; + + /* create video-dma2 page table */ + ptr1 = pt1->cpu; + for(i = m1; i <= m2 ; i++, ptr2++) { + *ptr2 = ptr1[i]; + } + fill = *(ptr2-1); + for(;i<1024;i++,ptr2++) { + *ptr2 = fill; + } + /* create video-dma3 page table */ + ptr1 = pt1->cpu; + for(i = m2; i <= m3; i++,ptr3++) { + *ptr3 = ptr1[i]; + } + fill = *(ptr3-1); + for(;i<1024;i++,ptr3++) { + *ptr3 = fill; + } + /* finally: finish up video-dma1 page table */ + ptr1 = pt1->cpu+m1; + fill = pt1->cpu[m1]; + for(i=m1;i<1024;i++,ptr1++) { + *ptr1 = fill; + } +/* + ptr1 = pt1->cpu; + ptr2 = pt2->cpu; + ptr3 = pt3->cpu; + for(j=0;j<40;j++) { + printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); + } + for(j=0;j<40;j++) { + printk("ptr2 %d: 0x%08x\n",j,ptr2[j]); + } + for(j=0;j<40;j++) { + printk("ptr3 %d: 0x%08x\n",j,ptr3[j]); + } +*/ + } else { + struct saa7146_pgtable *pt = &buf->pt[0]; + return saa7146_pgtable_build_single(pci, pt, list, length); + } + + return 0; +} + + +/********************************************************************************/ +/* file operations */ + +static int video_begin(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_format *fmt = NULL; + unsigned int resource; + int ret = 0, err = 0; + + DEB_EE("dev:%p, fh:%p\n", dev, fh); + + if ((vv->video_status & STATUS_CAPTURE) != 0) { + if (vv->video_fh == fh) { + DEB_S("already capturing\n"); + return 0; + } + DEB_S("already capturing in another open\n"); + return -EBUSY; + } + + if ((vv->video_status & STATUS_OVERLAY) != 0) { + DEB_S("warning: suspending overlay video for streaming capture\n"); + vv->ov_suspend = vv->video_fh; + err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ + if (0 != err) { + DEB_D("suspending video failed. aborting\n"); + return err; + } + } + + fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); + /* we need to have a valid format set here */ + if (!fmt) + return -EINVAL; + + if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { + resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; + } else { + resource = RESOURCE_DMA1_HPS; + } + + ret = saa7146_res_get(fh, resource); + if (0 == ret) { + DEB_S("cannot get capture resource %d\n", resource); + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + return -EBUSY; + } + + /* clear out beginning of streaming bit (rps register 0)*/ + saa7146_write(dev, MC2, MASK_27 ); + + /* enable rps0 irqs */ + SAA7146_IER_ENABLE(dev, MASK_27); + + vv->video_fh = fh; + vv->video_status = STATUS_CAPTURE; + + return 0; +} + +static int video_end(struct saa7146_fh *fh, struct file *file) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_dmaqueue *q = &vv->video_dmaq; + struct saa7146_format *fmt = NULL; + unsigned long flags; + unsigned int resource; + u32 dmas = 0; + DEB_EE("dev:%p, fh:%p\n", dev, fh); + + if ((vv->video_status & STATUS_CAPTURE) != STATUS_CAPTURE) { + DEB_S("not capturing\n"); + return 0; + } + + if (vv->video_fh != fh) { + DEB_S("capturing, but in another open\n"); + return -EBUSY; + } + + fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); + /* we need to have a valid format set here */ + if (!fmt) + return -EINVAL; + + if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { + resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; + dmas = MASK_22 | MASK_21 | MASK_20; + } else { + resource = RESOURCE_DMA1_HPS; + dmas = MASK_22; + } + spin_lock_irqsave(&dev->slock,flags); + + /* disable rps0 */ + saa7146_write(dev, MC1, MASK_28); + + /* disable rps0 irqs */ + SAA7146_IER_DISABLE(dev, MASK_27); + + /* shut down all used video dma transfers */ + saa7146_write(dev, MC1, dmas); + + if (q->curr) + saa7146_buffer_finish(dev, q, VIDEOBUF_DONE); + + spin_unlock_irqrestore(&dev->slock, flags); + + vv->video_fh = NULL; + vv->video_status = 0; + + saa7146_res_free(fh, resource); + + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + + return 0; +} + +static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + + strscpy((char *)cap->driver, "saa7146 v4l2", sizeof(cap->driver)); + strscpy((char *)cap->card, dev->ext->name, sizeof(cap->card)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_DEVICE_CAPS; + cap->capabilities |= dev->ext_vv_data->capabilities; + return 0; +} + +static int vidioc_g_fbuf(struct file *file, void *fh, struct v4l2_framebuffer *fb) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + *fb = vv->ov_fb; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + fb->flags = V4L2_FBUF_FLAG_PRIMARY; + return 0; +} + +static int vidioc_s_fbuf(struct file *file, void *fh, const struct v4l2_framebuffer *fb) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_format *fmt; + + DEB_EE("VIDIOC_S_FBUF\n"); + + if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = saa7146_format_by_fourcc(dev, fb->fmt.pixelformat); + if (NULL == fmt) + return -EINVAL; + + /* planar formats are not allowed for overlay video, clipping and video dma would clash */ + if (fmt->flags & FORMAT_IS_PLANAR) + DEB_S("planar pixelformat '%4.4s' not allowed for overlay\n", + (char *)&fmt->pixelformat); + + /* check if overlay is running */ + if (IS_OVERLAY_ACTIVE(fh) != 0) { + if (vv->video_fh != fh) { + DEB_D("refusing to change framebuffer information while overlay is active in another open\n"); + return -EBUSY; + } + } + + /* ok, accept it */ + vv->ov_fb = *fb; + vv->ov_fmt = fmt; + + if (vv->ov_fb.fmt.bytesperline < vv->ov_fb.fmt.width) { + vv->ov_fb.fmt.bytesperline = vv->ov_fb.fmt.width * fmt->depth / 8; + DEB_D("setting bytesperline to %d\n", vv->ov_fb.fmt.bytesperline); + } + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(formats)) + return -EINVAL; + f->pixelformat = formats[f->index].pixelformat; + return 0; +} + +int saa7146_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa7146_dev *dev = container_of(ctrl->handler, + struct saa7146_dev, ctrl_handler); + struct saa7146_vv *vv = dev->vv_data; + u32 val; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + val = saa7146_read(dev, BCS_CTRL); + val &= 0x00ffffff; + val |= (ctrl->val << 24); + saa7146_write(dev, BCS_CTRL, val); + saa7146_write(dev, MC2, MASK_22 | MASK_06); + break; + + case V4L2_CID_CONTRAST: + val = saa7146_read(dev, BCS_CTRL); + val &= 0xff00ffff; + val |= (ctrl->val << 16); + saa7146_write(dev, BCS_CTRL, val); + saa7146_write(dev, MC2, MASK_22 | MASK_06); + break; + + case V4L2_CID_SATURATION: + val = saa7146_read(dev, BCS_CTRL); + val &= 0xffffff00; + val |= (ctrl->val << 0); + saa7146_write(dev, BCS_CTRL, val); + saa7146_write(dev, MC2, MASK_22 | MASK_06); + break; + + case V4L2_CID_HFLIP: + /* fixme: we can support changing VFLIP and HFLIP here... */ + if ((vv->video_status & STATUS_CAPTURE)) + return -EBUSY; + vv->hflip = ctrl->val; + break; + + case V4L2_CID_VFLIP: + if ((vv->video_status & STATUS_CAPTURE)) + return -EBUSY; + vv->vflip = ctrl->val; + break; + + default: + return -EINVAL; + } + + if ((vv->video_status & STATUS_OVERLAY) != 0) { /* CHECK: && (vv->video_fh == fh)) */ + struct saa7146_fh *fh = vv->video_fh; + + saa7146_stop_preview(fh); + saa7146_start_preview(fh); + } + return 0; +} + +static int vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + parm->parm.capture.readbuffers = 1; + v4l2_video_std_frame_period(vv->standard->id, + &parm->parm.capture.timeperframe); + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + f->fmt.pix = vv->video_fmt; + return 0; +} + +static int vidioc_g_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + f->fmt.win = vv->ov.win; + return 0; +} + +static int vidioc_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + f->fmt.vbi = vv->vbi_fmt; + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_format *fmt; + enum v4l2_field field; + int maxw, maxh; + int calc_bpl; + + DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); + + fmt = saa7146_format_by_fourcc(dev, f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = vv->standard->h_max_out; + maxh = vv->standard->v_max_out; + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh / 2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + switch (field) { + case V4L2_FIELD_ALTERNATE: + vv->last_field = V4L2_FIELD_TOP; + maxh = maxh / 2; + break; + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + vv->last_field = V4L2_FIELD_INTERLACED; + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + vv->last_field = V4L2_FIELD_INTERLACED; + break; + default: + DEB_D("no known field mode '%d'\n", field); + return -EINVAL; + } + + f->fmt.pix.field = field; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + + calc_bpl = (f->fmt.pix.width * fmt->depth) / 8; + + if (f->fmt.pix.bytesperline < calc_bpl) + f->fmt.pix.bytesperline = calc_bpl; + + if (f->fmt.pix.bytesperline > (2 * PAGE_SIZE * fmt->depth) / 8) /* arbitrary constraint */ + f->fmt.pix.bytesperline = calc_bpl; + + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + DEB_D("w:%d, h:%d, bytesperline:%d, sizeimage:%d\n", + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); + + return 0; +} + + +static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + struct v4l2_window *win = &f->fmt.win; + enum v4l2_field field; + int maxw, maxh; + + DEB_EE("dev:%p\n", dev); + + if (NULL == vv->ov_fb.base) { + DEB_D("no fb base set\n"); + return -EINVAL; + } + if (NULL == vv->ov_fmt) { + DEB_D("no fb fmt set\n"); + return -EINVAL; + } + if (win->w.width < 48 || win->w.height < 32) { + DEB_D("min width/height. (%d,%d)\n", + win->w.width, win->w.height); + return -EINVAL; + } + if (win->clipcount > 16) { + DEB_D("clipcount too big\n"); + return -EINVAL; + } + + field = win->field; + maxw = vv->standard->h_max_out; + maxh = vv->standard->v_max_out; + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh / 2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_ALTERNATE: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + DEB_D("no known field mode '%d'\n", field); + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *__fh, struct v4l2_format *f) +{ + struct saa7146_fh *fh = __fh; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + int err; + + DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); + if (IS_CAPTURE_ACTIVE(fh) != 0) { + DEB_EE("streaming capture is active\n"); + return -EBUSY; + } + err = vidioc_try_fmt_vid_cap(file, fh, f); + if (0 != err) + return err; + vv->video_fmt = f->fmt.pix; + DEB_EE("set to pixelformat '%4.4s'\n", + (char *)&vv->video_fmt.pixelformat); + return 0; +} + +static int vidioc_s_fmt_vid_overlay(struct file *file, void *__fh, struct v4l2_format *f) +{ + struct saa7146_fh *fh = __fh; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + int err; + + DEB_EE("V4L2_BUF_TYPE_VIDEO_OVERLAY: dev:%p, fh:%p\n", dev, fh); + err = vidioc_try_fmt_vid_overlay(file, fh, f); + if (0 != err) + return err; + vv->ov.win = f->fmt.win; + vv->ov.nclips = f->fmt.win.clipcount; + if (vv->ov.nclips > 16) + vv->ov.nclips = 16; + memcpy(vv->ov.clips, f->fmt.win.clips, + sizeof(struct v4l2_clip) * vv->ov.nclips); + + /* vv->ov.fh is used to indicate that we have valid overlay information, too */ + vv->ov.fh = fh; + + /* check if our current overlay is active */ + if (IS_OVERLAY_ACTIVE(fh) != 0) { + saa7146_stop_preview(fh); + saa7146_start_preview(fh); + } + return 0; +} + +static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + *norm = vv->standard->id; + return 0; +} + + /* the saa7146 supfhrts (used in conjunction with the saa7111a for example) + PAL / NTSC / SECAM. if your hardware does not (or does more) + -- override this function in your extension */ +/* + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + if (e->index < 0 ) + return -EINVAL; + if( e->index < dev->ext_vv_data->num_stds ) { + DEB_EE("VIDIOC_ENUMSTD: index:%d\n", e->index); + v4l2_video_std_construct(e, dev->ext_vv_data->stds[e->index].id, dev->ext_vv_data->stds[e->index].name); + return 0; + } + return -EINVAL; + } + */ + +static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id id) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + int found = 0; + int err, i; + + DEB_EE("VIDIOC_S_STD\n"); + + if ((vv->video_status & STATUS_CAPTURE) == STATUS_CAPTURE) { + DEB_D("cannot change video standard while streaming capture is active\n"); + return -EBUSY; + } + + if ((vv->video_status & STATUS_OVERLAY) != 0) { + vv->ov_suspend = vv->video_fh; + err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ + if (0 != err) { + DEB_D("suspending video failed. aborting\n"); + return err; + } + } + + for (i = 0; i < dev->ext_vv_data->num_stds; i++) + if (id & dev->ext_vv_data->stds[i].id) + break; + if (i != dev->ext_vv_data->num_stds) { + vv->standard = &dev->ext_vv_data->stds[i]; + if (NULL != dev->ext_vv_data->std_callback) + dev->ext_vv_data->std_callback(dev, vv->standard); + found = 1; + } + + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + + if (!found) { + DEB_EE("VIDIOC_S_STD: standard not found\n"); + return -EINVAL; + } + + DEB_EE("VIDIOC_S_STD: set to standard to '%s'\n", vv->standard->name); + return 0; +} + +static int vidioc_overlay(struct file *file, void *fh, unsigned int on) +{ + int err; + + DEB_D("VIDIOC_OVERLAY on:%d\n", on); + if (on) + err = saa7146_start_preview(fh); + else + err = saa7146_stop_preview(fh); + return err; +} + +static int vidioc_reqbufs(struct file *file, void *__fh, struct v4l2_requestbuffers *b) +{ + struct saa7146_fh *fh = __fh; + + if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_reqbufs(&fh->video_q, b); + if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_reqbufs(&fh->vbi_q, b); + return -EINVAL; +} + +static int vidioc_querybuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct saa7146_fh *fh = __fh; + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_querybuf(&fh->video_q, buf); + if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_querybuf(&fh->vbi_q, buf); + return -EINVAL; +} + +static int vidioc_qbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct saa7146_fh *fh = __fh; + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_qbuf(&fh->video_q, buf); + if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_qbuf(&fh->vbi_q, buf); + return -EINVAL; +} + +static int vidioc_dqbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct saa7146_fh *fh = __fh; + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_dqbuf(&fh->video_q, buf, file->f_flags & O_NONBLOCK); + if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_dqbuf(&fh->vbi_q, buf, file->f_flags & O_NONBLOCK); + return -EINVAL; +} + +static int vidioc_streamon(struct file *file, void *__fh, enum v4l2_buf_type type) +{ + struct saa7146_fh *fh = __fh; + int err; + + DEB_D("VIDIOC_STREAMON, type:%d\n", type); + + err = video_begin(fh); + if (err) + return err; + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_streamon(&fh->video_q); + if (type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_streamon(&fh->vbi_q); + return -EINVAL; +} + +static int vidioc_streamoff(struct file *file, void *__fh, enum v4l2_buf_type type) +{ + struct saa7146_fh *fh = __fh; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + int err; + + DEB_D("VIDIOC_STREAMOFF, type:%d\n", type); + + /* ugly: we need to copy some checks from video_end(), + because videobuf_streamoff() relies on the capture running. + check and fix this */ + if ((vv->video_status & STATUS_CAPTURE) != STATUS_CAPTURE) { + DEB_S("not capturing\n"); + return 0; + } + + if (vv->video_fh != fh) { + DEB_S("capturing, but in another open\n"); + return -EBUSY; + } + + err = -EINVAL; + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + err = videobuf_streamoff(&fh->video_q); + else if (type == V4L2_BUF_TYPE_VBI_CAPTURE) + err = videobuf_streamoff(&fh->vbi_q); + if (0 != err) { + DEB_D("warning: videobuf_streamoff() failed\n"); + video_end(fh, file); + } else { + err = video_end(fh, file); + } + return err; +} + +const struct v4l2_ioctl_ops saa7146_video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_overlay = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_vid_overlay, + .vidioc_try_fmt_vid_overlay = vidioc_try_fmt_vid_overlay, + .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_vid_overlay, + + .vidioc_overlay = vidioc_overlay, + .vidioc_g_fbuf = vidioc_g_fbuf, + .vidioc_s_fbuf = vidioc_s_fbuf, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +const struct v4l2_ioctl_ops saa7146_vbi_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi_cap, + + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/*********************************************************************************/ +/* buffer handling functions */ + +static int buffer_activate (struct saa7146_dev *dev, + struct saa7146_buf *buf, + struct saa7146_buf *next) +{ + struct saa7146_vv *vv = dev->vv_data; + + buf->vb.state = VIDEOBUF_ACTIVE; + saa7146_set_capture(dev,buf,next); + + mod_timer(&vv->video_dmaq.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static void release_all_pagetables(struct saa7146_dev *dev, struct saa7146_buf *buf) +{ + saa7146_pgtable_free(dev->pci, &buf->pt[0]); + saa7146_pgtable_free(dev->pci, &buf->pt[1]); + saa7146_pgtable_free(dev->pci, &buf->pt[2]); +} + +static int buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + int size,err = 0; + + DEB_CAP("vbuf:%p\n", vb); + + /* sanity checks */ + if (vv->video_fmt.width < 48 || + vv->video_fmt.height < 32 || + vv->video_fmt.width > vv->standard->h_max_out || + vv->video_fmt.height > vv->standard->v_max_out) { + DEB_D("w (%d) / h (%d) out of bounds\n", + vv->video_fmt.width, vv->video_fmt.height); + return -EINVAL; + } + + size = vv->video_fmt.sizeimage; + if (0 != buf->vb.baddr && buf->vb.bsize < size) { + DEB_D("size mismatch\n"); + return -EINVAL; + } + + DEB_CAP("buffer_prepare [size=%dx%d,bytes=%d,fields=%s]\n", + vv->video_fmt.width, vv->video_fmt.height, + size, v4l2_field_names[vv->video_fmt.field]); + if (buf->vb.width != vv->video_fmt.width || + buf->vb.bytesperline != vv->video_fmt.bytesperline || + buf->vb.height != vv->video_fmt.height || + buf->vb.size != size || + buf->vb.field != field || + buf->vb.field != vv->video_fmt.field || + buf->fmt != &vv->video_fmt) { + saa7146_dma_free(dev,q,buf); + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + struct saa7146_format *sfmt; + + buf->vb.bytesperline = vv->video_fmt.bytesperline; + buf->vb.width = vv->video_fmt.width; + buf->vb.height = vv->video_fmt.height; + buf->vb.size = size; + buf->vb.field = field; + buf->fmt = &vv->video_fmt; + buf->vb.field = vv->video_fmt.field; + + sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + + release_all_pagetables(dev, buf); + if( 0 != IS_PLANAR(sfmt->trans)) { + saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[1]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); + } else { + saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); + } + + err = videobuf_iolock(q,&buf->vb, &vv->ov_fb); + if (err) + goto oops; + err = saa7146_pgtable_build(dev,buf); + if (err) + goto oops; + } + buf->vb.state = VIDEOBUF_PREPARED; + buf->activate = buffer_activate; + + return 0; + + oops: + DEB_D("error out\n"); + saa7146_dma_free(dev,q,buf); + + return err; +} + +static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_vv *vv = fh->dev->vv_data; + + if (0 == *count || *count > MAX_SAA7146_CAPTURE_BUFFERS) + *count = MAX_SAA7146_CAPTURE_BUFFERS; + + *size = vv->video_fmt.sizeimage; + + /* check if we exceed the "max_memory" parameter */ + if( (*count * *size) > (max_memory*1048576) ) { + *count = (max_memory*1048576) / *size; + } + + DEB_CAP("%d buffers, %d bytes each\n", *count, *size); + + return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_CAP("vbuf:%p\n", vb); + saa7146_buffer_queue(fh->dev, &vv->video_dmaq, buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_CAP("vbuf:%p\n", vb); + + saa7146_dma_free(dev,q,buf); + + release_all_pagetables(dev, buf); +} + +static const struct videobuf_queue_ops video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/********************************************************************************/ +/* file operations */ + +static void video_init(struct saa7146_dev *dev, struct saa7146_vv *vv) +{ + INIT_LIST_HEAD(&vv->video_dmaq.queue); + + timer_setup(&vv->video_dmaq.timeout, saa7146_buffer_timeout, 0); + vv->video_dmaq.dev = dev; + + /* set some default values */ + vv->standard = &dev->ext_vv_data->stds[0]; + + /* FIXME: what's this? */ + vv->current_hps_source = SAA7146_HPS_SOURCE_PORT_A; + vv->current_hps_sync = SAA7146_HPS_SYNC_PORT_A; +} + + +static int video_open(struct saa7146_dev *dev, struct file *file) +{ + struct saa7146_fh *fh = file->private_data; + + videobuf_queue_sg_init(&fh->video_q, &video_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct saa7146_buf), + file, &dev->v4l2_lock); + + return 0; +} + + +static void video_close(struct saa7146_dev *dev, struct file *file) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_vv *vv = dev->vv_data; + struct videobuf_queue *q = &fh->video_q; + + if (IS_CAPTURE_ACTIVE(fh) != 0) + video_end(fh, file); + else if (IS_OVERLAY_ACTIVE(fh) != 0) + saa7146_stop_preview(fh); + + videobuf_stop(q); + /* hmm, why is this function declared void? */ +} + + +static void video_irq_done(struct saa7146_dev *dev, unsigned long st) +{ + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_dmaqueue *q = &vv->video_dmaq; + + spin_lock(&dev->slock); + DEB_CAP("called\n"); + + /* only finish the buffer if we have one... */ + if( NULL != q->curr ) { + saa7146_buffer_finish(dev,q,VIDEOBUF_DONE); + } + saa7146_buffer_next(dev,q,0); + + spin_unlock(&dev->slock); +} + +static ssize_t video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + ssize_t ret = 0; + + DEB_EE("called\n"); + + if ((vv->video_status & STATUS_CAPTURE) != 0) { + /* fixme: should we allow read() captures while streaming capture? */ + if (vv->video_fh == fh) { + DEB_S("already capturing\n"); + return -EBUSY; + } + DEB_S("already capturing in another open\n"); + return -EBUSY; + } + + ret = video_begin(fh); + if( 0 != ret) { + goto out; + } + + ret = videobuf_read_one(&fh->video_q , data, count, ppos, + file->f_flags & O_NONBLOCK); + if (ret != 0) { + video_end(fh, file); + } else { + ret = video_end(fh, file); + } +out: + /* restart overlay if it was active before */ + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + + return ret; +} + +const struct saa7146_use_ops saa7146_video_uops = { + .init = video_init, + .open = video_open, + .release = video_close, + .irq_done = video_irq_done, + .read = video_read, +}; diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c index 53e495223ea0..cf6727d9c81f 100644 --- a/drivers/media/common/videobuf2/videobuf2-core.c +++ b/drivers/media/common/videobuf2/videobuf2-core.c @@ -502,27 +502,11 @@ static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers) * related information, if no buffers are left return the queue to an * uninitialized state. Might be called even if the queue has already been freed. */ -static int __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) +static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) { unsigned int buffer; - /* - * Sanity check: when preparing a buffer the queue lock is released for - * a short while (see __buf_prepare for the details), which would allow - * a race with a reqbufs which can call this function. Removing the - * buffers from underneath __buf_prepare is obviously a bad idea, so we - * check if any of the buffers is in the state PREPARING, and if so we - * just return -EAGAIN. - */ - for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; - ++buffer) { - if (q->bufs[buffer] == NULL) - continue; - if (q->bufs[buffer]->state == VB2_BUF_STATE_PREPARING) { - dprintk(q, 1, "preparing buffers, cannot free\n"); - return -EAGAIN; - } - } + lockdep_assert_held(&q->mmap_lock); /* Call driver-provided cleanup function for each buffer, if provided */ for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; @@ -616,7 +600,6 @@ static int __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) q->memory = VB2_MEMORY_UNKNOWN; INIT_LIST_HEAD(&q->queued_list); } - return 0; } bool vb2_buffer_in_use(struct vb2_queue *q, struct vb2_buffer *vb) @@ -798,10 +781,8 @@ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory, * queued without ever calling STREAMON. */ __vb2_queue_cancel(q); - ret = __vb2_queue_free(q, q->num_buffers); + __vb2_queue_free(q, q->num_buffers); mutex_unlock(&q->mmap_lock); - if (ret) - return ret; /* * In case of REQBUFS(0) return immediately without calling diff --git a/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd.c b/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd.c index 4cf2d7cfd3f5..0a1f3899d72c 100644 --- a/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd.c +++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd.c @@ -2162,11 +2162,11 @@ int cxd2880_tnrdmd_check_internal_cpu_status(struct cxd2880_tnrdmd else *task_completed = 0; - return ret; + return 0; } if (cpu_status != 0) { *task_completed = 0; - return ret; + return 0; } ret = cxd2880_tnrdmd_mon_internal_cpu_status_sub(tnr_dmd, &cpu_status); diff --git a/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd_dvbt.c b/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd_dvbt.c index fe3c6f8b1b3e..c7e79da8c432 100644 --- a/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd_dvbt.c +++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd_dvbt.c @@ -833,12 +833,12 @@ int cxd2880_tnrdmd_dvbt_check_demod_lock(struct cxd2880_tnrdmd else *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } if (sync_stat == 6) { *lock = CXD2880_TNRDMD_LOCK_RESULT_LOCKED; - return ret; + return 0; } ret = @@ -854,7 +854,7 @@ int cxd2880_tnrdmd_dvbt_check_demod_lock(struct cxd2880_tnrdmd else *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } int cxd2880_tnrdmd_dvbt_check_ts_lock(struct cxd2880_tnrdmd @@ -893,15 +893,15 @@ int cxd2880_tnrdmd_dvbt_check_ts_lock(struct cxd2880_tnrdmd else *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } if (ts_lock) { *lock = CXD2880_TNRDMD_LOCK_RESULT_LOCKED; - return ret; + return 0; } else if (!unlock_detected) { *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } ret = @@ -915,5 +915,5 @@ int cxd2880_tnrdmd_dvbt_check_ts_lock(struct cxd2880_tnrdmd else *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } diff --git a/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd_dvbt2.c b/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd_dvbt2.c index dd32004a12d8..a9ab983348c8 100644 --- a/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd_dvbt2.c +++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_tnrdmd_dvbt2.c @@ -1024,12 +1024,12 @@ int cxd2880_tnrdmd_dvbt2_check_demod_lock(struct cxd2880_tnrdmd else *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } if (sync_stat == 6) { *lock = CXD2880_TNRDMD_LOCK_RESULT_LOCKED; - return ret; + return 0; } ret = @@ -1045,7 +1045,7 @@ int cxd2880_tnrdmd_dvbt2_check_demod_lock(struct cxd2880_tnrdmd else *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } int cxd2880_tnrdmd_dvbt2_check_ts_lock(struct cxd2880_tnrdmd @@ -1084,15 +1084,15 @@ int cxd2880_tnrdmd_dvbt2_check_ts_lock(struct cxd2880_tnrdmd else *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } if (ts_lock) { *lock = CXD2880_TNRDMD_LOCK_RESULT_LOCKED; - return ret; + return 0; } else if (!unlock_detected) { *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } ret = @@ -1106,7 +1106,7 @@ int cxd2880_tnrdmd_dvbt2_check_ts_lock(struct cxd2880_tnrdmd else *lock = CXD2880_TNRDMD_LOCK_RESULT_NOTDETECT; - return ret; + return 0; } int cxd2880_tnrdmd_dvbt2_set_plp_cfg(struct cxd2880_tnrdmd diff --git a/drivers/media/dvb-frontends/drx39xyj/drxj.c b/drivers/media/dvb-frontends/drx39xyj/drxj.c index 1dff59ca21a1..6bf6559b127f 100644 --- a/drivers/media/dvb-frontends/drx39xyj/drxj.c +++ b/drivers/media/dvb-frontends/drx39xyj/drxj.c @@ -9539,7 +9539,8 @@ ctrl_get_qam_sig_quality(struct drx_demod_instance *demod) qam_sl_sig_power = DRXJ_QAM_SL_SIG_POWER_QAM256 << 2; break; default: - return -EIO; + rc = -EIO; + goto rw_error; } /* ------------------------------ */ @@ -10916,7 +10917,8 @@ ctrl_set_standard(struct drx_demod_instance *demod, enum drx_standard *standard) break; case DRX_STANDARD_AUTO: default: - return -EINVAL; + rc = -EINVAL; + goto rw_error; } /* @@ -11463,7 +11465,8 @@ static int drxj_open(struct drx_demod_instance *demod) if (DRX_ISPOWERDOWNMODE(demod->my_common_attr->current_power_mode)) { pr_err("Should powerup before loading the firmware."); - return -EINVAL; + rc = -EINVAL; + goto rw_error; } rc = drx_ctrl_u_code(demod, &ucode_info, UCODE_UPLOAD); diff --git a/drivers/media/dvb-frontends/dvb-pll.c b/drivers/media/dvb-frontends/dvb-pll.c index baf2a378e565..e35e00db7dbb 100644 --- a/drivers/media/dvb-frontends/dvb-pll.c +++ b/drivers/media/dvb-frontends/dvb-pll.c @@ -870,8 +870,9 @@ EXPORT_SYMBOL(dvb_pll_attach); static int -dvb_pll_probe(struct i2c_client *client, const struct i2c_device_id *id) +dvb_pll_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct dvb_pll_config *cfg; struct dvb_frontend *fe; unsigned int desc_id; @@ -941,7 +942,7 @@ static struct i2c_driver dvb_pll_driver = { .driver = { .name = "dvb_pll", }, - .probe = dvb_pll_probe, + .probe_new = dvb_pll_probe, .remove = dvb_pll_remove, .id_table = dvb_pll_id, }; diff --git a/drivers/media/dvb-frontends/m88ds3103.c b/drivers/media/dvb-frontends/m88ds3103.c index 4e844b2ef597..f26508b217ee 100644 --- a/drivers/media/dvb-frontends/m88ds3103.c +++ b/drivers/media/dvb-frontends/m88ds3103.c @@ -1760,9 +1760,9 @@ static struct i2c_adapter *m88ds3103_get_i2c_adapter(struct i2c_client *client) return dev->muxc->adapter[0]; } -static int m88ds3103_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int m88ds3103_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct m88ds3103_dev *dev; struct m88ds3103_platform_data *pdata = client->dev.platform_data; int ret; @@ -1941,7 +1941,7 @@ static struct i2c_driver m88ds3103_driver = { .name = "m88ds3103", .suppress_bind_attrs = true, }, - .probe = m88ds3103_probe, + .probe_new = m88ds3103_probe, .remove = m88ds3103_remove, .id_table = m88ds3103_id_table, }; diff --git a/drivers/media/dvb-frontends/mb86a16.c b/drivers/media/dvb-frontends/mb86a16.c index 2505f1e5794e..d3e29937cf4c 100644 --- a/drivers/media/dvb-frontends/mb86a16.c +++ b/drivers/media/dvb-frontends/mb86a16.c @@ -1498,6 +1498,7 @@ static int mb86a16_send_diseqc_msg(struct dvb_frontend *fe, struct dvb_diseqc_master_cmd *cmd) { struct mb86a16_state *state = fe->demodulator_priv; + int ret = -EREMOTEIO; int i; u8 regs; @@ -1510,8 +1511,10 @@ static int mb86a16_send_diseqc_msg(struct dvb_frontend *fe, regs = 0x18; - if (cmd->msg_len > 5 || cmd->msg_len < 4) - return -EINVAL; + if (cmd->msg_len > 5 || cmd->msg_len < 4) { + ret = -EINVAL; + goto err; + } for (i = 0; i < cmd->msg_len; i++) { if (mb86a16_write(state, regs, cmd->msg[i]) < 0) @@ -1532,7 +1535,7 @@ static int mb86a16_send_diseqc_msg(struct dvb_frontend *fe, err: dprintk(verbose, MB86A16_ERROR, 1, "I2C transfer error"); - return -EREMOTEIO; + return ret; } static int mb86a16_send_diseqc_burst(struct dvb_frontend *fe, diff --git a/drivers/media/dvb-frontends/mn88443x.c b/drivers/media/dvb-frontends/mn88443x.c index 452571b380b7..1f1753f2ab1a 100644 --- a/drivers/media/dvb-frontends/mn88443x.c +++ b/drivers/media/dvb-frontends/mn88443x.c @@ -673,9 +673,9 @@ static const struct regmap_config regmap_config = { .cache_type = REGCACHE_NONE, }; -static int mn88443x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mn88443x_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct mn88443x_config *conf = client->dev.platform_data; struct mn88443x_priv *chip; struct device *dev = &client->dev; @@ -800,7 +800,7 @@ static struct i2c_driver mn88443x_driver = { .name = "mn88443x", .of_match_table = of_match_ptr(mn88443x_of_match), }, - .probe = mn88443x_probe, + .probe_new = mn88443x_probe, .remove = mn88443x_remove, .id_table = mn88443x_i2c_id, }; diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c index c22d2a2b2a45..77a991bf4713 100644 --- a/drivers/media/dvb-frontends/tc90522.c +++ b/drivers/media/dvb-frontends/tc90522.c @@ -779,9 +779,9 @@ static const struct dvb_frontend_ops tc90522_ops_ter = { }; -static int tc90522_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tc90522_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct tc90522_state *state; struct tc90522_config *cfg; const struct dvb_frontend_ops *ops; @@ -840,7 +840,7 @@ static struct i2c_driver tc90522_driver = { .driver = { .name = "tc90522", }, - .probe = tc90522_probe, + .probe_new = tc90522_probe, .remove = tc90522_remove, .id_table = tc90522_id, }; diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 833241897d63..c3d5952ca27e 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -162,6 +162,19 @@ config VIDEO_IMX290 To compile this driver as a module, choose M here: the module will be called imx290. +config VIDEO_IMX296 + tristate "Sony IMX296 sensor support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the Sony + IMX296 camera. + + To compile this driver as a module, choose M here: the + module will be called imx296. + config VIDEO_IMX319 tristate "Sony IMX319 sensor support" depends on I2C && VIDEO_DEV @@ -228,6 +241,20 @@ config VIDEO_IMX412 To compile this driver as a module, choose M here: the module will be called imx412. +config VIDEO_IMX415 + tristate "Sony IMX415 sensor support" + depends on OF_GPIO + depends on I2C && VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Sony + IMX415 camera. + + To compile this driver as a module, choose M here: the + module will be called imx415. + config VIDEO_MAX9271_LIB tristate @@ -645,6 +672,19 @@ config VIDEO_OV8856 To compile this driver as a module, choose M here: the module will be called ov8856. +config VIDEO_OV8858 + tristate "OmniVision OV8858 sensor support" + depends on I2C && PM && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for OmniVision + OV8858 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called ov8858. + config VIDEO_OV8865 tristate "OmniVision OV8865 sensor support" depends on I2C && PM && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 4d6c052bb5a7..4f5e9d9cee85 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -43,11 +43,13 @@ obj-$(CONFIG_VIDEO_IMX219) += imx219.o obj-$(CONFIG_VIDEO_IMX258) += imx258.o obj-$(CONFIG_VIDEO_IMX274) += imx274.o obj-$(CONFIG_VIDEO_IMX290) += imx290.o +obj-$(CONFIG_VIDEO_IMX296) += imx296.o obj-$(CONFIG_VIDEO_IMX319) += imx319.o obj-$(CONFIG_VIDEO_IMX334) += imx334.o obj-$(CONFIG_VIDEO_IMX335) += imx335.o obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_IMX412) += imx412.o +obj-$(CONFIG_VIDEO_IMX415) += imx415.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o @@ -96,6 +98,7 @@ obj-$(CONFIG_VIDEO_OV7670) += ov7670.o obj-$(CONFIG_VIDEO_OV772X) += ov772x.o obj-$(CONFIG_VIDEO_OV7740) += ov7740.o obj-$(CONFIG_VIDEO_OV8856) += ov8856.o +obj-$(CONFIG_VIDEO_OV8858) += ov8858.o obj-$(CONFIG_VIDEO_OV8865) += ov8865.o obj-$(CONFIG_VIDEO_OV9282) += ov9282.o obj-$(CONFIG_VIDEO_OV9640) += ov9640.o diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index 216fe396973f..a22402b7acff 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -1393,9 +1393,9 @@ out_unlock: return ret; } -static int adv7180_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv7180_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct device_node *np = client->dev.of_node; struct adv7180_state *state; struct v4l2_subdev *sd; @@ -1610,7 +1610,7 @@ static struct i2c_driver adv7180_driver = { .pm = ADV7180_PM_OPS, .of_match_table = of_match_ptr(adv7180_of_id), }, - .probe = adv7180_probe, + .probe_new = adv7180_probe, .remove = adv7180_remove, .id_table = adv7180_id, }; diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index bda0c547ce44..9d218962d7c8 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -3401,9 +3401,9 @@ static void adv76xx_reset(struct adv76xx_state *state) } } -static int adv76xx_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int adv76xx_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); static const struct v4l2_dv_timings cea640x480 = V4L2_DV_BT_CEA_640X480P59_94; struct adv76xx_state *state; @@ -3686,7 +3686,7 @@ static struct i2c_driver adv76xx_driver = { .name = "adv7604", .of_match_table = of_match_ptr(adv76xx_of_id), }, - .probe = adv76xx_probe, + .probe_new = adv76xx_probe, .remove = adv76xx_remove, .id_table = adv76xx_i2c_id, }; diff --git a/drivers/media/i2c/ak7375.c b/drivers/media/i2c/ak7375.c index 1af9f698eecf..e7cec45bc271 100644 --- a/drivers/media/i2c/ak7375.c +++ b/drivers/media/i2c/ak7375.c @@ -6,6 +6,7 @@ #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-device.h> @@ -23,17 +24,29 @@ */ #define AK7375_CTRL_STEPS 64 #define AK7375_CTRL_DELAY_US 1000 +/* + * The vcm may take up 10 ms (tDELAY) to power on and start taking + * I2C messages. Based on AK7371 datasheet. + */ +#define AK7375_POWER_DELAY_US 10000 #define AK7375_REG_POSITION 0x0 #define AK7375_REG_CONT 0x2 #define AK7375_MODE_ACTIVE 0x0 #define AK7375_MODE_STANDBY 0x40 +static const char * const ak7375_supply_names[] = { + "vdd", + "vio", +}; + /* ak7375 device structure */ struct ak7375_device { struct v4l2_ctrl_handler ctrls_vcm; struct v4l2_subdev sd; struct v4l2_ctrl *focus; + struct regulator_bulk_data supplies[ARRAY_SIZE(ak7375_supply_names)]; + /* active or standby mode */ bool active; }; @@ -133,12 +146,24 @@ static int ak7375_probe(struct i2c_client *client) { struct ak7375_device *ak7375_dev; int ret; + unsigned int i; ak7375_dev = devm_kzalloc(&client->dev, sizeof(*ak7375_dev), GFP_KERNEL); if (!ak7375_dev) return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(ak7375_supply_names); i++) + ak7375_dev->supplies[i].supply = ak7375_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(ak7375_supply_names), + ak7375_dev->supplies); + if (ret) { + dev_err_probe(&client->dev, ret, "Failed to get regulators\n"); + return ret; + } + v4l2_i2c_subdev_init(&ak7375_dev->sd, client, &ak7375_ops); ak7375_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ak7375_dev->sd.internal_ops = &ak7375_int_ops; @@ -208,6 +233,11 @@ static int __maybe_unused ak7375_vcm_suspend(struct device *dev) if (ret) dev_err(dev, "%s I2C failure: %d\n", __func__, ret); + ret = regulator_bulk_disable(ARRAY_SIZE(ak7375_supply_names), + ak7375_dev->supplies); + if (ret) + return ret; + ak7375_dev->active = false; return 0; @@ -228,6 +258,14 @@ static int __maybe_unused ak7375_vcm_resume(struct device *dev) if (ak7375_dev->active) return 0; + ret = regulator_bulk_enable(ARRAY_SIZE(ak7375_supply_names), + ak7375_dev->supplies); + if (ret) + return ret; + + /* Wait for vcm to become ready */ + usleep_range(AK7375_POWER_DELAY_US, AK7375_POWER_DELAY_US + 500); + ret = ak7375_i2c_write(ak7375_dev, AK7375_REG_CONT, AK7375_MODE_ACTIVE, 1); if (ret) { diff --git a/drivers/media/i2c/cs53l32a.c b/drivers/media/i2c/cs53l32a.c index 9461589aea30..670f89de32d4 100644 --- a/drivers/media/i2c/cs53l32a.c +++ b/drivers/media/i2c/cs53l32a.c @@ -128,9 +128,9 @@ static const struct v4l2_subdev_ops cs53l32a_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int cs53l32a_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int cs53l32a_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct cs53l32a_state *state; struct v4l2_subdev *sd; int i; @@ -209,7 +209,7 @@ static struct i2c_driver cs53l32a_driver = { .driver = { .name = "cs53l32a", }, - .probe = cs53l32a_probe, + .probe_new = cs53l32a_probe, .remove = cs53l32a_remove, .id_table = cs53l32a_id, }; diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index 77bd79a5954e..f9471c9e3a74 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -42,10 +42,16 @@ /* External clock frequency is 24.0M */ #define IMX219_XCLK_FREQ 24000000 -/* Pixel rate is fixed at 182.4M for all the modes */ +/* Pixel rate is fixed for all the modes */ #define IMX219_PIXEL_RATE 182400000 +#define IMX219_PIXEL_RATE_4LANE 280800000 #define IMX219_DEFAULT_LINK_FREQ 456000000 +#define IMX219_DEFAULT_LINK_FREQ_4LANE 363000000 + +#define IMX219_REG_CSI_LANE_MODE 0x0114 +#define IMX219_CSI_2_LANE_MODE 0x01 +#define IMX219_CSI_4_LANE_MODE 0x03 /* V_TIMING internal */ #define IMX219_REG_VTS 0x0160 @@ -89,6 +95,12 @@ #define IMX219_REG_ORIENTATION 0x0172 +/* Binning Mode */ +#define IMX219_REG_BINNING_MODE 0x0174 +#define IMX219_BINNING_NONE 0x0000 +#define IMX219_BINNING_2X2 0x0101 +#define IMX219_BINNING_2X2_ANALOG 0x0303 + /* Test Pattern Control */ #define IMX219_REG_TEST_PATTERN 0x0600 #define IMX219_TEST_PATTERN_DISABLE 0 @@ -143,25 +155,66 @@ struct imx219_mode { /* Default register values */ struct imx219_reg_list reg_list; + + /* 2x2 binning is used */ + bool binning; }; -/* - * Register sets lifted off the i2C interface from the Raspberry Pi firmware - * driver. - * 3280x2464 = mode 2, 1920x1080 = mode 1, 1640x1232 = mode 4, 640x480 = mode 7. - */ -static const struct imx219_reg mode_3280x2464_regs[] = { - {0x0100, 0x00}, +static const struct imx219_reg imx219_common_regs[] = { + {0x0100, 0x00}, /* Mode Select */ + + /* To Access Addresses 3000-5fff, send the following commands */ {0x30eb, 0x0c}, {0x30eb, 0x05}, {0x300a, 0xff}, {0x300b, 0xff}, {0x30eb, 0x05}, {0x30eb, 0x09}, - {0x0114, 0x01}, - {0x0128, 0x00}, - {0x012a, 0x18}, + + /* PLL Clock Table */ + {0x0301, 0x05}, /* VTPXCK_DIV */ + {0x0303, 0x01}, /* VTSYSCK_DIV */ + {0x0304, 0x03}, /* PREPLLCK_VT_DIV 0x03 = AUTO set */ + {0x0305, 0x03}, /* PREPLLCK_OP_DIV 0x03 = AUTO set */ + {0x0306, 0x00}, /* PLL_VT_MPY */ + {0x0307, 0x39}, + {0x030b, 0x01}, /* OP_SYS_CLK_DIV */ + {0x030c, 0x00}, /* PLL_OP_MPY */ + {0x030d, 0x72}, + + /* Undocumented registers */ + {0x455e, 0x00}, + {0x471e, 0x4b}, + {0x4767, 0x0f}, + {0x4750, 0x14}, + {0x4540, 0x00}, + {0x47b4, 0x14}, + {0x4713, 0x30}, + {0x478b, 0x10}, + {0x478f, 0x10}, + {0x4793, 0x10}, + {0x4797, 0x0e}, + {0x479b, 0x0e}, + + /* Frame Bank Register Group "A" */ + {0x0162, 0x0d}, /* Line_Length_A */ + {0x0163, 0x78}, + {0x0170, 0x01}, /* X_ODD_INC_A */ + {0x0171, 0x01}, /* Y_ODD_INC_A */ + + /* Output setup registers */ + {0x0114, 0x01}, /* CSI 2-Lane Mode */ + {0x0128, 0x00}, /* DPHY Auto Mode */ + {0x012a, 0x18}, /* EXCK_Freq */ {0x012b, 0x00}, +}; + +/* + * Register sets lifted off the i2C interface from the Raspberry Pi firmware + * driver. + * 3280x2464 = mode 2, 1920x1080 = mode 1, 1640x1232 = mode 4, 640x480 = mode 7. + */ +static const struct imx219_reg mode_3280x2464_regs[] = { {0x0164, 0x00}, {0x0165, 0x00}, {0x0166, 0x0c}, @@ -174,53 +227,13 @@ static const struct imx219_reg mode_3280x2464_regs[] = { {0x016d, 0xd0}, {0x016e, 0x09}, {0x016f, 0xa0}, - {0x0170, 0x01}, - {0x0171, 0x01}, - {0x0174, 0x00}, - {0x0175, 0x00}, - {0x0301, 0x05}, - {0x0303, 0x01}, - {0x0304, 0x03}, - {0x0305, 0x03}, - {0x0306, 0x00}, - {0x0307, 0x39}, - {0x030b, 0x01}, - {0x030c, 0x00}, - {0x030d, 0x72}, {0x0624, 0x0c}, {0x0625, 0xd0}, {0x0626, 0x09}, {0x0627, 0xa0}, - {0x455e, 0x00}, - {0x471e, 0x4b}, - {0x4767, 0x0f}, - {0x4750, 0x14}, - {0x4540, 0x00}, - {0x47b4, 0x14}, - {0x4713, 0x30}, - {0x478b, 0x10}, - {0x478f, 0x10}, - {0x4793, 0x10}, - {0x4797, 0x0e}, - {0x479b, 0x0e}, - {0x0162, 0x0d}, - {0x0163, 0x78}, }; static const struct imx219_reg mode_1920_1080_regs[] = { - {0x0100, 0x00}, - {0x30eb, 0x05}, - {0x30eb, 0x0c}, - {0x300a, 0xff}, - {0x300b, 0xff}, - {0x30eb, 0x05}, - {0x30eb, 0x09}, - {0x0114, 0x01}, - {0x0128, 0x00}, - {0x012a, 0x18}, - {0x012b, 0x00}, - {0x0162, 0x0d}, - {0x0163, 0x78}, {0x0164, 0x02}, {0x0165, 0xa8}, {0x0166, 0x0a}, @@ -233,49 +246,13 @@ static const struct imx219_reg mode_1920_1080_regs[] = { {0x016d, 0x80}, {0x016e, 0x04}, {0x016f, 0x38}, - {0x0170, 0x01}, - {0x0171, 0x01}, - {0x0174, 0x00}, - {0x0175, 0x00}, - {0x0301, 0x05}, - {0x0303, 0x01}, - {0x0304, 0x03}, - {0x0305, 0x03}, - {0x0306, 0x00}, - {0x0307, 0x39}, - {0x030b, 0x01}, - {0x030c, 0x00}, - {0x030d, 0x72}, {0x0624, 0x07}, {0x0625, 0x80}, {0x0626, 0x04}, {0x0627, 0x38}, - {0x455e, 0x00}, - {0x471e, 0x4b}, - {0x4767, 0x0f}, - {0x4750, 0x14}, - {0x4540, 0x00}, - {0x47b4, 0x14}, - {0x4713, 0x30}, - {0x478b, 0x10}, - {0x478f, 0x10}, - {0x4793, 0x10}, - {0x4797, 0x0e}, - {0x479b, 0x0e}, }; static const struct imx219_reg mode_1640_1232_regs[] = { - {0x0100, 0x00}, - {0x30eb, 0x0c}, - {0x30eb, 0x05}, - {0x300a, 0xff}, - {0x300b, 0xff}, - {0x30eb, 0x05}, - {0x30eb, 0x09}, - {0x0114, 0x01}, - {0x0128, 0x00}, - {0x012a, 0x18}, - {0x012b, 0x00}, {0x0164, 0x00}, {0x0165, 0x00}, {0x0166, 0x0c}, @@ -288,53 +265,13 @@ static const struct imx219_reg mode_1640_1232_regs[] = { {0x016d, 0x68}, {0x016e, 0x04}, {0x016f, 0xd0}, - {0x0170, 0x01}, - {0x0171, 0x01}, - {0x0174, 0x01}, - {0x0175, 0x01}, - {0x0301, 0x05}, - {0x0303, 0x01}, - {0x0304, 0x03}, - {0x0305, 0x03}, - {0x0306, 0x00}, - {0x0307, 0x39}, - {0x030b, 0x01}, - {0x030c, 0x00}, - {0x030d, 0x72}, {0x0624, 0x06}, {0x0625, 0x68}, {0x0626, 0x04}, {0x0627, 0xd0}, - {0x455e, 0x00}, - {0x471e, 0x4b}, - {0x4767, 0x0f}, - {0x4750, 0x14}, - {0x4540, 0x00}, - {0x47b4, 0x14}, - {0x4713, 0x30}, - {0x478b, 0x10}, - {0x478f, 0x10}, - {0x4793, 0x10}, - {0x4797, 0x0e}, - {0x479b, 0x0e}, - {0x0162, 0x0d}, - {0x0163, 0x78}, }; static const struct imx219_reg mode_640_480_regs[] = { - {0x0100, 0x00}, - {0x30eb, 0x05}, - {0x30eb, 0x0c}, - {0x300a, 0xff}, - {0x300b, 0xff}, - {0x30eb, 0x05}, - {0x30eb, 0x09}, - {0x0114, 0x01}, - {0x0128, 0x00}, - {0x012a, 0x18}, - {0x012b, 0x00}, - {0x0162, 0x0d}, - {0x0163, 0x78}, {0x0164, 0x03}, {0x0165, 0xe8}, {0x0166, 0x08}, @@ -347,35 +284,10 @@ static const struct imx219_reg mode_640_480_regs[] = { {0x016d, 0x80}, {0x016e, 0x01}, {0x016f, 0xe0}, - {0x0170, 0x01}, - {0x0171, 0x01}, - {0x0174, 0x03}, - {0x0175, 0x03}, - {0x0301, 0x05}, - {0x0303, 0x01}, - {0x0304, 0x03}, - {0x0305, 0x03}, - {0x0306, 0x00}, - {0x0307, 0x39}, - {0x030b, 0x01}, - {0x030c, 0x00}, - {0x030d, 0x72}, {0x0624, 0x06}, {0x0625, 0x68}, {0x0626, 0x04}, {0x0627, 0xd0}, - {0x455e, 0x00}, - {0x471e, 0x4b}, - {0x4767, 0x0f}, - {0x4750, 0x14}, - {0x4540, 0x00}, - {0x47b4, 0x14}, - {0x4713, 0x30}, - {0x478b, 0x10}, - {0x478f, 0x10}, - {0x4793, 0x10}, - {0x4797, 0x0e}, - {0x479b, 0x0e}, }; static const struct imx219_reg raw8_framefmt_regs[] = { @@ -394,6 +306,10 @@ static const s64 imx219_link_freq_menu[] = { IMX219_DEFAULT_LINK_FREQ, }; +static const s64 imx219_link_freq_4lane_menu[] = { + IMX219_DEFAULT_LINK_FREQ_4LANE, +}; + static const char * const imx219_test_pattern_menu[] = { "Disabled", "Color Bars", @@ -485,6 +401,7 @@ static const struct imx219_mode supported_modes[] = { .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), .regs = mode_3280x2464_regs, }, + .binning = false, }, { /* 1080P 30fps cropped */ @@ -501,6 +418,7 @@ static const struct imx219_mode supported_modes[] = { .num_of_regs = ARRAY_SIZE(mode_1920_1080_regs), .regs = mode_1920_1080_regs, }, + .binning = false, }, { /* 2x2 binned 30fps mode */ @@ -517,6 +435,7 @@ static const struct imx219_mode supported_modes[] = { .num_of_regs = ARRAY_SIZE(mode_1640_1232_regs), .regs = mode_1640_1232_regs, }, + .binning = true, }, { /* 640x480 30fps mode */ @@ -533,6 +452,7 @@ static const struct imx219_mode supported_modes[] = { .num_of_regs = ARRAY_SIZE(mode_640_480_regs), .regs = mode_640_480_regs, }, + .binning = true, }, }; @@ -569,6 +489,9 @@ struct imx219 { /* Streaming on/off */ bool streaming; + + /* Two or Four lanes */ + u8 lanes; }; static inline struct imx219 *to_imx219(struct v4l2_subdev *_sd) @@ -979,6 +902,35 @@ static int imx219_set_framefmt(struct imx219 *imx219) return -EINVAL; } +static int imx219_set_binning(struct imx219 *imx219) +{ + if (!imx219->mode->binning) { + return imx219_write_reg(imx219, IMX219_REG_BINNING_MODE, + IMX219_REG_VALUE_16BIT, + IMX219_BINNING_NONE); + } + + switch (imx219->fmt.code) { + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + return imx219_write_reg(imx219, IMX219_REG_BINNING_MODE, + IMX219_REG_VALUE_16BIT, + IMX219_BINNING_2X2_ANALOG); + + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + return imx219_write_reg(imx219, IMX219_REG_BINNING_MODE, + IMX219_REG_VALUE_16BIT, + IMX219_BINNING_2X2); + } + + return -EINVAL; +} + static const struct v4l2_rect * __imx219_get_pad_crop(struct imx219 *imx219, struct v4l2_subdev_state *sd_state, @@ -1031,6 +983,13 @@ static int imx219_get_selection(struct v4l2_subdev *sd, return -EINVAL; } +static int imx219_configure_lanes(struct imx219 *imx219) +{ + return imx219_write_reg(imx219, IMX219_REG_CSI_LANE_MODE, + IMX219_REG_VALUE_08BIT, (imx219->lanes == 2) ? + IMX219_CSI_2_LANE_MODE : IMX219_CSI_4_LANE_MODE); +}; + static int imx219_start_streaming(struct imx219 *imx219) { struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); @@ -1041,6 +1000,20 @@ static int imx219_start_streaming(struct imx219 *imx219) if (ret < 0) return ret; + /* Send all registers that are common to all modes */ + ret = imx219_write_regs(imx219, imx219_common_regs, ARRAY_SIZE(imx219_common_regs)); + if (ret) { + dev_err(&client->dev, "%s failed to send mfg header\n", __func__); + goto err_rpm_put; + } + + /* Configure two or four Lane mode */ + ret = imx219_configure_lanes(imx219); + if (ret) { + dev_err(&client->dev, "%s failed to configure lanes\n", __func__); + goto err_rpm_put; + } + /* Apply default values of current mode */ reg_list = &imx219->mode->reg_list; ret = imx219_write_regs(imx219, reg_list->regs, reg_list->num_of_regs); @@ -1056,6 +1029,13 @@ static int imx219_start_streaming(struct imx219 *imx219) goto err_rpm_put; } + ret = imx219_set_binning(imx219); + if (ret) { + dev_err(&client->dev, "%s failed to set binning: %d\n", + __func__, ret); + goto err_rpm_put; + } + /* Apply customized values from user */ ret = __v4l2_ctrl_handler_setup(imx219->sd.ctrl_handler); if (ret) @@ -1272,6 +1252,11 @@ static const struct v4l2_subdev_internal_ops imx219_internal_ops = { .open = imx219_open, }; +static unsigned long imx219_get_pixel_rate(struct imx219 *imx219) +{ + return (imx219->lanes == 2) ? IMX219_PIXEL_RATE : IMX219_PIXEL_RATE_4LANE; +} + /* Initialize control handlers */ static int imx219_init_controls(struct imx219 *imx219) { @@ -1293,15 +1278,16 @@ static int imx219_init_controls(struct imx219 *imx219) /* By default, PIXEL_RATE is read only */ imx219->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_PIXEL_RATE, - IMX219_PIXEL_RATE, - IMX219_PIXEL_RATE, 1, - IMX219_PIXEL_RATE); + imx219_get_pixel_rate(imx219), + imx219_get_pixel_rate(imx219), 1, + imx219_get_pixel_rate(imx219)); imx219->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(imx219_link_freq_menu) - 1, 0, - imx219_link_freq_menu); + (imx219->lanes == 2) ? imx219_link_freq_menu : + imx219_link_freq_4lane_menu); if (imx219->link_freq) imx219->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; @@ -1396,7 +1382,7 @@ static void imx219_free_controls(struct imx219 *imx219) mutex_destroy(&imx219->mutex); } -static int imx219_check_hwcfg(struct device *dev) +static int imx219_check_hwcfg(struct device *dev, struct imx219 *imx219) { struct fwnode_handle *endpoint; struct v4l2_fwnode_endpoint ep_cfg = { @@ -1416,10 +1402,12 @@ static int imx219_check_hwcfg(struct device *dev) } /* Check the number of MIPI CSI2 data lanes */ - if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { - dev_err(dev, "only 2 data lanes are currently supported\n"); + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2 && + ep_cfg.bus.mipi_csi2.num_data_lanes != 4) { + dev_err(dev, "only 2 or 4 data lanes are currently supported\n"); goto error_out; } + imx219->lanes = ep_cfg.bus.mipi_csi2.num_data_lanes; /* Check the link frequency set in device tree */ if (!ep_cfg.nr_of_link_frequencies) { @@ -1428,7 +1416,8 @@ static int imx219_check_hwcfg(struct device *dev) } if (ep_cfg.nr_of_link_frequencies != 1 || - ep_cfg.link_frequencies[0] != IMX219_DEFAULT_LINK_FREQ) { + (ep_cfg.link_frequencies[0] != ((imx219->lanes == 2) ? + IMX219_DEFAULT_LINK_FREQ : IMX219_DEFAULT_LINK_FREQ_4LANE))) { dev_err(dev, "Link frequency not supported: %lld\n", ep_cfg.link_frequencies[0]); goto error_out; @@ -1456,7 +1445,7 @@ static int imx219_probe(struct i2c_client *client) v4l2_i2c_subdev_init(&imx219->sd, client, &imx219_subdev_ops); /* Check the hardware configuration in device tree */ - if (imx219_check_hwcfg(dev)) + if (imx219_check_hwcfg(dev, imx219)) return -EINVAL; /* Get system clock (xclk) */ diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 218ded13fd80..49d6c8bdec41 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -152,13 +152,10 @@ #define IMX290_PIXEL_ARRAY_RECORDING_WIDTH 1920 #define IMX290_PIXEL_ARRAY_RECORDING_HEIGHT 1080 -static const char * const imx290_supply_name[] = { - "vdda", - "vddd", - "vdddo", -}; +/* Equivalent value for 16bpp */ +#define IMX290_BLACK_LEVEL_DEFAULT 3840 -#define IMX290_NUM_SUPPLIES ARRAY_SIZE(imx290_supply_name) +#define IMX290_NUM_SUPPLIES 3 struct imx290_regval { u32 reg; @@ -180,11 +177,10 @@ struct imx290 { struct clk *xclk; struct regmap *regmap; u8 nlanes; - u8 bpp; struct v4l2_subdev sd; struct media_pad pad; - struct v4l2_mbus_framefmt current_format; + const struct imx290_mode *current_mode; struct regulator_bulk_data supplies[IMX290_NUM_SUPPLIES]; @@ -195,35 +191,16 @@ struct imx290 { struct v4l2_ctrl *pixel_rate; struct v4l2_ctrl *hblank; struct v4l2_ctrl *vblank; - - struct mutex lock; -}; - -struct imx290_pixfmt { - u32 code; - u8 bpp; -}; - -static const struct imx290_pixfmt imx290_formats[] = { - { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, - { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, }; -static const struct regmap_config imx290_regmap_config = { - .reg_bits = 16, - .val_bits = 8, -}; +static inline struct imx290 *to_imx290(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx290, sd); +} -static const char * const imx290_test_pattern_menu[] = { - "Disabled", - "Sequence Pattern 1", - "Horizontal Color-bar Chart", - "Vertical Color-bar Chart", - "Sequence Pattern 2", - "Gradation Pattern 1", - "Gradation Pattern 2", - "000/555h Toggle Pattern", -}; +/* ----------------------------------------------------------------------------- + * Modes and formats + */ static const struct imx290_regval imx290_global_init_settings[] = { { IMX290_CTRL_07, IMX290_WINMODE_1080P }, @@ -338,7 +315,6 @@ static const struct imx290_regval imx290_10bit_settings[] = { { 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[] = { @@ -348,7 +324,6 @@ static const struct imx290_regval imx290_12bit_settings[] = { { 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 */ @@ -438,11 +413,45 @@ static inline int imx290_modes_num(const struct imx290 *imx290) return ARRAY_SIZE(imx290_modes_4lanes); } -static inline struct imx290 *to_imx290(struct v4l2_subdev *_sd) +struct imx290_format_info { + u32 code; + u8 bpp; + const struct imx290_regval *regs; + unsigned int num_regs; +}; + +static const struct imx290_format_info imx290_formats[] = { + { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .bpp = 10, + .regs = imx290_10bit_settings, + .num_regs = ARRAY_SIZE(imx290_10bit_settings), + }, { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .bpp = 12, + .regs = imx290_12bit_settings, + .num_regs = ARRAY_SIZE(imx290_12bit_settings), + } +}; + +static const struct imx290_format_info *imx290_format_info(u32 code) { - return container_of(_sd, struct imx290, sd); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(imx290_formats); ++i) { + const struct imx290_format_info *info = &imx290_formats[i]; + + if (info->code == code) + return info; + } + + return NULL; } +/* ----------------------------------------------------------------------------- + * Register access + */ + static int __always_unused imx290_read(struct imx290 *imx290, u32 addr, u32 *value) { u8 data[3] = { 0, 0, 0 }; @@ -501,28 +510,82 @@ static int imx290_set_register_array(struct imx290 *imx290, return 0; } -/* Stop streaming */ -static int imx290_stop_streaming(struct imx290 *imx290) +static int imx290_set_data_lanes(struct imx290 *imx290) { int ret = 0; + u32 frsel; - imx290_write(imx290, IMX290_STANDBY, 0x01, &ret); + switch (imx290->nlanes) { + case 2: + default: + frsel = 0x02; + break; + case 4: + frsel = 0x01; + break; + } - msleep(30); + imx290_write(imx290, IMX290_PHY_LANE_NUM, imx290->nlanes - 1, &ret); + imx290_write(imx290, IMX290_CSI_LANE_MODE, imx290->nlanes - 1, &ret); + imx290_write(imx290, IMX290_FR_FDG_SEL, frsel, &ret); - return imx290_write(imx290, IMX290_XMSTA, 0x01, &ret); + return ret; +} + +static int imx290_set_black_level(struct imx290 *imx290, + const struct v4l2_mbus_framefmt *format, + unsigned int black_level, int *err) +{ + unsigned int bpp = imx290_format_info(format->code)->bpp; + + return imx290_write(imx290, IMX290_BLKLEVEL, + black_level >> (16 - bpp), err); +} + +static int imx290_setup_format(struct imx290 *imx290, + const struct v4l2_mbus_framefmt *format) +{ + const struct imx290_format_info *info; + int ret; + + info = imx290_format_info(format->code); + + ret = imx290_set_register_array(imx290, info->regs, info->num_regs); + if (ret < 0) { + dev_err(imx290->dev, "Could not set format registers\n"); + return ret; + } + + return imx290_set_black_level(imx290, format, + IMX290_BLACK_LEVEL_DEFAULT, &ret); } +/* ---------------------------------------------------------------------------- + * Controls + */ + static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) { struct imx290 *imx290 = container_of(ctrl->handler, struct imx290, ctrls); + const struct v4l2_mbus_framefmt *format; + struct v4l2_subdev_state *state; int ret = 0; + /* + * Return immediately for controls that don't need to be applied to the + * device. + */ + if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) + return 0; + /* V4L2 controls values will be applied only when power is already up */ if (!pm_runtime_get_if_in_use(imx290->dev)) return 0; + state = v4l2_subdev_get_locked_active_state(&imx290->sd); + format = v4l2_subdev_get_pad_format(&imx290->sd, state, 0); + switch (ctrl->id) { case V4L2_CID_ANALOGUE_GAIN: ret = imx290_write(imx290, IMX290_GAIN, ctrl->val, NULL); @@ -535,7 +598,7 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_TEST_PATTERN: if (ctrl->val) { - imx290_write(imx290, IMX290_BLKLEVEL, 0, &ret); + imx290_set_black_level(imx290, format, 0, &ret); usleep_range(10000, 11000); imx290_write(imx290, IMX290_PGCTRL, (u8)(IMX290_PGCTRL_REGEN | @@ -544,20 +607,18 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) } else { imx290_write(imx290, IMX290_PGCTRL, 0x00, &ret); usleep_range(10000, 11000); - if (imx290->bpp == 10) - imx290_write(imx290, IMX290_BLKLEVEL, 0x3c, - &ret); - else /* 12 bits per pixel */ - imx290_write(imx290, IMX290_BLKLEVEL, 0xf0, - &ret); + imx290_set_black_level(imx290, format, + IMX290_BLACK_LEVEL_DEFAULT, &ret); } break; + default: ret = -EINVAL; break; } - pm_runtime_put(imx290->dev); + pm_runtime_mark_last_busy(imx290->dev); + pm_runtime_put_autosuspend(imx290->dev); return ret; } @@ -566,14 +627,217 @@ 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) +static const char * const imx290_test_pattern_menu[] = { + "Disabled", + "Sequence Pattern 1", + "Horizontal Color-bar Chart", + "Vertical Color-bar Chart", + "Sequence Pattern 2", + "Gradation Pattern 1", + "Gradation Pattern 2", + "000/555h Toggle Pattern", +}; + +static void imx290_ctrl_update(struct imx290 *imx290, + const struct v4l2_mbus_framefmt *format, + const struct imx290_mode *mode) { - if (which == V4L2_SUBDEV_FORMAT_ACTIVE) - return &imx290->current_format; - else - return v4l2_subdev_get_try_format(&imx290->sd, state, 0); + unsigned int hblank = mode->hmax - mode->width; + unsigned int vblank = IMX290_VMAX_DEFAULT - mode->height; + s64 link_freq = imx290_link_freqs_ptr(imx290)[mode->link_freq_index]; + u64 pixel_rate; + + /* pixel rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ + pixel_rate = link_freq * 2 * imx290->nlanes; + do_div(pixel_rate, imx290_format_info(format->code)->bpp); + + __v4l2_ctrl_s_ctrl(imx290->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64(imx290->pixel_rate, pixel_rate); + + __v4l2_ctrl_modify_range(imx290->hblank, hblank, hblank, 1, hblank); + __v4l2_ctrl_modify_range(imx290->vblank, vblank, vblank, 1, vblank); +} + +static int imx290_ctrl_init(struct imx290 *imx290) +{ + struct v4l2_fwnode_device_properties props; + int ret; + + ret = v4l2_fwnode_device_parse(imx290->dev, &props); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(&imx290->ctrls, 9); + + /* + * 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); + + /* + * Set the link frequency, pixel rate, horizontal blanking and vertical + * blanking to hardcoded values, they will be updated by + * imx290_ctrl_update(). + */ + 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, 1); + + 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->hblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_HBLANK, 1, 1, 1, 1); + if (imx290->hblank) + imx290->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx290->vblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_VBLANK, 1, 1, 1, 1); + 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; +} + +/* ---------------------------------------------------------------------------- + * Subdev operations + */ + +/* Start streaming */ +static int imx290_start_streaming(struct imx290 *imx290, + struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt *format; + int ret; + + /* Set init register settings */ + ret = imx290_set_register_array(imx290, imx290_global_init_settings, + ARRAY_SIZE( + imx290_global_init_settings)); + if (ret < 0) { + dev_err(imx290->dev, "Could not set init registers\n"); + return ret; + } + + /* Set data lane count */ + ret = imx290_set_data_lanes(imx290); + if (ret < 0) { + dev_err(imx290->dev, "Could not set data lanes\n"); + return ret; + } + + /* Apply the register values related to current frame format */ + format = v4l2_subdev_get_pad_format(&imx290->sd, state, 0); + ret = imx290_setup_format(imx290, format); + if (ret < 0) { + dev_err(imx290->dev, "Could not set frame format\n"); + return ret; + } + + /* Apply default values of current mode */ + ret = imx290_set_register_array(imx290, imx290->current_mode->data, + imx290->current_mode->data_size); + if (ret < 0) { + dev_err(imx290->dev, "Could not set current mode\n"); + return ret; + } + + ret = imx290_write(imx290, IMX290_HMAX, imx290->current_mode->hmax, + NULL); + if (ret) + return ret; + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx290->sd.ctrl_handler); + if (ret) { + dev_err(imx290->dev, "Could not sync v4l2 controls\n"); + return ret; + } + + imx290_write(imx290, IMX290_STANDBY, 0x00, &ret); + + msleep(30); + + /* Start streaming */ + return imx290_write(imx290, IMX290_XMSTA, 0x00, &ret); +} + +/* Stop streaming */ +static int imx290_stop_streaming(struct imx290 *imx290) +{ + int ret = 0; + + imx290_write(imx290, IMX290_STANDBY, 0x01, &ret); + + msleep(30); + + return imx290_write(imx290, IMX290_XMSTA, 0x01, &ret); +} + +static int imx290_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx290 *imx290 = to_imx290(sd); + struct v4l2_subdev_state *state; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) { + ret = pm_runtime_resume_and_get(imx290->dev); + if (ret < 0) + goto unlock; + + ret = imx290_start_streaming(imx290, state); + if (ret) { + dev_err(imx290->dev, "Start stream failed\n"); + pm_runtime_put_sync(imx290->dev); + goto unlock; + } + } else { + imx290_stop_streaming(imx290); + pm_runtime_mark_last_busy(imx290->dev); + pm_runtime_put_autosuspend(imx290->dev); + } + +unlock: + v4l2_subdev_unlock_state(state); + return ret; } static int imx290_enum_mbus_code(struct v4l2_subdev *sd, @@ -595,8 +859,7 @@ static int imx290_enum_frame_size(struct v4l2_subdev *sd, const struct imx290 *imx290 = to_imx290(sd); const struct imx290_mode *imx290_modes = imx290_modes_ptr(imx290); - if ((fse->code != imx290_formats[0].code) && - (fse->code != imx290_formats[1].code)) + if (!imx290_format_info(fse->code)) return -EINVAL; if (fse->index >= imx290_modes_num(imx290)) @@ -610,47 +873,6 @@ static int imx290_enum_frame_size(struct v4l2_subdev *sd, return 0; } -static int imx290_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - struct imx290 *imx290 = to_imx290(sd); - struct v4l2_mbus_framefmt *framefmt; - - mutex_lock(&imx290->lock); - - framefmt = imx290_get_pad_format(imx290, sd_state, fmt->which); - fmt->format = *framefmt; - - mutex_unlock(&imx290->lock); - - return 0; -} - -static inline u8 imx290_get_link_freq_index(struct imx290 *imx290) -{ - return imx290->current_mode->link_freq_index; -} - -static s64 imx290_get_link_freq(struct imx290 *imx290) -{ - u8 index = imx290_get_link_freq_index(imx290); - - return *(imx290_link_freqs_ptr(imx290) + index); -} - -static u64 imx290_calc_pixel_rate(struct imx290 *imx290) -{ - s64 link_freq = imx290_get_link_freq(imx290); - u8 nlanes = imx290->nlanes; - u64 pixel_rate; - - /* pixel rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ - pixel_rate = link_freq * 2 * nlanes; - do_div(pixel_rate, imx290->bpp); - return pixel_rate; -} - static int imx290_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) @@ -658,9 +880,6 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, struct imx290 *imx290 = to_imx290(sd); const struct imx290_mode *mode; struct v4l2_mbus_framefmt *format; - unsigned int i; - - mutex_lock(&imx290->lock); mode = v4l2_find_nearest_size(imx290_modes_ptr(imx290), imx290_modes_num(imx290), width, height, @@ -669,48 +888,21 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, fmt->format.width = mode->width; fmt->format.height = mode->height; - for (i = 0; i < ARRAY_SIZE(imx290_formats); i++) - if (imx290_formats[i].code == fmt->format.code) - break; - - if (i >= ARRAY_SIZE(imx290_formats)) - i = 0; + if (!imx290_format_info(fmt->format.code)) + fmt->format.code = imx290_formats[0].code; - fmt->format.code = imx290_formats[i].code; fmt->format.field = V4L2_FIELD_NONE; - format = imx290_get_pad_format(imx290, sd_state, fmt->which); + format = v4l2_subdev_get_pad_format(sd, sd_state, 0); if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { imx290->current_mode = mode; - imx290->bpp = imx290_formats[i].bpp; - - if (imx290->link_freq) - __v4l2_ctrl_s_ctrl(imx290->link_freq, - imx290_get_link_freq_index(imx290)); - 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); - } + imx290_ctrl_update(imx290, &fmt->format, mode); } *format = fmt->format; - mutex_unlock(&imx290->lock); - return 0; } @@ -718,14 +910,11 @@ 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); + format = v4l2_subdev_get_pad_format(sd, sd_state, 0); sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP + (IMX290_PIXEL_ARRAY_RECORDING_HEIGHT - format->height) / 2; @@ -734,7 +923,6 @@ static int imx290_get_selection(struct v4l2_subdev *sd, sel->r.width = format->width; sel->r.height = format->height; - mutex_unlock(&imx290->lock); return 0; } @@ -763,179 +951,116 @@ static int imx290_get_selection(struct v4l2_subdev *sd, static int imx290_entity_init_cfg(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state) { - struct v4l2_subdev_format fmt = { 0 }; - - fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; - fmt.format.width = 1920; - fmt.format.height = 1080; + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .format = { + .width = 1920, + .height = 1080, + }, + }; imx290_set_fmt(subdev, sd_state, &fmt); return 0; } -static int imx290_write_current_format(struct imx290 *imx290) -{ - int ret; +static const struct v4l2_subdev_video_ops imx290_video_ops = { + .s_stream = imx290_set_stream, +}; - switch (imx290->current_format.code) { - case MEDIA_BUS_FMT_SRGGB10_1X10: - ret = imx290_set_register_array(imx290, imx290_10bit_settings, - ARRAY_SIZE( - imx290_10bit_settings)); - if (ret < 0) { - dev_err(imx290->dev, "Could not set format registers\n"); - return ret; - } - break; - case MEDIA_BUS_FMT_SRGGB12_1X12: - ret = imx290_set_register_array(imx290, imx290_12bit_settings, - ARRAY_SIZE( - imx290_12bit_settings)); - if (ret < 0) { - dev_err(imx290->dev, "Could not set format registers\n"); - return ret; - } - break; - default: - dev_err(imx290->dev, "Unknown pixel format\n"); - return -EINVAL; - } +static const struct v4l2_subdev_pad_ops imx290_pad_ops = { + .init_cfg = imx290_entity_init_cfg, + .enum_mbus_code = imx290_enum_mbus_code, + .enum_frame_size = imx290_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx290_set_fmt, + .get_selection = imx290_get_selection, +}; - return 0; -} +static const struct v4l2_subdev_ops imx290_subdev_ops = { + .video = &imx290_video_ops, + .pad = &imx290_pad_ops, +}; -/* Start streaming */ -static int imx290_start_streaming(struct imx290 *imx290) +static const struct media_entity_operations imx290_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int imx290_subdev_init(struct imx290 *imx290) { + struct i2c_client *client = to_i2c_client(imx290->dev); + const struct v4l2_mbus_framefmt *format; + struct v4l2_subdev_state *state; int ret; - /* Set init register settings */ - ret = imx290_set_register_array(imx290, imx290_global_init_settings, - ARRAY_SIZE( - imx290_global_init_settings)); - if (ret < 0) { - dev_err(imx290->dev, "Could not set init registers\n"); - return ret; - } + imx290->current_mode = &imx290_modes_ptr(imx290)[0]; - /* Apply the register values related to current frame format */ - ret = imx290_write_current_format(imx290); + v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops); + imx290->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx290->sd.dev = imx290->dev; + imx290->sd.entity.ops = &imx290_subdev_entity_ops; + imx290->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + imx290->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&imx290->sd.entity, 1, &imx290->pad); if (ret < 0) { - dev_err(imx290->dev, "Could not set frame format\n"); + dev_err(imx290->dev, "Could not register media entity\n"); return ret; } - /* Apply default values of current mode */ - ret = imx290_set_register_array(imx290, imx290->current_mode->data, - imx290->current_mode->data_size); + ret = imx290_ctrl_init(imx290); if (ret < 0) { - dev_err(imx290->dev, "Could not set current mode\n"); - return ret; + dev_err(imx290->dev, "Control initialization error %d\n", ret); + goto err_media; } - ret = imx290_write(imx290, IMX290_HMAX, imx290->current_mode->hmax, - NULL); - if (ret) - return ret; + imx290->sd.state_lock = imx290->ctrls.lock; - /* Apply customized values from user */ - ret = v4l2_ctrl_handler_setup(imx290->sd.ctrl_handler); - if (ret) { - dev_err(imx290->dev, "Could not sync v4l2 controls\n"); - return ret; + ret = v4l2_subdev_init_finalize(&imx290->sd); + if (ret < 0) { + dev_err(imx290->dev, "subdev initialization error %d\n", ret); + goto err_ctrls; } - imx290_write(imx290, IMX290_STANDBY, 0x00, &ret); - - msleep(30); - - /* Start streaming */ - return imx290_write(imx290, IMX290_XMSTA, 0x00, &ret); -} - -static int imx290_set_stream(struct v4l2_subdev *sd, int enable) -{ - struct imx290 *imx290 = to_imx290(sd); - int ret = 0; + state = v4l2_subdev_lock_and_get_active_state(&imx290->sd); + format = v4l2_subdev_get_pad_format(&imx290->sd, state, 0); + imx290_ctrl_update(imx290, format, imx290->current_mode); + v4l2_subdev_unlock_state(state); - if (enable) { - ret = pm_runtime_resume_and_get(imx290->dev); - if (ret < 0) - goto unlock_and_return; - - ret = imx290_start_streaming(imx290); - if (ret) { - dev_err(imx290->dev, "Start stream failed\n"); - pm_runtime_put(imx290->dev); - goto unlock_and_return; - } - } else { - imx290_stop_streaming(imx290); - pm_runtime_put(imx290->dev); - } - -unlock_and_return: + return 0; +err_ctrls: + v4l2_ctrl_handler_free(&imx290->ctrls); +err_media: + media_entity_cleanup(&imx290->sd.entity); return ret; } -static int imx290_get_regulators(struct device *dev, struct imx290 *imx290) +static void imx290_subdev_cleanup(struct imx290 *imx290) { - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(imx290->supplies); i++) - imx290->supplies[i].supply = imx290_supply_name[i]; - - return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx290->supplies), - imx290->supplies); + v4l2_subdev_cleanup(&imx290->sd); + media_entity_cleanup(&imx290->sd.entity); + v4l2_ctrl_handler_free(&imx290->ctrls); } -static int imx290_set_data_lanes(struct imx290 *imx290) -{ - int ret = 0, laneval, frsel; - - switch (imx290->nlanes) { - case 2: - laneval = 0x01; - frsel = 0x02; - break; - case 4: - laneval = 0x03; - frsel = 0x01; - break; - default: - /* - * We should never hit this since the data lane count is - * validated in probe itself - */ - dev_err(imx290->dev, "Lane configuration not supported\n"); - return -EINVAL; - } - - 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); - - return ret; -} +/* ---------------------------------------------------------------------------- + * Power management + */ -static int imx290_power_on(struct device *dev) +static int imx290_power_on(struct imx290 *imx290) { - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct imx290 *imx290 = to_imx290(sd); int ret; ret = clk_prepare_enable(imx290->xclk); if (ret) { - dev_err(dev, "Failed to enable clock\n"); + dev_err(imx290->dev, "Failed to enable clock\n"); return ret; } ret = regulator_bulk_enable(ARRAY_SIZE(imx290->supplies), imx290->supplies); if (ret) { - dev_err(dev, "Failed to enable regulators\n"); + dev_err(imx290->dev, "Failed to enable regulators\n"); clk_disable_unprepare(imx290->xclk); return ret; } @@ -944,123 +1069,86 @@ static int imx290_power_on(struct device *dev) gpiod_set_value_cansleep(imx290->rst_gpio, 0); usleep_range(30000, 31000); - /* Set data lane count */ - imx290_set_data_lanes(imx290); - return 0; } -static int imx290_power_off(struct device *dev) +static void imx290_power_off(struct imx290 *imx290) { - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct imx290 *imx290 = to_imx290(sd); - clk_disable_unprepare(imx290->xclk); gpiod_set_value_cansleep(imx290->rst_gpio, 1); regulator_bulk_disable(ARRAY_SIZE(imx290->supplies), imx290->supplies); +} + +static int imx290_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx290 *imx290 = to_imx290(sd); + + return imx290_power_on(imx290); +} + +static int imx290_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx290 *imx290 = to_imx290(sd); + + imx290_power_off(imx290); return 0; } static const struct dev_pm_ops imx290_pm_ops = { - SET_RUNTIME_PM_OPS(imx290_power_off, imx290_power_on, NULL) -}; - -static const struct v4l2_subdev_video_ops imx290_video_ops = { - .s_stream = imx290_set_stream, + SET_RUNTIME_PM_OPS(imx290_runtime_suspend, imx290_runtime_resume, NULL) }; -static const struct v4l2_subdev_pad_ops imx290_pad_ops = { - .init_cfg = imx290_entity_init_cfg, - .enum_mbus_code = imx290_enum_mbus_code, - .enum_frame_size = imx290_enum_frame_size, - .get_fmt = imx290_get_fmt, - .set_fmt = imx290_set_fmt, - .get_selection = imx290_get_selection, -}; +/* ---------------------------------------------------------------------------- + * Probe & remove + */ -static const struct v4l2_subdev_ops imx290_subdev_ops = { - .video = &imx290_video_ops, - .pad = &imx290_pad_ops, +static const struct regmap_config imx290_regmap_config = { + .reg_bits = 16, + .val_bits = 8, }; -static const struct media_entity_operations imx290_subdev_entity_ops = { - .link_validate = v4l2_subdev_link_validate, +static const char * const imx290_supply_name[IMX290_NUM_SUPPLIES] = { + "vdda", + "vddd", + "vdddo", }; -static int imx290_ctrl_init(struct imx290 *imx290) +static int imx290_get_regulators(struct device *dev, 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)); + unsigned int i; - 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); + for (i = 0; i < ARRAY_SIZE(imx290->supplies); i++) + imx290->supplies[i].supply = imx290_supply_name[i]; - 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; + return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx290->supplies), + imx290->supplies); +} - 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; +static int imx290_init_clk(struct imx290 *imx290) +{ + u32 xclk_freq; + int ret; - v4l2_ctrl_new_fwnode_properties(&imx290->ctrls, &imx290_ctrl_ops, - &props); + ret = fwnode_property_read_u32(dev_fwnode(imx290->dev), + "clock-frequency", &xclk_freq); + if (ret) { + dev_err(imx290->dev, "Could not get xclk frequency\n"); + return ret; + } - imx290->sd.ctrl_handler = &imx290->ctrls; + /* external clock must be 37.125 MHz */ + if (xclk_freq != 37125000) { + dev_err(imx290->dev, "External clock frequency %u is not supported\n", + xclk_freq); + return -EINVAL; + } - if (imx290->ctrls.error) { - ret = imx290->ctrls.error; - v4l2_ctrl_handler_free(&imx290->ctrls); + ret = clk_set_rate(imx290->xclk, xclk_freq); + if (ret) { + dev_err(imx290->dev, "Could not set xclk frequency\n"); return ret; } @@ -1089,171 +1177,159 @@ static s64 imx290_check_link_freqs(const struct imx290 *imx290, return 0; } -static int imx290_probe(struct i2c_client *client) +static int imx290_parse_dt(struct imx290 *imx290) { - struct device *dev = &client->dev; - struct fwnode_handle *endpoint; /* Only CSI2 is supported for now: */ struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; - struct imx290 *imx290; - u32 xclk_freq; - s64 fq; + struct fwnode_handle *endpoint; int ret; + s64 fq; - imx290 = devm_kzalloc(dev, sizeof(*imx290), GFP_KERNEL); - if (!imx290) - return -ENOMEM; - - imx290->dev = dev; - imx290->regmap = devm_regmap_init_i2c(client, &imx290_regmap_config); - if (IS_ERR(imx290->regmap)) { - dev_err(dev, "Unable to initialize I2C\n"); - return -ENODEV; - } - - endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(imx290->dev), NULL); if (!endpoint) { - dev_err(dev, "Endpoint node not found\n"); + dev_err(imx290->dev, "Endpoint node not found\n"); return -EINVAL; } ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep); fwnode_handle_put(endpoint); if (ret == -ENXIO) { - dev_err(dev, "Unsupported bus type, should be CSI2\n"); - goto free_err; + dev_err(imx290->dev, "Unsupported bus type, should be CSI2\n"); + goto done; } else if (ret) { - dev_err(dev, "Parsing endpoint node failed\n"); - goto free_err; + dev_err(imx290->dev, "Parsing endpoint node failed\n"); + goto done; } /* Get number of data lanes */ imx290->nlanes = ep.bus.mipi_csi2.num_data_lanes; if (imx290->nlanes != 2 && imx290->nlanes != 4) { - dev_err(dev, "Invalid data lanes: %d\n", imx290->nlanes); + dev_err(imx290->dev, "Invalid data lanes: %d\n", imx290->nlanes); ret = -EINVAL; - goto free_err; + goto done; } - dev_dbg(dev, "Using %u data lanes\n", imx290->nlanes); + dev_dbg(imx290->dev, "Using %u data lanes\n", imx290->nlanes); if (!ep.nr_of_link_frequencies) { - dev_err(dev, "link-frequency property not found in DT\n"); + dev_err(imx290->dev, "link-frequency property not found in DT\n"); ret = -EINVAL; - goto free_err; + goto done; } /* Check that link frequences for all the modes are in device tree */ fq = imx290_check_link_freqs(imx290, &ep); if (fq) { - dev_err(dev, "Link frequency of %lld is not supported\n", fq); + dev_err(imx290->dev, "Link frequency of %lld is not supported\n", + fq); ret = -EINVAL; - goto free_err; + goto done; } - /* get system clock (xclk) */ - imx290->xclk = devm_clk_get(dev, "xclk"); - if (IS_ERR(imx290->xclk)) { - dev_err(dev, "Could not get xclk"); - ret = PTR_ERR(imx290->xclk); - goto free_err; - } + ret = 0; - ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", - &xclk_freq); - if (ret) { - dev_err(dev, "Could not get xclk frequency\n"); - goto free_err; - } +done: + v4l2_fwnode_endpoint_free(&ep); + return ret; +} - /* external clock must be 37.125 MHz */ - if (xclk_freq != 37125000) { - dev_err(dev, "External clock frequency %u is not supported\n", - xclk_freq); - ret = -EINVAL; - goto free_err; - } +static int imx290_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx290 *imx290; + int ret; - ret = clk_set_rate(imx290->xclk, xclk_freq); - if (ret) { - dev_err(dev, "Could not set xclk frequency\n"); - goto free_err; + imx290 = devm_kzalloc(dev, sizeof(*imx290), GFP_KERNEL); + if (!imx290) + return -ENOMEM; + + imx290->dev = dev; + imx290->regmap = devm_regmap_init_i2c(client, &imx290_regmap_config); + if (IS_ERR(imx290->regmap)) { + dev_err(dev, "Unable to initialize I2C\n"); + return -ENODEV; } + ret = imx290_parse_dt(imx290); + if (ret) + return ret; + + /* Acquire resources. */ + imx290->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(imx290->xclk)) + return dev_err_probe(dev, PTR_ERR(imx290->xclk), + "Could not get xclk"); + ret = imx290_get_regulators(dev, imx290); - if (ret < 0) { - dev_err(dev, "Cannot get regulators\n"); - goto free_err; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot get regulators\n"); imx290->rst_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); - if (IS_ERR(imx290->rst_gpio)) { - dev_err(dev, "Cannot get reset gpio\n"); - ret = PTR_ERR(imx290->rst_gpio); - goto free_err; - } + if (IS_ERR(imx290->rst_gpio)) + return dev_err_probe(dev, PTR_ERR(imx290->rst_gpio), + "Cannot get reset gpio\n"); - mutex_init(&imx290->lock); + /* Initialize external clock frequency. */ + ret = imx290_init_clk(imx290); + if (ret) + return ret; /* - * Initialize the frame format. In particular, imx290->current_mode - * and imx290->bpp are set to defaults: imx290_calc_pixel_rate() call - * below relies on these fields. + * Enable power management. The driver supports runtime PM, but needs to + * work when runtime PM is disabled in the kernel. To that end, power + * the sensor on manually here. */ - imx290_entity_init_cfg(&imx290->sd, NULL); - - ret = imx290_ctrl_init(imx290); + ret = imx290_power_on(imx290); if (ret < 0) { - dev_err(dev, "Control initialization error %d\n", ret); - goto free_mutex; + dev_err(dev, "Could not power on the device\n"); + return ret; } - v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops); - imx290->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - imx290->sd.dev = &client->dev; - imx290->sd.entity.ops = &imx290_subdev_entity_ops; - imx290->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + /* + * Enable runtime PM with autosuspend. As the device has been powered + * manually, mark it as active, and increase the usage count without + * resuming the device. + */ + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); - imx290->pad.flags = MEDIA_PAD_FL_SOURCE; - ret = media_entity_pads_init(&imx290->sd.entity, 1, &imx290->pad); - if (ret < 0) { - dev_err(dev, "Could not register media entity\n"); - goto free_ctrl; - } + /* Initialize the V4L2 subdev. */ + ret = imx290_subdev_init(imx290); + if (ret) + goto err_pm; + /* + * Finally, register the V4L2 subdev. This must be done after + * initializing everything as the subdev can be used immediately after + * being registered. + */ ret = v4l2_async_register_subdev(&imx290->sd); if (ret < 0) { dev_err(dev, "Could not register v4l2 device\n"); - goto free_entity; - } - - /* Power on the device to match runtime PM state below */ - ret = imx290_power_on(dev); - if (ret < 0) { - dev_err(dev, "Could not power on the device\n"); - goto free_entity; + goto err_subdev; } - pm_runtime_set_active(dev); - pm_runtime_enable(dev); - pm_runtime_idle(dev); - - v4l2_fwnode_endpoint_free(&ep); + /* + * Decrease the PM usage count. The device will get suspended after the + * autosuspend delay, turning the power off. + */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); return 0; -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); - +err_subdev: + imx290_subdev_cleanup(imx290); +err_pm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + imx290_power_off(imx290); return ret; } @@ -1263,14 +1339,15 @@ static void imx290_remove(struct i2c_client *client) struct imx290 *imx290 = to_imx290(sd); v4l2_async_unregister_subdev(sd); - media_entity_cleanup(&sd->entity); - v4l2_ctrl_handler_free(sd->ctrl_handler); - - mutex_destroy(&imx290->lock); + imx290_subdev_cleanup(imx290); + /* + * Disable runtime PM. In case runtime PM is disabled in the kernel, + * make sure to turn power off manually. + */ pm_runtime_disable(imx290->dev); if (!pm_runtime_status_suspended(imx290->dev)) - imx290_power_off(imx290->dev); + imx290_power_off(imx290); pm_runtime_set_suspended(imx290->dev); } diff --git a/drivers/media/i2c/imx296.c b/drivers/media/i2c/imx296.c new file mode 100644 index 000000000000..3c12b6edeac9 --- /dev/null +++ b/drivers/media/i2c/imx296.c @@ -0,0 +1,1172 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for IMX296 CMOS Image Sensor from Sony + * + * Copyright 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/clk.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define IMX296_PIXEL_ARRAY_WIDTH 1456 +#define IMX296_PIXEL_ARRAY_HEIGHT 1088 + +#define IMX296_REG_8BIT(n) ((1 << 16) | (n)) +#define IMX296_REG_16BIT(n) ((2 << 16) | (n)) +#define IMX296_REG_24BIT(n) ((3 << 16) | (n)) +#define IMX296_REG_SIZE_SHIFT 16 +#define IMX296_REG_ADDR_MASK 0xffff + +#define IMX296_CTRL00 IMX296_REG_8BIT(0x3000) +#define IMX296_CTRL00_STANDBY BIT(0) +#define IMX296_CTRL08 IMX296_REG_8BIT(0x3008) +#define IMX296_CTRL08_REGHOLD BIT(0) +#define IMX296_CTRL0A IMX296_REG_8BIT(0x300a) +#define IMX296_CTRL0A_XMSTA BIT(0) +#define IMX296_CTRL0B IMX296_REG_8BIT(0x300b) +#define IMX296_CTRL0B_TRIGEN BIT(0) +#define IMX296_CTRL0D IMX296_REG_8BIT(0x300d) +#define IMX296_CTRL0D_WINMODE_ALL (0 << 0) +#define IMX296_CTRL0D_WINMODE_FD_BINNING (2 << 0) +#define IMX296_CTRL0D_HADD_ON_BINNING BIT(5) +#define IMX296_CTRL0D_SAT_CNT BIT(6) +#define IMX296_CTRL0E IMX296_REG_8BIT(0x300e) +#define IMX296_CTRL0E_VREVERSE BIT(0) +#define IMX296_CTRL0E_HREVERSE BIT(1) +#define IMX296_VMAX IMX296_REG_24BIT(0x3010) +#define IMX296_HMAX IMX296_REG_16BIT(0x3014) +#define IMX296_TMDCTRL IMX296_REG_8BIT(0x301d) +#define IMX296_TMDCTRL_LATCH BIT(0) +#define IMX296_TMDOUT IMX296_REG_16BIT(0x301e) +#define IMX296_TMDOUT_MASK 0x3ff +#define IMX296_WDSEL IMX296_REG_8BIT(0x3021) +#define IMX296_WDSEL_NORMAL (0 << 0) +#define IMX296_WDSEL_MULTI_2 (1 << 0) +#define IMX296_WDSEL_MULTI_4 (3 << 0) +#define IMX296_BLKLEVELAUTO IMX296_REG_8BIT(0x3022) +#define IMX296_BLKLEVELAUTO_ON 0x01 +#define IMX296_BLKLEVELAUTO_OFF 0xf0 +#define IMX296_SST IMX296_REG_8BIT(0x3024) +#define IMX296_SST_EN BIT(0) +#define IMX296_CTRLTOUT IMX296_REG_8BIT(0x3026) +#define IMX296_CTRLTOUT_TOUT1SEL_LOW (0 << 0) +#define IMX296_CTRLTOUT_TOUT1SEL_PULSE (3 << 0) +#define IMX296_CTRLTOUT_TOUT2SEL_LOW (0 << 2) +#define IMX296_CTRLTOUT_TOUT2SEL_PULSE (3 << 2) +#define IMX296_CTRLTRIG IMX296_REG_8BIT(0x3029) +#define IMX296_CTRLTRIG_TOUT1_SEL_LOW (0 << 0) +#define IMX296_CTRLTRIG_TOUT1_SEL_PULSE1 (1 << 0) +#define IMX296_CTRLTRIG_TOUT2_SEL_LOW (0 << 4) +#define IMX296_CTRLTRIG_TOUT2_SEL_PULSE2 (2 << 4) +#define IMX296_SYNCSEL IMX296_REG_8BIT(0x3036) +#define IMX296_SYNCSEL_NORMAL 0xc0 +#define IMX296_SYNCSEL_HIZ 0xf0 +#define IMX296_PULSE1 IMX296_REG_8BIT(0x306d) +#define IMX296_PULSE1_EN_NOR BIT(0) +#define IMX296_PULSE1_EN_TRIG BIT(1) +#define IMX296_PULSE1_POL_HIGH (0 << 2) +#define IMX296_PULSE1_POL_LOW (1 << 2) +#define IMX296_PULSE1_UP IMX296_REG_24BIT(0x3070) +#define IMX296_PULSE1_DN IMX296_REG_24BIT(0x3074) +#define IMX296_PULSE2 IMX296_REG_8BIT(0x3079) +#define IMX296_PULSE2_EN_NOR BIT(0) +#define IMX296_PULSE2_EN_TRIG BIT(1) +#define IMX296_PULSE2_POL_HIGH (0 << 2) +#define IMX296_PULSE2_POL_LOW (1 << 2) +#define IMX296_PULSE2_UP IMX296_REG_24BIT(0x307c) +#define IMX296_PULSE2_DN IMX296_REG_24BIT(0x3080) +#define IMX296_INCKSEL(n) IMX296_REG_8BIT(0x3089 + (n)) +#define IMX296_SHS1 IMX296_REG_24BIT(0x308d) +#define IMX296_SHS2 IMX296_REG_24BIT(0x3090) +#define IMX296_SHS3 IMX296_REG_24BIT(0x3094) +#define IMX296_SHS4 IMX296_REG_24BIT(0x3098) +#define IMX296_VBLANKLP IMX296_REG_8BIT(0x309c) +#define IMX296_VBLANKLP_NORMAL 0x04 +#define IMX296_VBLANKLP_LOW_POWER 0x2c +#define IMX296_EXP_CNT IMX296_REG_8BIT(0x30a3) +#define IMX296_EXP_CNT_RESET BIT(0) +#define IMX296_EXP_MAX IMX296_REG_16BIT(0x30a6) +#define IMX296_VINT IMX296_REG_8BIT(0x30aa) +#define IMX296_VINT_EN BIT(0) +#define IMX296_LOWLAGTRG IMX296_REG_8BIT(0x30ae) +#define IMX296_LOWLAGTRG_FAST BIT(0) +#define IMX296_I2CCTRL IMX296_REG_8BIT(0x30ef) +#define IMX296_I2CCTRL_I2CACKEN BIT(0) + +#define IMX296_SENSOR_INFO IMX296_REG_16BIT(0x3148) +#define IMX296_SENSOR_INFO_MONO BIT(15) +#define IMX296_SENSOR_INFO_IMX296LQ 0x4a00 +#define IMX296_SENSOR_INFO_IMX296LL 0xca00 +#define IMX296_S_SHSA IMX296_REG_16BIT(0x31ca) +#define IMX296_S_SHSB IMX296_REG_16BIT(0x31d2) +/* + * Registers 0x31c8 to 0x31cd, 0x31d0 to 0x31d5, 0x31e2, 0x31e3, 0x31ea and + * 0x31eb are related to exposure mode but otherwise not documented. + */ + +#define IMX296_GAINCTRL IMX296_REG_8BIT(0x3200) +#define IMX296_GAINCTRL_WD_GAIN_MODE_NORMAL 0x01 +#define IMX296_GAINCTRL_WD_GAIN_MODE_MULTI 0x41 +#define IMX296_GAIN IMX296_REG_16BIT(0x3204) +#define IMX296_GAIN_MIN 0 +#define IMX296_GAIN_MAX 480 +#define IMX296_GAIN1 IMX296_REG_16BIT(0x3208) +#define IMX296_GAIN2 IMX296_REG_16BIT(0x320c) +#define IMX296_GAIN3 IMX296_REG_16BIT(0x3210) +#define IMX296_GAINDLY IMX296_REG_8BIT(0x3212) +#define IMX296_GAINDLY_NONE 0x08 +#define IMX296_GAINDLY_1FRAME 0x09 +#define IMX296_PGCTRL IMX296_REG_8BIT(0x3238) +#define IMX296_PGCTRL_REGEN BIT(0) +#define IMX296_PGCTRL_THRU BIT(1) +#define IMX296_PGCTRL_CLKEN BIT(2) +#define IMX296_PGCTRL_MODE(n) ((n) << 3) +#define IMX296_PGHPOS IMX296_REG_16BIT(0x3239) +#define IMX296_PGVPOS IMX296_REG_16BIT(0x323c) +#define IMX296_PGHPSTEP IMX296_REG_8BIT(0x323e) +#define IMX296_PGVPSTEP IMX296_REG_8BIT(0x323f) +#define IMX296_PGHPNUM IMX296_REG_8BIT(0x3240) +#define IMX296_PGVPNUM IMX296_REG_8BIT(0x3241) +#define IMX296_PGDATA1 IMX296_REG_16BIT(0x3244) +#define IMX296_PGDATA2 IMX296_REG_16BIT(0x3246) +#define IMX296_PGHGSTEP IMX296_REG_8BIT(0x3249) +#define IMX296_BLKLEVEL IMX296_REG_16BIT(0x3254) + +#define IMX296_FID0_ROI IMX296_REG_8BIT(0x3300) +#define IMX296_FID0_ROIH1ON BIT(0) +#define IMX296_FID0_ROIV1ON BIT(1) +#define IMX296_FID0_ROIPH1 IMX296_REG_16BIT(0x3310) +#define IMX296_FID0_ROIPV1 IMX296_REG_16BIT(0x3312) +#define IMX296_FID0_ROIWH1 IMX296_REG_16BIT(0x3314) +#define IMX296_FID0_ROIWH1_MIN 80 +#define IMX296_FID0_ROIWV1 IMX296_REG_16BIT(0x3316) +#define IMX296_FID0_ROIWV1_MIN 4 + +#define IMX296_CM_HSST_STARTTMG IMX296_REG_16BIT(0x4018) +#define IMX296_CM_HSST_ENDTMG IMX296_REG_16BIT(0x401a) +#define IMX296_DA_HSST_STARTTMG IMX296_REG_16BIT(0x404d) +#define IMX296_DA_HSST_ENDTMG IMX296_REG_16BIT(0x4050) +#define IMX296_LM_HSST_STARTTMG IMX296_REG_16BIT(0x4094) +#define IMX296_LM_HSST_ENDTMG IMX296_REG_16BIT(0x4096) +#define IMX296_SST_SIEASTA1_SET IMX296_REG_8BIT(0x40c9) +#define IMX296_SST_SIEASTA1PRE_1U IMX296_REG_16BIT(0x40cc) +#define IMX296_SST_SIEASTA1PRE_1D IMX296_REG_16BIT(0x40ce) +#define IMX296_SST_SIEASTA1PRE_2U IMX296_REG_16BIT(0x40d0) +#define IMX296_SST_SIEASTA1PRE_2D IMX296_REG_16BIT(0x40d2) +#define IMX296_HSST IMX296_REG_8BIT(0x40dc) +#define IMX296_HSST_EN BIT(2) + +#define IMX296_CKREQSEL IMX296_REG_8BIT(0x4101) +#define IMX296_CKREQSEL_HS BIT(2) +#define IMX296_GTTABLENUM IMX296_REG_8BIT(0x4114) +#define IMX296_CTRL418C IMX296_REG_8BIT(0x418c) + +struct imx296_clk_params { + unsigned int freq; + u8 incksel[4]; + u8 ctrl418c; +}; + +static const struct imx296_clk_params imx296_clk_params[] = { + { 37125000, { 0x80, 0x0b, 0x80, 0x08 }, 116 }, + { 54000000, { 0xb0, 0x0f, 0xb0, 0x0c }, 168 }, + { 74250000, { 0x80, 0x0f, 0x80, 0x0c }, 232 }, +}; + +static const char * const imx296_supply_names[] = { + "dvdd", + "ovdd", + "avdd", +}; + +struct imx296 { + struct device *dev; + struct clk *clk; + struct regulator_bulk_data supplies[ARRAY_SIZE(imx296_supply_names)]; + struct gpio_desc *reset; + struct regmap *regmap; + + const struct imx296_clk_params *clk_params; + bool mono; + + bool streaming; + + struct v4l2_subdev subdev; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; +}; + +static inline struct imx296 *to_imx296(struct v4l2_subdev *sd) +{ + return container_of(sd, struct imx296, subdev); +} + +static int imx296_read(struct imx296 *sensor, u32 addr) +{ + u8 data[3] = { 0, 0, 0 }; + int ret; + + ret = regmap_raw_read(sensor->regmap, addr & IMX296_REG_ADDR_MASK, data, + (addr >> IMX296_REG_SIZE_SHIFT) & 3); + if (ret < 0) + return ret; + + return (data[2] << 16) | (data[1] << 8) | data[0]; +} + +static int imx296_write(struct imx296 *sensor, u32 addr, u32 value, int *err) +{ + u8 data[3] = { value & 0xff, (value >> 8) & 0xff, value >> 16 }; + int ret; + + if (err && *err) + return *err; + + ret = regmap_raw_write(sensor->regmap, addr & IMX296_REG_ADDR_MASK, + data, (addr >> IMX296_REG_SIZE_SHIFT) & 3); + if (ret < 0) { + dev_err(sensor->dev, "%u-bit write to 0x%04x failed: %d\n", + ((addr >> IMX296_REG_SIZE_SHIFT) & 3) * 8, + addr & IMX296_REG_ADDR_MASK, ret); + if (err) + *err = ret; + } + + return ret; +} + +static int imx296_power_on(struct imx296 *sensor) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret < 0) + return ret; + + udelay(1); + + ret = gpiod_direction_output(sensor->reset, 0); + if (ret < 0) + goto err_supply; + + udelay(1); + + ret = clk_prepare_enable(sensor->clk); + if (ret < 0) + goto err_reset; + + /* + * The documentation doesn't explicitly say how much time is required + * after providing a clock and before starting I2C communication. It + * mentions a delay of 20µs in 4-wire mode, but tests showed that a + * delay of 100µs resulted in I2C communication failures, while 500µs + * seems to be enough. Be conservative. + */ + usleep_range(1000, 2000); + + return 0; + +err_reset: + gpiod_direction_output(sensor->reset, 1); +err_supply: + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); + return ret; +} + +static void imx296_power_off(struct imx296 *sensor) +{ + clk_disable_unprepare(sensor->clk); + gpiod_direction_output(sensor->reset, 1); + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +static const char * const imx296_test_pattern_menu[] = { + "Disabled", + "Multiple Pixels", + "Sequence 1", + "Sequence 2", + "Gradient", + "Row", + "Column", + "Cross", + "Stripe", + "Checks", +}; + +static int imx296_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx296 *sensor = container_of(ctrl->handler, struct imx296, ctrls); + const struct v4l2_mbus_framefmt *format; + struct v4l2_subdev_state *state; + unsigned int vmax; + int ret = 0; + + if (!sensor->streaming) + return 0; + + state = v4l2_subdev_get_locked_active_state(&sensor->subdev); + format = v4l2_subdev_get_pad_format(&sensor->subdev, state, 0); + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + /* Clamp the exposure value to VMAX. */ + vmax = format->height + sensor->vblank->cur.val; + ctrl->val = min_t(int, ctrl->val, vmax); + imx296_write(sensor, IMX296_SHS1, vmax - ctrl->val, &ret); + break; + + case V4L2_CID_ANALOGUE_GAIN: + imx296_write(sensor, IMX296_GAIN, ctrl->val, &ret); + break; + + case V4L2_CID_VBLANK: + imx296_write(sensor, IMX296_VMAX, format->height + ctrl->val, + &ret); + break; + + case V4L2_CID_TEST_PATTERN: + if (ctrl->val) { + imx296_write(sensor, IMX296_PGHPOS, 8, &ret); + imx296_write(sensor, IMX296_PGVPOS, 8, &ret); + imx296_write(sensor, IMX296_PGHPSTEP, 8, &ret); + imx296_write(sensor, IMX296_PGVPSTEP, 8, &ret); + imx296_write(sensor, IMX296_PGHPNUM, 100, &ret); + imx296_write(sensor, IMX296_PGVPNUM, 100, &ret); + imx296_write(sensor, IMX296_PGDATA1, 0x300, &ret); + imx296_write(sensor, IMX296_PGDATA2, 0x100, &ret); + imx296_write(sensor, IMX296_PGHGSTEP, 0, &ret); + imx296_write(sensor, IMX296_BLKLEVEL, 0, &ret); + imx296_write(sensor, IMX296_BLKLEVELAUTO, + IMX296_BLKLEVELAUTO_OFF, &ret); + imx296_write(sensor, IMX296_PGCTRL, + IMX296_PGCTRL_REGEN | + IMX296_PGCTRL_CLKEN | + IMX296_PGCTRL_MODE(ctrl->val - 1), &ret); + } else { + imx296_write(sensor, IMX296_PGCTRL, + IMX296_PGCTRL_CLKEN, &ret); + imx296_write(sensor, IMX296_BLKLEVEL, 0x3c, &ret); + imx296_write(sensor, IMX296_BLKLEVELAUTO, + IMX296_BLKLEVELAUTO_ON, &ret); + } + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops imx296_ctrl_ops = { + .s_ctrl = imx296_s_ctrl, +}; + +static int imx296_ctrls_init(struct imx296 *sensor) +{ + struct v4l2_fwnode_device_properties props; + unsigned int hblank; + int ret; + + ret = v4l2_fwnode_device_parse(sensor->dev, &props); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(&sensor->ctrls, 9); + + v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 1048575, 1, 1104); + v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, IMX296_GAIN_MIN, + IMX296_GAIN_MAX, 1, IMX296_GAIN_MIN); + + /* + * Horizontal blanking is controlled through the HMAX register, which + * contains a line length in INCK clock units. The INCK frequency is + * fixed to 74.25 MHz. The HMAX value is currently fixed to 1100, + * convert it to a number of pixels based on the nominal pixel rate. + */ + hblank = 1100 * 1188000000ULL / 10 / 74250000 + - IMX296_PIXEL_ARRAY_WIDTH; + sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, 1, + hblank); + if (sensor->hblank) + sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops, + V4L2_CID_VBLANK, 30, + 1048575 - IMX296_PIXEL_ARRAY_HEIGHT, + 1, 30); + /* + * The sensor calculates the MIPI timings internally to achieve a bit + * rate between 1122 and 1198 Mbps. The exact value is unfortunately not + * reported, at least according to the documentation. Report a nominal + * rate of 1188 Mbps as that is used by the datasheet in multiple + * examples. + */ + v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE, + 1122000000 / 10, 1198000000 / 10, 1, 1188000000 / 10); + v4l2_ctrl_new_std_menu_items(&sensor->ctrls, &imx296_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx296_test_pattern_menu) - 1, + 0, 0, imx296_test_pattern_menu); + + v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &imx296_ctrl_ops, + &props); + + if (sensor->ctrls.error) { + dev_err(sensor->dev, "failed to add controls (%d)\n", + sensor->ctrls.error); + v4l2_ctrl_handler_free(&sensor->ctrls); + return sensor->ctrls.error; + } + + sensor->subdev.ctrl_handler = &sensor->ctrls; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdev Operations + */ + +/* + * This table is extracted from vendor data that is entirely undocumented. The + * first register write is required to activate the CSI-2 output. The other + * entries may or may not be optional? + */ +static const struct { + unsigned int reg; + unsigned int value; +} imx296_init_table[] = { + { IMX296_REG_8BIT(0x3005), 0xf0 }, + { IMX296_REG_8BIT(0x309e), 0x04 }, + { IMX296_REG_8BIT(0x30a0), 0x04 }, + { IMX296_REG_8BIT(0x30a1), 0x3c }, + { IMX296_REG_8BIT(0x30a4), 0x5f }, + { IMX296_REG_8BIT(0x30a8), 0x91 }, + { IMX296_REG_8BIT(0x30ac), 0x28 }, + { IMX296_REG_8BIT(0x30af), 0x09 }, + { IMX296_REG_8BIT(0x30df), 0x00 }, + { IMX296_REG_8BIT(0x3165), 0x00 }, + { IMX296_REG_8BIT(0x3169), 0x10 }, + { IMX296_REG_8BIT(0x316a), 0x02 }, + { IMX296_REG_8BIT(0x31c8), 0xf3 }, /* Exposure-related */ + { IMX296_REG_8BIT(0x31d0), 0xf4 }, /* Exposure-related */ + { IMX296_REG_8BIT(0x321a), 0x00 }, + { IMX296_REG_8BIT(0x3226), 0x02 }, + { IMX296_REG_8BIT(0x3256), 0x01 }, + { IMX296_REG_8BIT(0x3541), 0x72 }, + { IMX296_REG_8BIT(0x3516), 0x77 }, + { IMX296_REG_8BIT(0x350b), 0x7f }, + { IMX296_REG_8BIT(0x3758), 0xa3 }, + { IMX296_REG_8BIT(0x3759), 0x00 }, + { IMX296_REG_8BIT(0x375a), 0x85 }, + { IMX296_REG_8BIT(0x375b), 0x00 }, + { IMX296_REG_8BIT(0x3832), 0xf5 }, + { IMX296_REG_8BIT(0x3833), 0x00 }, + { IMX296_REG_8BIT(0x38a2), 0xf6 }, + { IMX296_REG_8BIT(0x38a3), 0x00 }, + { IMX296_REG_8BIT(0x3a00), 0x80 }, + { IMX296_REG_8BIT(0x3d48), 0xa3 }, + { IMX296_REG_8BIT(0x3d49), 0x00 }, + { IMX296_REG_8BIT(0x3d4a), 0x85 }, + { IMX296_REG_8BIT(0x3d4b), 0x00 }, + { IMX296_REG_8BIT(0x400e), 0x58 }, + { IMX296_REG_8BIT(0x4014), 0x1c }, + { IMX296_REG_8BIT(0x4041), 0x2a }, + { IMX296_REG_8BIT(0x40a2), 0x06 }, + { IMX296_REG_8BIT(0x40c1), 0xf6 }, + { IMX296_REG_8BIT(0x40c7), 0x0f }, + { IMX296_REG_8BIT(0x40c8), 0x00 }, + { IMX296_REG_8BIT(0x4174), 0x00 }, +}; + +static int imx296_setup(struct imx296 *sensor, struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt *format; + const struct v4l2_rect *crop; + unsigned int i; + int ret = 0; + + format = v4l2_subdev_get_pad_format(&sensor->subdev, state, 0); + crop = v4l2_subdev_get_pad_crop(&sensor->subdev, state, 0); + + for (i = 0; i < ARRAY_SIZE(imx296_init_table); ++i) + imx296_write(sensor, imx296_init_table[i].reg, + imx296_init_table[i].value, &ret); + + if (crop->width != IMX296_PIXEL_ARRAY_WIDTH || + crop->height != IMX296_PIXEL_ARRAY_HEIGHT) { + imx296_write(sensor, IMX296_FID0_ROI, + IMX296_FID0_ROIH1ON | IMX296_FID0_ROIV1ON, &ret); + imx296_write(sensor, IMX296_FID0_ROIPH1, crop->left, &ret); + imx296_write(sensor, IMX296_FID0_ROIPV1, crop->top, &ret); + imx296_write(sensor, IMX296_FID0_ROIWH1, crop->width, &ret); + imx296_write(sensor, IMX296_FID0_ROIWV1, crop->height, &ret); + } else { + imx296_write(sensor, IMX296_FID0_ROI, 0, &ret); + } + + imx296_write(sensor, IMX296_CTRL0D, + (crop->width != format->width ? + IMX296_CTRL0D_HADD_ON_BINNING : 0) | + (crop->height != format->height ? + IMX296_CTRL0D_WINMODE_FD_BINNING : 0), + &ret); + + /* + * HMAX and VMAX configure horizontal and vertical blanking by + * specifying the total line time and frame time respectively. The line + * time is specified in operational clock units (which appears to be the + * output of an internal PLL, fixed at 74.25 MHz regardless of the + * exernal clock frequency), while the frame time is specified as a + * number of lines. + * + * In the vertical direction the sensor outputs the following: + * + * - one line for the FS packet + * - two lines of embedded data (DT 0x12) + * - six null lines (DT 0x10) + * - four lines of vertical effective optical black (DT 0x37) + * - 8 to 1088 lines of active image data (RAW10, DT 0x2b) + * - one line for the FE packet + * - 16 or more lines of vertical blanking + */ + imx296_write(sensor, IMX296_HMAX, 1100, &ret); + imx296_write(sensor, IMX296_VMAX, + format->height + sensor->vblank->cur.val, &ret); + + for (i = 0; i < ARRAY_SIZE(sensor->clk_params->incksel); ++i) + imx296_write(sensor, IMX296_INCKSEL(i), + sensor->clk_params->incksel[i], &ret); + imx296_write(sensor, IMX296_GTTABLENUM, 0xc5, &ret); + imx296_write(sensor, IMX296_CTRL418C, sensor->clk_params->ctrl418c, + &ret); + + imx296_write(sensor, IMX296_GAINDLY, IMX296_GAINDLY_NONE, &ret); + imx296_write(sensor, IMX296_BLKLEVEL, 0x03c, &ret); + + return ret; +} + +static int imx296_stream_on(struct imx296 *sensor) +{ + int ret = 0; + + imx296_write(sensor, IMX296_CTRL00, 0, &ret); + usleep_range(2000, 5000); + imx296_write(sensor, IMX296_CTRL0A, 0, &ret); + + return ret; +} + +static int imx296_stream_off(struct imx296 *sensor) +{ + int ret = 0; + + imx296_write(sensor, IMX296_CTRL0A, IMX296_CTRL0A_XMSTA, &ret); + imx296_write(sensor, IMX296_CTRL00, IMX296_CTRL00_STANDBY, &ret); + + return ret; +} + +static int imx296_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx296 *sensor = to_imx296(sd); + struct v4l2_subdev_state *state; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + if (!enable) { + ret = imx296_stream_off(sensor); + + pm_runtime_mark_last_busy(sensor->dev); + pm_runtime_put_autosuspend(sensor->dev); + + sensor->streaming = false; + + goto unlock; + } + + ret = pm_runtime_resume_and_get(sensor->dev); + if (ret < 0) + goto unlock; + + ret = imx296_setup(sensor, state); + if (ret < 0) + goto err_pm; + + /* + * Set streaming to true to ensure __v4l2_ctrl_handler_setup() will set + * the controls. The flag is reset to false further down if an error + * occurs. + */ + sensor->streaming = true; + + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls); + if (ret < 0) + goto err_pm; + + ret = imx296_stream_on(sensor); + if (ret) + goto err_pm; + +unlock: + v4l2_subdev_unlock_state(state); + + return ret; + +err_pm: + /* + * In case of error, turn the power off synchronously as the device + * likely has no other chance to recover. + */ + pm_runtime_put_sync(sensor->dev); + sensor->streaming = false; + + goto unlock; +} + +static int imx296_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx296 *sensor = to_imx296(sd); + + if (code->index != 0) + return -EINVAL; + + code->code = sensor->mono ? MEDIA_BUS_FMT_Y10_1X10 + : MEDIA_BUS_FMT_SBGGR10_1X10; + + return 0; +} + +static int imx296_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_pad_format(sd, state, fse->pad); + + if (fse->index >= 2 || fse->code != format->code) + return -EINVAL; + + fse->min_width = IMX296_PIXEL_ARRAY_WIDTH / (fse->index + 1); + fse->max_width = fse->min_width; + fse->min_height = IMX296_PIXEL_ARRAY_HEIGHT / (fse->index + 1); + fse->max_height = fse->min_height; + + return 0; +} + +static int imx296_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + fmt->format = *v4l2_subdev_get_pad_format(sd, state, fmt->pad); + + return 0; +} + +static int imx296_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct imx296 *sensor = to_imx296(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + crop = v4l2_subdev_get_pad_crop(sd, state, fmt->pad); + format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); + + /* + * Binning is only allowed when cropping is disabled according to the + * documentation. This should be double-checked. + */ + if (crop->width == IMX296_PIXEL_ARRAY_WIDTH && + crop->height == IMX296_PIXEL_ARRAY_HEIGHT) { + unsigned int width; + unsigned int height; + unsigned int hratio; + unsigned int vratio; + + /* Clamp the width and height to avoid dividing by zero. */ + width = clamp_t(unsigned int, fmt->format.width, + crop->width / 2, crop->width); + height = clamp_t(unsigned int, fmt->format.height, + crop->height / 2, crop->height); + + hratio = DIV_ROUND_CLOSEST(crop->width, width); + vratio = DIV_ROUND_CLOSEST(crop->height, height); + + format->width = crop->width / hratio; + format->height = crop->height / vratio; + } else { + format->width = crop->width; + format->height = crop->height; + } + + format->code = sensor->mono ? MEDIA_BUS_FMT_Y10_1X10 + : MEDIA_BUS_FMT_SBGGR10_1X10; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_RAW; + format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + format->quantization = V4L2_QUANTIZATION_FULL_RANGE; + format->xfer_func = V4L2_XFER_FUNC_NONE; + + fmt->format = *format; + + return 0; +} + +static int imx296_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_get_pad_crop(sd, state, sel->pad); + break; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX296_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX296_PIXEL_ARRAY_HEIGHT; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int imx296_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + struct v4l2_rect rect; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* + * Clamp the crop rectangle boundaries and align them to a multiple of 4 + * pixels to satisfy hardware requirements. + */ + rect.left = clamp(ALIGN(sel->r.left, 4), 0, + IMX296_PIXEL_ARRAY_WIDTH - IMX296_FID0_ROIWH1_MIN); + rect.top = clamp(ALIGN(sel->r.top, 4), 0, + IMX296_PIXEL_ARRAY_HEIGHT - IMX296_FID0_ROIWV1_MIN); + rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 4), + IMX296_FID0_ROIWH1_MIN, IMX296_PIXEL_ARRAY_WIDTH); + rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 4), + IMX296_FID0_ROIWV1_MIN, IMX296_PIXEL_ARRAY_HEIGHT); + + rect.width = min_t(unsigned int, rect.width, + IMX296_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min_t(unsigned int, rect.height, + IMX296_PIXEL_ARRAY_HEIGHT - rect.top); + + crop = v4l2_subdev_get_pad_crop(sd, state, sel->pad); + + if (rect.width != crop->width || rect.height != crop->height) { + /* + * Reset the output image size if the crop rectangle size has + * been modified. + */ + format = v4l2_subdev_get_pad_format(sd, state, sel->pad); + format->width = rect.width; + format->height = rect.height; + } + + *crop = rect; + sel->r = rect; + + return 0; +} + +static int imx296_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_selection sel = { + .target = V4L2_SEL_TGT_CROP, + .r.width = IMX296_PIXEL_ARRAY_WIDTH, + .r.height = IMX296_PIXEL_ARRAY_HEIGHT, + }; + struct v4l2_subdev_format format = { + .format = { + .width = IMX296_PIXEL_ARRAY_WIDTH, + .height = IMX296_PIXEL_ARRAY_HEIGHT, + }, + }; + + imx296_set_selection(sd, state, &sel); + imx296_set_format(sd, state, &format); + + return 0; +} + +static const struct v4l2_subdev_video_ops imx296_subdev_video_ops = { + .s_stream = imx296_s_stream, +}; + +static const struct v4l2_subdev_pad_ops imx296_subdev_pad_ops = { + .enum_mbus_code = imx296_enum_mbus_code, + .enum_frame_size = imx296_enum_frame_size, + .get_fmt = imx296_get_format, + .set_fmt = imx296_set_format, + .get_selection = imx296_get_selection, + .set_selection = imx296_set_selection, + .init_cfg = imx296_init_cfg, +}; + +static const struct v4l2_subdev_ops imx296_subdev_ops = { + .video = &imx296_subdev_video_ops, + .pad = &imx296_subdev_pad_ops, +}; + +static int imx296_subdev_init(struct imx296 *sensor) +{ + struct i2c_client *client = to_i2c_client(sensor->dev); + int ret; + + v4l2_i2c_subdev_init(&sensor->subdev, client, &imx296_subdev_ops); + + ret = imx296_ctrls_init(sensor); + if (ret < 0) + return ret; + + sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad); + if (ret < 0) { + v4l2_ctrl_handler_free(&sensor->ctrls); + return ret; + } + + sensor->subdev.state_lock = sensor->subdev.ctrl_handler->lock; + + v4l2_subdev_init_finalize(&sensor->subdev); + + return ret; +} + +static void imx296_subdev_cleanup(struct imx296 *sensor) +{ + media_entity_cleanup(&sensor->subdev.entity); + v4l2_ctrl_handler_free(&sensor->ctrls); +} + +/* ----------------------------------------------------------------------------- + * Power management + */ + +static int __maybe_unused imx296_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct imx296 *sensor = to_imx296(subdev); + + return imx296_power_on(sensor); +} + +static int __maybe_unused imx296_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct imx296 *sensor = to_imx296(subdev); + + imx296_power_off(sensor); + + return 0; +} + +static const struct dev_pm_ops imx296_pm_ops = { + SET_RUNTIME_PM_OPS(imx296_runtime_suspend, imx296_runtime_resume, NULL) +}; + +/* ----------------------------------------------------------------------------- + * Probe & Remove + */ + +static int imx296_read_temperature(struct imx296 *sensor, int *temp) +{ + int tmdout; + int ret; + + ret = imx296_write(sensor, IMX296_TMDCTRL, IMX296_TMDCTRL_LATCH, NULL); + if (ret < 0) + return ret; + + tmdout = imx296_read(sensor, IMX296_TMDOUT) & IMX296_TMDOUT_MASK; + if (tmdout < 0) + return tmdout; + + /* T(°C) = 246.312 - 0.304 * TMDOUT */; + *temp = 246312 - 304 * tmdout; + + return imx296_write(sensor, IMX296_TMDCTRL, 0, NULL); +} + +static int imx296_identify_model(struct imx296 *sensor) +{ + unsigned int model; + int temp = 0; + int ret; + + model = (uintptr_t)of_device_get_match_data(sensor->dev); + if (model) { + dev_dbg(sensor->dev, + "sensor model auto-detection disabled, forcing 0x%04x\n", + model); + sensor->mono = model & IMX296_SENSOR_INFO_MONO; + return 0; + } + + /* + * While most registers can be read when the sensor is in standby, this + * is not the case of the sensor info register :-( + */ + ret = imx296_write(sensor, IMX296_CTRL00, 0, NULL); + if (ret < 0) { + dev_err(sensor->dev, + "failed to get sensor out of standby (%d)\n", ret); + return ret; + } + + ret = imx296_read(sensor, IMX296_SENSOR_INFO); + if (ret < 0) { + dev_err(sensor->dev, "failed to read sensor information (%d)\n", + ret); + goto done; + } + + model = (ret >> 6) & 0x1ff; + + switch (model) { + case 296: + sensor->mono = ret & IMX296_SENSOR_INFO_MONO; + break; + /* + * The IMX297 seems to share features with the IMX296, it may be + * possible to support it in the same driver. + */ + case 297: + default: + dev_err(sensor->dev, "invalid device model 0x%04x\n", ret); + ret = -ENODEV; + goto done; + } + + ret = imx296_read_temperature(sensor, &temp); + if (ret < 0) + goto done; + + dev_info(sensor->dev, "found IMX%u%s (%u.%uC)\n", model, + sensor->mono ? "LL" : "LQ", temp / 1000, (temp / 100) % 10); + +done: + imx296_write(sensor, IMX296_CTRL00, IMX296_CTRL00_STANDBY, NULL); + return ret; +} + +static const struct regmap_config imx296_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + + .wr_table = &(const struct regmap_access_table) { + .no_ranges = (const struct regmap_range[]) { + { + .range_min = IMX296_SENSOR_INFO & 0xffff, + .range_max = (IMX296_SENSOR_INFO & 0xffff) + 1, + }, + }, + .n_no_ranges = 1, + }, +}; + +static int imx296_probe(struct i2c_client *client) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + unsigned long clk_rate; + struct imx296 *sensor; + unsigned int i; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE\n"); + return -EIO; + } + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->dev = &client->dev; + + /* Acquire resources. */ + for (i = 0; i < ARRAY_SIZE(sensor->supplies); ++i) + sensor->supplies[i].supply = imx296_supply_names[i]; + + ret = devm_regulator_bulk_get(sensor->dev, ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret) { + dev_err_probe(sensor->dev, ret, "failed to get supplies\n"); + return ret; + } + + sensor->reset = devm_gpiod_get_optional(sensor->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset)) + return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset), + "failed to get reset GPIO\n"); + + sensor->clk = devm_clk_get(sensor->dev, "inck"); + if (IS_ERR(sensor->clk)) + return dev_err_probe(sensor->dev, PTR_ERR(sensor->clk), + "failed to get clock\n"); + + clk_rate = clk_get_rate(sensor->clk); + for (i = 0; i < ARRAY_SIZE(imx296_clk_params); ++i) { + if (clk_rate == imx296_clk_params[i].freq) { + sensor->clk_params = &imx296_clk_params[i]; + break; + } + } + + if (!sensor->clk_params) { + dev_err(sensor->dev, "unsupported clock rate %lu\n", clk_rate); + return -EINVAL; + } + + sensor->regmap = devm_regmap_init_i2c(client, &imx296_regmap_config); + if (IS_ERR(sensor->regmap)) + return PTR_ERR(sensor->regmap); + + /* + * Enable power management. The driver supports runtime PM, but needs to + * work when runtime PM is disabled in the kernel. To that end, power + * the sensor on manually here, identify it, and fully initialize it. + */ + ret = imx296_power_on(sensor); + if (ret < 0) + return ret; + + ret = imx296_identify_model(sensor); + if (ret < 0) + goto err_power; + + /* Initialize the V4L2 subdev. */ + ret = imx296_subdev_init(sensor); + if (ret < 0) + goto err_power; + + /* + * Enable runtime PM. As the device has been powered manually, mark it + * as active, and increase the usage count without resuming the device. + */ + pm_runtime_set_active(sensor->dev); + pm_runtime_get_noresume(sensor->dev); + pm_runtime_enable(sensor->dev); + + /* Register the V4L2 subdev. */ + ret = v4l2_async_register_subdev(&sensor->subdev); + if (ret < 0) + goto err_pm; + + /* + * Finally, enable autosuspend and decrease the usage count. The device + * will get suspended after the autosuspend delay, turning the power + * off. + */ + pm_runtime_set_autosuspend_delay(sensor->dev, 1000); + pm_runtime_use_autosuspend(sensor->dev); + pm_runtime_put_autosuspend(sensor->dev); + + return 0; + +err_pm: + pm_runtime_disable(sensor->dev); + pm_runtime_put_noidle(sensor->dev); + imx296_subdev_cleanup(sensor); +err_power: + imx296_power_off(sensor); + return ret; +} + +static void imx296_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct imx296 *sensor = to_imx296(subdev); + + v4l2_async_unregister_subdev(subdev); + + imx296_subdev_cleanup(sensor); + + /* + * Disable runtime PM. In case runtime PM is disabled in the kernel, + * make sure to turn power off manually. + */ + pm_runtime_disable(sensor->dev); + if (!pm_runtime_status_suspended(sensor->dev)) + imx296_power_off(sensor); + pm_runtime_set_suspended(sensor->dev); +} + +static const struct of_device_id imx296_of_match[] = { + { .compatible = "sony,imx296", .data = NULL }, + { .compatible = "sony,imx296ll", .data = (void *)IMX296_SENSOR_INFO_IMX296LL }, + { .compatible = "sony,imx296lq", .data = (void *)IMX296_SENSOR_INFO_IMX296LQ }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, imx296_of_match); + +static struct i2c_driver imx296_i2c_driver = { + .driver = { + .of_match_table = imx296_of_match, + .name = "imx296", + .pm = &imx296_pm_ops + }, + .probe_new = imx296_probe, + .remove = imx296_remove, +}; + +module_i2c_driver(imx296_i2c_driver); + +MODULE_DESCRIPTION("Sony IMX296 Camera driver"); +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/imx415.c b/drivers/media/i2c/imx415.c new file mode 100644 index 000000000000..d90392df98c7 --- /dev/null +++ b/drivers/media/i2c/imx415.c @@ -0,0 +1,1300 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Sony IMX415 CMOS Image Sensor. + * + * Copyright (C) 2023 WolfVision GmbH. + */ + +#include <linux/clk.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define IMX415_PIXEL_ARRAY_TOP 0 +#define IMX415_PIXEL_ARRAY_LEFT 0 +#define IMX415_PIXEL_ARRAY_WIDTH 3864 +#define IMX415_PIXEL_ARRAY_HEIGHT 2192 +#define IMX415_PIXEL_ARRAY_VBLANK 58 + +#define IMX415_NUM_CLK_PARAM_REGS 11 + +#define IMX415_REG_8BIT(n) ((1 << 16) | (n)) +#define IMX415_REG_16BIT(n) ((2 << 16) | (n)) +#define IMX415_REG_24BIT(n) ((3 << 16) | (n)) +#define IMX415_REG_SIZE_SHIFT 16 +#define IMX415_REG_ADDR_MASK 0xffff + +#define IMX415_MODE IMX415_REG_8BIT(0x3000) +#define IMX415_MODE_OPERATING (0) +#define IMX415_MODE_STANDBY BIT(0) +#define IMX415_REGHOLD IMX415_REG_8BIT(0x3001) +#define IMX415_REGHOLD_INVALID (0) +#define IMX415_REGHOLD_VALID BIT(0) +#define IMX415_XMSTA IMX415_REG_8BIT(0x3002) +#define IMX415_XMSTA_START (0) +#define IMX415_XMSTA_STOP BIT(0) +#define IMX415_BCWAIT_TIME IMX415_REG_16BIT(0x3008) +#define IMX415_CPWAIT_TIME IMX415_REG_16BIT(0x300A) +#define IMX415_WINMODE IMX415_REG_8BIT(0x301C) +#define IMX415_ADDMODE IMX415_REG_8BIT(0x3022) +#define IMX415_REVERSE IMX415_REG_8BIT(0x3030) +#define IMX415_HREVERSE_SHIFT (0) +#define IMX415_VREVERSE_SHIFT BIT(0) +#define IMX415_ADBIT IMX415_REG_8BIT(0x3031) +#define IMX415_MDBIT IMX415_REG_8BIT(0x3032) +#define IMX415_SYS_MODE IMX415_REG_8BIT(0x3033) +#define IMX415_OUTSEL IMX415_REG_8BIT(0x30C0) +#define IMX415_DRV IMX415_REG_8BIT(0x30C1) +#define IMX415_VMAX IMX415_REG_24BIT(0x3024) +#define IMX415_HMAX IMX415_REG_16BIT(0x3028) +#define IMX415_SHR0 IMX415_REG_24BIT(0x3050) +#define IMX415_GAIN_PCG_0 IMX415_REG_16BIT(0x3090) +#define IMX415_AGAIN_MIN 0 +#define IMX415_AGAIN_MAX 100 +#define IMX415_AGAIN_STEP 1 +#define IMX415_BLKLEVEL IMX415_REG_16BIT(0x30E2) +#define IMX415_BLKLEVEL_DEFAULT 50 +#define IMX415_TPG_EN_DUOUT IMX415_REG_8BIT(0x30E4) +#define IMX415_TPG_PATSEL_DUOUT IMX415_REG_8BIT(0x30E6) +#define IMX415_TPG_COLORWIDTH IMX415_REG_8BIT(0x30E8) +#define IMX415_TESTCLKEN_MIPI IMX415_REG_8BIT(0x3110) +#define IMX415_INCKSEL1 IMX415_REG_8BIT(0x3115) +#define IMX415_INCKSEL2 IMX415_REG_8BIT(0x3116) +#define IMX415_INCKSEL3 IMX415_REG_16BIT(0x3118) +#define IMX415_INCKSEL4 IMX415_REG_16BIT(0x311A) +#define IMX415_INCKSEL5 IMX415_REG_8BIT(0x311E) +#define IMX415_DIG_CLP_MODE IMX415_REG_8BIT(0x32C8) +#define IMX415_WRJ_OPEN IMX415_REG_8BIT(0x3390) +#define IMX415_SENSOR_INFO IMX415_REG_16BIT(0x3F12) +#define IMX415_SENSOR_INFO_MASK 0xFFF +#define IMX415_CHIP_ID 0x514 +#define IMX415_LANEMODE IMX415_REG_16BIT(0x4001) +#define IMX415_LANEMODE_2 1 +#define IMX415_LANEMODE_4 3 +#define IMX415_TXCLKESC_FREQ IMX415_REG_16BIT(0x4004) +#define IMX415_INCKSEL6 IMX415_REG_8BIT(0x400C) +#define IMX415_TCLKPOST IMX415_REG_16BIT(0x4018) +#define IMX415_TCLKPREPARE IMX415_REG_16BIT(0x401A) +#define IMX415_TCLKTRAIL IMX415_REG_16BIT(0x401C) +#define IMX415_TCLKZERO IMX415_REG_16BIT(0x401E) +#define IMX415_THSPREPARE IMX415_REG_16BIT(0x4020) +#define IMX415_THSZERO IMX415_REG_16BIT(0x4022) +#define IMX415_THSTRAIL IMX415_REG_16BIT(0x4024) +#define IMX415_THSEXIT IMX415_REG_16BIT(0x4026) +#define IMX415_TLPX IMX415_REG_16BIT(0x4028) +#define IMX415_INCKSEL7 IMX415_REG_8BIT(0x4074) + +struct imx415_reg { + u32 address; + u32 val; +}; + +static const char *const imx415_supply_names[] = { + "dvdd", + "ovdd", + "avdd", +}; + +/* + * The IMX415 data sheet uses lane rates but v4l2 uses link frequency to + * describe MIPI CSI-2 speed. This driver uses lane rates wherever possible + * and converts them to link frequencies by a factor of two when needed. + */ +static const s64 link_freq_menu_items[] = { + 594000000 / 2, 720000000 / 2, 891000000 / 2, + 1440000000 / 2, 1485000000 / 2, +}; + +struct imx415_clk_params { + u64 lane_rate; + u64 inck; + struct imx415_reg regs[IMX415_NUM_CLK_PARAM_REGS]; +}; + +/* INCK Settings - includes all lane rate and INCK dependent registers */ +static const struct imx415_clk_params imx415_clk_params[] = { + { + .lane_rate = 594000000, + .inck = 27000000, + .regs[0] = { IMX415_BCWAIT_TIME, 0x05D }, + .regs[1] = { IMX415_CPWAIT_TIME, 0x042 }, + .regs[2] = { IMX415_SYS_MODE, 0x7 }, + .regs[3] = { IMX415_INCKSEL1, 0x00 }, + .regs[4] = { IMX415_INCKSEL2, 0x23 }, + .regs[5] = { IMX415_INCKSEL3, 0x084 }, + .regs[6] = { IMX415_INCKSEL4, 0x0E7 }, + .regs[7] = { IMX415_INCKSEL5, 0x23 }, + .regs[8] = { IMX415_INCKSEL6, 0x0 }, + .regs[9] = { IMX415_INCKSEL7, 0x1 }, + .regs[10] = { IMX415_TXCLKESC_FREQ, 0x06C0 }, + }, + { + .lane_rate = 720000000, + .inck = 24000000, + .regs[0] = { IMX415_BCWAIT_TIME, 0x054 }, + .regs[1] = { IMX415_CPWAIT_TIME, 0x03B }, + .regs[2] = { IMX415_SYS_MODE, 0x9 }, + .regs[3] = { IMX415_INCKSEL1, 0x00 }, + .regs[4] = { IMX415_INCKSEL2, 0x23 }, + .regs[5] = { IMX415_INCKSEL3, 0x0B4 }, + .regs[6] = { IMX415_INCKSEL4, 0x0FC }, + .regs[7] = { IMX415_INCKSEL5, 0x23 }, + .regs[8] = { IMX415_INCKSEL6, 0x0 }, + .regs[9] = { IMX415_INCKSEL7, 0x1 }, + .regs[10] = { IMX415_TXCLKESC_FREQ, 0x0600 }, + }, + { + .lane_rate = 891000000, + .inck = 27000000, + .regs[0] = { IMX415_BCWAIT_TIME, 0x05D }, + .regs[1] = { IMX415_CPWAIT_TIME, 0x042 }, + .regs[2] = { IMX415_SYS_MODE, 0x5 }, + .regs[3] = { IMX415_INCKSEL1, 0x00 }, + .regs[4] = { IMX415_INCKSEL2, 0x23 }, + .regs[5] = { IMX415_INCKSEL3, 0x0C6 }, + .regs[6] = { IMX415_INCKSEL4, 0x0E7 }, + .regs[7] = { IMX415_INCKSEL5, 0x23 }, + .regs[8] = { IMX415_INCKSEL6, 0x0 }, + .regs[9] = { IMX415_INCKSEL7, 0x1 }, + .regs[10] = { IMX415_TXCLKESC_FREQ, 0x06C0 }, + }, + { + .lane_rate = 1440000000, + .inck = 24000000, + .regs[0] = { IMX415_BCWAIT_TIME, 0x054 }, + .regs[1] = { IMX415_CPWAIT_TIME, 0x03B }, + .regs[2] = { IMX415_SYS_MODE, 0x8 }, + .regs[3] = { IMX415_INCKSEL1, 0x00 }, + .regs[4] = { IMX415_INCKSEL2, 0x23 }, + .regs[5] = { IMX415_INCKSEL3, 0x0B4 }, + .regs[6] = { IMX415_INCKSEL4, 0x0FC }, + .regs[7] = { IMX415_INCKSEL5, 0x23 }, + .regs[8] = { IMX415_INCKSEL6, 0x1 }, + .regs[9] = { IMX415_INCKSEL7, 0x0 }, + .regs[10] = { IMX415_TXCLKESC_FREQ, 0x0600 }, + }, + { + .lane_rate = 1485000000, + .inck = 27000000, + .regs[0] = { IMX415_BCWAIT_TIME, 0x05D }, + .regs[1] = { IMX415_CPWAIT_TIME, 0x042 }, + .regs[2] = { IMX415_SYS_MODE, 0x8 }, + .regs[3] = { IMX415_INCKSEL1, 0x00 }, + .regs[4] = { IMX415_INCKSEL2, 0x23 }, + .regs[5] = { IMX415_INCKSEL3, 0x0A5 }, + .regs[6] = { IMX415_INCKSEL4, 0x0E7 }, + .regs[7] = { IMX415_INCKSEL5, 0x23 }, + .regs[8] = { IMX415_INCKSEL6, 0x1 }, + .regs[9] = { IMX415_INCKSEL7, 0x0 }, + .regs[10] = { IMX415_TXCLKESC_FREQ, 0x06C0 }, + }, +}; + +/* all-pixel 2-lane 720 Mbps 15.74 Hz mode */ +static const struct imx415_reg imx415_mode_2_720[] = { + { IMX415_VMAX, 0x08CA }, + { IMX415_HMAX, 0x07F0 }, + { IMX415_LANEMODE, IMX415_LANEMODE_2 }, + { IMX415_TCLKPOST, 0x006F }, + { IMX415_TCLKPREPARE, 0x002F }, + { IMX415_TCLKTRAIL, 0x002F }, + { IMX415_TCLKZERO, 0x00BF }, + { IMX415_THSPREPARE, 0x002F }, + { IMX415_THSZERO, 0x0057 }, + { IMX415_THSTRAIL, 0x002F }, + { IMX415_THSEXIT, 0x004F }, + { IMX415_TLPX, 0x0027 }, +}; + +/* all-pixel 2-lane 1440 Mbps 30.01 Hz mode */ +static const struct imx415_reg imx415_mode_2_1440[] = { + { IMX415_VMAX, 0x08CA }, + { IMX415_HMAX, 0x042A }, + { IMX415_LANEMODE, IMX415_LANEMODE_2 }, + { IMX415_TCLKPOST, 0x009F }, + { IMX415_TCLKPREPARE, 0x0057 }, + { IMX415_TCLKTRAIL, 0x0057 }, + { IMX415_TCLKZERO, 0x0187 }, + { IMX415_THSPREPARE, 0x005F }, + { IMX415_THSZERO, 0x00A7 }, + { IMX415_THSTRAIL, 0x005F }, + { IMX415_THSEXIT, 0x0097 }, + { IMX415_TLPX, 0x004F }, +}; + +/* all-pixel 4-lane 891 Mbps 30 Hz mode */ +static const struct imx415_reg imx415_mode_4_891[] = { + { IMX415_VMAX, 0x08CA }, + { IMX415_HMAX, 0x044C }, + { IMX415_LANEMODE, IMX415_LANEMODE_4 }, + { IMX415_TCLKPOST, 0x007F }, + { IMX415_TCLKPREPARE, 0x0037 }, + { IMX415_TCLKTRAIL, 0x0037 }, + { IMX415_TCLKZERO, 0x00F7 }, + { IMX415_THSPREPARE, 0x003F }, + { IMX415_THSZERO, 0x006F }, + { IMX415_THSTRAIL, 0x003F }, + { IMX415_THSEXIT, 0x005F }, + { IMX415_TLPX, 0x002F }, +}; + +struct imx415_mode_reg_list { + u32 num_of_regs; + const struct imx415_reg *regs; +}; + +/* + * Mode : number of lanes, lane rate and frame rate dependent settings + * + * pixel_rate and hmax_pix are needed to calculate hblank for the v4l2 ctrl + * interface. These values can not be found in the data sheet and should be + * treated as virtual values. Use following table when adding new modes. + * + * lane_rate lanes fps hmax_pix pixel_rate + * + * 594 2 10.000 4400 99000000 + * 891 2 15.000 4400 148500000 + * 720 2 15.748 4064 144000000 + * 1782 2 30.000 4400 297000000 + * 2079 2 30.000 4400 297000000 + * 1440 2 30.019 4510 304615385 + * + * 594 4 20.000 5500 247500000 + * 594 4 25.000 4400 247500000 + * 720 4 25.000 4400 247500000 + * 720 4 30.019 4510 304615385 + * 891 4 30.000 4400 297000000 + * 1440 4 30.019 4510 304615385 + * 1440 4 60.038 4510 609230769 + * 1485 4 60.000 4400 594000000 + * 1782 4 60.000 4400 594000000 + * 2079 4 60.000 4400 594000000 + * 2376 4 90.164 4392 891000000 + */ +struct imx415_mode { + u64 lane_rate; + u32 lanes; + u32 hmax_pix; + u64 pixel_rate; + struct imx415_mode_reg_list reg_list; +}; + +/* mode configs */ +static const struct imx415_mode supported_modes[] = { + { + .lane_rate = 720000000, + .lanes = 2, + .hmax_pix = 4064, + .pixel_rate = 144000000, + .reg_list = { + .num_of_regs = ARRAY_SIZE(imx415_mode_2_720), + .regs = imx415_mode_2_720, + }, + }, + { + .lane_rate = 1440000000, + .lanes = 2, + .hmax_pix = 4510, + .pixel_rate = 304615385, + .reg_list = { + .num_of_regs = ARRAY_SIZE(imx415_mode_2_1440), + .regs = imx415_mode_2_1440, + }, + }, + { + .lane_rate = 891000000, + .lanes = 4, + .hmax_pix = 4400, + .pixel_rate = 297000000, + .reg_list = { + .num_of_regs = ARRAY_SIZE(imx415_mode_4_891), + .regs = imx415_mode_4_891, + }, + }, +}; + +static const struct regmap_config imx415_regmap_config = { + .reg_bits = 16, + .val_bits = 8, +}; + +static const char *const imx415_test_pattern_menu[] = { + "disabled", + "solid black", + "solid white", + "solid dark gray", + "solid light gray", + "stripes light/dark grey", + "stripes dark/light grey", + "stripes black/dark grey", + "stripes dark grey/black", + "stripes black/white", + "stripes white/black", + "horizontal color bar", + "vertical color bar", +}; + +struct imx415 { + struct device *dev; + struct clk *clk; + struct regulator_bulk_data supplies[ARRAY_SIZE(imx415_supply_names)]; + struct gpio_desc *reset; + struct regmap *regmap; + + const struct imx415_clk_params *clk_params; + + bool streaming; + + struct v4l2_subdev subdev; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + + unsigned int cur_mode; + unsigned int num_data_lanes; +}; + +/* + * This table includes fixed register settings and a bunch of undocumented + * registers that have to be set to another value than default. + */ +static const struct imx415_reg imx415_init_table[] = { + /* use all-pixel readout mode, no flip */ + { IMX415_WINMODE, 0x00 }, + { IMX415_ADDMODE, 0x00 }, + { IMX415_REVERSE, 0x00 }, + /* use RAW 10-bit mode */ + { IMX415_ADBIT, 0x00 }, + { IMX415_MDBIT, 0x00 }, + /* output VSYNC on XVS and low on XHS */ + { IMX415_OUTSEL, 0x22 }, + { IMX415_DRV, 0x00 }, + + /* SONY magic registers */ + { IMX415_REG_8BIT(0x32D4), 0x21 }, + { IMX415_REG_8BIT(0x32EC), 0xA1 }, + { IMX415_REG_8BIT(0x3452), 0x7F }, + { IMX415_REG_8BIT(0x3453), 0x03 }, + { IMX415_REG_8BIT(0x358A), 0x04 }, + { IMX415_REG_8BIT(0x35A1), 0x02 }, + { IMX415_REG_8BIT(0x36BC), 0x0C }, + { IMX415_REG_8BIT(0x36CC), 0x53 }, + { IMX415_REG_8BIT(0x36CD), 0x00 }, + { IMX415_REG_8BIT(0x36CE), 0x3C }, + { IMX415_REG_8BIT(0x36D0), 0x8C }, + { IMX415_REG_8BIT(0x36D1), 0x00 }, + { IMX415_REG_8BIT(0x36D2), 0x71 }, + { IMX415_REG_8BIT(0x36D4), 0x3C }, + { IMX415_REG_8BIT(0x36D6), 0x53 }, + { IMX415_REG_8BIT(0x36D7), 0x00 }, + { IMX415_REG_8BIT(0x36D8), 0x71 }, + { IMX415_REG_8BIT(0x36DA), 0x8C }, + { IMX415_REG_8BIT(0x36DB), 0x00 }, + { IMX415_REG_8BIT(0x3724), 0x02 }, + { IMX415_REG_8BIT(0x3726), 0x02 }, + { IMX415_REG_8BIT(0x3732), 0x02 }, + { IMX415_REG_8BIT(0x3734), 0x03 }, + { IMX415_REG_8BIT(0x3736), 0x03 }, + { IMX415_REG_8BIT(0x3742), 0x03 }, + { IMX415_REG_8BIT(0x3862), 0xE0 }, + { IMX415_REG_8BIT(0x38CC), 0x30 }, + { IMX415_REG_8BIT(0x38CD), 0x2F }, + { IMX415_REG_8BIT(0x395C), 0x0C }, + { IMX415_REG_8BIT(0x3A42), 0xD1 }, + { IMX415_REG_8BIT(0x3A4C), 0x77 }, + { IMX415_REG_8BIT(0x3AE0), 0x02 }, + { IMX415_REG_8BIT(0x3AEC), 0x0C }, + { IMX415_REG_8BIT(0x3B00), 0x2E }, + { IMX415_REG_8BIT(0x3B06), 0x29 }, + { IMX415_REG_8BIT(0x3B98), 0x25 }, + { IMX415_REG_8BIT(0x3B99), 0x21 }, + { IMX415_REG_8BIT(0x3B9B), 0x13 }, + { IMX415_REG_8BIT(0x3B9C), 0x13 }, + { IMX415_REG_8BIT(0x3B9D), 0x13 }, + { IMX415_REG_8BIT(0x3B9E), 0x13 }, + { IMX415_REG_8BIT(0x3BA1), 0x00 }, + { IMX415_REG_8BIT(0x3BA2), 0x06 }, + { IMX415_REG_8BIT(0x3BA3), 0x0B }, + { IMX415_REG_8BIT(0x3BA4), 0x10 }, + { IMX415_REG_8BIT(0x3BA5), 0x14 }, + { IMX415_REG_8BIT(0x3BA6), 0x18 }, + { IMX415_REG_8BIT(0x3BA7), 0x1A }, + { IMX415_REG_8BIT(0x3BA8), 0x1A }, + { IMX415_REG_8BIT(0x3BA9), 0x1A }, + { IMX415_REG_8BIT(0x3BAC), 0xED }, + { IMX415_REG_8BIT(0x3BAD), 0x01 }, + { IMX415_REG_8BIT(0x3BAE), 0xF6 }, + { IMX415_REG_8BIT(0x3BAF), 0x02 }, + { IMX415_REG_8BIT(0x3BB0), 0xA2 }, + { IMX415_REG_8BIT(0x3BB1), 0x03 }, + { IMX415_REG_8BIT(0x3BB2), 0xE0 }, + { IMX415_REG_8BIT(0x3BB3), 0x03 }, + { IMX415_REG_8BIT(0x3BB4), 0xE0 }, + { IMX415_REG_8BIT(0x3BB5), 0x03 }, + { IMX415_REG_8BIT(0x3BB6), 0xE0 }, + { IMX415_REG_8BIT(0x3BB7), 0x03 }, + { IMX415_REG_8BIT(0x3BB8), 0xE0 }, + { IMX415_REG_8BIT(0x3BBA), 0xE0 }, + { IMX415_REG_8BIT(0x3BBC), 0xDA }, + { IMX415_REG_8BIT(0x3BBE), 0x88 }, + { IMX415_REG_8BIT(0x3BC0), 0x44 }, + { IMX415_REG_8BIT(0x3BC2), 0x7B }, + { IMX415_REG_8BIT(0x3BC4), 0xA2 }, + { IMX415_REG_8BIT(0x3BC8), 0xBD }, + { IMX415_REG_8BIT(0x3BCA), 0xBD }, +}; + +static inline struct imx415 *to_imx415(struct v4l2_subdev *sd) +{ + return container_of(sd, struct imx415, subdev); +} + +static int imx415_read(struct imx415 *sensor, u32 addr) +{ + u8 data[3] = { 0 }; + int ret; + + ret = regmap_raw_read(sensor->regmap, addr & IMX415_REG_ADDR_MASK, data, + (addr >> IMX415_REG_SIZE_SHIFT) & 3); + if (ret < 0) + return ret; + + return (data[2] << 16) | (data[1] << 8) | data[0]; +} + +static int imx415_write(struct imx415 *sensor, u32 addr, u32 value) +{ + u8 data[3] = { value & 0xff, (value >> 8) & 0xff, value >> 16 }; + int ret; + + ret = regmap_raw_write(sensor->regmap, addr & IMX415_REG_ADDR_MASK, + data, (addr >> IMX415_REG_SIZE_SHIFT) & 3); + if (ret < 0) + dev_err_ratelimited(sensor->dev, + "%u-bit write to 0x%04x failed: %d\n", + ((addr >> IMX415_REG_SIZE_SHIFT) & 3) * 8, + addr & IMX415_REG_ADDR_MASK, ret); + + return 0; +} + +static int imx415_set_testpattern(struct imx415 *sensor, int val) +{ + int ret; + + if (val) { + ret = imx415_write(sensor, IMX415_BLKLEVEL, 0x00); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_TPG_EN_DUOUT, 0x01); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_TPG_PATSEL_DUOUT, val - 1); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_TPG_COLORWIDTH, 0x01); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_TESTCLKEN_MIPI, 0x20); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_DIG_CLP_MODE, 0x00); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_WRJ_OPEN, 0x00); + } else { + ret = imx415_write(sensor, IMX415_BLKLEVEL, + IMX415_BLKLEVEL_DEFAULT); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_TPG_EN_DUOUT, 0x00); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_TESTCLKEN_MIPI, 0x00); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_DIG_CLP_MODE, 0x01); + if (ret) + return ret; + ret = imx415_write(sensor, IMX415_WRJ_OPEN, 0x01); + } + return 0; +} + +static int imx415_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx415 *sensor = container_of(ctrl->handler, struct imx415, + ctrls); + const struct v4l2_mbus_framefmt *format; + struct v4l2_subdev_state *state; + unsigned int vmax; + unsigned int flip; + + if (!sensor->streaming) + return 0; + + state = v4l2_subdev_get_locked_active_state(&sensor->subdev); + format = v4l2_subdev_get_pad_format(&sensor->subdev, state, 0); + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + /* clamp the exposure value to VMAX. */ + vmax = format->height + sensor->vblank->cur.val; + ctrl->val = min_t(int, ctrl->val, vmax); + return imx415_write(sensor, IMX415_SHR0, vmax - ctrl->val); + + case V4L2_CID_ANALOGUE_GAIN: + /* analogue gain in 0.3 dB step size */ + return imx415_write(sensor, IMX415_GAIN_PCG_0, ctrl->val); + + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + flip = (sensor->hflip->val << IMX415_HREVERSE_SHIFT) | + (sensor->vflip->val << IMX415_VREVERSE_SHIFT); + return imx415_write(sensor, IMX415_REVERSE, flip); + + case V4L2_CID_TEST_PATTERN: + return imx415_set_testpattern(sensor, ctrl->val); + + default: + return -EINVAL; + } +} + +static const struct v4l2_ctrl_ops imx415_ctrl_ops = { + .s_ctrl = imx415_s_ctrl, +}; + +static int imx415_ctrls_init(struct imx415 *sensor) +{ + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl *ctrl; + u64 pixel_rate = supported_modes[sensor->cur_mode].pixel_rate; + u64 lane_rate = supported_modes[sensor->cur_mode].lane_rate; + u32 exposure_max = IMX415_PIXEL_ARRAY_HEIGHT + + IMX415_PIXEL_ARRAY_VBLANK - 8; + u32 hblank; + unsigned int i; + int ret; + + ret = v4l2_fwnode_device_parse(sensor->dev, &props); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(&sensor->ctrls, 10); + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); ++i) { + if (lane_rate == link_freq_menu_items[i] * 2) + break; + } + if (i == ARRAY_SIZE(link_freq_menu_items)) { + return dev_err_probe(sensor->dev, -EINVAL, + "lane rate %llu not supported\n", + lane_rate); + } + + ctrl = v4l2_ctrl_new_int_menu(&sensor->ctrls, &imx415_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, i, + link_freq_menu_items); + + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, V4L2_CID_EXPOSURE, + 4, exposure_max, 1, exposure_max); + + v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, IMX415_AGAIN_MIN, + IMX415_AGAIN_MAX, IMX415_AGAIN_STEP, + IMX415_AGAIN_MIN); + + hblank = supported_modes[sensor->cur_mode].hmax_pix - + IMX415_PIXEL_ARRAY_WIDTH; + ctrl = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, 1, hblank); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, + V4L2_CID_VBLANK, + IMX415_PIXEL_ARRAY_VBLANK, + IMX415_PIXEL_ARRAY_VBLANK, 1, + IMX415_PIXEL_ARRAY_VBLANK); + if (sensor->vblank) + sensor->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * The pixel rate used here is a virtual value and can be used for + * calculating the frame rate together with hblank. It may not + * necessarily be the physically correct pixel clock. + */ + v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE, pixel_rate, + pixel_rate, 1, pixel_rate); + + sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std_menu_items(&sensor->ctrls, &imx415_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx415_test_pattern_menu) - 1, + 0, 0, imx415_test_pattern_menu); + + v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &imx415_ctrl_ops, + &props); + + if (sensor->ctrls.error) { + dev_err_probe(sensor->dev, sensor->ctrls.error, + "failed to add controls\n"); + v4l2_ctrl_handler_free(&sensor->ctrls); + return sensor->ctrls.error; + } + sensor->subdev.ctrl_handler = &sensor->ctrls; + + return 0; +} + +static int imx415_set_mode(struct imx415 *sensor, int mode) +{ + const struct imx415_reg *reg; + unsigned int i; + int ret = 0; + + if (mode >= ARRAY_SIZE(supported_modes)) { + dev_err(sensor->dev, "Mode %d not supported\n", mode); + return -EINVAL; + } + + for (i = 0; i < supported_modes[mode].reg_list.num_of_regs; ++i) { + reg = &supported_modes[mode].reg_list.regs[i]; + ret = imx415_write(sensor, reg->address, reg->val); + if (ret) + return ret; + } + + for (i = 0; i < IMX415_NUM_CLK_PARAM_REGS; ++i) { + reg = &sensor->clk_params->regs[i]; + ret = imx415_write(sensor, reg->address, reg->val); + if (ret) + return ret; + } + + return 0; +} + +static int imx415_setup(struct imx415 *sensor, struct v4l2_subdev_state *state) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(imx415_init_table); ++i) { + ret = imx415_write(sensor, imx415_init_table[i].address, + imx415_init_table[i].val); + if (ret) + return ret; + } + + return imx415_set_mode(sensor, sensor->cur_mode); +} + +static int imx415_wakeup(struct imx415 *sensor) +{ + int ret; + + ret = imx415_write(sensor, IMX415_MODE, IMX415_MODE_OPERATING); + if (ret) + return ret; + + /* + * According to the datasheet we have to wait at least 63 us after + * leaving standby mode. But this doesn't work even after 30 ms. + * So probably this should be 63 ms and therefore we wait for 80 ms. + */ + msleep(80); + + return 0; +} + +static int imx415_stream_on(struct imx415 *sensor) +{ + int ret; + + ret = imx415_wakeup(sensor); + if (ret) + return ret; + + return imx415_write(sensor, IMX415_XMSTA, IMX415_XMSTA_START); +} + +static int imx415_stream_off(struct imx415 *sensor) +{ + int ret; + + ret = imx415_write(sensor, IMX415_XMSTA, IMX415_XMSTA_STOP); + if (ret) + return ret; + + return imx415_write(sensor, IMX415_MODE, IMX415_MODE_STANDBY); +} + +static int imx415_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx415 *sensor = to_imx415(sd); + struct v4l2_subdev_state *state; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + if (!enable) { + ret = imx415_stream_off(sensor); + + pm_runtime_mark_last_busy(sensor->dev); + pm_runtime_put_autosuspend(sensor->dev); + + sensor->streaming = false; + + goto unlock; + } + + ret = pm_runtime_resume_and_get(sensor->dev); + if (ret < 0) + goto unlock; + + ret = imx415_setup(sensor, state); + if (ret) + goto err_pm; + + /* + * Set streaming to true to ensure __v4l2_ctrl_handler_setup() will set + * the controls. The flag is reset to false further down if an error + * occurs. + */ + sensor->streaming = true; + + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls); + if (ret < 0) + goto err_pm; + + ret = imx415_stream_on(sensor); + if (ret) + goto err_pm; + + ret = 0; + +unlock: + v4l2_subdev_unlock_state(state); + + return ret; + +err_pm: + /* + * In case of error, turn the power off synchronously as the device + * likely has no other chance to recover. + */ + pm_runtime_put_sync(sensor->dev); + sensor->streaming = false; + + goto unlock; +} + +static int imx415_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGBRG10_1X10; + + return 0; +} + +static int imx415_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_pad_format(sd, state, fse->pad); + + if (fse->index > 0 || fse->code != format->code) + return -EINVAL; + + fse->min_width = IMX415_PIXEL_ARRAY_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = IMX415_PIXEL_ARRAY_HEIGHT; + fse->max_height = fse->min_height; + return 0; +} + +static int imx415_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + fmt->format = *v4l2_subdev_get_pad_format(sd, state, fmt->pad); + + return 0; +} + +static int imx415_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); + + format->width = fmt->format.width; + format->height = fmt->format.height; + format->code = MEDIA_BUS_FMT_SGBRG10_1X10; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_RAW; + format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + format->quantization = V4L2_QUANTIZATION_DEFAULT; + format->xfer_func = V4L2_XFER_FUNC_NONE; + + fmt->format = *format; + return 0; +} + +static int imx415_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: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = IMX415_PIXEL_ARRAY_TOP; + sel->r.left = IMX415_PIXEL_ARRAY_LEFT; + sel->r.width = IMX415_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX415_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +static int imx415_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_format format = { + .format = { + .width = IMX415_PIXEL_ARRAY_WIDTH, + .height = IMX415_PIXEL_ARRAY_HEIGHT, + }, + }; + + imx415_set_format(sd, state, &format); + + return 0; +} + +static const struct v4l2_subdev_video_ops imx415_subdev_video_ops = { + .s_stream = imx415_s_stream, +}; + +static const struct v4l2_subdev_pad_ops imx415_subdev_pad_ops = { + .enum_mbus_code = imx415_enum_mbus_code, + .enum_frame_size = imx415_enum_frame_size, + .get_fmt = imx415_get_format, + .set_fmt = imx415_set_format, + .get_selection = imx415_get_selection, + .init_cfg = imx415_init_cfg, +}; + +static const struct v4l2_subdev_ops imx415_subdev_ops = { + .video = &imx415_subdev_video_ops, + .pad = &imx415_subdev_pad_ops, +}; + +static int imx415_subdev_init(struct imx415 *sensor) +{ + struct i2c_client *client = to_i2c_client(sensor->dev); + int ret; + + v4l2_i2c_subdev_init(&sensor->subdev, client, &imx415_subdev_ops); + + ret = imx415_ctrls_init(sensor); + if (ret) + return ret; + + sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad); + if (ret < 0) { + v4l2_ctrl_handler_free(&sensor->ctrls); + return ret; + } + + sensor->subdev.state_lock = sensor->subdev.ctrl_handler->lock; + v4l2_subdev_init_finalize(&sensor->subdev); + + return 0; +} + +static void imx415_subdev_cleanup(struct imx415 *sensor) +{ + media_entity_cleanup(&sensor->subdev.entity); + v4l2_ctrl_handler_free(&sensor->ctrls); +} + +static int imx415_power_on(struct imx415 *sensor) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret < 0) + return ret; + + gpiod_set_value_cansleep(sensor->reset, 0); + + udelay(1); + + ret = clk_prepare_enable(sensor->clk); + if (ret < 0) + goto err_reset; + + /* + * Data sheet states that 20 us are required before communication start, + * but this doesn't work in all cases. Use 100 us to be on the safe + * side. + */ + usleep_range(100, 200); + + return 0; + +err_reset: + gpiod_set_value_cansleep(sensor->reset, 1); + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); + return ret; +} + +static void imx415_power_off(struct imx415 *sensor) +{ + clk_disable_unprepare(sensor->clk); + gpiod_set_value_cansleep(sensor->reset, 1); + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); +} + +static int imx415_identify_model(struct imx415 *sensor) +{ + int model, ret; + + /* + * While most registers can be read when the sensor is in standby, this + * is not the case of the sensor info register :-( + */ + ret = imx415_wakeup(sensor); + if (ret) + return dev_err_probe(sensor->dev, ret, + "failed to get sensor out of standby\n"); + + ret = imx415_read(sensor, IMX415_SENSOR_INFO); + if (ret < 0) { + dev_err_probe(sensor->dev, ret, + "failed to read sensor information\n"); + goto done; + } + + model = ret & IMX415_SENSOR_INFO_MASK; + + switch (model) { + case IMX415_CHIP_ID: + dev_info(sensor->dev, "Detected IMX415 image sensor\n"); + break; + default: + ret = dev_err_probe(sensor->dev, -ENODEV, + "invalid device model 0x%04x\n", model); + goto done; + } + + ret = 0; + +done: + imx415_write(sensor, IMX415_MODE, IMX415_MODE_STANDBY); + return ret; +} + +static int imx415_check_inck(unsigned long inck, u64 link_frequency) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(imx415_clk_params); ++i) { + if ((imx415_clk_params[i].lane_rate == link_frequency * 2) && + imx415_clk_params[i].inck == inck) + break; + } + + if (i == ARRAY_SIZE(imx415_clk_params)) + return -EINVAL; + else + return 0; +} + +static int imx415_parse_hw_config(struct imx415 *sensor) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *ep; + u64 lane_rate; + unsigned long inck; + unsigned int i, j; + int ret; + + for (i = 0; i < ARRAY_SIZE(sensor->supplies); ++i) + sensor->supplies[i].supply = imx415_supply_names[i]; + + ret = devm_regulator_bulk_get(sensor->dev, ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret) + return dev_err_probe(sensor->dev, ret, + "failed to get supplies\n"); + + sensor->reset = devm_gpiod_get_optional(sensor->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset)) + return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset), + "failed to get reset GPIO\n"); + + sensor->clk = devm_clk_get(sensor->dev, "inck"); + if (IS_ERR(sensor->clk)) + return dev_err_probe(sensor->dev, PTR_ERR(sensor->clk), + "failed to get clock\n"); + + ep = fwnode_graph_get_next_endpoint(dev_fwnode(sensor->dev), NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + switch (bus_cfg.bus.mipi_csi2.num_data_lanes) { + case 2: + case 4: + sensor->num_data_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; + break; + default: + ret = dev_err_probe(sensor->dev, -EINVAL, + "invalid number of CSI2 data lanes %d\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); + goto done_endpoint_free; + } + + if (!bus_cfg.nr_of_link_frequencies) { + ret = dev_err_probe(sensor->dev, -EINVAL, + "no link frequencies defined"); + goto done_endpoint_free; + } + + /* + * Check if there exists a sensor mode defined for current INCK, + * number of lanes and given lane rates. + */ + inck = clk_get_rate(sensor->clk); + for (i = 0; i < bus_cfg.nr_of_link_frequencies; ++i) { + if (imx415_check_inck(inck, bus_cfg.link_frequencies[i])) { + dev_dbg(sensor->dev, + "INCK %lu Hz not supported for this link freq", + inck); + continue; + } + + for (j = 0; j < ARRAY_SIZE(supported_modes); ++j) { + if (sensor->num_data_lanes != supported_modes[j].lanes) + continue; + if (bus_cfg.link_frequencies[i] * 2 != + supported_modes[j].lane_rate) + continue; + sensor->cur_mode = j; + break; + } + if (j < ARRAY_SIZE(supported_modes)) + break; + } + if (i == bus_cfg.nr_of_link_frequencies) { + ret = dev_err_probe(sensor->dev, -EINVAL, + "no valid sensor mode defined\n"); + goto done_endpoint_free; + } + + lane_rate = supported_modes[sensor->cur_mode].lane_rate; + for (i = 0; i < ARRAY_SIZE(imx415_clk_params); ++i) { + if (lane_rate == imx415_clk_params[i].lane_rate && + inck == imx415_clk_params[i].inck) { + sensor->clk_params = &imx415_clk_params[i]; + break; + } + } + if (i == ARRAY_SIZE(imx415_clk_params)) { + ret = dev_err_probe(sensor->dev, -EINVAL, + "Mode %d not supported\n", + sensor->cur_mode); + goto done_endpoint_free; + } + + ret = 0; + dev_dbg(sensor->dev, "clock: %lu Hz, lane_rate: %llu bps, lanes: %d\n", + inck, lane_rate, sensor->num_data_lanes); + +done_endpoint_free: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int imx415_probe(struct i2c_client *client) +{ + struct imx415 *sensor; + int ret; + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->dev = &client->dev; + + ret = imx415_parse_hw_config(sensor); + if (ret) + return ret; + + sensor->regmap = devm_regmap_init_i2c(client, &imx415_regmap_config); + if (IS_ERR(sensor->regmap)) + return PTR_ERR(sensor->regmap); + + /* + * Enable power management. The driver supports runtime PM, but needs to + * work when runtime PM is disabled in the kernel. To that end, power + * the sensor on manually here, identify it, and fully initialize it. + */ + ret = imx415_power_on(sensor); + if (ret) + return ret; + + ret = imx415_identify_model(sensor); + if (ret) + goto err_power; + + ret = imx415_subdev_init(sensor); + if (ret) + goto err_power; + + /* + * Enable runtime PM. As the device has been powered manually, mark it + * as active, and increase the usage count without resuming the device. + */ + pm_runtime_set_active(sensor->dev); + pm_runtime_get_noresume(sensor->dev); + pm_runtime_enable(sensor->dev); + + ret = v4l2_async_register_subdev_sensor(&sensor->subdev); + if (ret < 0) + goto err_pm; + + /* + * Finally, enable autosuspend and decrease the usage count. The device + * will get suspended after the autosuspend delay, turning the power + * off. + */ + pm_runtime_set_autosuspend_delay(sensor->dev, 1000); + pm_runtime_use_autosuspend(sensor->dev); + pm_runtime_put_autosuspend(sensor->dev); + + return 0; + +err_pm: + pm_runtime_disable(sensor->dev); + pm_runtime_put_noidle(sensor->dev); + imx415_subdev_cleanup(sensor); +err_power: + imx415_power_off(sensor); + return ret; +} + +static void imx415_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct imx415 *sensor = to_imx415(subdev); + + v4l2_async_unregister_subdev(subdev); + + imx415_subdev_cleanup(sensor); + + /* + * Disable runtime PM. In case runtime PM is disabled in the kernel, + * make sure to turn power off manually. + */ + pm_runtime_disable(sensor->dev); + if (!pm_runtime_status_suspended(sensor->dev)) + imx415_power_off(sensor); + pm_runtime_set_suspended(sensor->dev); +} + +static int imx415_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct imx415 *sensor = to_imx415(subdev); + + return imx415_power_on(sensor); +} + +static int imx415_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct imx415 *sensor = to_imx415(subdev); + + imx415_power_off(sensor); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(imx415_pm_ops, imx415_runtime_suspend, + imx415_runtime_resume, NULL); + +static const struct of_device_id imx415_of_match[] = { + { .compatible = "sony,imx415" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, imx415_of_match); + +static struct i2c_driver imx415_driver = { + .probe_new = imx415_probe, + .remove = imx415_remove, + .driver = { + .name = "imx415", + .of_match_table = imx415_of_match, + .pm = pm_ptr(&imx415_pm_ops), + }, +}; + +module_i2c_driver(imx415_driver); + +MODULE_DESCRIPTION("Sony IMX415 image sensor driver"); +MODULE_AUTHOR("Gerald Loacker <gerald.loacker@wolfvision.net>"); +MODULE_AUTHOR("Michael Riesch <michael.riesch@wolfvision.net>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ir-kbd-i2c.c b/drivers/media/i2c/ir-kbd-i2c.c index 25bf1132dbff..51921068931d 100644 --- a/drivers/media/i2c/ir-kbd-i2c.c +++ b/drivers/media/i2c/ir-kbd-i2c.c @@ -757,8 +757,9 @@ static int zilog_tx_duty_cycle(struct rc_dev *dev, u32 duty_cycle) return 0; } -static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int ir_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); char *ir_codes = NULL; const char *name = NULL; u64 rc_proto = RC_PROTO_BIT_UNKNOWN; @@ -987,7 +988,7 @@ static struct i2c_driver ir_kbd_driver = { .driver = { .name = "ir-kbd-i2c", }, - .probe = ir_probe, + .probe_new = ir_probe, .remove = ir_remove, .id_table = ir_kbd_id, }; diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c index 9c083cf14231..701038d6d19b 100644 --- a/drivers/media/i2c/max9286.c +++ b/drivers/media/i2c/max9286.c @@ -72,7 +72,7 @@ #define MAX9286_DATATYPE_USER_YUV_12BIT (10 << 0) #define MAX9286_DATATYPE_USER_24BIT (9 << 0) #define MAX9286_DATATYPE_RAW14 (8 << 0) -#define MAX9286_DATATYPE_RAW11 (7 << 0) +#define MAX9286_DATATYPE_RAW12 (7 << 0) #define MAX9286_DATATYPE_RAW10 (6 << 0) #define MAX9286_DATATYPE_RAW8 (5 << 0) #define MAX9286_DATATYPE_YUV422_10BIT (4 << 0) @@ -81,13 +81,21 @@ #define MAX9286_DATATYPE_RGB565 (1 << 0) #define MAX9286_DATATYPE_RGB888 (0 << 0) /* Register 0x15 */ +#define MAX9286_CSI_IMAGE_TYP BIT(7) #define MAX9286_VC(n) ((n) << 5) #define MAX9286_VCTYPE BIT(4) #define MAX9286_CSIOUTEN BIT(3) -#define MAX9286_0X15_RESV (3 << 0) +#define MAX9286_SWP_ENDIAN BIT(2) +#define MAX9286_EN_CCBSYB_CLK_STR BIT(1) +#define MAX9286_EN_GPI_CCBSYB BIT(0) /* Register 0x1b */ #define MAX9286_SWITCHIN(n) (1 << ((n) + 4)) #define MAX9286_ENEQ(n) (1 << (n)) +/* Register 0x1c */ +#define MAX9286_HIGHIMM(n) BIT((n) + 4) +#define MAX9286_I2CSEL BIT(2) +#define MAX9286_HIBW BIT(1) +#define MAX9286_BWS BIT(0) /* Register 0x27 */ #define MAX9286_LOCKED BIT(7) /* Register 0x31 */ @@ -136,9 +144,20 @@ #define MAX9286_N_PADS 5 #define MAX9286_SRC_PAD 4 +struct max9286_format_info { + u32 code; + u8 datatype; +}; + +struct max9286_i2c_speed { + u32 rate; + u8 mstbt; +}; + struct max9286_source { struct v4l2_subdev *sd; struct fwnode_handle *fwnode; + struct regulator *regulator; }; struct max9286_asd { @@ -168,13 +187,18 @@ struct max9286_priv { /* The initial reverse control channel amplitude. */ u32 init_rev_chan_mv; u32 rev_chan_mv; + u8 i2c_mstbt; + u32 bus_width; + bool use_gpio_poc; u32 gpio_poc[2]; struct v4l2_ctrl_handler ctrls; - struct v4l2_ctrl *pixelrate; + struct v4l2_ctrl *pixelrate_ctrl; + unsigned int pixelrate; struct v4l2_mbus_framefmt fmt[MAX9286_N_SINKS]; + struct v4l2_fract interval; /* Protects controls and fmt structures */ struct mutex mutex; @@ -214,6 +238,45 @@ static inline struct max9286_priv *sd_to_max9286(struct v4l2_subdev *sd) return container_of(sd, struct max9286_priv, sd); } +static const struct max9286_format_info max9286_formats[] = { + { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .datatype = MAX9286_DATATYPE_YUV422_8BIT, + }, { + .code = MEDIA_BUS_FMT_VYUY8_1X16, + .datatype = MAX9286_DATATYPE_YUV422_8BIT, + }, { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .datatype = MAX9286_DATATYPE_YUV422_8BIT, + }, { + .code = MEDIA_BUS_FMT_YVYU8_1X16, + .datatype = MAX9286_DATATYPE_YUV422_8BIT, + }, { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .datatype = MAX9286_DATATYPE_RAW12, + }, { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .datatype = MAX9286_DATATYPE_RAW12, + }, { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .datatype = MAX9286_DATATYPE_RAW12, + }, { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .datatype = MAX9286_DATATYPE_RAW12, + }, +}; + +static const struct max9286_i2c_speed max9286_i2c_speeds[] = { + { .rate = 8470, .mstbt = MAX9286_I2CMSTBT_8KBPS }, + { .rate = 28300, .mstbt = MAX9286_I2CMSTBT_28KBPS }, + { .rate = 84700, .mstbt = MAX9286_I2CMSTBT_84KBPS }, + { .rate = 105000, .mstbt = MAX9286_I2CMSTBT_105KBPS }, + { .rate = 173000, .mstbt = MAX9286_I2CMSTBT_173KBPS }, + { .rate = 339000, .mstbt = MAX9286_I2CMSTBT_339KBPS }, + { .rate = 533000, .mstbt = MAX9286_I2CMSTBT_533KBPS }, + { .rate = 837000, .mstbt = MAX9286_I2CMSTBT_837KBPS }, +}; + /* ----------------------------------------------------------------------------- * I2C IO */ @@ -334,7 +397,7 @@ error: static void max9286_configure_i2c(struct max9286_priv *priv, bool localack) { u8 config = MAX9286_I2CSLVSH_469NS_234NS | MAX9286_I2CSLVTO_1024US | - MAX9286_I2CMSTBT_105KBPS; + priv->i2c_mstbt; if (localack) config |= MAX9286_I2CLOCACK; @@ -475,6 +538,77 @@ static int max9286_check_config_link(struct max9286_priv *priv, return 0; } +static void max9286_set_video_format(struct max9286_priv *priv, + const struct v4l2_mbus_framefmt *format) +{ + const struct max9286_format_info *info = NULL; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(max9286_formats); ++i) { + if (max9286_formats[i].code == format->code) { + info = &max9286_formats[i]; + break; + } + } + + if (WARN_ON(!info)) + return; + + /* + * Video format setup: disable CSI output, set VC according to Link + * number, enable I2C clock stretching when CCBSY is low, enable CCBSY + * in external GPI-to-GPO mode. + */ + max9286_write(priv, 0x15, MAX9286_VCTYPE | MAX9286_EN_CCBSYB_CLK_STR | + MAX9286_EN_GPI_CCBSYB); + + /* Enable CSI-2 Lane D0-D3 only, DBL mode. */ + max9286_write(priv, 0x12, MAX9286_CSIDBL | MAX9286_DBL | + MAX9286_CSILANECNT(priv->csi2_data_lanes) | + info->datatype); + + /* + * Enable HS/VS encoding, use HS as line valid source, use D14/15 for + * HS/VS, invert VS. + */ + max9286_write(priv, 0x0c, MAX9286_HVEN | MAX9286_DESEL | + MAX9286_INVVS | MAX9286_HVSRC_D14); +} + +static void max9286_set_fsync_period(struct max9286_priv *priv) +{ + u32 fsync; + + if (!priv->interval.numerator || !priv->interval.denominator) { + /* + * Special case, a null interval enables automatic FRAMESYNC + * mode. FRAMESYNC is taken from the slowest link. + */ + max9286_write(priv, 0x01, MAX9286_FSYNCMODE_INT_HIZ | + MAX9286_FSYNCMETH_AUTO); + return; + } + + /* + * Manual FRAMESYNC + * + * The FRAMESYNC generator is configured with a period expressed as a + * number of PCLK periods. + */ + fsync = div_u64((u64)priv->pixelrate * priv->interval.numerator, + priv->interval.denominator); + + dev_dbg(&priv->client->dev, "fsync period %u (pclk %u)\n", fsync, + priv->pixelrate); + + max9286_write(priv, 0x01, MAX9286_FSYNCMODE_INT_OUT | + MAX9286_FSYNCMETH_MANUAL); + + max9286_write(priv, 0x06, (fsync >> 0) & 0xff); + max9286_write(priv, 0x07, (fsync >> 8) & 0xff); + max9286_write(priv, 0x08, (fsync >> 16) & 0xff); +} + /* ----------------------------------------------------------------------------- * V4L2 Subdev */ @@ -513,11 +647,13 @@ static int max9286_set_pixelrate(struct max9286_priv *priv) return -EINVAL; } + priv->pixelrate = pixelrate; + /* * The CSI-2 transmitter pixel rate is the single source rate multiplied * by the number of available sources. */ - return v4l2_ctrl_s_ctrl_int64(priv->pixelrate, + return v4l2_ctrl_s_ctrl_int64(priv->pixelrate_ctrl, pixelrate * priv->nsources); } @@ -657,6 +793,17 @@ static int max9286_s_stream(struct v4l2_subdev *sd, int enable) int ret; if (enable) { + const struct v4l2_mbus_framefmt *format; + + /* + * Get the format from the first used sink pad, as all sink + * formats must be identical. + */ + format = &priv->fmt[__ffs(priv->bound_sources)]; + + max9286_set_video_format(priv, format); + max9286_set_fsync_period(priv); + /* * The frame sync between cameras is transmitted across the * reverse channel as GPIO. We must open all channels while @@ -698,13 +845,17 @@ static int max9286_s_stream(struct v4l2_subdev *sd, int enable) } /* - * Enable CSI output, VC set according to link number. - * Bit 7 must be set (chip manual says it's 0 and reserved). + * Configure the CSI-2 output to line interleaved mode (W x (N + * x H), as opposed to the (N x W) x H mode that outputs the + * images stitched side-by-side) and enable it. */ - max9286_write(priv, 0x15, 0x80 | MAX9286_VCTYPE | - MAX9286_CSIOUTEN | MAX9286_0X15_RESV); + max9286_write(priv, 0x15, MAX9286_CSI_IMAGE_TYP | MAX9286_VCTYPE | + MAX9286_CSIOUTEN | MAX9286_EN_CCBSYB_CLK_STR | + MAX9286_EN_GPI_CCBSYB); } else { - max9286_write(priv, 0x15, MAX9286_VCTYPE | MAX9286_0X15_RESV); + max9286_write(priv, 0x15, MAX9286_VCTYPE | + MAX9286_EN_CCBSYB_CLK_STR | + MAX9286_EN_GPI_CCBSYB); /* Stop all cameras. */ for_each_source(priv, source) @@ -716,6 +867,32 @@ static int max9286_s_stream(struct v4l2_subdev *sd, int enable) return 0; } +static int max9286_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *interval) +{ + struct max9286_priv *priv = sd_to_max9286(sd); + + if (interval->pad != MAX9286_SRC_PAD) + return -EINVAL; + + interval->interval = priv->interval; + + return 0; +} + +static int max9286_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *interval) +{ + struct max9286_priv *priv = sd_to_max9286(sd); + + if (interval->pad != MAX9286_SRC_PAD) + return -EINVAL; + + priv->interval = interval->interval; + + return 0; +} + static int max9286_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -749,22 +926,20 @@ static int max9286_set_fmt(struct v4l2_subdev *sd, { struct max9286_priv *priv = sd_to_max9286(sd); struct v4l2_mbus_framefmt *cfg_fmt; + unsigned int i; if (format->pad == MAX9286_SRC_PAD) return -EINVAL; - /* Refuse non YUV422 formats as we hardcode DT to 8 bit YUV422 */ - switch (format->format.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: - break; - default: - format->format.code = MEDIA_BUS_FMT_UYVY8_1X16; - break; + /* Validate the format. */ + for (i = 0; i < ARRAY_SIZE(max9286_formats); ++i) { + if (max9286_formats[i].code == format->format.code) + break; } + if (i == ARRAY_SIZE(max9286_formats)) + format->format.code = max9286_formats[0].code; + cfg_fmt = max9286_get_pad_format(priv, sd_state, format->pad, format->which); if (!cfg_fmt) @@ -807,6 +982,8 @@ static int max9286_get_fmt(struct v4l2_subdev *sd, static const struct v4l2_subdev_video_ops max9286_video_ops = { .s_stream = max9286_s_stream, + .g_frame_interval = max9286_g_frame_interval, + .s_frame_interval = max9286_s_frame_interval, }; static const struct v4l2_subdev_pad_ops max9286_pad_ops = { @@ -820,16 +997,20 @@ static const struct v4l2_subdev_ops max9286_subdev_ops = { .pad = &max9286_pad_ops, }; +static const struct v4l2_mbus_framefmt max9286_default_format = { + .width = 1280, + .height = 800, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .field = V4L2_FIELD_NONE, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + static void max9286_init_format(struct v4l2_mbus_framefmt *fmt) { - fmt->width = 1280; - fmt->height = 800; - fmt->code = MEDIA_BUS_FMT_UYVY8_1X16; - fmt->colorspace = V4L2_COLORSPACE_SRGB; - fmt->field = V4L2_FIELD_NONE; - fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; - fmt->quantization = V4L2_QUANTIZATION_DEFAULT; - fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; + *fmt = max9286_default_format; } static int max9286_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) @@ -891,10 +1072,10 @@ static int max9286_v4l2_register(struct max9286_priv *priv) priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; v4l2_ctrl_handler_init(&priv->ctrls, 1); - priv->pixelrate = v4l2_ctrl_new_std(&priv->ctrls, - &max9286_ctrl_ops, - V4L2_CID_PIXEL_RATE, - 1, INT_MAX, 1, 50000000); + priv->pixelrate_ctrl = v4l2_ctrl_new_std(&priv->ctrls, + &max9286_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, INT_MAX, 1, 50000000); priv->sd.ctrl_handler = &priv->ctrls; ret = priv->ctrls.error; @@ -932,6 +1113,7 @@ static int max9286_v4l2_register(struct max9286_priv *priv) err_put_node: fwnode_handle_put(ep); err_async: + v4l2_ctrl_handler_free(&priv->ctrls); max9286_v4l2_notifier_unregister(priv); return ret; @@ -975,6 +1157,7 @@ static int max9286_setup(struct max9286_priv *priv) (2 << 6) | (1 << 4) | (0 << 2) | (3 << 0), /* 210x */ (3 << 6) | (2 << 4) | (1 << 2) | (0 << 0), /* 3210 */ }; + int cfg; /* * Set the I2C bus speed. @@ -993,24 +1176,27 @@ static int max9286_setup(struct max9286_priv *priv) max9286_write(priv, 0x0b, link_order[priv->route_mask]); max9286_write(priv, 0x69, (0xf & ~priv->route_mask)); - /* - * Video format setup: - * Disable CSI output, VC is set according to Link number. - */ - max9286_write(priv, 0x15, MAX9286_VCTYPE | MAX9286_0X15_RESV); + max9286_set_video_format(priv, &max9286_default_format); + max9286_set_fsync_period(priv); - /* Enable CSI-2 Lane D0-D3 only, DBL mode, YUV422 8-bit. */ - max9286_write(priv, 0x12, MAX9286_CSIDBL | MAX9286_DBL | - MAX9286_CSILANECNT(priv->csi2_data_lanes) | - MAX9286_DATATYPE_YUV422_8BIT); + cfg = max9286_read(priv, 0x1c); + if (cfg < 0) + return cfg; + + dev_dbg(&priv->client->dev, "power-up config: %s immunity, %u-bit bus\n", + cfg & MAX9286_HIGHIMM(0) ? "high" : "legacy", + cfg & MAX9286_BWS ? 32 : cfg & MAX9286_HIBW ? 27 : 24); - /* Automatic: FRAMESYNC taken from the slowest Link. */ - max9286_write(priv, 0x01, MAX9286_FSYNCMODE_INT_HIZ | - MAX9286_FSYNCMETH_AUTO); + if (priv->bus_width) { + cfg &= ~(MAX9286_HIBW | MAX9286_BWS); - /* Enable HS/VS encoding, use D14/15 for HS/VS, invert VS. */ - max9286_write(priv, 0x0c, MAX9286_HVEN | MAX9286_INVVS | - MAX9286_HVSRC_D14); + if (priv->bus_width == 27) + cfg |= MAX9286_HIBW; + else if (priv->bus_width == 32) + cfg |= MAX9286_BWS; + + max9286_write(priv, 0x1c, cfg); + } /* * The overlap window seems to provide additional validation by tracking @@ -1088,9 +1274,6 @@ static int max9286_parse_gpios(struct max9286_priv *priv) struct device *dev = &priv->client->dev; int ret; - /* GPIO values default to high */ - priv->gpio_state = BIT(0) | BIT(1); - /* * Parse the "gpio-poc" vendor property. If the property is not * specified the camera power is controlled by a regulator. @@ -1102,18 +1285,7 @@ static int max9286_parse_gpios(struct max9286_priv *priv) * If gpio lines are not used for the camera power, register * a gpio controller for consumers. */ - ret = max9286_register_gpio(priv); - if (ret) - return ret; - - priv->regulator = devm_regulator_get(dev, "poc"); - if (IS_ERR(priv->regulator)) { - return dev_err_probe(dev, PTR_ERR(priv->regulator), - "Unable to get PoC regulator (%ld)\n", - PTR_ERR(priv->regulator)); - } - - return 0; + return max9286_register_gpio(priv); } /* If the property is specified make sure it is well formed. */ @@ -1124,21 +1296,75 @@ static int max9286_parse_gpios(struct max9286_priv *priv) return -EINVAL; } + priv->use_gpio_poc = true; + return 0; +} + +static int max9286_poc_power_on(struct max9286_priv *priv) +{ + struct max9286_source *source; + unsigned int enabled = 0; + int ret; + + /* Enable the global regulator if available. */ + if (priv->regulator) + return regulator_enable(priv->regulator); + + if (priv->use_gpio_poc) + return max9286_gpio_set(priv, priv->gpio_poc[0], + !priv->gpio_poc[1]); + + /* Otherwise use the per-port regulators. */ + for_each_source(priv, source) { + ret = regulator_enable(source->regulator); + if (ret < 0) + goto error; + + enabled |= BIT(to_index(priv, source)); + } + return 0; + +error: + for_each_source(priv, source) { + if (enabled & BIT(to_index(priv, source))) + regulator_disable(source->regulator); + } + + return ret; +} + +static int max9286_poc_power_off(struct max9286_priv *priv) +{ + struct max9286_source *source; + int ret = 0; + + if (priv->regulator) + return regulator_disable(priv->regulator); + + if (priv->use_gpio_poc) + return max9286_gpio_set(priv, priv->gpio_poc[0], + priv->gpio_poc[1]); + + for_each_source(priv, source) { + int err; + + err = regulator_disable(source->regulator); + if (!ret) + ret = err; + } + + return ret; } static int max9286_poc_enable(struct max9286_priv *priv, bool enable) { int ret; - /* If the regulator is not available, use gpio to control power. */ - if (!priv->regulator) - ret = max9286_gpio_set(priv, priv->gpio_poc[0], - enable ^ priv->gpio_poc[1]); - else if (enable) - ret = regulator_enable(priv->regulator); + if (enable) + ret = max9286_poc_power_on(priv); else - ret = regulator_disable(priv->regulator); + ret = max9286_poc_power_off(priv); if (ret < 0) dev_err(&priv->client->dev, "Unable to turn power %s\n", @@ -1208,6 +1434,8 @@ static int max9286_parse_dt(struct max9286_priv *priv) struct device_node *node = NULL; unsigned int i2c_mux_mask = 0; u32 reverse_channel_microvolt; + u32 i2c_clk_freq = 105000; + unsigned int i; /* Balance the of_node_put() performed by of_find_node_by_name(). */ of_node_get(dev->of_node); @@ -1298,6 +1526,40 @@ static int max9286_parse_dt(struct max9286_priv *priv) } of_node_put(node); + of_property_read_u32(dev->of_node, "maxim,bus-width", &priv->bus_width); + switch (priv->bus_width) { + case 0: + /* + * The property isn't specified in the device tree, the driver + * will keep the default value selected by the BWS pin. + */ + case 24: + case 27: + case 32: + break; + default: + dev_err(dev, "Invalid %s value %u\n", "maxim,bus-width", + priv->bus_width); + return -EINVAL; + } + + of_property_read_u32(dev->of_node, "maxim,i2c-remote-bus-hz", + &i2c_clk_freq); + for (i = 0; i < ARRAY_SIZE(max9286_i2c_speeds); ++i) { + const struct max9286_i2c_speed *speed = &max9286_i2c_speeds[i]; + + if (speed->rate == i2c_clk_freq) { + priv->i2c_mstbt = speed->mstbt; + break; + } + } + + if (i == ARRAY_SIZE(max9286_i2c_speeds)) { + dev_err(dev, "Invalid %s value %u\n", "maxim,i2c-remote-bus-hz", + i2c_clk_freq); + return -EINVAL; + } + /* * Parse the initial value of the reverse channel amplitude from * the firmware interface and convert it to millivolts. @@ -1317,6 +1579,44 @@ static int max9286_parse_dt(struct max9286_priv *priv) return 0; } +static int max9286_get_poc_supplies(struct max9286_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct max9286_source *source; + int ret; + + /* Start by getting the global regulator. */ + priv->regulator = devm_regulator_get_optional(dev, "poc"); + if (!IS_ERR(priv->regulator)) + return 0; + + if (PTR_ERR(priv->regulator) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(priv->regulator), + "Unable to get PoC regulator\n"); + + /* If there's no global regulator, get per-port regulators. */ + dev_dbg(dev, + "No global PoC regulator, looking for per-port regulators\n"); + priv->regulator = NULL; + + for_each_source(priv, source) { + unsigned int index = to_index(priv, source); + char name[10]; + + snprintf(name, sizeof(name), "port%u-poc", index); + source->regulator = devm_regulator_get(dev, name); + if (IS_ERR(source->regulator)) { + ret = PTR_ERR(source->regulator); + dev_err_probe(dev, ret, + "Unable to get port %u PoC regulator\n", + index); + return ret; + } + } + + return 0; +} + static int max9286_probe(struct i2c_client *client) { struct max9286_priv *priv; @@ -1330,10 +1630,19 @@ static int max9286_probe(struct i2c_client *client) priv->client = client; + /* GPIO values default to high */ + priv->gpio_state = BIT(0) | BIT(1); + + ret = max9286_parse_dt(priv); + if (ret) + goto err_cleanup_dt; + priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH); - if (IS_ERR(priv->gpiod_pwdn)) - return PTR_ERR(priv->gpiod_pwdn); + if (IS_ERR(priv->gpiod_pwdn)) { + ret = PTR_ERR(priv->gpiod_pwdn); + goto err_cleanup_dt; + } gpiod_set_consumer_name(priv->gpiod_pwdn, "max9286-pwdn"); gpiod_set_value_cansleep(priv->gpiod_pwdn, 1); @@ -1360,9 +1669,11 @@ static int max9286_probe(struct i2c_client *client) if (ret) goto err_powerdown; - ret = max9286_parse_dt(priv); - if (ret) - goto err_powerdown; + if (!priv->use_gpio_poc) { + ret = max9286_get_poc_supplies(priv); + if (ret) + goto err_cleanup_dt; + } ret = max9286_init(priv); if (ret < 0) @@ -1370,10 +1681,10 @@ static int max9286_probe(struct i2c_client *client) return 0; -err_cleanup_dt: - max9286_cleanup_dt(priv); err_powerdown: gpiod_set_value_cansleep(priv->gpiod_pwdn, 0); +err_cleanup_dt: + max9286_cleanup_dt(priv); return ret; } diff --git a/drivers/media/i2c/msp3400-driver.c b/drivers/media/i2c/msp3400-driver.c index 4ce7a15a9884..12032e28b428 100644 --- a/drivers/media/i2c/msp3400-driver.c +++ b/drivers/media/i2c/msp3400-driver.c @@ -663,8 +663,9 @@ static const char * const opmode_str[] = { [OPMODE_AUTOSELECT] = "autodetect and autoselect", }; -static int msp_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int msp_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct msp_state *state; struct v4l2_subdev *sd; struct v4l2_ctrl_handler *hdl; @@ -891,7 +892,7 @@ static struct i2c_driver msp_driver = { .name = "msp3400", .pm = &msp3400_pm_ops, }, - .probe = msp_probe, + .probe_new = msp_probe, .remove = msp_remove, .id_table = msp_id, }; diff --git a/drivers/media/i2c/mt9p031.c b/drivers/media/i2c/mt9p031.c index 4ffc2f6e7db4..9e023a4b9bd1 100644 --- a/drivers/media/i2c/mt9p031.c +++ b/drivers/media/i2c/mt9p031.c @@ -1102,9 +1102,9 @@ done: return pdata; } -static int mt9p031_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int mt9p031_probe(struct i2c_client *client) { + const struct i2c_device_id *did = i2c_client_get_device_id(client); struct mt9p031_platform_data *pdata = mt9p031_get_pdata(client); struct i2c_adapter *adapter = client->adapter; struct mt9p031 *mt9p031; @@ -1248,7 +1248,7 @@ static struct i2c_driver mt9p031_i2c_driver = { .of_match_table = of_match_ptr(mt9p031_of_match), .name = "mt9p031", }, - .probe = mt9p031_probe, + .probe_new = mt9p031_probe, .remove = mt9p031_remove, .id_table = mt9p031_id, }; diff --git a/drivers/media/i2c/mt9v032.c b/drivers/media/i2c/mt9v032.c index bc4388ccc2a8..7cfd4ebdd2e6 100644 --- a/drivers/media/i2c/mt9v032.c +++ b/drivers/media/i2c/mt9v032.c @@ -1044,9 +1044,9 @@ done: return pdata; } -static int mt9v032_probe(struct i2c_client *client, - const struct i2c_device_id *did) +static int mt9v032_probe(struct i2c_client *client) { + const struct i2c_device_id *did = i2c_client_get_device_id(client); struct mt9v032_platform_data *pdata = mt9v032_get_pdata(client); struct mt9v032 *mt9v032; unsigned int i; @@ -1296,7 +1296,7 @@ static struct i2c_driver mt9v032_driver = { .name = "mt9v032", .of_match_table = of_match_ptr(mt9v032_of_match), }, - .probe = mt9v032_probe, + .probe_new = mt9v032_probe, .remove = mt9v032_remove, .id_table = mt9v032_id, }; diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c index f3731f932a94..89d126240c34 100644 --- a/drivers/media/i2c/ov2740.c +++ b/drivers/media/i2c/ov2740.c @@ -629,8 +629,10 @@ static int ov2740_init_controls(struct ov2740 *ov2740) V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov2740_test_pattern_menu) - 1, 0, 0, ov2740_test_pattern_menu); - if (ctrl_hdlr->error) + if (ctrl_hdlr->error) { + v4l2_ctrl_handler_free(ctrl_hdlr); return ctrl_hdlr->error; + } ov2740->sd.ctrl_handler = ctrl_hdlr; diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c index e0f908af581b..1536649b9e90 100644 --- a/drivers/media/i2c/ov5640.c +++ b/drivers/media/i2c/ov5640.c @@ -50,6 +50,7 @@ #define OV5640_REG_SYS_CTRL0 0x3008 #define OV5640_REG_SYS_CTRL0_SW_PWDN 0x42 #define OV5640_REG_SYS_CTRL0_SW_PWUP 0x02 +#define OV5640_REG_SYS_CTRL0_SW_RST 0x82 #define OV5640_REG_CHIP_ID 0x300a #define OV5640_REG_IO_MIPI_CTRL00 0x300e #define OV5640_REG_PAD_OUTPUT_ENABLE01 0x3017 @@ -520,7 +521,18 @@ static u32 ov5640_code_to_bpp(struct ov5640_dev *sensor, u32 code) */ /* YUV422 UYVY VGA@30fps */ -static const struct v4l2_mbus_framefmt ov5640_default_fmt = { +static const struct v4l2_mbus_framefmt ov5640_csi2_default_fmt = { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .width = 640, + .height = 480, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SRGB), + .quantization = V4L2_QUANTIZATION_FULL_RANGE, + .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SRGB), + .field = V4L2_FIELD_NONE, +}; + +static const struct v4l2_mbus_framefmt ov5640_dvp_default_fmt = { .code = MEDIA_BUS_FMT_UYVY8_2X8, .width = 640, .height = 480, @@ -532,7 +544,7 @@ static const struct v4l2_mbus_framefmt ov5640_default_fmt = { }; static const struct reg_value ov5640_init_setting[] = { - {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 5}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x11, 0, 0}, {0x3103, 0x03, 0, 0}, {0x3630, 0x36, 0, 0}, {0x3631, 0x0e, 0, 0}, {0x3632, 0xe2, 0, 0}, {0x3633, 0x12, 0, 0}, {0x3621, 0xe0, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, @@ -2424,24 +2436,48 @@ static void ov5640_power(struct ov5640_dev *sensor, bool enable) gpiod_set_value_cansleep(sensor->pwdn_gpio, enable ? 0 : 1); } -static void ov5640_reset(struct ov5640_dev *sensor) +/* + * From section 2.7 power up sequence: + * t0 + t1 + t2 >= 5ms Delay from DOVDD stable to PWDN pull down + * t3 >= 1ms Delay from PWDN pull down to RESETB pull up + * t4 >= 20ms Delay from RESETB pull up to SCCB (i2c) stable + * + * Some modules don't expose RESETB/PWDN pins directly, instead providing a + * "PWUP" GPIO which is wired through appropriate delays and inverters to the + * pins. + * + * In such cases, this gpio should be mapped to pwdn_gpio in the driver, and we + * should still toggle the pwdn_gpio below with the appropriate delays, while + * the calls to reset_gpio will be ignored. + */ +static void ov5640_powerup_sequence(struct ov5640_dev *sensor) { - if (!sensor->reset_gpio) - return; - - gpiod_set_value_cansleep(sensor->reset_gpio, 0); + if (sensor->pwdn_gpio) { + gpiod_set_value_cansleep(sensor->reset_gpio, 0); - /* camera power cycle */ - ov5640_power(sensor, false); - usleep_range(5000, 10000); - ov5640_power(sensor, true); - usleep_range(5000, 10000); + /* camera power cycle */ + ov5640_power(sensor, false); + usleep_range(5000, 10000); + ov5640_power(sensor, true); + usleep_range(5000, 10000); - gpiod_set_value_cansleep(sensor->reset_gpio, 1); - usleep_range(1000, 2000); + gpiod_set_value_cansleep(sensor->reset_gpio, 1); + usleep_range(1000, 2000); - gpiod_set_value_cansleep(sensor->reset_gpio, 0); + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + } else { + /* software reset */ + ov5640_write_reg(sensor, OV5640_REG_SYS_CTRL0, + OV5640_REG_SYS_CTRL0_SW_RST); + } usleep_range(20000, 25000); + + /* + * software standby: allows registers programming; + * exit at restore_mode() for CSI, s_stream(1) for DVP + */ + ov5640_write_reg(sensor, OV5640_REG_SYS_CTRL0, + OV5640_REG_SYS_CTRL0_SW_PWDN); } static int ov5640_set_power_on(struct ov5640_dev *sensor) @@ -2464,8 +2500,7 @@ static int ov5640_set_power_on(struct ov5640_dev *sensor) goto xclk_off; } - ov5640_reset(sensor); - ov5640_power(sensor, true); + ov5640_powerup_sequence(sensor); ret = ov5640_init_slave_id(sensor); if (ret) @@ -3316,6 +3351,7 @@ static int ov5640_g_volatile_ctrl(struct v4l2_ctrl *ctrl) break; } + pm_runtime_mark_last_busy(&sensor->i2c_client->dev); pm_runtime_put_autosuspend(&sensor->i2c_client->dev); return 0; @@ -3391,6 +3427,7 @@ static int ov5640_s_ctrl(struct v4l2_ctrl *ctrl) break; } + pm_runtime_mark_last_busy(&sensor->i2c_client->dev); pm_runtime_put_autosuspend(&sensor->i2c_client->dev); return ret; @@ -3458,7 +3495,7 @@ static int ov5640_init_controls(struct ov5640_dev *sensor) /* Auto/manual gain */ ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 1); - ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 1023, 1, 0); ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, @@ -3710,8 +3747,10 @@ static int ov5640_s_stream(struct v4l2_subdev *sd, int enable) out: mutex_unlock(&sensor->lock); - if (!enable || ret) + if (!enable || ret) { + pm_runtime_mark_last_busy(&sensor->i2c_client->dev); pm_runtime_put_autosuspend(&sensor->i2c_client->dev); + } return ret; } @@ -3719,11 +3758,13 @@ out: static int ov5640_init_cfg(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { + struct ov5640_dev *sensor = to_ov5640_dev(sd); struct v4l2_mbus_framefmt *fmt = v4l2_subdev_get_try_format(sd, state, 0); struct v4l2_rect *crop = v4l2_subdev_get_try_crop(sd, state, 0); - *fmt = ov5640_default_fmt; + *fmt = ov5640_is_csi2(sensor) ? ov5640_csi2_default_fmt : + ov5640_dvp_default_fmt; crop->left = OV5640_PIXEL_ARRAY_LEFT; crop->top = OV5640_PIXEL_ARRAY_TOP; @@ -3812,7 +3853,6 @@ static int ov5640_probe(struct i2c_client *client) * default init sequence initialize sensor to * YUV422 UYVY VGA@30fps */ - sensor->fmt = ov5640_default_fmt; sensor->frame_interval.numerator = 1; sensor->frame_interval.denominator = ov5640_framerates[OV5640_30_FPS]; sensor->current_fr = OV5640_30_FPS; @@ -3845,6 +3885,9 @@ static int ov5640_probe(struct i2c_client *client) return -EINVAL; } + sensor->fmt = ov5640_is_csi2(sensor) ? ov5640_csi2_default_fmt : + ov5640_dvp_default_fmt; + /* get system clock (xclk) */ sensor->xclk = devm_clk_get(dev, "xclk"); if (IS_ERR(sensor->xclk)) { @@ -3912,6 +3955,7 @@ static int ov5640_probe(struct i2c_client *client) 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; diff --git a/drivers/media/i2c/ov5670.c b/drivers/media/i2c/ov5670.c index bc9fc3bc90c2..f79d908f4531 100644 --- a/drivers/media/i2c/ov5670.c +++ b/drivers/media/i2c/ov5670.c @@ -1,15 +1,24 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2017 Intel Corporation. +#include <asm/unaligned.h> #include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> #include <media/v4l2-fwnode.h> +#define OV5670_XVCLK_FREQ 19200000 + #define OV5670_REG_CHIP_ID 0x300a #define OV5670_CHIP_ID 0x005670 @@ -65,6 +74,10 @@ #define OV5670_REG_VALUE_16BIT 2 #define OV5670_REG_VALUE_24BIT 3 +/* Pixel Array */ +#define OV5670_NATIVE_WIDTH 2624 +#define OV5670_NATIVE_HEIGHT 1980 + /* Initial number of frames to skip to avoid possible garbage */ #define OV5670_NUM_OF_SKIP_FRAMES 2 @@ -83,6 +96,14 @@ struct ov5670_link_freq_config { const struct ov5670_reg_list reg_list; }; +static const char * const ov5670_supply_names[] = { + "avdd", /* Analog power */ + "dvdd", /* Digital power */ + "dovdd", /* Digital output power */ +}; + +#define OV5670_NUM_SUPPLIES ARRAY_SIZE(ov5670_supply_names) + struct ov5670_mode { /* Frame width in pixels */ u32 width; @@ -99,10 +120,25 @@ struct ov5670_mode { /* Link frequency needed for this resolution */ u32 link_freq_index; + /* Analog crop rectangle */ + const struct v4l2_rect *analog_crop; + /* Sensor register settings for this resolution */ const struct ov5670_reg_list reg_list; }; +/* + * All the modes supported by the driver are obtained by subsampling the + * full pixel array. The below values are reflected in registers from + * 0x3800-0x3807 in the modes register-value tables. + */ +static const struct v4l2_rect ov5670_analog_crop = { + .left = 12, + .top = 4, + .width = 2600, + .height = 1952, +}; + static const struct ov5670_reg mipi_data_rate_840mbps[] = { {0x0300, 0x04}, {0x0301, 0x00}, @@ -1750,66 +1786,73 @@ static const struct ov5670_mode supported_modes[] = { .height = 1944, .vts_def = OV5670_VTS_30FPS, .vts_min = OV5670_VTS_30FPS, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + .analog_crop = &ov5670_analog_crop, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_2592x1944_regs), .regs = mode_2592x1944_regs, }, - .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, }, { .width = 1296, .height = 972, .vts_def = OV5670_VTS_30FPS, .vts_min = 996, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + .analog_crop = &ov5670_analog_crop, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_1296x972_regs), .regs = mode_1296x972_regs, }, - .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, }, { .width = 648, .height = 486, .vts_def = OV5670_VTS_30FPS, .vts_min = 516, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + .analog_crop = &ov5670_analog_crop, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_648x486_regs), .regs = mode_648x486_regs, }, - .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, }, { .width = 2560, .height = 1440, .vts_def = OV5670_VTS_30FPS, .vts_min = OV5670_VTS_30FPS, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + .analog_crop = &ov5670_analog_crop, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_2560x1440_regs), .regs = mode_2560x1440_regs, }, - .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, }, { .width = 1280, .height = 720, .vts_def = OV5670_VTS_30FPS, .vts_min = 1020, + + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + .analog_crop = &ov5670_analog_crop, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), .regs = mode_1280x720_regs, }, - .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, }, { .width = 640, .height = 360, .vts_def = OV5670_VTS_30FPS, .vts_min = 510, + .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, + .analog_crop = &ov5670_analog_crop, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_640x360_regs), .regs = mode_640x360_regs, }, - .link_freq_index = OV5670_LINK_FREQ_422MHZ_INDEX, } }; @@ -1828,6 +1871,16 @@ struct ov5670 { /* Current mode */ const struct ov5670_mode *cur_mode; + /* xvclk input clock */ + struct clk *xvclk; + + /* Regulators */ + struct regulator_bulk_data supplies[OV5670_NUM_SUPPLIES]; + + /* Power-down and reset gpios. */ + struct gpio_desc *pwdn_gpio; /* PWDNB pin. */ + struct gpio_desc *reset_gpio; /* XSHUTDOWN pin. */ + /* To serialize asynchronus callbacks */ struct mutex mutex; @@ -1935,27 +1988,6 @@ static int ov5670_write_reg_list(struct ov5670 *ov5670, return ov5670_write_regs(ov5670, r_list->regs, r_list->num_of_regs); } -/* Open sub-device */ -static int ov5670_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct ov5670 *ov5670 = to_ov5670(sd); - struct v4l2_mbus_framefmt *try_fmt = - v4l2_subdev_get_try_format(sd, fh->state, 0); - - mutex_lock(&ov5670->mutex); - - /* Initialize try_fmt */ - try_fmt->width = ov5670->cur_mode->width; - try_fmt->height = ov5670->cur_mode->height; - try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; - try_fmt->field = V4L2_FIELD_NONE; - - /* No crop or compose */ - mutex_unlock(&ov5670->mutex); - - return 0; -} - static int ov5670_update_digital_gain(struct ov5670 *ov5670, u32 d_gain) { int ret; @@ -2006,7 +2038,7 @@ static int ov5670_set_ctrl(struct v4l2_ctrl *ctrl) struct ov5670, ctrl_handler); struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); s64 max; - int ret = 0; + int ret; /* Propagate change of current control to all related controls */ switch (ctrl->id) { @@ -2045,7 +2077,13 @@ static int ov5670_set_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_TEST_PATTERN: ret = ov5670_enable_test_pattern(ov5670, ctrl->val); break; + case V4L2_CID_HBLANK: + case V4L2_CID_LINK_FREQ: + case V4L2_CID_PIXEL_RATE: + ret = 0; + break; default: + ret = -EINVAL; dev_info(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", __func__, ctrl->id, ctrl->val); break; @@ -2155,6 +2193,28 @@ error: return ret; } +static int ov5670_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt = + v4l2_subdev_get_try_format(sd, state, 0); + const struct ov5670_mode *default_mode = &supported_modes[0]; + struct v4l2_rect *crop = v4l2_subdev_get_try_crop(sd, state, 0); + + fmt->width = default_mode->width; + fmt->height = default_mode->height; + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SRGB); + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SRGB); + + *crop = *default_mode->analog_crop; + + return 0; +} + static int ov5670_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -2404,6 +2464,49 @@ unlock_and_return: return ret; } +static int __maybe_unused ov5670_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5670 *ov5670 = to_ov5670(sd); + unsigned long delay_us; + int ret; + + ret = clk_prepare_enable(ov5670->xvclk); + if (ret) + return ret; + + ret = regulator_bulk_enable(OV5670_NUM_SUPPLIES, ov5670->supplies); + if (ret) { + clk_disable_unprepare(ov5670->xvclk); + return ret; + } + + gpiod_set_value_cansleep(ov5670->pwdn_gpio, 0); + gpiod_set_value_cansleep(ov5670->reset_gpio, 0); + + /* 8192 * 2 clock pulses before the first SCCB transaction. */ + delay_us = DIV_ROUND_UP(8192 * 2 * 1000, + DIV_ROUND_UP(OV5670_XVCLK_FREQ, 1000)); + fsleep(delay_us); + + return 0; +} + +static int __maybe_unused ov5670_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5670 *ov5670 = to_ov5670(sd); + + gpiod_set_value_cansleep(ov5670->reset_gpio, 1); + gpiod_set_value_cansleep(ov5670->pwdn_gpio, 1); + regulator_bulk_disable(OV5670_NUM_SUPPLIES, ov5670->supplies); + clk_disable_unprepare(ov5670->xvclk); + + return 0; +} + static int __maybe_unused ov5670_suspend(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); @@ -2438,15 +2541,64 @@ static const struct v4l2_subdev_core_ops ov5670_core_ops = { .unsubscribe_event = v4l2_event_subdev_unsubscribe, }; +static const struct v4l2_rect * +__ov5670_get_pad_crop(struct ov5670 *sensor, struct v4l2_subdev_state *state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + const struct ov5670_mode *mode = sensor->cur_mode; + + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&sensor->sd, state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return mode->analog_crop; + } + + return NULL; +} + +static int ov5670_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct ov5670 *sensor = to_ov5670(subdev); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + mutex_lock(&sensor->mutex); + sel->r = *__ov5670_get_pad_crop(sensor, state, sel->pad, + sel->which); + mutex_unlock(&sensor->mutex); + break; + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = OV5670_NATIVE_WIDTH; + sel->r.height = OV5670_NATIVE_HEIGHT; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = ov5670_analog_crop; + break; + default: + return -EINVAL; + } + + return 0; +} + static const struct v4l2_subdev_video_ops ov5670_video_ops = { .s_stream = ov5670_set_stream, }; static const struct v4l2_subdev_pad_ops ov5670_pad_ops = { + .init_cfg = ov5670_init_cfg, .enum_mbus_code = ov5670_enum_mbus_code, .get_fmt = ov5670_get_pad_format, .set_fmt = ov5670_set_pad_format, .enum_frame_size = ov5670_enum_frame_size, + .get_selection = ov5670_get_selection, + .set_selection = ov5670_get_selection, }; static const struct v4l2_subdev_sensor_ops ov5670_sensor_ops = { @@ -2464,9 +2616,34 @@ static const struct media_entity_operations ov5670_subdev_entity_ops = { .link_validate = v4l2_subdev_link_validate, }; -static const struct v4l2_subdev_internal_ops ov5670_internal_ops = { - .open = ov5670_open, -}; +static int ov5670_regulators_probe(struct ov5670 *ov5670) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + unsigned int i; + + for (i = 0; i < OV5670_NUM_SUPPLIES; i++) + ov5670->supplies[i].supply = ov5670_supply_names[i]; + + return devm_regulator_bulk_get(&client->dev, OV5670_NUM_SUPPLIES, + ov5670->supplies); +} + +static int ov5670_gpio_probe(struct ov5670 *ov5670) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov5670->sd); + + ov5670->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(ov5670->pwdn_gpio)) + return PTR_ERR(ov5670->pwdn_gpio); + + ov5670->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ov5670->reset_gpio)) + return PTR_ERR(ov5670->reset_gpio); + + return 0; +} static int ov5670_probe(struct i2c_client *client) { @@ -2476,10 +2653,6 @@ static int ov5670_probe(struct i2c_client *client) bool full_power; int ret; - device_property_read_u32(&client->dev, "clock-frequency", &input_clk); - if (input_clk != 19200000) - return -EINVAL; - ov5670 = devm_kzalloc(&client->dev, sizeof(*ov5670), GFP_KERNEL); if (!ov5670) { ret = -ENOMEM; @@ -2487,16 +2660,50 @@ static int ov5670_probe(struct i2c_client *client) goto error_print; } + ov5670->xvclk = devm_clk_get(&client->dev, NULL); + if (!IS_ERR_OR_NULL(ov5670->xvclk)) + input_clk = clk_get_rate(ov5670->xvclk); + else if (PTR_ERR(ov5670->xvclk) == -ENOENT) + device_property_read_u32(&client->dev, "clock-frequency", + &input_clk); + else + return dev_err_probe(&client->dev, PTR_ERR(ov5670->xvclk), + "error getting clock\n"); + + if (input_clk != OV5670_XVCLK_FREQ) { + dev_err(&client->dev, + "Unsupported clock frequency %u\n", input_clk); + return -EINVAL; + } + /* Initialize subdev */ v4l2_i2c_subdev_init(&ov5670->sd, client, &ov5670_subdev_ops); + ret = ov5670_regulators_probe(ov5670); + if (ret) { + err_msg = "Regulators probe failed"; + goto error_print; + } + + ret = ov5670_gpio_probe(ov5670); + if (ret) { + err_msg = "GPIO probe failed"; + goto error_print; + } + full_power = acpi_dev_state_d0(&client->dev); if (full_power) { + ret = ov5670_runtime_resume(&client->dev); + if (ret) { + err_msg = "Power up failed"; + goto error_print; + } + /* Check module identity */ ret = ov5670_identify_module(ov5670); if (ret) { err_msg = "ov5670_identify_module() error"; - goto error_print; + goto error_power_off; } } @@ -2511,7 +2718,6 @@ static int ov5670_probe(struct i2c_client *client) goto error_mutex_destroy; } - ov5670->sd.internal_ops = &ov5670_internal_ops; ov5670->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; ov5670->sd.entity.ops = &ov5670_subdev_entity_ops; @@ -2525,24 +2731,27 @@ static int ov5670_probe(struct i2c_client *client) goto error_handler_free; } - /* Async register for subdev */ - ret = v4l2_async_register_subdev_sensor(&ov5670->sd); - if (ret < 0) { - err_msg = "v4l2_async_register_subdev() error"; - goto error_entity_cleanup; - } - ov5670->streaming = false; /* Set the device's state to active if it's in D0 state. */ if (full_power) pm_runtime_set_active(&client->dev); pm_runtime_enable(&client->dev); + + /* Async register for subdev */ + ret = v4l2_async_register_subdev_sensor(&ov5670->sd); + if (ret < 0) { + err_msg = "v4l2_async_register_subdev() error"; + goto error_pm_disable; + } + pm_runtime_idle(&client->dev); return 0; -error_entity_cleanup: +error_pm_disable: + pm_runtime_disable(&client->dev); + media_entity_cleanup(&ov5670->sd.entity); error_handler_free: @@ -2551,6 +2760,10 @@ error_handler_free: error_mutex_destroy: mutex_destroy(&ov5670->mutex); +error_power_off: + if (full_power) + ov5670_runtime_suspend(&client->dev); + error_print: dev_err(&client->dev, "%s: %s %d\n", __func__, err_msg, ret); @@ -2568,10 +2781,12 @@ static void ov5670_remove(struct i2c_client *client) mutex_destroy(&ov5670->mutex); pm_runtime_disable(&client->dev); + ov5670_runtime_suspend(&client->dev); } static const struct dev_pm_ops ov5670_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ov5670_suspend, ov5670_resume) + SET_RUNTIME_PM_OPS(ov5670_runtime_suspend, ov5670_runtime_resume, NULL) }; #ifdef CONFIG_ACPI @@ -2583,11 +2798,18 @@ static const struct acpi_device_id ov5670_acpi_ids[] = { MODULE_DEVICE_TABLE(acpi, ov5670_acpi_ids); #endif +static const struct of_device_id ov5670_of_ids[] = { + { .compatible = "ovti,ov5670" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ov5670_of_ids); + static struct i2c_driver ov5670_i2c_driver = { .driver = { .name = "ov5670", .pm = &ov5670_pm_ops, .acpi_match_table = ACPI_PTR(ov5670_acpi_ids), + .of_match_table = ov5670_of_ids, }, .probe_new = ov5670_probe, .remove = ov5670_remove, diff --git a/drivers/media/i2c/ov5675.c b/drivers/media/i2c/ov5675.c index 94dc8cb7a7c0..d55180b3b7aa 100644 --- a/drivers/media/i2c/ov5675.c +++ b/drivers/media/i2c/ov5675.c @@ -3,10 +3,14 @@ #include <asm/unaligned.h> #include <linux/acpi.h> +#include <linux/clk.h> #include <linux/delay.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-fwnode.h> @@ -17,7 +21,7 @@ #define OV5675_LINK_FREQ_450MHZ 450000000ULL #define OV5675_SCLK 90000000LL -#define OV5675_MCLK 19200000 +#define OV5675_XVCLK_19_2 19200000 #define OV5675_DATA_LANES 2 #define OV5675_RGB_DEPTH 10 @@ -76,6 +80,14 @@ #define to_ov5675(_sd) container_of(_sd, struct ov5675, sd) +static const char * const ov5675_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OV5675_NUM_SUPPLIES ARRAY_SIZE(ov5675_supply_names) + enum { OV5675_LINK_FREQ_900MBPS, }; @@ -484,6 +496,9 @@ struct ov5675 { struct v4l2_subdev sd; struct media_pad pad; struct v4l2_ctrl_handler ctrl_handler; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[OV5675_NUM_SUPPLIES]; /* V4L2 Controls */ struct v4l2_ctrl *link_freq; @@ -764,12 +779,14 @@ static const struct v4l2_ctrl_ops ov5675_ctrl_ops = { static int ov5675_init_controls(struct ov5675 *ov5675) { + struct i2c_client *client = v4l2_get_subdevdata(&ov5675->sd); + struct v4l2_fwnode_device_properties props; struct v4l2_ctrl_handler *ctrl_hdlr; s64 exposure_max, h_blank; int ret; ctrl_hdlr = &ov5675->ctrl_handler; - ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); if (ret) return ret; @@ -820,12 +837,28 @@ static int ov5675_init_controls(struct ov5675 *ov5675) v4l2_ctrl_new_std(ctrl_hdlr, &ov5675_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); - if (ctrl_hdlr->error) + if (ctrl_hdlr->error) { + v4l2_ctrl_handler_free(ctrl_hdlr); return ctrl_hdlr->error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov5675_ctrl_ops, + &props); + if (ret) + goto error; ov5675->sd.ctrl_handler = ctrl_hdlr; return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; } static void ov5675_update_pad_format(const struct ov5675_mode *mode, @@ -944,6 +977,56 @@ static int ov5675_set_stream(struct v4l2_subdev *sd, int enable) return ret; } +static int ov5675_power_off(struct device *dev) +{ + /* 512 xvclk cycles after the last SCCB transation or MIPI frame end */ + u32 delay_us = DIV_ROUND_UP(512, OV5675_XVCLK_19_2 / 1000 / 1000); + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov5675 *ov5675 = to_ov5675(sd); + + usleep_range(delay_us, delay_us * 2); + + clk_disable_unprepare(ov5675->xvclk); + gpiod_set_value_cansleep(ov5675->reset_gpio, 1); + regulator_bulk_disable(OV5675_NUM_SUPPLIES, ov5675->supplies); + + return 0; +} + +static int ov5675_power_on(struct device *dev) +{ + u32 delay_us = DIV_ROUND_UP(8192, OV5675_XVCLK_19_2 / 1000 / 1000); + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov5675 *ov5675 = to_ov5675(sd); + int ret; + + ret = clk_prepare_enable(ov5675->xvclk); + if (ret < 0) { + dev_err(dev, "failed to enable xvclk: %d\n", ret); + return ret; + } + + gpiod_set_value_cansleep(ov5675->reset_gpio, 1); + + ret = regulator_bulk_enable(OV5675_NUM_SUPPLIES, ov5675->supplies); + if (ret) { + clk_disable_unprepare(ov5675->xvclk); + return ret; + } + + /* Reset pulse should be at least 2ms and reset gpio released only once + * regulators are stable. + */ + usleep_range(2000, 2200); + + gpiod_set_value_cansleep(ov5675->reset_gpio, 0); + + /* 8192 xvclk cycles prior to the first SCCB transation */ + usleep_range(delay_us, delay_us * 2); + + return 0; +} + static int __maybe_unused ov5675_suspend(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); @@ -1040,6 +1123,31 @@ static int ov5675_get_format(struct v4l2_subdev *sd, return 0; } +static int ov5675_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + 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 = 2624; + sel->r.height = 2000; + return 0; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = 16; + sel->r.left = 16; + sel->r.width = 2592; + sel->r.height = 1944; + return 0; + } + return -EINVAL; +} + static int ov5675_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -1089,6 +1197,7 @@ static const struct v4l2_subdev_video_ops ov5675_video_ops = { static const struct v4l2_subdev_pad_ops ov5675_pad_ops = { .set_fmt = ov5675_set_format, .get_fmt = ov5675_get_format, + .get_selection = ov5675_get_selection, .enum_mbus_code = ov5675_enum_mbus_code, .enum_frame_size = ov5675_enum_frame_size, }; @@ -1106,32 +1215,60 @@ static const struct v4l2_subdev_internal_ops ov5675_internal_ops = { .open = ov5675_open, }; -static int ov5675_check_hwcfg(struct device *dev) +static int ov5675_get_hwcfg(struct ov5675 *ov5675, struct device *dev) { struct fwnode_handle *ep; struct fwnode_handle *fwnode = dev_fwnode(dev); struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = V4L2_MBUS_CSI2_DPHY }; - u32 mclk; + u32 xvclk_rate; int ret; unsigned int i, j; if (!fwnode) return -ENXIO; - ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); + ov5675->xvclk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(ov5675->xvclk)) + return dev_err_probe(dev, PTR_ERR(ov5675->xvclk), + "failed to get xvclk: %ld\n", + PTR_ERR(ov5675->xvclk)); - if (ret) { - dev_err(dev, "can't get clock frequency"); - return ret; + if (ov5675->xvclk) { + xvclk_rate = clk_get_rate(ov5675->xvclk); + } else { + ret = fwnode_property_read_u32(fwnode, "clock-frequency", + &xvclk_rate); + + if (ret) { + dev_err(dev, "can't get clock frequency"); + return ret; + } } - if (mclk != OV5675_MCLK) { - dev_err(dev, "external clock %d is not supported", mclk); + if (xvclk_rate != OV5675_XVCLK_19_2) { + dev_err(dev, "external clock rate %u is unsupported", + xvclk_rate); return -EINVAL; } + ov5675->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ov5675->reset_gpio)) { + ret = PTR_ERR(ov5675->reset_gpio); + dev_err(dev, "failed to get reset-gpios: %d\n", ret); + return ret; + } + + for (i = 0; i < OV5675_NUM_SUPPLIES; i++) + ov5675->supplies[i].supply = ov5675_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, OV5675_NUM_SUPPLIES, + ov5675->supplies); + if (ret) + return ret; + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!ep) return -ENXIO; @@ -1185,6 +1322,10 @@ static void ov5675_remove(struct i2c_client *client) v4l2_ctrl_handler_free(sd->ctrl_handler); pm_runtime_disable(&client->dev); mutex_destroy(&ov5675->mutex); + + if (!pm_runtime_status_suspended(&client->dev)) + ov5675_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); } static int ov5675_probe(struct i2c_client *client) @@ -1193,25 +1334,31 @@ static int ov5675_probe(struct i2c_client *client) bool full_power; int ret; - ret = ov5675_check_hwcfg(&client->dev); + ov5675 = devm_kzalloc(&client->dev, sizeof(*ov5675), GFP_KERNEL); + if (!ov5675) + return -ENOMEM; + + ret = ov5675_get_hwcfg(ov5675, &client->dev); if (ret) { - dev_err(&client->dev, "failed to check HW configuration: %d", + dev_err(&client->dev, "failed to get HW configuration: %d", ret); return ret; } - ov5675 = devm_kzalloc(&client->dev, sizeof(*ov5675), GFP_KERNEL); - if (!ov5675) - return -ENOMEM; - v4l2_i2c_subdev_init(&ov5675->sd, client, &ov5675_subdev_ops); + ret = ov5675_power_on(&client->dev); + if (ret) { + dev_err(&client->dev, "failed to power on: %d\n", ret); + return ret; + } + full_power = acpi_dev_state_d0(&client->dev); if (full_power) { ret = ov5675_identify_module(ov5675); if (ret) { dev_err(&client->dev, "failed to find sensor: %d", ret); - return ret; + goto probe_power_off; } } @@ -1241,11 +1388,6 @@ static int ov5675_probe(struct i2c_client *client) goto probe_error_media_entity_cleanup; } - /* - * Device is already turned on by i2c-core with ACPI domain PM. - * Enable runtime PM and turn off the device. - */ - /* Set the device's state to active if it's in D0 state. */ if (full_power) pm_runtime_set_active(&client->dev); @@ -1260,12 +1402,15 @@ probe_error_media_entity_cleanup: probe_error_v4l2_ctrl_handler_free: v4l2_ctrl_handler_free(ov5675->sd.ctrl_handler); mutex_destroy(&ov5675->mutex); +probe_power_off: + ov5675_power_off(&client->dev); return ret; } static const struct dev_pm_ops ov5675_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ov5675_suspend, ov5675_resume) + SET_RUNTIME_PM_OPS(ov5675_power_off, ov5675_power_on, NULL) }; #ifdef CONFIG_ACPI @@ -1277,11 +1422,18 @@ static const struct acpi_device_id ov5675_acpi_ids[] = { MODULE_DEVICE_TABLE(acpi, ov5675_acpi_ids); #endif +static const struct of_device_id ov5675_of_match[] = { + { .compatible = "ovti,ov5675", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ov5675_of_match); + static struct i2c_driver ov5675_i2c_driver = { .driver = { .name = "ov5675", .pm = &ov5675_pm_ops, .acpi_match_table = ACPI_PTR(ov5675_acpi_ids), + .of_match_table = ov5675_of_match, }, .probe_new = ov5675_probe, .remove = ov5675_remove, diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c index 11d3bef65d43..b1bb0833571e 100644 --- a/drivers/media/i2c/ov7670.c +++ b/drivers/media/i2c/ov7670.c @@ -1840,16 +1840,16 @@ static int ov7670_parse_dt(struct device *dev, if (bus_cfg.bus_type != V4L2_MBUS_PARALLEL) { dev_err(dev, "Unsupported media bus type\n"); - return ret; + return -EINVAL; } info->mbus_config = bus_cfg.bus.parallel.flags; return 0; } -static int ov7670_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ov7670_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct v4l2_fract tpf; struct v4l2_subdev *sd; struct ov7670_info *info; @@ -2038,7 +2038,7 @@ static struct i2c_driver ov7670_driver = { .name = "ov7670", .of_match_table = of_match_ptr(ov7670_of_match), }, - .probe = ov7670_probe, + .probe_new = ov7670_probe, .remove = ov7670_remove, .id_table = ov7670_id, }; diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c index 4189e3fc3d53..a238e63425f8 100644 --- a/drivers/media/i2c/ov772x.c +++ b/drivers/media/i2c/ov772x.c @@ -1462,7 +1462,7 @@ static int ov772x_probe(struct i2c_client *client) priv->subdev.ctrl_handler = &priv->hdl; if (priv->hdl.error) { ret = priv->hdl.error; - goto error_mutex_destroy; + goto error_ctrl_free; } priv->clk = clk_get(&client->dev, NULL); @@ -1515,7 +1515,6 @@ error_clk_put: clk_put(priv->clk); error_ctrl_free: v4l2_ctrl_handler_free(&priv->hdl); -error_mutex_destroy: mutex_destroy(&priv->lock); return ret; diff --git a/drivers/media/i2c/ov8858.c b/drivers/media/i2c/ov8858.c new file mode 100644 index 000000000000..9ca8a17bfbb9 --- /dev/null +++ b/drivers/media/i2c/ov8858.c @@ -0,0 +1,2008 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Jacopo Mondi <jacopo.mondi@ideasonboard.com> + * Copyright (C) 2022 Nicholas Roth <nicholas@rothemail.net> + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + */ + +#include <asm/unaligned.h> + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> + +#define OV8858_LINK_FREQ 360000000U +#define OV8858_XVCLK_FREQ 24000000 + +#define OV8858_REG_SIZE_SHIFT 16 +#define OV8858_REG_ADDR_MASK 0xffff +#define OV8858_REG_8BIT(n) ((1U << OV8858_REG_SIZE_SHIFT) | (n)) +#define OV8858_REG_16BIT(n) ((2U << OV8858_REG_SIZE_SHIFT) | (n)) +#define OV8858_REG_24BIT(n) ((3U << OV8858_REG_SIZE_SHIFT) | (n)) + +#define OV8858_REG_SC_CTRL0100 OV8858_REG_8BIT(0x0100) +#define OV8858_MODE_SW_STANDBY 0x0 +#define OV8858_MODE_STREAMING 0x1 + +#define OV8858_REG_CHIP_ID OV8858_REG_24BIT(0x300a) +#define OV8858_CHIP_ID 0x008858 + +#define OV8858_REG_SUB_ID OV8858_REG_8BIT(0x302a) +#define OV8858_R1A 0xb0 +#define OV8858_R2A 0xb2 + +#define OV8858_REG_LONG_EXPO OV8858_REG_24BIT(0x3500) +#define OV8858_EXPOSURE_MIN 4 +#define OV8858_EXPOSURE_STEP 1 +#define OV8858_EXPOSURE_MARGIN 4 + +#define OV8858_REG_LONG_GAIN OV8858_REG_16BIT(0x3508) +#define OV8858_LONG_GAIN_MIN 0x0 +#define OV8858_LONG_GAIN_MAX 0x7ff +#define OV8858_LONG_GAIN_STEP 1 +#define OV8858_LONG_GAIN_DEFAULT 0x80 + +#define OV8858_REG_LONG_DIGIGAIN OV8858_REG_16BIT(0x350a) +#define OV8858_LONG_DIGIGAIN_H_MASK 0x3fc0 +#define OV8858_LONG_DIGIGAIN_L_MASK 0x3f +#define OV8858_LONG_DIGIGAIN_H_SHIFT 2 +#define OV8858_LONG_DIGIGAIN_MIN 0x0 +#define OV8858_LONG_DIGIGAIN_MAX 0x3fff +#define OV8858_LONG_DIGIGAIN_STEP 1 +#define OV8858_LONG_DIGIGAIN_DEFAULT 0x200 + +#define OV8858_REG_VTS OV8858_REG_16BIT(0x380e) +#define OV8858_VTS_MAX 0x7fff + +#define OV8858_REG_TEST_PATTERN OV8858_REG_8BIT(0x5e00) +#define OV8858_TEST_PATTERN_ENABLE 0x80 +#define OV8858_TEST_PATTERN_DISABLE 0x0 + +#define REG_NULL 0xffff + +static const char * const ov8858_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +struct regval { + u16 addr; + u8 val; +}; + +struct regval_modes { + const struct regval *mode_2lanes; + const struct regval *mode_4lanes; +}; + +struct ov8858_mode { + u32 width; + u32 height; + u32 hts_def; + u32 vts_def; + u32 exp_def; + const struct regval_modes reg_modes; +}; + +struct ov8858 { + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(ov8858_supply_names)]; + + struct v4l2_subdev subdev; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + + const struct regval *global_regs; + + unsigned int num_lanes; +}; + +static inline struct ov8858 *sd_to_ov8858(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov8858, subdev); +} + +static const struct regval ov8858_global_regs_r1a[] = { + {0x0100, 0x00}, + {0x0100, 0x00}, + {0x0100, 0x00}, + {0x0100, 0x00}, + {0x0302, 0x1e}, + {0x0303, 0x00}, + {0x0304, 0x03}, + {0x030e, 0x00}, + {0x030f, 0x09}, + {0x0312, 0x01}, + {0x031e, 0x0c}, + {0x3600, 0x00}, + {0x3601, 0x00}, + {0x3602, 0x00}, + {0x3603, 0x00}, + {0x3604, 0x22}, + {0x3605, 0x30}, + {0x3606, 0x00}, + {0x3607, 0x20}, + {0x3608, 0x11}, + {0x3609, 0x28}, + {0x360a, 0x00}, + {0x360b, 0x06}, + {0x360c, 0xdc}, + {0x360d, 0x40}, + {0x360e, 0x0c}, + {0x360f, 0x20}, + {0x3610, 0x07}, + {0x3611, 0x20}, + {0x3612, 0x88}, + {0x3613, 0x80}, + {0x3614, 0x58}, + {0x3615, 0x00}, + {0x3616, 0x4a}, + {0x3617, 0xb0}, + {0x3618, 0x56}, + {0x3619, 0x70}, + {0x361a, 0x99}, + {0x361b, 0x00}, + {0x361c, 0x07}, + {0x361d, 0x00}, + {0x361e, 0x00}, + {0x361f, 0x00}, + {0x3638, 0xff}, + {0x3633, 0x0c}, + {0x3634, 0x0c}, + {0x3635, 0x0c}, + {0x3636, 0x0c}, + {0x3645, 0x13}, + {0x3646, 0x83}, + {0x364a, 0x07}, + {0x3015, 0x01}, + {0x3018, 0x32}, + {0x3020, 0x93}, + {0x3022, 0x01}, + {0x3031, 0x0a}, + {0x3034, 0x00}, + {0x3106, 0x01}, + {0x3305, 0xf1}, + {0x3308, 0x00}, + {0x3309, 0x28}, + {0x330a, 0x00}, + {0x330b, 0x20}, + {0x330c, 0x00}, + {0x330d, 0x00}, + {0x330e, 0x00}, + {0x330f, 0x40}, + {0x3307, 0x04}, + {0x3500, 0x00}, + {0x3501, 0x4d}, + {0x3502, 0x40}, + {0x3503, 0x00}, + {0x3505, 0x80}, + {0x3508, 0x04}, + {0x3509, 0x00}, + {0x350c, 0x00}, + {0x350d, 0x80}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3700, 0x18}, + {0x3701, 0x0c}, + {0x3702, 0x28}, + {0x3703, 0x19}, + {0x3704, 0x14}, + {0x3705, 0x00}, + {0x3706, 0x35}, + {0x3707, 0x04}, + {0x3708, 0x24}, + {0x3709, 0x33}, + {0x370a, 0x00}, + {0x370b, 0xb5}, + {0x370c, 0x04}, + {0x3718, 0x12}, + {0x3719, 0x31}, + {0x3712, 0x42}, + {0x3714, 0x24}, + {0x371e, 0x19}, + {0x371f, 0x40}, + {0x3720, 0x05}, + {0x3721, 0x05}, + {0x3724, 0x06}, + {0x3725, 0x01}, + {0x3726, 0x06}, + {0x3728, 0x05}, + {0x3729, 0x02}, + {0x372a, 0x03}, + {0x372b, 0x53}, + {0x372c, 0xa3}, + {0x372d, 0x53}, + {0x372e, 0x06}, + {0x372f, 0x10}, + {0x3730, 0x01}, + {0x3731, 0x06}, + {0x3732, 0x14}, + {0x3733, 0x10}, + {0x3734, 0x40}, + {0x3736, 0x20}, + {0x373a, 0x05}, + {0x373b, 0x06}, + {0x373c, 0x0a}, + {0x373e, 0x03}, + {0x3755, 0x10}, + {0x3758, 0x00}, + {0x3759, 0x4c}, + {0x375a, 0x06}, + {0x375b, 0x13}, + {0x375c, 0x20}, + {0x375d, 0x02}, + {0x375e, 0x00}, + {0x375f, 0x14}, + {0x3768, 0x22}, + {0x3769, 0x44}, + {0x376a, 0x44}, + {0x3761, 0x00}, + {0x3762, 0x00}, + {0x3763, 0x00}, + {0x3766, 0xff}, + {0x376b, 0x00}, + {0x3772, 0x23}, + {0x3773, 0x02}, + {0x3774, 0x16}, + {0x3775, 0x12}, + {0x3776, 0x04}, + {0x3777, 0x00}, + {0x3778, 0x1b}, + {0x37a0, 0x44}, + {0x37a1, 0x3d}, + {0x37a2, 0x3d}, + {0x37a3, 0x00}, + {0x37a4, 0x00}, + {0x37a5, 0x00}, + {0x37a6, 0x00}, + {0x37a7, 0x44}, + {0x37a8, 0x4c}, + {0x37a9, 0x4c}, + {0x3760, 0x00}, + {0x376f, 0x01}, + {0x37aa, 0x44}, + {0x37ab, 0x2e}, + {0x37ac, 0x2e}, + {0x37ad, 0x33}, + {0x37ae, 0x0d}, + {0x37af, 0x0d}, + {0x37b0, 0x00}, + {0x37b1, 0x00}, + {0x37b2, 0x00}, + {0x37b3, 0x42}, + {0x37b4, 0x42}, + {0x37b5, 0x33}, + {0x37b6, 0x00}, + {0x37b7, 0x00}, + {0x37b8, 0x00}, + {0x37b9, 0xff}, + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x0c}, + {0x3804, 0x0c}, + {0x3805, 0xd3}, + {0x3806, 0x09}, + {0x3807, 0xa3}, + {0x3808, 0x06}, + {0x3809, 0x60}, + {0x380a, 0x04}, + {0x380b, 0xc8}, + {0x380c, 0x07}, + {0x380d, 0x88}, + {0x380e, 0x04}, + {0x380f, 0xdc}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3813, 0x02}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3820, 0x00}, + {0x3821, 0x67}, + {0x382a, 0x03}, + {0x382b, 0x01}, + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x18}, + {0x3841, 0xff}, + {0x3846, 0x48}, + {0x3d85, 0x14}, + {0x3f08, 0x08}, + {0x3f0a, 0x80}, + {0x4000, 0xf1}, + {0x4001, 0x10}, + {0x4005, 0x10}, + {0x4002, 0x27}, + {0x4009, 0x81}, + {0x400b, 0x0c}, + {0x401b, 0x00}, + {0x401d, 0x00}, + {0x4020, 0x00}, + {0x4021, 0x04}, + {0x4022, 0x04}, + {0x4023, 0xb9}, + {0x4024, 0x05}, + {0x4025, 0x2a}, + {0x4026, 0x05}, + {0x4027, 0x2b}, + {0x4028, 0x00}, + {0x4029, 0x02}, + {0x402a, 0x04}, + {0x402b, 0x04}, + {0x402c, 0x02}, + {0x402d, 0x02}, + {0x402e, 0x08}, + {0x402f, 0x02}, + {0x401f, 0x00}, + {0x4034, 0x3f}, + {0x403d, 0x04}, + {0x4300, 0xff}, + {0x4301, 0x00}, + {0x4302, 0x0f}, + {0x4316, 0x00}, + {0x4500, 0x38}, + {0x4503, 0x18}, + {0x4600, 0x00}, + {0x4601, 0xcb}, + {0x481f, 0x32}, + {0x4837, 0x16}, + {0x4850, 0x10}, + {0x4851, 0x32}, + {0x4b00, 0x2a}, + {0x4b0d, 0x00}, + {0x4d00, 0x04}, + {0x4d01, 0x18}, + {0x4d02, 0xc3}, + {0x4d03, 0xff}, + {0x4d04, 0xff}, + {0x4d05, 0xff}, + {0x5000, 0x7e}, + {0x5001, 0x01}, + {0x5002, 0x08}, + {0x5003, 0x20}, + {0x5046, 0x12}, + {0x5901, 0x00}, + {0x5e00, 0x00}, + {0x5e01, 0x41}, + {0x382d, 0x7f}, + {0x4825, 0x3a}, + {0x4826, 0x40}, + {0x4808, 0x25}, + {REG_NULL, 0x00}, +}; + +static const struct regval ov8858_global_regs_r2a_2lane[] = { + /* + * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz. + * v00_01_00 (05/29/2014) : initial setting + * AM19 : 3617 <- 0xC0 + * AM20 : change FWC_6K_EN to be default 0x3618=0x5a + */ + {0x0103, 0x01}, /* software reset */ + {0x0100, 0x00}, /* software standby */ + {0x0302, 0x1e}, /* pll1_multi */ + {0x0303, 0x00}, /* pll1_divm */ + {0x0304, 0x03}, /* pll1_div_mipi */ + {0x030e, 0x02}, /* pll2_rdiv */ + {0x030f, 0x04}, /* pll2_divsp */ + {0x0312, 0x03}, /* pll2_pre_div0, pll2_r_divdac */ + {0x031e, 0x0c}, /* pll1_no_lat */ + {0x3600, 0x00}, + {0x3601, 0x00}, + {0x3602, 0x00}, + {0x3603, 0x00}, + {0x3604, 0x22}, + {0x3605, 0x20}, + {0x3606, 0x00}, + {0x3607, 0x20}, + {0x3608, 0x11}, + {0x3609, 0x28}, + {0x360a, 0x00}, + {0x360b, 0x05}, + {0x360c, 0xd4}, + {0x360d, 0x40}, + {0x360e, 0x0c}, + {0x360f, 0x20}, + {0x3610, 0x07}, + {0x3611, 0x20}, + {0x3612, 0x88}, + {0x3613, 0x80}, + {0x3614, 0x58}, + {0x3615, 0x00}, + {0x3616, 0x4a}, + {0x3617, 0x90}, + {0x3618, 0x5a}, + {0x3619, 0x70}, + {0x361a, 0x99}, + {0x361b, 0x0a}, + {0x361c, 0x07}, + {0x361d, 0x00}, + {0x361e, 0x00}, + {0x361f, 0x00}, + {0x3638, 0xff}, + {0x3633, 0x0f}, + {0x3634, 0x0f}, + {0x3635, 0x0f}, + {0x3636, 0x12}, + {0x3645, 0x13}, + {0x3646, 0x83}, + {0x364a, 0x07}, + {0x3015, 0x00}, + {0x3018, 0x32}, /* MIPI 2 lane */ + {0x3020, 0x93}, /* Clock switch output normal, pclk_div =/1 */ + {0x3022, 0x01}, /* pd_mipi enable when rst_sync */ + {0x3031, 0x0a}, /* MIPI 10-bit mode */ + {0x3034, 0x00}, + {0x3106, 0x01}, /* sclk_div, sclk_pre_div */ + {0x3305, 0xf1}, + {0x3308, 0x00}, + {0x3309, 0x28}, + {0x330a, 0x00}, + {0x330b, 0x20}, + {0x330c, 0x00}, + {0x330d, 0x00}, + {0x330e, 0x00}, + {0x330f, 0x40}, + {0x3307, 0x04}, + {0x3500, 0x00}, /* exposure H */ + {0x3501, 0x4d}, /* exposure M */ + {0x3502, 0x40}, /* exposure L */ + {0x3503, 0x80}, /* gain delay ?, exposure delay 1 frame, real gain */ + {0x3505, 0x80}, /* gain option */ + {0x3508, 0x02}, /* gain H */ + {0x3509, 0x00}, /* gain L */ + {0x350c, 0x00}, /* short gain H */ + {0x350d, 0x80}, /* short gain L */ + {0x3510, 0x00}, /* short exposure H */ + {0x3511, 0x02}, /* short exposure M */ + {0x3512, 0x00}, /* short exposure L */ + {0x3700, 0x18}, + {0x3701, 0x0c}, + {0x3702, 0x28}, + {0x3703, 0x19}, + {0x3704, 0x14}, + {0x3705, 0x00}, + {0x3706, 0x82}, + {0x3707, 0x04}, + {0x3708, 0x24}, + {0x3709, 0x33}, + {0x370a, 0x01}, + {0x370b, 0x82}, + {0x370c, 0x04}, + {0x3718, 0x12}, + {0x3719, 0x31}, + {0x3712, 0x42}, + {0x3714, 0x24}, + {0x371e, 0x19}, + {0x371f, 0x40}, + {0x3720, 0x05}, + {0x3721, 0x05}, + {0x3724, 0x06}, + {0x3725, 0x01}, + {0x3726, 0x06}, + {0x3728, 0x05}, + {0x3729, 0x02}, + {0x372a, 0x03}, + {0x372b, 0x53}, + {0x372c, 0xa3}, + {0x372d, 0x53}, + {0x372e, 0x06}, + {0x372f, 0x10}, + {0x3730, 0x01}, + {0x3731, 0x06}, + {0x3732, 0x14}, + {0x3733, 0x10}, + {0x3734, 0x40}, + {0x3736, 0x20}, + {0x373a, 0x05}, + {0x373b, 0x06}, + {0x373c, 0x0a}, + {0x373e, 0x03}, + {0x3750, 0x0a}, + {0x3751, 0x0e}, + {0x3755, 0x10}, + {0x3758, 0x00}, + {0x3759, 0x4c}, + {0x375a, 0x06}, + {0x375b, 0x13}, + {0x375c, 0x20}, + {0x375d, 0x02}, + {0x375e, 0x00}, + {0x375f, 0x14}, + {0x3768, 0x22}, + {0x3769, 0x44}, + {0x376a, 0x44}, + {0x3761, 0x00}, + {0x3762, 0x00}, + {0x3763, 0x00}, + {0x3766, 0xff}, + {0x376b, 0x00}, + {0x3772, 0x23}, + {0x3773, 0x02}, + {0x3774, 0x16}, + {0x3775, 0x12}, + {0x3776, 0x04}, + {0x3777, 0x00}, + {0x3778, 0x17}, + {0x37a0, 0x44}, + {0x37a1, 0x3d}, + {0x37a2, 0x3d}, + {0x37a3, 0x00}, + {0x37a4, 0x00}, + {0x37a5, 0x00}, + {0x37a6, 0x00}, + {0x37a7, 0x44}, + {0x37a8, 0x4c}, + {0x37a9, 0x4c}, + {0x3760, 0x00}, + {0x376f, 0x01}, + {0x37aa, 0x44}, + {0x37ab, 0x2e}, + {0x37ac, 0x2e}, + {0x37ad, 0x33}, + {0x37ae, 0x0d}, + {0x37af, 0x0d}, + {0x37b0, 0x00}, + {0x37b1, 0x00}, + {0x37b2, 0x00}, + {0x37b3, 0x42}, + {0x37b4, 0x42}, + {0x37b5, 0x31}, + {0x37b6, 0x00}, + {0x37b7, 0x00}, + {0x37b8, 0x00}, + {0x37b9, 0xff}, + {0x3800, 0x00}, /* x start H */ + {0x3801, 0x0c}, /* x start L */ + {0x3802, 0x00}, /* y start H */ + {0x3803, 0x0c}, /* y start L */ + {0x3804, 0x0c}, /* x end H */ + {0x3805, 0xd3}, /* x end L */ + {0x3806, 0x09}, /* y end H */ + {0x3807, 0xa3}, /* y end L */ + {0x3808, 0x06}, /* x output size H */ + {0x3809, 0x60}, /* x output size L */ + {0x380a, 0x04}, /* y output size H */ + {0x380b, 0xc8}, /* y output size L */ + {0x380c, 0x07}, /* HTS H */ + {0x380d, 0x88}, /* HTS L */ + {0x380e, 0x04}, /* VTS H */ + {0x380f, 0xdc}, /* VTS L */ + {0x3810, 0x00}, /* ISP x win H */ + {0x3811, 0x04}, /* ISP x win L */ + {0x3813, 0x02}, /* ISP y win L */ + {0x3814, 0x03}, /* x odd inc */ + {0x3815, 0x01}, /* x even inc */ + {0x3820, 0x00}, /* vflip off */ + {0x3821, 0x67}, /* mirror on, bin on */ + {0x382a, 0x03}, /* y odd inc */ + {0x382b, 0x01}, /* y even inc */ + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x18}, + {0x3841, 0xff}, /* window auto size enable */ + {0x3846, 0x48}, + {0x3d85, 0x16}, /* OTP power up load data enable with BIST */ + {0x3d8c, 0x73}, /* OTP setting start High */ + {0x3d8d, 0xde}, /* OTP setting start Low */ + {0x3f08, 0x08}, + {0x3f0a, 0x00}, + {0x4000, 0xf1}, /* out_range_trig, format_chg_trig */ + {0x4001, 0x10}, /* total 128 black column */ + {0x4005, 0x10}, /* BLC target L */ + {0x4002, 0x27}, /* value used to limit BLC offset */ + {0x4009, 0x81}, /* final BLC offset limitation enable */ + {0x400b, 0x0c}, /* DCBLC on, DCBLC manual mode on */ + {0x401b, 0x00}, /* zero line R coefficient */ + {0x401d, 0x00}, /* zoro line T coefficient */ + {0x4020, 0x00}, /* Anchor left start H */ + {0x4021, 0x04}, /* Anchor left start L */ + {0x4022, 0x06}, /* Anchor left end H */ + {0x4023, 0x00}, /* Anchor left end L */ + {0x4024, 0x0f}, /* Anchor right start H */ + {0x4025, 0x2a}, /* Anchor right start L */ + {0x4026, 0x0f}, /* Anchor right end H */ + {0x4027, 0x2b}, /* Anchor right end L */ + {0x4028, 0x00}, /* top zero line start */ + {0x4029, 0x02}, /* top zero line number */ + {0x402a, 0x04}, /* top black line start */ + {0x402b, 0x04}, /* top black line number */ + {0x402c, 0x00}, /* bottom zero line start */ + {0x402d, 0x02}, /* bottom zoro line number */ + {0x402e, 0x04}, /* bottom black line start */ + {0x402f, 0x04}, /* bottom black line number */ + {0x401f, 0x00}, /* interpolation x/y disable, Anchor one disable */ + {0x4034, 0x3f}, + {0x403d, 0x04}, /* md_precision_en */ + {0x4300, 0xff}, /* clip max H */ + {0x4301, 0x00}, /* clip min H */ + {0x4302, 0x0f}, /* clip min L, clip max L */ + {0x4316, 0x00}, + {0x4500, 0x58}, + {0x4503, 0x18}, + {0x4600, 0x00}, + {0x4601, 0xcb}, + {0x481f, 0x32}, /* clk prepare min */ + {0x4837, 0x16}, /* global timing */ + {0x4850, 0x10}, /* lane 1 = 1, lane 0 = 0 */ + {0x4851, 0x32}, /* lane 3 = 3, lane 2 = 2 */ + {0x4b00, 0x2a}, + {0x4b0d, 0x00}, + {0x4d00, 0x04}, /* temperature sensor */ + {0x4d01, 0x18}, + {0x4d02, 0xc3}, + {0x4d03, 0xff}, + {0x4d04, 0xff}, + {0x4d05, 0xff}, /* temperature sensor */ + {0x5000, 0xfe}, /* lenc on, slave/master AWB gain/statistics enable */ + {0x5001, 0x01}, /* BLC on */ + {0x5002, 0x08}, /* H scale off, WBMATCH off, OTP_DPC */ + {0x5003, 0x20}, /* DPC_DBC buffer control enable, WB */ + {0x501e, 0x93}, /* enable digital gain */ + {0x5046, 0x12}, + {0x5780, 0x3e}, /* DPC */ + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x00}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, /* DPC */ + {0x5871, 0x0d}, /* Lenc */ + {0x5870, 0x18}, + {0x586e, 0x10}, + {0x586f, 0x08}, + {0x58f7, 0x01}, + {0x58f8, 0x3d}, /* Lenc */ + {0x5901, 0x00}, /* H skip off, V skip off */ + {0x5b00, 0x02}, /* OTP DPC start address */ + {0x5b01, 0x10}, /* OTP DPC start address */ + {0x5b02, 0x03}, /* OTP DPC end address */ + {0x5b03, 0xcf}, /* OTP DPC end address */ + {0x5b05, 0x6c}, /* recover method = 2b11, */ + {0x5e00, 0x00}, /* use 0x3ff to test pattern off */ + {0x5e01, 0x41}, /* window cut enable */ + {0x382d, 0x7f}, + {0x4825, 0x3a}, /* lpx_p_min */ + {0x4826, 0x40}, /* hs_prepare_min */ + {0x4808, 0x25}, /* wake up delay in 1/1024 s */ + {0x3763, 0x18}, + {0x3768, 0xcc}, + {0x470b, 0x28}, + {0x4202, 0x00}, + {0x400d, 0x10}, /* BLC offset trigger L */ + {0x4040, 0x04}, /* BLC gain th2 */ + {0x403e, 0x04}, /* BLC gain th1 */ + {0x4041, 0xc6}, /* BLC */ + {0x3007, 0x80}, + {0x400a, 0x01}, + {REG_NULL, 0x00}, +}; + +/* + * Xclk 24Mhz + * max_framerate 30fps + * mipi_datarate per lane 720Mbps + */ +static const struct regval ov8858_1632x1224_regs_2lane[] = { + /* + * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz. + * v00_01_00 (05/29/2014) : initial setting + * AM19 : 3617 <- 0xC0 + * AM20 : change FWC_6K_EN to be default 0x3618=0x5a + */ + {0x0100, 0x00}, + {0x3501, 0x4d}, /* exposure M */ + {0x3502, 0x40}, /* exposure L */ + {0x3778, 0x17}, + {0x3808, 0x06}, /* x output size H */ + {0x3809, 0x60}, /* x output size L */ + {0x380a, 0x04}, /* y output size H */ + {0x380b, 0xc8}, /* y output size L */ + {0x380c, 0x07}, /* HTS H */ + {0x380d, 0x88}, /* HTS L */ + {0x380e, 0x04}, /* VTS H */ + {0x380f, 0xdc}, /* VTS L */ + {0x3814, 0x03}, /* x odd inc */ + {0x3821, 0x67}, /* mirror on, bin on */ + {0x382a, 0x03}, /* y odd inc */ + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3f0a, 0x00}, + {0x4001, 0x10}, /* total 128 black column */ + {0x4022, 0x06}, /* Anchor left end H */ + {0x4023, 0x00}, /* Anchor left end L */ + {0x4025, 0x2a}, /* Anchor right start L */ + {0x4027, 0x2b}, /* Anchor right end L */ + {0x402b, 0x04}, /* top black line number */ + {0x402f, 0x04}, /* bottom black line number */ + {0x4500, 0x58}, + {0x4600, 0x00}, + {0x4601, 0xcb}, + {0x382d, 0x7f}, + {0x0100, 0x01}, + {REG_NULL, 0x00}, +}; + +/* + * Xclk 24Mhz + * max_framerate 15fps + * mipi_datarate per lane 720Mbps + */ +static const struct regval ov8858_3264x2448_regs_2lane[] = { + {0x0100, 0x00}, + {0x3501, 0x9a}, /* exposure M */ + {0x3502, 0x20}, /* exposure L */ + {0x3778, 0x1a}, + {0x3808, 0x0c}, /* x output size H */ + {0x3809, 0xc0}, /* x output size L */ + {0x380a, 0x09}, /* y output size H */ + {0x380b, 0x90}, /* y output size L */ + {0x380c, 0x07}, /* HTS H */ + {0x380d, 0x94}, /* HTS L */ + {0x380e, 0x09}, /* VTS H */ + {0x380f, 0xaa}, /* VTS L */ + {0x3814, 0x01}, /* x odd inc */ + {0x3821, 0x46}, /* mirror on, bin off */ + {0x382a, 0x01}, /* y odd inc */ + {0x3830, 0x06}, + {0x3836, 0x01}, + {0x3f0a, 0x00}, + {0x4001, 0x00}, /* total 256 black column */ + {0x4022, 0x0c}, /* Anchor left end H */ + {0x4023, 0x60}, /* Anchor left end L */ + {0x4025, 0x36}, /* Anchor right start L */ + {0x4027, 0x37}, /* Anchor right end L */ + {0x402b, 0x08}, /* top black line number */ + {0x402f, 0x08}, /* bottom black line number */ + {0x4500, 0x58}, + {0x4600, 0x01}, + {0x4601, 0x97}, + {0x382d, 0xff}, + {REG_NULL, 0x00}, +}; + +static const struct regval ov8858_global_regs_r2a_4lane[] = { + /* + * MIPI=720Mbps, SysClk=144Mhz,Dac Clock=360Mhz. + * v00_01_00 (05/29/2014) : initial setting + * AM19 : 3617 <- 0xC0 + * AM20 : change FWC_6K_EN to be default 0x3618=0x5a + */ + {0x0103, 0x01}, /* software reset for OVTATool only */ + {0x0103, 0x01}, /* software reset */ + {0x0100, 0x00}, /* software standby */ + {0x0302, 0x1e}, /* pll1_multi */ + {0x0303, 0x00}, /* pll1_divm */ + {0x0304, 0x03}, /* pll1_div_mipi */ + {0x030e, 0x00}, /* pll2_rdiv */ + {0x030f, 0x04}, /* pll2_divsp */ + {0x0312, 0x01}, /* pll2_pre_div0, pll2_r_divdac */ + {0x031e, 0x0c}, /* pll1_no_lat */ + {0x3600, 0x00}, + {0x3601, 0x00}, + {0x3602, 0x00}, + {0x3603, 0x00}, + {0x3604, 0x22}, + {0x3605, 0x20}, + {0x3606, 0x00}, + {0x3607, 0x20}, + {0x3608, 0x11}, + {0x3609, 0x28}, + {0x360a, 0x00}, + {0x360b, 0x05}, + {0x360c, 0xd4}, + {0x360d, 0x40}, + {0x360e, 0x0c}, + {0x360f, 0x20}, + {0x3610, 0x07}, + {0x3611, 0x20}, + {0x3612, 0x88}, + {0x3613, 0x80}, + {0x3614, 0x58}, + {0x3615, 0x00}, + {0x3616, 0x4a}, + {0x3617, 0x90}, + {0x3618, 0x5a}, + {0x3619, 0x70}, + {0x361a, 0x99}, + {0x361b, 0x0a}, + {0x361c, 0x07}, + {0x361d, 0x00}, + {0x361e, 0x00}, + {0x361f, 0x00}, + {0x3638, 0xff}, + {0x3633, 0x0f}, + {0x3634, 0x0f}, + {0x3635, 0x0f}, + {0x3636, 0x12}, + {0x3645, 0x13}, + {0x3646, 0x83}, + {0x364a, 0x07}, + {0x3015, 0x01}, + {0x3018, 0x72}, /* MIPI 4 lane */ + {0x3020, 0x93}, /* Clock switch output normal, pclk_div =/1 */ + {0x3022, 0x01}, /* pd_mipi enable when rst_sync */ + {0x3031, 0x0a}, /* MIPI 10-bit mode */ + {0x3034, 0x00}, + {0x3106, 0x01}, /* sclk_div, sclk_pre_div */ + {0x3305, 0xf1}, + {0x3308, 0x00}, + {0x3309, 0x28}, + {0x330a, 0x00}, + {0x330b, 0x20}, + {0x330c, 0x00}, + {0x330d, 0x00}, + {0x330e, 0x00}, + {0x330f, 0x40}, + {0x3307, 0x04}, + {0x3500, 0x00}, /* exposure H */ + {0x3501, 0x4d}, /* exposure M */ + {0x3502, 0x40}, /* exposure L */ + {0x3503, 0x80}, /* gain delay ?, exposure delay 1 frame, real gain */ + {0x3505, 0x80}, /* gain option */ + {0x3508, 0x02}, /* gain H */ + {0x3509, 0x00}, /* gain L */ + {0x350c, 0x00}, /* short gain H */ + {0x350d, 0x80}, /* short gain L */ + {0x3510, 0x00}, /* short exposure H */ + {0x3511, 0x02}, /* short exposure M */ + {0x3512, 0x00}, /* short exposure L */ + {0x3700, 0x30}, + {0x3701, 0x18}, + {0x3702, 0x50}, + {0x3703, 0x32}, + {0x3704, 0x28}, + {0x3705, 0x00}, + {0x3706, 0x82}, + {0x3707, 0x08}, + {0x3708, 0x48}, + {0x3709, 0x66}, + {0x370a, 0x01}, + {0x370b, 0x82}, + {0x370c, 0x07}, + {0x3718, 0x14}, + {0x3719, 0x31}, + {0x3712, 0x44}, + {0x3714, 0x24}, + {0x371e, 0x31}, + {0x371f, 0x7f}, + {0x3720, 0x0a}, + {0x3721, 0x0a}, + {0x3724, 0x0c}, + {0x3725, 0x02}, + {0x3726, 0x0c}, + {0x3728, 0x0a}, + {0x3729, 0x03}, + {0x372a, 0x06}, + {0x372b, 0xa6}, + {0x372c, 0xa6}, + {0x372d, 0xa6}, + {0x372e, 0x0c}, + {0x372f, 0x20}, + {0x3730, 0x02}, + {0x3731, 0x0c}, + {0x3732, 0x28}, + {0x3733, 0x10}, + {0x3734, 0x40}, + {0x3736, 0x30}, + {0x373a, 0x0a}, + {0x373b, 0x0b}, + {0x373c, 0x14}, + {0x373e, 0x06}, + {0x3750, 0x0a}, + {0x3751, 0x0e}, + {0x3755, 0x10}, + {0x3758, 0x00}, + {0x3759, 0x4c}, + {0x375a, 0x0c}, + {0x375b, 0x26}, + {0x375c, 0x20}, + {0x375d, 0x04}, + {0x375e, 0x00}, + {0x375f, 0x28}, + {0x3768, 0x22}, + {0x3769, 0x44}, + {0x376a, 0x44}, + {0x3761, 0x00}, + {0x3762, 0x00}, + {0x3763, 0x00}, + {0x3766, 0xff}, + {0x376b, 0x00}, + {0x3772, 0x46}, + {0x3773, 0x04}, + {0x3774, 0x2c}, + {0x3775, 0x13}, + {0x3776, 0x08}, + {0x3777, 0x00}, + {0x3778, 0x17}, + {0x37a0, 0x88}, + {0x37a1, 0x7a}, + {0x37a2, 0x7a}, + {0x37a3, 0x00}, + {0x37a4, 0x00}, + {0x37a5, 0x00}, + {0x37a6, 0x00}, + {0x37a7, 0x88}, + {0x37a8, 0x98}, + {0x37a9, 0x98}, + {0x3760, 0x00}, + {0x376f, 0x01}, + {0x37aa, 0x88}, + {0x37ab, 0x5c}, + {0x37ac, 0x5c}, + {0x37ad, 0x55}, + {0x37ae, 0x19}, + {0x37af, 0x19}, + {0x37b0, 0x00}, + {0x37b1, 0x00}, + {0x37b2, 0x00}, + {0x37b3, 0x84}, + {0x37b4, 0x84}, + {0x37b5, 0x60}, + {0x37b6, 0x00}, + {0x37b7, 0x00}, + {0x37b8, 0x00}, + {0x37b9, 0xff}, + {0x3800, 0x00}, /* x start H */ + {0x3801, 0x0c}, /* x start L */ + {0x3802, 0x00}, /* y start H */ + {0x3803, 0x0c}, /* y start L */ + {0x3804, 0x0c}, /* x end H */ + {0x3805, 0xd3}, /* x end L */ + {0x3806, 0x09}, /* y end H */ + {0x3807, 0xa3}, /* y end L */ + {0x3808, 0x06}, /* x output size H */ + {0x3809, 0x60}, /* x output size L */ + {0x380a, 0x04}, /* y output size H */ + {0x380b, 0xc8}, /* y output size L */ + {0x380c, 0x07}, /* HTS H */ + {0x380d, 0x88}, /* HTS L */ + {0x380e, 0x04}, /* VTS H */ + {0x380f, 0xdc}, /* VTS L */ + {0x3810, 0x00}, /* ISP x win H */ + {0x3811, 0x04}, /* ISP x win L */ + {0x3813, 0x02}, /* ISP y win L */ + {0x3814, 0x03}, /* x odd inc */ + {0x3815, 0x01}, /* x even inc */ + {0x3820, 0x00}, /* vflip off */ + {0x3821, 0x67}, /* mirror on, bin o */ + {0x382a, 0x03}, /* y odd inc */ + {0x382b, 0x01}, /* y even inc */ + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3837, 0x18}, + {0x3841, 0xff}, /* window auto size enable */ + {0x3846, 0x48}, + {0x3d85, 0x16}, /* OTP power up load data/setting enable */ + {0x3d8c, 0x73}, /* OTP setting start High */ + {0x3d8d, 0xde}, /* OTP setting start Low */ + {0x3f08, 0x10}, + {0x3f0a, 0x00}, + {0x4000, 0xf1}, /* out_range/format_chg/gain/exp_chg trig enable */ + {0x4001, 0x10}, /* total 128 black column */ + {0x4005, 0x10}, /* BLC target L */ + {0x4002, 0x27}, /* value used to limit BLC offset */ + {0x4009, 0x81}, /* final BLC offset limitation enable */ + {0x400b, 0x0c}, /* DCBLC on, DCBLC manual mode on */ + {0x401b, 0x00}, /* zero line R coefficient */ + {0x401d, 0x00}, /* zoro line T coefficient */ + {0x4020, 0x00}, /* Anchor left start H */ + {0x4021, 0x04}, /* Anchor left start L */ + {0x4022, 0x06}, /* Anchor left end H */ + {0x4023, 0x00}, /* Anchor left end L */ + {0x4024, 0x0f}, /* Anchor right start H */ + {0x4025, 0x2a}, /* Anchor right start L */ + {0x4026, 0x0f}, /* Anchor right end H */ + {0x4027, 0x2b}, /* Anchor right end L */ + {0x4028, 0x00}, /* top zero line start */ + {0x4029, 0x02}, /* top zero line number */ + {0x402a, 0x04}, /* top black line start */ + {0x402b, 0x04}, /* top black line number */ + {0x402c, 0x00}, /* bottom zero line start */ + {0x402d, 0x02}, /* bottom zoro line number */ + {0x402e, 0x04}, /* bottom black line start */ + {0x402f, 0x04}, /* bottom black line number */ + {0x401f, 0x00}, /* interpolation x/y disable, Anchor one disable */ + {0x4034, 0x3f}, + {0x403d, 0x04}, /* md_precision_en */ + {0x4300, 0xff}, /* clip max H */ + {0x4301, 0x00}, /* clip min H */ + {0x4302, 0x0f}, /* clip min L, clip max L */ + {0x4316, 0x00}, + {0x4500, 0x58}, + {0x4503, 0x18}, + {0x4600, 0x00}, + {0x4601, 0xcb}, + {0x481f, 0x32}, /* clk prepare min */ + {0x4837, 0x16}, /* global timing */ + {0x4850, 0x10}, /* lane 1 = 1, lane 0 = 0 */ + {0x4851, 0x32}, /* lane 3 = 3, lane 2 = 2 */ + {0x4b00, 0x2a}, + {0x4b0d, 0x00}, + {0x4d00, 0x04}, /* temperature sensor */ + {0x4d01, 0x18}, + {0x4d02, 0xc3}, + {0x4d03, 0xff}, + {0x4d04, 0xff}, + {0x4d05, 0xff}, /* temperature sensor */ + {0x5000, 0xfe}, /* lenc on, slave/master AWB gain/statistics enable */ + {0x5001, 0x01}, /* BLC on */ + {0x5002, 0x08}, /* WBMATCH sensor's gain, H scale/WBMATCH/OTP_DPC off */ + {0x5003, 0x20}, /* DPC_DBC buffer control enable, WB */ + {0x501e, 0x93}, /* enable digital gain */ + {0x5046, 0x12}, + {0x5780, 0x3e}, /* DPC */ + {0x5781, 0x0f}, + {0x5782, 0x44}, + {0x5783, 0x02}, + {0x5784, 0x01}, + {0x5785, 0x00}, + {0x5786, 0x00}, + {0x5787, 0x04}, + {0x5788, 0x02}, + {0x5789, 0x0f}, + {0x578a, 0xfd}, + {0x578b, 0xf5}, + {0x578c, 0xf5}, + {0x578d, 0x03}, + {0x578e, 0x08}, + {0x578f, 0x0c}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x00}, + {0x5793, 0x52}, + {0x5794, 0xa3}, /* DPC */ + {0x5871, 0x0d}, /* Lenc */ + {0x5870, 0x18}, + {0x586e, 0x10}, + {0x586f, 0x08}, + {0x58f7, 0x01}, + {0x58f8, 0x3d}, /* Lenc */ + {0x5901, 0x00}, /* H skip off, V skip off */ + {0x5b00, 0x02}, /* OTP DPC start address */ + {0x5b01, 0x10}, /* OTP DPC start address */ + {0x5b02, 0x03}, /* OTP DPC end address */ + {0x5b03, 0xcf}, /* OTP DPC end address */ + {0x5b05, 0x6c}, /* recover method = 2b11 */ + {0x5e00, 0x00}, /* use 0x3ff to test pattern off */ + {0x5e01, 0x41}, /* window cut enable */ + {0x382d, 0x7f}, + {0x4825, 0x3a}, /* lpx_p_min */ + {0x4826, 0x40}, /* hs_prepare_min */ + {0x4808, 0x25}, /* wake up delay in 1/1024 s */ + {0x3763, 0x18}, + {0x3768, 0xcc}, + {0x470b, 0x28}, + {0x4202, 0x00}, + {0x400d, 0x10}, /* BLC offset trigger L */ + {0x4040, 0x04}, /* BLC gain th2 */ + {0x403e, 0x04}, /* BLC gain th1 */ + {0x4041, 0xc6}, /* BLC */ + {0x3007, 0x80}, + {0x400a, 0x01}, + {REG_NULL, 0x00}, +}; + +/* + * Xclk 24Mhz + * max_framerate 60fps + * mipi_datarate per lane 720Mbps + */ +static const struct regval ov8858_1632x1224_regs_4lane[] = { + {0x0100, 0x00}, + {0x3501, 0x4d}, /* exposure M */ + {0x3502, 0x40}, /* exposure L */ + {0x3808, 0x06}, /* x output size H */ + {0x3809, 0x60}, /* x output size L */ + {0x380a, 0x04}, /* y output size H */ + {0x380b, 0xc8}, /* y output size L */ + {0x380c, 0x07}, /* HTS H */ + {0x380d, 0x88}, /* HTS L */ + {0x380e, 0x04}, /* VTS H */ + {0x380f, 0xdc}, /* VTS L */ + {0x3814, 0x03}, /* x odd inc */ + {0x3821, 0x67}, /* mirror on, bin on */ + {0x382a, 0x03}, /* y odd inc */ + {0x3830, 0x08}, + {0x3836, 0x02}, + {0x3f0a, 0x00}, + {0x4001, 0x10}, /* total 128 black column */ + {0x4022, 0x06}, /* Anchor left end H */ + {0x4023, 0x00}, /* Anchor left end L */ + {0x4025, 0x2a}, /* Anchor right start L */ + {0x4027, 0x2b}, /* Anchor right end L */ + {0x402b, 0x04}, /* top black line number */ + {0x402f, 0x04}, /* bottom black line number */ + {0x4500, 0x58}, + {0x4600, 0x00}, + {0x4601, 0xcb}, + {0x382d, 0x7f}, + {0x0100, 0x01}, + {REG_NULL, 0x00}, +}; + +/* + * Xclk 24Mhz + * max_framerate 30fps + * mipi_datarate per lane 720Mbps + */ +static const struct regval ov8858_3264x2448_regs_4lane[] = { + {0x0100, 0x00}, + {0x3501, 0x9a}, /* exposure M */ + {0x3502, 0x20}, /* exposure L */ + {0x3808, 0x0c}, /* x output size H */ + {0x3809, 0xc0}, /* x output size L */ + {0x380a, 0x09}, /* y output size H */ + {0x380b, 0x90}, /* y output size L */ + {0x380c, 0x07}, /* HTS H */ + {0x380d, 0x94}, /* HTS L */ + {0x380e, 0x09}, /* VTS H */ + {0x380f, 0xaa}, /* VTS L */ + {0x3814, 0x01}, /* x odd inc */ + {0x3821, 0x46}, /* mirror on, bin off */ + {0x382a, 0x01}, /* y odd inc */ + {0x3830, 0x06}, + {0x3836, 0x01}, + {0x3f0a, 0x00}, + {0x4001, 0x00}, /* total 256 black column */ + {0x4022, 0x0c}, /* Anchor left end H */ + {0x4023, 0x60}, /* Anchor left end L */ + {0x4025, 0x36}, /* Anchor right start L */ + {0x4027, 0x37}, /* Anchor right end L */ + {0x402b, 0x08}, /* top black line number */ + {0x402f, 0x08}, /* interpolation x/y disable, Anchor one disable */ + {0x4500, 0x58}, + {0x4600, 0x01}, + {0x4601, 0x97}, + {0x382d, 0xff}, + {REG_NULL, 0x00}, +}; + +static const struct ov8858_mode ov8858_modes[] = { + { + .width = 3264, + .height = 2448, + .exp_def = 2464, + .hts_def = 1940 * 2, + .vts_def = 2472, + .reg_modes = { + .mode_2lanes = ov8858_3264x2448_regs_2lane, + .mode_4lanes = ov8858_3264x2448_regs_4lane, + }, + }, + { + .width = 1632, + .height = 1224, + .exp_def = 1232, + .hts_def = 1928 * 2, + .vts_def = 1244, + .reg_modes = { + .mode_2lanes = ov8858_1632x1224_regs_2lane, + .mode_4lanes = ov8858_1632x1224_regs_4lane, + }, + }, +}; + +static const s64 link_freq_menu_items[] = { + OV8858_LINK_FREQ +}; + +static const char * const ov8858_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* ---------------------------------------------------------------------------- + * HW access + */ + +static int ov8858_write(struct ov8858 *ov8858, u32 reg, u32 val, int *err) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8858->subdev); + unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3; + u16 addr = reg & OV8858_REG_ADDR_MASK; + u8 buf[6]; + int ret; + + if (err && *err) + return *err; + + put_unaligned_be16(addr, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + + ret = i2c_master_send(client, buf, len + 2); + if (ret != len + 2) { + ret = ret < 0 ? ret : -EIO; + if (err) + *err = ret; + + dev_err(&client->dev, + "Failed to write reg %u: %d\n", addr, ret); + return ret; + } + + return 0; +} + +static int ov8858_write_array(struct ov8858 *ov8858, const struct regval *regs) +{ + unsigned int i; + int ret = 0; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; ++i) { + ov8858_write(ov8858, OV8858_REG_8BIT(regs[i].addr), + regs[i].val, &ret); + } + + return ret; +} + +static int ov8858_read(struct ov8858 *ov8858, u32 reg, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8858->subdev); + __be16 reg_addr_be = cpu_to_be16(reg & OV8858_REG_ADDR_MASK); + unsigned int len = (reg >> OV8858_REG_SIZE_SHIFT) & 3; + struct i2c_msg msgs[2]; + __be32 data_be = 0; + u8 *data_be_p; + int ret; + + 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)) { + ret = ret < 0 ? ret : -EIO; + dev_err(&client->dev, + "Failed to read reg %u: %d\n", reg, ret); + return ret; + } + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* ---------------------------------------------------------------------------- + * Streaming + */ + +static int ov8858_start_stream(struct ov8858 *ov8858, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *format; + const struct ov8858_mode *mode; + const struct regval *reg_list; + int ret; + + ret = ov8858_write_array(ov8858, ov8858->global_regs); + if (ret) + return ret; + + format = v4l2_subdev_get_pad_format(&ov8858->subdev, state, 0); + mode = v4l2_find_nearest_size(ov8858_modes, ARRAY_SIZE(ov8858_modes), + width, height, format->width, + format->height); + + reg_list = ov8858->num_lanes == 4 + ? mode->reg_modes.mode_4lanes + : mode->reg_modes.mode_2lanes; + + ret = ov8858_write_array(ov8858, reg_list); + if (ret) + return ret; + + /* 200 usec max to let PLL stabilize. */ + fsleep(200); + + ret = __v4l2_ctrl_handler_setup(&ov8858->ctrl_handler); + if (ret) + return ret; + + ret = ov8858_write(ov8858, OV8858_REG_SC_CTRL0100, + OV8858_MODE_STREAMING, NULL); + if (ret) + return ret; + + /* t5 (fixed) = 10msec before entering streaming state */ + fsleep(10000); + + return 0; +} + +static int ov8858_stop_stream(struct ov8858 *ov8858) +{ + return ov8858_write(ov8858, OV8858_REG_SC_CTRL0100, + OV8858_MODE_SW_STANDBY, NULL); +} + +static int ov8858_s_stream(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov8858 *ov8858 = sd_to_ov8858(sd); + struct v4l2_subdev_state *state; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + if (on) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto unlock_and_return; + + ret = ov8858_start_stream(ov8858, state); + if (ret) { + dev_err(&client->dev, "Failed to start streaming\n"); + pm_runtime_put_sync(&client->dev); + goto unlock_and_return; + } + } else { + ov8858_stop_stream(ov8858); + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + } + +unlock_and_return: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static const struct v4l2_subdev_video_ops ov8858_video_ops = { + .s_stream = ov8858_s_stream, +}; + +/* ---------------------------------------------------------------------------- + * Pad ops + */ + +static int ov8858_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct ov8858 *ov8858 = sd_to_ov8858(sd); + const struct ov8858_mode *mode; + s64 h_blank, vblank_def; + + mode = v4l2_find_nearest_size(ov8858_modes, ARRAY_SIZE(ov8858_modes), + width, height, fmt->format.width, + fmt->format.height); + + fmt->format.code = MEDIA_BUS_FMT_SBGGR10_1X10; + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + + /* Store the format in the current subdev state. */ + *v4l2_subdev_get_pad_format(sd, state, 0) = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + /* Adjust control limits when a new mode is applied. */ + h_blank = mode->hts_def - mode->width; + __v4l2_ctrl_modify_range(ov8858->hblank, h_blank, h_blank, 1, + h_blank); + + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(ov8858->vblank, vblank_def, + OV8858_VTS_MAX - mode->height, 1, + vblank_def); + + return 0; +} + +static int ov8858_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(ov8858_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) + return -EINVAL; + + fse->min_width = ov8858_modes[fse->index].width; + fse->max_width = ov8858_modes[fse->index].width; + fse->max_height = ov8858_modes[fse->index].height; + fse->min_height = ov8858_modes[fse->index].height; + + return 0; +} + +static int ov8858_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *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 ov8858_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + const struct ov8858_mode *def_mode = &ov8858_modes[0]; + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .format = { + .width = def_mode->width, + .height = def_mode->height, + }, + }; + + ov8858_set_fmt(sd, sd_state, &fmt); + + return 0; +} + +static const struct v4l2_subdev_pad_ops ov8858_pad_ops = { + .init_cfg = ov8858_init_cfg, + .enum_mbus_code = ov8858_enum_mbus_code, + .enum_frame_size = ov8858_enum_frame_sizes, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ov8858_set_fmt, +}; + +static const struct v4l2_subdev_core_ops ov8858_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops ov8858_subdev_ops = { + .core = &ov8858_core_ops, + .video = &ov8858_video_ops, + .pad = &ov8858_pad_ops, +}; + +/* ---------------------------------------------------------------------------- + * Controls handling + */ + +static int ov8858_enable_test_pattern(struct ov8858 *ov8858, u32 pattern) +{ + u32 val; + + if (pattern) + val = (pattern - 1) | OV8858_TEST_PATTERN_ENABLE; + else + val = OV8858_TEST_PATTERN_DISABLE; + + return ov8858_write(ov8858, OV8858_REG_TEST_PATTERN, val, NULL); +} + +static int ov8858_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov8858 *ov8858 = container_of(ctrl->handler, + struct ov8858, ctrl_handler); + + struct i2c_client *client = v4l2_get_subdevdata(&ov8858->subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_subdev_state *state; + u16 digi_gain; + s64 max_exp; + int ret; + + /* + * The control handler and the subdev state use the same mutex and the + * mutex is guaranteed to be locked: + * - by the core when s_ctrl is called int the VIDIOC_S_CTRL call path + * - by the driver when s_ctrl is called in the s_stream(1) call path + */ + state = v4l2_subdev_get_locked_active_state(&ov8858->subdev); + format = v4l2_subdev_get_pad_format(&ov8858->subdev, state, 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_exp = format->height + ctrl->val - OV8858_EXPOSURE_MARGIN; + __v4l2_ctrl_modify_range(ov8858->exposure, + ov8858->exposure->minimum, max_exp, + ov8858->exposure->step, + ov8858->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 exposure are fractional part */ + ret = ov8858_write(ov8858, OV8858_REG_LONG_EXPO, + ctrl->val << 4, NULL); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ov8858_write(ov8858, OV8858_REG_LONG_GAIN, + ctrl->val, NULL); + break; + case V4L2_CID_DIGITAL_GAIN: + /* + * Digital gain is assembled as: + * 0x350a[7:0] = dgain[13:6] + * 0x350b[5:0] = dgain[5:0] + * Reassemble the control value to write it in one go. + */ + digi_gain = (ctrl->val & OV8858_LONG_DIGIGAIN_L_MASK) + | ((ctrl->val & OV8858_LONG_DIGIGAIN_H_MASK) << + OV8858_LONG_DIGIGAIN_H_SHIFT); + ret = ov8858_write(ov8858, OV8858_REG_LONG_DIGIGAIN, + digi_gain, NULL); + break; + case V4L2_CID_VBLANK: + ret = ov8858_write(ov8858, OV8858_REG_VTS, + ctrl->val + format->height, NULL); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov8858_enable_test_pattern(ov8858, ctrl->val); + break; + default: + ret = -EINVAL; + dev_warn(&client->dev, "%s Unhandled id: 0x%x\n", + __func__, ctrl->id); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov8858_ctrl_ops = { + .s_ctrl = ov8858_set_ctrl, +}; + +/* ---------------------------------------------------------------------------- + * Power Management + */ + +static int ov8858_power_on(struct ov8858 *ov8858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8858->subdev); + struct device *dev = &client->dev; + unsigned long delay_us; + int ret; + + if (clk_get_rate(ov8858->xvclk) != OV8858_XVCLK_FREQ) + dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n"); + + ret = clk_prepare_enable(ov8858->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ov8858_supply_names), + ov8858->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + /* + * The chip manual only suggests 8192 cycles prior to first SCCB + * transaction, but a double sleep between the release of gpios + * helps with sporadic failures observed at probe time. + */ + delay_us = DIV_ROUND_UP(8192, OV8858_XVCLK_FREQ / 1000 / 1000); + + gpiod_set_value_cansleep(ov8858->reset_gpio, 0); + fsleep(delay_us); + gpiod_set_value_cansleep(ov8858->pwdn_gpio, 0); + fsleep(delay_us); + + return 0; + +disable_clk: + clk_disable_unprepare(ov8858->xvclk); + + return ret; +} + +static void ov8858_power_off(struct ov8858 *ov8858) +{ + gpiod_set_value_cansleep(ov8858->pwdn_gpio, 1); + clk_disable_unprepare(ov8858->xvclk); + gpiod_set_value_cansleep(ov8858->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(ov8858_supply_names), + ov8858->supplies); +} + +static int ov8858_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8858 *ov8858 = sd_to_ov8858(sd); + + return ov8858_power_on(ov8858); +} + +static int ov8858_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8858 *ov8858 = sd_to_ov8858(sd); + + ov8858_power_off(ov8858); + + return 0; +} + +static const struct dev_pm_ops ov8858_pm_ops = { + SET_RUNTIME_PM_OPS(ov8858_runtime_suspend, + ov8858_runtime_resume, NULL) +}; + +/* ---------------------------------------------------------------------------- + * Probe and initialization + */ + +static int ov8858_init_ctrls(struct ov8858 *ov8858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8858->subdev); + struct v4l2_ctrl_handler *handler = &ov8858->ctrl_handler; + const struct ov8858_mode *mode = &ov8858_modes[0]; + struct v4l2_fwnode_device_properties props; + s64 exposure_max, vblank_def; + unsigned int pixel_rate; + struct v4l2_ctrl *ctrl; + u32 h_blank; + int ret; + + ret = v4l2_ctrl_handler_init(handler, 10); + if (ret) + return ret; + + 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; + + /* pixel rate = link frequency * 2 * lanes / bpp */ + pixel_rate = OV8858_LINK_FREQ * 2 * ov8858->num_lanes / 10; + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, + 0, pixel_rate, 1, pixel_rate); + + h_blank = mode->hts_def - mode->width; + ov8858->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, + h_blank, h_blank, 1, h_blank); + if (ov8858->hblank) + ov8858->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts_def - mode->height; + ov8858->vblank = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OV8858_VTS_MAX - mode->height, + 1, vblank_def); + + exposure_max = mode->vts_def - OV8858_EXPOSURE_MARGIN; + ov8858->exposure = v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, + V4L2_CID_EXPOSURE, + OV8858_EXPOSURE_MIN, + exposure_max, OV8858_EXPOSURE_STEP, + mode->exp_def); + + v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV8858_LONG_GAIN_MIN, OV8858_LONG_GAIN_MAX, + OV8858_LONG_GAIN_STEP, OV8858_LONG_GAIN_DEFAULT); + + v4l2_ctrl_new_std(handler, &ov8858_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV8858_LONG_DIGIGAIN_MIN, OV8858_LONG_DIGIGAIN_MAX, + OV8858_LONG_DIGIGAIN_STEP, + OV8858_LONG_DIGIGAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(handler, &ov8858_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov8858_test_pattern_menu) - 1, + 0, 0, ov8858_test_pattern_menu); + + if (handler->error) { + ret = handler->error; + 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, &ov8858_ctrl_ops, + &props); + if (ret) + goto err_free_handler; + + ov8858->subdev.ctrl_handler = handler; + + return 0; + +err_free_handler: + dev_err(&client->dev, "Failed to init controls: %d\n", ret); + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int ov8858_check_sensor_id(struct ov8858 *ov8858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8858->subdev); + u32 id = 0; + int ret; + + ret = ov8858_read(ov8858, OV8858_REG_CHIP_ID, &id); + if (ret) + return ret; + + if (id != OV8858_CHIP_ID) { + dev_err(&client->dev, "Unexpected sensor id 0x%x\n", id); + return -ENODEV; + } + + ret = ov8858_read(ov8858, OV8858_REG_SUB_ID, &id); + if (ret) + return ret; + + dev_info(&client->dev, "Detected OV8858 sensor, revision 0x%x\n", id); + + if (id == OV8858_R2A) { + /* R2A supports 2 and 4 lanes modes. */ + ov8858->global_regs = ov8858->num_lanes == 4 + ? ov8858_global_regs_r2a_4lane + : ov8858_global_regs_r2a_2lane; + } else if (ov8858->num_lanes == 2) { + /* + * R1A only supports 2 lanes mode and it's only partially + * supported. + */ + ov8858->global_regs = ov8858_global_regs_r1a; + dev_warn(&client->dev, "R1A may not work well!\n"); + } else { + dev_err(&client->dev, + "Unsupported number of data lanes for R1A revision.\n"); + return -EINVAL; + } + + return 0; +} + +static int ov8858_configure_regulators(struct ov8858 *ov8858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov8858->subdev); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ov8858_supply_names); i++) + ov8858->supplies[i].supply = ov8858_supply_names[i]; + + return devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(ov8858_supply_names), + ov8858->supplies); +} + +static int ov8858_parse_of(struct ov8858 *ov8858) +{ + struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + struct i2c_client *client = v4l2_get_subdevdata(&ov8858->subdev); + struct device *dev = &client->dev; + struct fwnode_handle *endpoint; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "Failed to get endpoint\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(endpoint, &vep); + if (ret) { + dev_err(dev, "Failed to parse endpoint: %d\n", ret); + fwnode_handle_put(endpoint); + return ret; + } + + ov8858->num_lanes = vep.bus.mipi_csi2.num_data_lanes; + switch (ov8858->num_lanes) { + case 4: + case 2: + break; + default: + dev_err(dev, "Unsupported number of data lanes %u\n", + ov8858->num_lanes); + fwnode_handle_put(endpoint); + return -EINVAL; + } + + ov8858->subdev.fwnode = endpoint; + + return 0; +} + +static int ov8858_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct v4l2_subdev *sd; + struct ov8858 *ov8858; + int ret; + + ov8858 = devm_kzalloc(dev, sizeof(*ov8858), GFP_KERNEL); + if (!ov8858) + return -ENOMEM; + + ov8858->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(ov8858->xvclk)) + return dev_err_probe(dev, PTR_ERR(ov8858->xvclk), + "Failed to get xvclk\n"); + + ov8858->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ov8858->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ov8858->reset_gpio), + "Failed to get reset gpio\n"); + + ov8858->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(ov8858->pwdn_gpio)) + return dev_err_probe(dev, PTR_ERR(ov8858->pwdn_gpio), + "Failed to get powerdown gpio\n"); + + v4l2_i2c_subdev_init(&ov8858->subdev, client, &ov8858_subdev_ops); + + ret = ov8858_configure_regulators(ov8858); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = ov8858_parse_of(ov8858); + if (ret) + return ret; + + ret = ov8858_init_ctrls(ov8858); + if (ret) + goto err_put_fwnode; + + sd = &ov8858->subdev; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + ov8858->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov8858->pad); + if (ret < 0) + goto err_free_handler; + + sd->state_lock = ov8858->ctrl_handler.lock; + ret = v4l2_subdev_init_finalize(sd); + if (ret < 0) { + dev_err(&client->dev, "Subdev initialization error %d\n", ret); + goto err_clean_entity; + } + + ret = ov8858_power_on(ov8858); + if (ret) + goto err_clean_entity; + + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + + ret = ov8858_check_sensor_id(ov8858); + if (ret) + goto err_power_off; + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + ret = v4l2_async_register_subdev_sensor(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_power_off; + } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; + +err_power_off: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + ov8858_power_off(ov8858); +err_clean_entity: + media_entity_cleanup(&sd->entity); +err_free_handler: + v4l2_ctrl_handler_free(&ov8858->ctrl_handler); +err_put_fwnode: + fwnode_handle_put(ov8858->subdev.fwnode); + + return ret; +} + +static void ov8858_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov8858 *ov8858 = sd_to_ov8858(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&ov8858->ctrl_handler); + fwnode_handle_put(ov8858->subdev.fwnode); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + ov8858_power_off(ov8858); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id ov8858_of_match[] = { + { .compatible = "ovti,ov8858" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ov8858_of_match); + +static struct i2c_driver ov8858_i2c_driver = { + .driver = { + .name = "ov8858", + .pm = &ov8858_pm_ops, + .of_match_table = ov8858_of_match, + }, + .probe_new = &ov8858_probe, + .remove = &ov8858_remove, +}; + +module_i2c_driver(ov8858_i2c_driver); + +MODULE_DESCRIPTION("OmniVision OV8858 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index 37a55d53af56..7f46cac38aab 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -148,7 +148,6 @@ struct ov9282_mode { /** * struct ov9282 - ov9282 sensor device structure * @dev: Pointer to generic device - * @client: Pointer to i2c client * @sd: V4L2 sub-device * @pad: Media pad. Only one pad supported * @reset_gpio: Sensor reset gpio @@ -170,7 +169,6 @@ struct ov9282_mode { */ struct ov9282 { struct device *dev; - struct i2c_client *client; struct v4l2_subdev sd; struct media_pad pad; struct gpio_desc *reset_gpio; @@ -1144,10 +1142,9 @@ static int ov9282_parse_hw_config(struct ov9282 *ov9282) } ret = ov9282_configure_regulators(ov9282); - if (ret) { - dev_err(ov9282->dev, "Failed to get power regulators\n"); - return ret; - } + if (ret) + return dev_err_probe(ov9282->dev, ret, + "Failed to get power regulators\n"); rate = clk_get_rate(ov9282->inclk); if (rate != OV9282_INCLK_RATE) { diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-core.c b/drivers/media/i2c/s5c73m3/s5c73m3-core.c index 59b03b0860d5..7938a3327d3e 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-core.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-core.c @@ -26,7 +26,6 @@ #include <media/v4l2-device.h> #include <media/v4l2-subdev.h> #include <media/v4l2-mediabus.h> -#include <media/i2c/s5c73m3.h> #include <media/v4l2-fwnode.h> #include "s5c73m3.h" @@ -436,7 +435,7 @@ static int __s5c73m3_s_stream(struct s5c73m3 *state, struct v4l2_subdev *sd, state->streaming = !!on; if (!on) - return ret; + return 0; if (state->apply_fiv) { ret = s5c73m3_set_frame_rate(state); @@ -1522,25 +1521,16 @@ static const struct v4l2_subdev_ops oif_subdev_ops = { .video = &s5c73m3_oif_video_ops, }; -static int s5c73m3_get_platform_data(struct s5c73m3 *state) +static int s5c73m3_get_dt_data(struct s5c73m3 *state) { - struct i2c_client *c = state->i2c_client; - struct device *dev = &c->dev; - const struct s5c73m3_platform_data *pdata = dev->platform_data; + struct device *dev = &state->i2c_client->dev; struct device_node *node = dev->of_node; struct device_node *node_ep; struct v4l2_fwnode_endpoint ep = { .bus_type = 0 }; int ret; - if (!node) { - if (!pdata) { - dev_err(dev, "Platform data not specified\n"); - return -EINVAL; - } - - state->mclk_frequency = pdata->mclk_frequency; - return 0; - } + if (!node) + return -EINVAL; state->clock = devm_clk_get(dev, S5C73M3_CLK_NAME); if (IS_ERR(state->clock)) @@ -1603,7 +1593,7 @@ static int s5c73m3_probe(struct i2c_client *client) return -ENOMEM; state->i2c_client = client; - ret = s5c73m3_get_platform_data(state); + ret = s5c73m3_get_dt_data(state); if (ret < 0) return ret; diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c b/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c index e3543ae384ed..1c8103670fa2 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c @@ -23,7 +23,6 @@ #include <media/v4l2-device.h> #include <media/v4l2-subdev.h> #include <media/v4l2-mediabus.h> -#include <media/i2c/s5c73m3.h> #include "s5c73m3.h" diff --git a/drivers/media/i2c/s5c73m3/s5c73m3.h b/drivers/media/i2c/s5c73m3/s5c73m3.h index 1fc7df41c5ee..627e80cf5b72 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3.h +++ b/drivers/media/i2c/s5c73m3/s5c73m3.h @@ -16,7 +16,6 @@ #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-subdev.h> -#include <media/i2c/s5c73m3.h> #define DRIVER_NAME "S5C73M3" diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c index 86e70a980218..efeda3956f81 100644 --- a/drivers/media/i2c/saa7115.c +++ b/drivers/media/i2c/saa7115.c @@ -1804,9 +1804,9 @@ static int saa711x_detect_chip(struct i2c_client *client, return -ENODEV; } -static int saa711x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa711x_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct saa711x_state *state; struct v4l2_subdev *sd; struct v4l2_ctrl_handler *hdl; @@ -1951,7 +1951,7 @@ static struct i2c_driver saa711x_driver = { .driver = { .name = "saa7115", }, - .probe = saa711x_probe, + .probe_new = saa711x_probe, .remove = saa711x_remove, .id_table = saa711x_id, }; diff --git a/drivers/media/i2c/saa7127.c b/drivers/media/i2c/saa7127.c index 78c9388c2ea1..f98f3a1c38a9 100644 --- a/drivers/media/i2c/saa7127.c +++ b/drivers/media/i2c/saa7127.c @@ -708,9 +708,9 @@ static const struct v4l2_subdev_ops saa7127_ops = { /* ----------------------------------------------------------------------- */ -static int saa7127_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa7127_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct saa7127_state *state; struct v4l2_subdev *sd; struct v4l2_sliced_vbi_data vbi = { 0, 0, 0, 0 }; /* set to disabled */ @@ -810,7 +810,7 @@ static struct i2c_driver saa7127_driver = { .driver = { .name = "saa7127", }, - .probe = saa7127_probe, + .probe_new = saa7127_probe, .remove = saa7127_remove, .id_table = saa7127_id, }; diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c index 826baf4e064d..5dcabee6677d 100644 --- a/drivers/media/i2c/st-vgxy61.c +++ b/drivers/media/i2c/st-vgxy61.c @@ -5,7 +5,6 @@ * Copyright (C) 2022 STMicroelectronics SA */ -#include <asm-generic/unaligned.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> @@ -15,6 +14,9 @@ #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/units.h> + +#include <asm/unaligned.h> + #include <media/mipi-csi2.h> #include <media/v4l2-async.h> #include <media/v4l2-ctrls.h> diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c index d1f552bd81d4..4063754a6732 100644 --- a/drivers/media/i2c/tc358746.c +++ b/drivers/media/i2c/tc358746.c @@ -406,7 +406,7 @@ tc358746_apply_pll_config(struct tc358746 *tc358746) val = PLL_FRS(ilog2(post)) | RESETB | PLL_EN; mask = PLL_FRS_MASK | RESETB | PLL_EN; - tc358746_update_bits(tc358746, PLLCTL1_REG, mask, val); + err = tc358746_update_bits(tc358746, PLLCTL1_REG, mask, val); if (err) return err; @@ -988,6 +988,8 @@ static int __maybe_unused tc358746_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) { struct tc358746 *tc358746 = to_tc358746(sd); + u32 val; + int err; /* 32-bit registers starting from CLW_DPHYCONTTX */ reg->size = reg->reg < CLW_DPHYCONTTX_REG ? 2 : 4; @@ -995,12 +997,13 @@ tc358746_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) if (!pm_runtime_get_if_in_use(sd->dev)) return 0; - tc358746_read(tc358746, reg->reg, (u32 *)®->val); + err = tc358746_read(tc358746, reg->reg, &val); + reg->val = val; pm_runtime_mark_last_busy(sd->dev); pm_runtime_put_sync_autosuspend(sd->dev); - return 0; + return err; } static int __maybe_unused diff --git a/drivers/media/i2c/tda1997x.c b/drivers/media/i2c/tda1997x.c index 83931826cf6f..27f6393dc327 100644 --- a/drivers/media/i2c/tda1997x.c +++ b/drivers/media/i2c/tda1997x.c @@ -2519,9 +2519,9 @@ static struct snd_soc_component_driver tda1997x_codec_driver = { .endianness = 1, }; -static int tda1997x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tda1997x_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct tda1997x_state *state; struct tda1997x_platform_data *pdata; struct v4l2_subdev *sd; @@ -2834,7 +2834,7 @@ static struct i2c_driver tda1997x_i2c_driver = { .name = "tda1997x", .of_match_table = of_match_ptr(tda1997x_of_id), }, - .probe = tda1997x_probe, + .probe_new = tda1997x_probe, .remove = tda1997x_remove, .id_table = tda1997x_i2c_id, }; diff --git a/drivers/media/i2c/tvaudio.c b/drivers/media/i2c/tvaudio.c index 9f1ed078b661..a54c76d9e23b 100644 --- a/drivers/media/i2c/tvaudio.c +++ b/drivers/media/i2c/tvaudio.c @@ -1934,8 +1934,9 @@ static const struct v4l2_subdev_ops tvaudio_ops = { /* i2c registration */ -static int tvaudio_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int tvaudio_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct CHIPSTATE *chip; struct CHIPDESC *desc; struct v4l2_subdev *sd; @@ -2094,7 +2095,7 @@ static struct i2c_driver tvaudio_driver = { .driver = { .name = "tvaudio", }, - .probe = tvaudio_probe, + .probe_new = tvaudio_probe, .remove = tvaudio_remove, .id_table = tvaudio_id, }; diff --git a/drivers/media/i2c/tvp514x.c b/drivers/media/i2c/tvp514x.c index a746d96875f9..f294cae72b01 100644 --- a/drivers/media/i2c/tvp514x.c +++ b/drivers/media/i2c/tvp514x.c @@ -1017,14 +1017,14 @@ done: /** * tvp514x_probe() - decoder driver i2c probe handler * @client: i2c driver client device structure - * @id: i2c driver id table * * Register decoder as an i2c client device and V4L2 * device. */ static int -tvp514x_probe(struct i2c_client *client, const struct i2c_device_id *id) +tvp514x_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct tvp514x_platform_data *pdata = tvp514x_get_pdata(client); struct tvp514x_decoder *decoder; struct v4l2_subdev *sd; @@ -1208,7 +1208,7 @@ static struct i2c_driver tvp514x_driver = { .of_match_table = of_match_ptr(tvp514x_of_match), .name = TVP514X_MODULE_NAME, }, - .probe = tvp514x_probe, + .probe_new = tvp514x_probe, .remove = tvp514x_remove, .id_table = tvp514x_id, }; diff --git a/drivers/media/i2c/video-i2c.c b/drivers/media/i2c/video-i2c.c index f15ef2d13059..dddf9827b314 100644 --- a/drivers/media/i2c/video-i2c.c +++ b/drivers/media/i2c/video-i2c.c @@ -757,9 +757,9 @@ static void video_i2c_release(struct video_device *vdev) kfree(data); } -static int video_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int video_i2c_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct video_i2c_data *data; struct v4l2_device *v4l2_dev; struct vb2_queue *queue; @@ -959,7 +959,7 @@ static struct i2c_driver video_i2c_driver = { .of_match_table = video_i2c_of_match, .pm = &video_i2c_pm_ops, }, - .probe = video_i2c_probe, + .probe_new = video_i2c_probe, .remove = video_i2c_remove, .id_table = video_i2c_id_table, }; diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c index b8bcbc734eaf..e7216a985ba6 100644 --- a/drivers/media/mc/mc-entity.c +++ b/drivers/media/mc/mc-entity.c @@ -226,7 +226,13 @@ EXPORT_SYMBOL_GPL(media_entity_pads_init); * Graph traversal */ -/* +/** + * media_entity_has_pad_interdep - Check interdependency between two pads + * + * @entity: The entity + * @pad0: The first pad index + * @pad1: The second pad index + * * This function checks the interdependency inside the entity between @pad0 * and @pad1. If two pads are interdependent they are part of the same pipeline * and enabling one of the pads means that the other pad will become "locked" @@ -236,6 +242,13 @@ EXPORT_SYMBOL_GPL(media_entity_pads_init); * to check the dependency inside the entity between @pad0 and @pad1. If the * has_pad_interdep operation is not implemented, all pads of the entity are * considered to be interdependent. + * + * One of @pad0 and @pad1 must be a sink pad and the other one a source pad. + * The function returns false if both pads are sinks or sources. + * + * The caller must hold entity->graph_obj.mdev->mutex. + * + * Return: true if the pads are connected internally and false otherwise. */ static bool media_entity_has_pad_interdep(struct media_entity *entity, unsigned int pad0, unsigned int pad1) @@ -295,7 +308,7 @@ static struct media_entity *stack_pop(struct media_graph *graph) * * Reserve resources for graph walk in media device's current * state. The memory must be released using - * media_graph_walk_free(). + * media_graph_walk_cleanup(). * * Returns error on failure, zero on success. */ @@ -703,7 +716,7 @@ done: __must_check int __media_pipeline_start(struct media_pad *pad, struct media_pipeline *pipe) { - struct media_device *mdev = pad->entity->graph_obj.mdev; + struct media_device *mdev = pad->graph_obj.mdev; struct media_pipeline_pad *err_ppad; struct media_pipeline_pad *ppad; int ret; @@ -711,8 +724,8 @@ __must_check int __media_pipeline_start(struct media_pad *pad, lockdep_assert_held(&mdev->graph_mutex); /* - * If the entity is already part of a pipeline, that pipeline must - * be the same as the pipe given to media_pipeline_start(). + * If the pad is already part of a pipeline, that pipeline must be the + * same as the pipe given to media_pipeline_start(). */ if (WARN_ON(pad->pipe && pad->pipe != pipe)) return -EINVAL; @@ -851,7 +864,7 @@ EXPORT_SYMBOL_GPL(__media_pipeline_start); __must_check int media_pipeline_start(struct media_pad *pad, struct media_pipeline *pipe) { - struct media_device *mdev = pad->entity->graph_obj.mdev; + struct media_device *mdev = pad->graph_obj.mdev; int ret; mutex_lock(&mdev->graph_mutex); @@ -888,7 +901,7 @@ EXPORT_SYMBOL_GPL(__media_pipeline_stop); void media_pipeline_stop(struct media_pad *pad) { - struct media_device *mdev = pad->entity->graph_obj.mdev; + struct media_device *mdev = pad->graph_obj.mdev; mutex_lock(&mdev->graph_mutex); __media_pipeline_stop(pad); @@ -898,7 +911,7 @@ EXPORT_SYMBOL_GPL(media_pipeline_stop); __must_check int media_pipeline_alloc_start(struct media_pad *pad) { - struct media_device *mdev = pad->entity->graph_obj.mdev; + struct media_device *mdev = pad->graph_obj.mdev; struct media_pipeline *new_pipe = NULL; struct media_pipeline *pipe; int ret; @@ -906,7 +919,7 @@ __must_check int media_pipeline_alloc_start(struct media_pad *pad) mutex_lock(&mdev->graph_mutex); /* - * Is the entity already part of a pipeline? If not, we need to allocate + * Is the pad already part of a pipeline? If not, we need to allocate * a pipe. */ pipe = media_pad_pipeline(pad); @@ -932,6 +945,61 @@ out: } EXPORT_SYMBOL_GPL(media_pipeline_alloc_start); +struct media_pad * +__media_pipeline_pad_iter_next(struct media_pipeline *pipe, + struct media_pipeline_pad_iter *iter, + struct media_pad *pad) +{ + if (!pad) + iter->cursor = pipe->pads.next; + + if (iter->cursor == &pipe->pads) + return NULL; + + pad = list_entry(iter->cursor, struct media_pipeline_pad, list)->pad; + iter->cursor = iter->cursor->next; + + return pad; +} +EXPORT_SYMBOL_GPL(__media_pipeline_pad_iter_next); + +int media_pipeline_entity_iter_init(struct media_pipeline *pipe, + struct media_pipeline_entity_iter *iter) +{ + return media_entity_enum_init(&iter->ent_enum, pipe->mdev); +} +EXPORT_SYMBOL_GPL(media_pipeline_entity_iter_init); + +void media_pipeline_entity_iter_cleanup(struct media_pipeline_entity_iter *iter) +{ + media_entity_enum_cleanup(&iter->ent_enum); +} +EXPORT_SYMBOL_GPL(media_pipeline_entity_iter_cleanup); + +struct media_entity * +__media_pipeline_entity_iter_next(struct media_pipeline *pipe, + struct media_pipeline_entity_iter *iter, + struct media_entity *entity) +{ + if (!entity) + iter->cursor = pipe->pads.next; + + while (iter->cursor != &pipe->pads) { + struct media_pipeline_pad *ppad; + struct media_entity *entity; + + ppad = list_entry(iter->cursor, struct media_pipeline_pad, list); + entity = ppad->pad->entity; + iter->cursor = iter->cursor->next; + + if (!media_entity_enum_test_and_set(&iter->ent_enum, entity)) + return entity; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(__media_pipeline_entity_iter_next); + /* ----------------------------------------------------------------------------- * Links management */ diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig index dff0b450f387..480194543d05 100644 --- a/drivers/media/pci/Kconfig +++ b/drivers/media/pci/Kconfig @@ -27,6 +27,7 @@ if MEDIA_ANALOG_TV_SUPPORT source "drivers/media/pci/dt3155/Kconfig" source "drivers/media/pci/ivtv/Kconfig" +source "drivers/media/pci/saa7146/Kconfig" endif @@ -57,6 +58,7 @@ source "drivers/media/pci/pluto2/Kconfig" source "drivers/media/pci/pt1/Kconfig" source "drivers/media/pci/pt3/Kconfig" source "drivers/media/pci/smipcie/Kconfig" +source "drivers/media/pci/ttpci/Kconfig" endif diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile index 8f887a8a7f17..8bed619b7130 100644 --- a/drivers/media/pci/Makefile +++ b/drivers/media/pci/Makefile @@ -5,7 +5,8 @@ # Please keep it alphabetically sorted by directory # (e. g. LC_ALL=C sort Makefile) -obj-y += b2c2/ \ +obj-y += ttpci/ \ + b2c2/ \ pluto2/ \ dm1105/ \ pt1/ \ @@ -13,6 +14,7 @@ obj-y += b2c2/ \ mantis/ \ ngene/ \ ddbridge/ \ + saa7146/ \ smipcie/ \ netup_unidvb/ \ intel/ diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c index 390bd5ea3472..3b76a9d0383a 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c @@ -1843,6 +1843,9 @@ static void cio2_pci_remove(struct pci_dev *pci_dev) v4l2_device_unregister(&cio2->v4l2_dev); media_device_cleanup(&cio2->media_dev); mutex_destroy(&cio2->lock); + + pm_runtime_forbid(&pci_dev->dev); + pm_runtime_get_noresume(&pci_dev->dev); } static int __maybe_unused cio2_runtime_suspend(struct device *dev) diff --git a/drivers/media/pci/saa7134/saa7134-core.c b/drivers/media/pci/saa7134/saa7134-core.c index 96328b0af164..cf2871306987 100644 --- a/drivers/media/pci/saa7134/saa7134-core.c +++ b/drivers/media/pci/saa7134/saa7134-core.c @@ -978,7 +978,7 @@ static void saa7134_unregister_video(struct saa7134_dev *dev) } if (dev->radio_dev) { if (video_is_registered(dev->radio_dev)) - vb2_video_unregister_device(dev->radio_dev); + video_unregister_device(dev->radio_dev); else video_device_release(dev->radio_dev); dev->radio_dev = NULL; diff --git a/drivers/media/pci/saa7146/Kconfig b/drivers/media/pci/saa7146/Kconfig new file mode 100644 index 000000000000..3bbb68a0ed7b --- /dev/null +++ b/drivers/media/pci/saa7146/Kconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_HEXIUM_GEMINI + tristate "Hexium Gemini frame grabber" + depends on PCI && VIDEO_DEV && I2C + select VIDEO_SAA7146_VV + help + This is a video4linux driver for the Hexium Gemini frame + grabber card by Hexium. Please note that the Gemini Dual + card is *not* fully supported. + + To compile this driver as a module, choose M here: the + module will be called hexium_gemini. + +config VIDEO_HEXIUM_ORION + tristate "Hexium HV-PCI6 and Orion frame grabber" + depends on PCI && VIDEO_DEV && I2C + select VIDEO_SAA7146_VV + help + This is a video4linux driver for the Hexium HV-PCI6 and + Orion frame grabber cards by Hexium. + + To compile this driver as a module, choose M here: the + module will be called hexium_orion. + +config VIDEO_MXB + tristate "Siemens-Nixdorf 'Multimedia eXtension Board'" + depends on PCI && VIDEO_DEV && I2C + select VIDEO_SAA7146_VV + select VIDEO_TUNER + select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TDA9840 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TEA6415C if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TEA6420 if MEDIA_SUBDRV_AUTOSELECT + help + This is a video4linux driver for the 'Multimedia eXtension Board' + TV card by Siemens-Nixdorf. + + To compile this driver as a module, choose M here: the + module will be called mxb. diff --git a/drivers/media/pci/saa7146/Makefile b/drivers/media/pci/saa7146/Makefile new file mode 100644 index 000000000000..37c9336f83d5 --- /dev/null +++ b/drivers/media/pci/saa7146/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_MXB) += mxb.o +obj-$(CONFIG_VIDEO_HEXIUM_ORION) += hexium_orion.o +obj-$(CONFIG_VIDEO_HEXIUM_GEMINI) += hexium_gemini.o + +ccflags-y += -I$(srctree)/drivers/media/i2c diff --git a/drivers/media/pci/saa7146/hexium_gemini.c b/drivers/media/pci/saa7146/hexium_gemini.c new file mode 100644 index 000000000000..3947701cd6c7 --- /dev/null +++ b/drivers/media/pci/saa7146/hexium_gemini.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + hexium_gemini.c - v4l2 driver for Hexium Gemini frame grabber cards + + Visit http://www.mihu.de/linux/saa7146/ and follow the link + to "hexium" for further details about this card. + + Copyright (C) 2003 Michael Hunold <michael@mihu.de> + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DEBUG_VARIABLE debug + +#include <media/drv-intf/saa7146_vv.h> +#include <linux/module.h> +#include <linux/kernel.h> + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +static int hexium_num; + +#define HEXIUM_GEMINI 4 +#define HEXIUM_GEMINI_DUAL 5 + +#define HEXIUM_INPUTS 9 +static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { + { 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, +}; + +#define HEXIUM_AUDIOS 0 + +struct hexium_data +{ + s8 adr; + u8 byte; +}; + +#define HEXIUM_GEMINI_V_1_0 1 +#define HEXIUM_GEMINI_DUAL_V_1_0 2 + +struct hexium +{ + int type; + + struct video_device video_dev; + struct i2c_adapter i2c_adapter; + + int cur_input; /* current input */ + v4l2_std_id cur_std; /* current standard */ +}; + +/* Samsung KS0127B decoder default registers */ +static u8 hexium_ks0127b[0x100]={ +/*00*/ 0x00,0x52,0x30,0x40,0x01,0x0C,0x2A,0x10, +/*08*/ 0x00,0x00,0x00,0x60,0x00,0x00,0x0F,0x06, +/*10*/ 0x00,0x00,0xE4,0xC0,0x00,0x00,0x00,0x00, +/*18*/ 0x14,0x9B,0xFE,0xFF,0xFC,0xFF,0x03,0x22, +/*20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*28*/ 0x00,0x00,0x00,0x00,0x00,0x2C,0x9B,0x00, +/*30*/ 0x00,0x00,0x10,0x80,0x80,0x10,0x80,0x80, +/*38*/ 0x01,0x04,0x00,0x00,0x00,0x29,0xC0,0x00, +/*40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*48*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*50*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*58*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*68*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*70*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*78*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*88*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*90*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*98*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*A0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*A8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*B0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*B8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*C0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*C8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*D0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*D8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*E0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*E8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*F0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*F8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +static struct hexium_data hexium_pal[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_ntsc[] = { + { 0x01, 0x53 }, { 0x12, 0x04 }, { 0x2D, 0x23 }, { 0x2E, 0x81 }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_secam[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_input_select[] = { + { 0x02, 0x60 }, + { 0x02, 0x64 }, + { 0x02, 0x61 }, + { 0x02, 0x65 }, + { 0x02, 0x62 }, + { 0x02, 0x66 }, + { 0x02, 0x68 }, + { 0x02, 0x69 }, + { 0x02, 0x6A }, +}; + +/* fixme: h_offset = 0 for Hexium Gemini *Dual*, which + are currently *not* supported*/ +static struct saa7146_standard hexium_standards[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 28, .v_field = 288, + .h_offset = 1, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 28, .v_field = 240, + .h_offset = 1, .h_pixels = 640, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 28, .v_field = 288, + .h_offset = 1, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int hexium_init_done(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + union i2c_smbus_data data; + int i = 0; + + DEB_D("hexium_init_done called\n"); + + /* initialize the helper ics to useful values */ + for (i = 0; i < sizeof(hexium_ks0127b); i++) { + data.byte = hexium_ks0127b[i]; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { + pr_err("hexium_init_done() failed for address 0x%02x\n", + i); + } + } + + return 0; +} + +static int hexium_set_input(struct hexium *hexium, int input) +{ + union i2c_smbus_data data; + + DEB_D("\n"); + + data.byte = hexium_input_select[input].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, hexium_input_select[input].adr, I2C_SMBUS_BYTE_DATA, &data)) { + return -1; + } + + return 0; +} + +static int hexium_set_standard(struct hexium *hexium, struct hexium_data *vdec) +{ + union i2c_smbus_data data; + int i = 0; + + DEB_D("\n"); + + while (vdec[i].adr != -1) { + data.byte = vdec[i].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, vdec[i].adr, I2C_SMBUS_BYTE_DATA, &data)) { + pr_err("hexium_init_done: hexium_set_standard() failed for address 0x%02x\n", + i); + return -1; + } + i++; + } + return 0; +} + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + DEB_EE("VIDIOC_ENUMINPUT %d\n", i->index); + + if (i->index >= HEXIUM_INPUTS) + return -EINVAL; + + memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D("v4l2_ioctl: VIDIOC_ENUMINPUT %d\n", i->index); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + *input = hexium->cur_input; + + DEB_D("VIDIOC_G_INPUT: %d\n", *input); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE("VIDIOC_S_INPUT %d\n", input); + + if (input >= HEXIUM_INPUTS) + return -EINVAL; + + hexium->cur_input = input; + hexium_set_input(hexium, input); + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct hexium *hexium; + int ret; + + DEB_EE("\n"); + + hexium = kzalloc(sizeof(*hexium), GFP_KERNEL); + if (!hexium) + return -ENOMEM; + + dev->ext_priv = hexium; + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + strscpy(hexium->i2c_adapter.name, "hexium gemini", + sizeof(hexium->i2c_adapter.name)); + saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { + DEB_S("cannot register i2c-device. skipping.\n"); + kfree(hexium); + return -EFAULT; + } + + /* set HWControl GPIO number 2 */ + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + saa7146_write(dev, DD1_INIT, 0x07000700); + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + /* the rest */ + hexium->cur_input = 0; + hexium_init_done(dev); + + hexium_set_standard(hexium, hexium_pal); + hexium->cur_std = V4L2_STD_PAL; + + hexium_set_input(hexium, 0); + hexium->cur_input = 0; + + ret = saa7146_vv_init(dev, &vv_data); + if (ret) { + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return ret; + } + + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + ret = saa7146_register_device(&hexium->video_dev, dev, "hexium gemini", VFL_TYPE_VIDEO); + if (ret < 0) { + pr_err("cannot register capture v4l2 device. skipping.\n"); + saa7146_vv_release(dev); + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return ret; + } + + pr_info("found 'hexium gemini' frame grabber-%d\n", hexium_num); + hexium_num++; + + return 0; +} + +static int hexium_detach(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE("dev:%p\n", dev); + + saa7146_unregister_device(&hexium->video_dev, dev); + saa7146_vv_release(dev); + + hexium_num--; + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + if (V4L2_STD_PAL == std->id) { + hexium_set_standard(hexium, hexium_pal); + hexium->cur_std = V4L2_STD_PAL; + return 0; + } else if (V4L2_STD_NTSC == std->id) { + hexium_set_standard(hexium, hexium_ntsc); + hexium->cur_std = V4L2_STD_NTSC; + return 0; + } else if (V4L2_STD_SECAM == std->id) { + hexium_set_standard(hexium, hexium_secam); + hexium->cur_std = V4L2_STD_SECAM; + return 0; + } + + return -1; +} + +static struct saa7146_extension hexium_extension; + +static struct saa7146_pci_extension_data hexium_gemini_4bnc = { + .ext_priv = "Hexium Gemini (4 BNC)", + .ext = &hexium_extension, +}; + +static struct saa7146_pci_extension_data hexium_gemini_dual_4bnc = { + .ext_priv = "Hexium Gemini Dual (4 BNC)", + .ext = &hexium_extension, +}; + +static const struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2401, + .driver_data = (unsigned long) &hexium_gemini_4bnc, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2402, + .driver_data = (unsigned long) &hexium_gemini_dual_4bnc, + }, + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = HEXIUM_INPUTS, + .capabilities = 0, + .stds = &hexium_standards[0], + .num_stds = ARRAY_SIZE(hexium_standards), + .std_callback = &std_callback, +}; + +static struct saa7146_extension hexium_extension = { + .name = "hexium gemini", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .attach = hexium_attach, + .detach = hexium_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init hexium_init_module(void) +{ + if (0 != saa7146_register_extension(&hexium_extension)) { + DEB_S("failed to register extension\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit hexium_cleanup_module(void) +{ + saa7146_unregister_extension(&hexium_extension); +} + +module_init(hexium_init_module); +module_exit(hexium_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for Hexium Gemini frame grabber cards"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/saa7146/hexium_orion.c b/drivers/media/pci/saa7146/hexium_orion.c new file mode 100644 index 000000000000..2eb4bee16b71 --- /dev/null +++ b/drivers/media/pci/saa7146/hexium_orion.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + hexium_orion.c - v4l2 driver for the Hexium Orion frame grabber cards + + Visit http://www.mihu.de/linux/saa7146/ and follow the link + to "hexium" for further details about this card. + + Copyright (C) 2003 Michael Hunold <michael@mihu.de> + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DEBUG_VARIABLE debug + +#include <media/drv-intf/saa7146_vv.h> +#include <linux/module.h> +#include <linux/kernel.h> + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +static int hexium_num; + +#define HEXIUM_HV_PCI6_ORION 1 +#define HEXIUM_ORION_1SVHS_3BNC 2 +#define HEXIUM_ORION_4BNC 3 + +#define HEXIUM_INPUTS 9 +static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { + { 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 0, 0, V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, +}; + +#define HEXIUM_AUDIOS 0 + +struct hexium_data +{ + s8 adr; + u8 byte; +}; + +struct hexium +{ + int type; + struct video_device video_dev; + struct i2c_adapter i2c_adapter; + + int cur_input; /* current input */ +}; + +/* Philips SAA7110 decoder default registers */ +static u8 hexium_saa7110[53]={ +/*00*/ 0x4C,0x3C,0x0D,0xEF,0xBD,0xF0,0x00,0x00, +/*08*/ 0xF8,0xF8,0x60,0x60,0x40,0x86,0x18,0x90, +/*10*/ 0x00,0x2C,0x40,0x46,0x42,0x1A,0xFF,0xDA, +/*18*/ 0xF0,0x8B,0x00,0x00,0x00,0x00,0x00,0x00, +/*20*/ 0xD9,0x17,0x40,0x41,0x80,0x41,0x80,0x4F, +/*28*/ 0xFE,0x01,0x0F,0x0F,0x03,0x01,0x81,0x03, +/*30*/ 0x44,0x75,0x01,0x8C,0x03 +}; + +static struct { + struct hexium_data data[8]; +} hexium_input_select[] = { +{ + { /* cvbs 1 */ + { 0x06, 0x00 }, + { 0x20, 0xD9 }, + { 0x21, 0x17 }, // 0x16, + { 0x22, 0x40 }, + { 0x2C, 0x03 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, // ?? + { 0x21, 0x16 }, // 0x03, + } +}, { + { /* cvbs 2 */ + { 0x06, 0x00 }, + { 0x20, 0x78 }, + { 0x21, 0x07 }, // 0x03, + { 0x22, 0xD2 }, + { 0x2C, 0x83 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ? + { 0x21, 0x03 }, + } +}, { + { /* cvbs 3 */ + { 0x06, 0x00 }, + { 0x20, 0xBA }, + { 0x21, 0x07 }, // 0x05, + { 0x22, 0x91 }, + { 0x2C, 0x03 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x05 }, // 0x03, + } +}, { + { /* cvbs 4 */ + { 0x06, 0x00 }, + { 0x20, 0xD8 }, + { 0x21, 0x17 }, // 0x16, + { 0x22, 0x40 }, + { 0x2C, 0x03 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, // ?? + { 0x21, 0x16 }, // 0x03, + } +}, { + { /* cvbs 5 */ + { 0x06, 0x00 }, + { 0x20, 0xB8 }, + { 0x21, 0x07 }, // 0x05, + { 0x22, 0x91 }, + { 0x2C, 0x03 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x05 }, // 0x03, + } +}, { + { /* cvbs 6 */ + { 0x06, 0x00 }, + { 0x20, 0x7C }, + { 0x21, 0x07 }, // 0x03 + { 0x22, 0xD2 }, + { 0x2C, 0x83 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x03 }, + } +}, { + { /* y/c 1 */ + { 0x06, 0x80 }, + { 0x20, 0x59 }, + { 0x21, 0x17 }, + { 0x22, 0x42 }, + { 0x2C, 0xA3 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, + { 0x21, 0x12 }, + } +}, { + { /* y/c 2 */ + { 0x06, 0x80 }, + { 0x20, 0x9A }, + { 0x21, 0x17 }, + { 0x22, 0xB1 }, + { 0x2C, 0x13 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, + { 0x21, 0x14 }, + } +}, { + { /* y/c 3 */ + { 0x06, 0x80 }, + { 0x20, 0x3C }, + { 0x21, 0x27 }, + { 0x22, 0xC1 }, + { 0x2C, 0x23 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, + { 0x21, 0x21 }, + } +} +}; + +static struct saa7146_standard hexium_standards[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 16, .v_field = 288, + .h_offset = 1, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 16, .v_field = 240, + .h_offset = 1, .h_pixels = 640, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 16, .v_field = 288, + .h_offset = 1, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +/* this is only called for old HV-PCI6/Orion cards + without eeprom */ +static int hexium_probe(struct saa7146_dev *dev) +{ + struct hexium *hexium = NULL; + union i2c_smbus_data data; + int err = 0; + + DEB_EE("\n"); + + /* there are no hexium orion cards with revision 0 saa7146s */ + if (0 == dev->revision) { + return -EFAULT; + } + + hexium = kzalloc(sizeof(*hexium), GFP_KERNEL); + if (!hexium) + return -ENOMEM; + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + saa7146_write(dev, DD1_INIT, 0x01000100); + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + strscpy(hexium->i2c_adapter.name, "hexium orion", + sizeof(hexium->i2c_adapter.name)); + saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { + DEB_S("cannot register i2c-device. skipping.\n"); + kfree(hexium); + return -EFAULT; + } + + /* set SAA7110 control GPIO 0 */ + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTHI); + /* set HWControl GPIO number 2 */ + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + mdelay(10); + + /* detect newer Hexium Orion cards by subsystem ids */ + if (0x17c8 == dev->pci->subsystem_vendor && 0x0101 == dev->pci->subsystem_device) { + pr_info("device is a Hexium Orion w/ 1 SVHS + 3 BNC inputs\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_ORION_1SVHS_3BNC; + return 0; + } + + if (0x17c8 == dev->pci->subsystem_vendor && 0x2101 == dev->pci->subsystem_device) { + pr_info("device is a Hexium Orion w/ 4 BNC inputs\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_ORION_4BNC; + return 0; + } + + /* check if this is an old hexium Orion card by looking at + a saa7110 at address 0x4e */ + err = i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_READ, + 0x00, I2C_SMBUS_BYTE_DATA, &data); + if (err == 0) { + pr_info("device is a Hexium HV-PCI6/Orion (old)\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_HV_PCI6_ORION; + return 0; + } + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return -EFAULT; +} + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int hexium_init_done(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + union i2c_smbus_data data; + int i = 0; + + DEB_D("hexium_init_done called\n"); + + /* initialize the helper ics to useful values */ + for (i = 0; i < sizeof(hexium_saa7110); i++) { + data.byte = hexium_saa7110[i]; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { + pr_err("failed for address 0x%02x\n", i); + } + } + + return 0; +} + +static int hexium_set_input(struct hexium *hexium, int input) +{ + union i2c_smbus_data data; + int i = 0; + + DEB_D("\n"); + + for (i = 0; i < 8; i++) { + int adr = hexium_input_select[input].data[i].adr; + data.byte = hexium_input_select[input].data[i].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, adr, I2C_SMBUS_BYTE_DATA, &data)) { + return -1; + } + pr_debug("%d: 0x%02x => 0x%02x\n", input, adr, data.byte); + } + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + DEB_EE("VIDIOC_ENUMINPUT %d\n", i->index); + + if (i->index >= HEXIUM_INPUTS) + return -EINVAL; + + memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D("v4l2_ioctl: VIDIOC_ENUMINPUT %d\n", i->index); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + *input = hexium->cur_input; + + DEB_D("VIDIOC_G_INPUT: %d\n", *input); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + if (input >= HEXIUM_INPUTS) + return -EINVAL; + + hexium->cur_input = input; + hexium_set_input(hexium, input); + + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + int ret; + + DEB_EE("\n"); + + ret = saa7146_vv_init(dev, &vv_data); + if (ret) { + pr_err("Error in saa7146_vv_init()\n"); + return ret; + } + + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + if (0 != saa7146_register_device(&hexium->video_dev, dev, "hexium orion", VFL_TYPE_VIDEO)) { + pr_err("cannot register capture v4l2 device. skipping.\n"); + return -1; + } + + pr_err("found 'hexium orion' frame grabber-%d\n", hexium_num); + hexium_num++; + + /* the rest */ + hexium->cur_input = 0; + hexium_init_done(dev); + + return 0; +} + +static int hexium_detach(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE("dev:%p\n", dev); + + saa7146_unregister_device(&hexium->video_dev, dev); + saa7146_vv_release(dev); + + hexium_num--; + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) +{ + return 0; +} + +static struct saa7146_extension extension; + +static struct saa7146_pci_extension_data hexium_hv_pci6 = { + .ext_priv = "Hexium HV-PCI6 / Orion", + .ext = &extension, +}; + +static struct saa7146_pci_extension_data hexium_orion_1svhs_3bnc = { + .ext_priv = "Hexium HV-PCI6 / Orion (1 SVHS/3 BNC)", + .ext = &extension, +}; + +static struct saa7146_pci_extension_data hexium_orion_4bnc = { + .ext_priv = "Hexium HV-PCI6 / Orion (4 BNC)", + .ext = &extension, +}; + +static const struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long) &hexium_hv_pci6, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x0101, + .driver_data = (unsigned long) &hexium_orion_1svhs_3bnc, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2101, + .driver_data = (unsigned long) &hexium_orion_4bnc, + }, + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = HEXIUM_INPUTS, + .capabilities = 0, + .stds = &hexium_standards[0], + .num_stds = ARRAY_SIZE(hexium_standards), + .std_callback = &std_callback, +}; + +static struct saa7146_extension extension = { + .name = "hexium HV-PCI6 Orion", + .flags = 0, // SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .probe = hexium_probe, + .attach = hexium_attach, + .detach = hexium_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init hexium_init_module(void) +{ + if (0 != saa7146_register_extension(&extension)) { + DEB_S("failed to register extension\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit hexium_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(hexium_init_module); +module_exit(hexium_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for Hexium Orion frame grabber cards"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/saa7146/mxb.c b/drivers/media/pci/saa7146/mxb.c new file mode 100644 index 000000000000..7ded8f5b05cb --- /dev/null +++ b/drivers/media/pci/saa7146/mxb.c @@ -0,0 +1,873 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + mxb - v4l2 driver for the Multimedia eXtension Board + + Copyright (C) 1998-2006 Michael Hunold <michael@mihu.de> + + Visit http://www.themm.net/~mihu/linux/saa7146/mxb.html + for further details about this card. + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DEBUG_VARIABLE debug + +#include <media/drv-intf/saa7146_vv.h> +#include <media/tuner.h> +#include <media/v4l2-common.h> +#include <media/i2c/saa7115.h> +#include <linux/module.h> +#include <linux/kernel.h> + +#include "tea6415c.h" +#include "tea6420.h" + +#define MXB_AUDIOS 6 + +#define I2C_SAA7111A 0x24 +#define I2C_TDA9840 0x42 +#define I2C_TEA6415C 0x43 +#define I2C_TEA6420_1 0x4c +#define I2C_TEA6420_2 0x4d +#define I2C_TUNER 0x60 + +#define MXB_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) + +/* global variable */ +static int mxb_num; + +/* initial frequence the tuner will be tuned to. + in verden (lower saxony, germany) 4148 is a + channel called "phoenix" */ +static int freq = 4148; +module_param(freq, int, 0644); +MODULE_PARM_DESC(freq, "initial frequency the tuner will be tuned to while setup"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); + +#define MXB_INPUTS 4 +enum { TUNER, AUX1, AUX3, AUX3_YC }; + +static struct v4l2_input mxb_inputs[MXB_INPUTS] = { + { TUNER, "Tuner", V4L2_INPUT_TYPE_TUNER, 0x3f, 0, + V4L2_STD_PAL_BG | V4L2_STD_PAL_I, 0, V4L2_IN_CAP_STD }, + { AUX1, "AUX1", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { AUX3, "AUX3 Composite", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { AUX3_YC, "AUX3 S-Video", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, +}; + +/* this array holds the information, which port of the saa7146 each + input actually uses. the mxb uses port 0 for every input */ +static struct { + int hps_source; + int hps_sync; +} input_port_selection[MXB_INPUTS] = { + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, +}; + +/* this array holds the information of the audio source (mxb_audios), + which has to be switched corresponding to the video source (mxb_channels) */ +static int video_audio_connect[MXB_INPUTS] = + { 0, 1, 3, 3 }; + +struct mxb_routing { + u32 input; + u32 output; +}; + +/* these are the available audio sources, which can switched + to the line- and cd-output individually */ +static struct v4l2_audio mxb_audios[MXB_AUDIOS] = { + { + .index = 0, + .name = "Tuner", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 1, + .name = "AUX1", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 2, + .name = "AUX2", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 3, + .name = "AUX3", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 4, + .name = "Radio (X9)", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 5, + .name = "CD-ROM (X10)", + .capability = V4L2_AUDCAP_STEREO, + } +}; + +/* These are the necessary input-output-pins for bringing one audio source + (see above) to the CD-output. Note that gain is set to 0 in this table. */ +static struct mxb_routing TEA6420_cd[MXB_AUDIOS + 1][2] = { + { { 1, 1 }, { 1, 1 } }, /* Tuner */ + { { 5, 1 }, { 6, 1 } }, /* AUX 1 */ + { { 4, 1 }, { 6, 1 } }, /* AUX 2 */ + { { 3, 1 }, { 6, 1 } }, /* AUX 3 */ + { { 1, 1 }, { 3, 1 } }, /* Radio */ + { { 1, 1 }, { 2, 1 } }, /* CD-Rom */ + { { 6, 1 }, { 6, 1 } } /* Mute */ +}; + +/* These are the necessary input-output-pins for bringing one audio source + (see above) to the line-output. Note that gain is set to 0 in this table. */ +static struct mxb_routing TEA6420_line[MXB_AUDIOS + 1][2] = { + { { 2, 3 }, { 1, 2 } }, + { { 5, 3 }, { 6, 2 } }, + { { 4, 3 }, { 6, 2 } }, + { { 3, 3 }, { 6, 2 } }, + { { 2, 3 }, { 3, 2 } }, + { { 2, 3 }, { 2, 2 } }, + { { 6, 3 }, { 6, 2 } } /* Mute */ +}; + +struct mxb +{ + struct video_device video_dev; + struct video_device vbi_dev; + + struct i2c_adapter i2c_adapter; + + struct v4l2_subdev *saa7111a; + struct v4l2_subdev *tda9840; + struct v4l2_subdev *tea6415c; + struct v4l2_subdev *tuner; + struct v4l2_subdev *tea6420_1; + struct v4l2_subdev *tea6420_2; + + int cur_mode; /* current audio mode (mono, stereo, ...) */ + int cur_input; /* current input */ + int cur_audinput; /* current audio input */ + int cur_mute; /* current mute status */ + struct v4l2_frequency cur_freq; /* current frequency the tuner is tuned to */ +}; + +#define saa7111a_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->saa7111a, o, f, ##args) +#define tda9840_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tda9840, o, f, ##args) +#define tea6415c_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tea6415c, o, f, ##args) +#define tuner_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tuner, o, f, ##args) +#define call_all(dev, o, f, args...) \ + v4l2_device_call_until_err(&dev->v4l2_dev, 0, o, f, ##args) + +static void mxb_update_audmode(struct mxb *mxb) +{ + struct v4l2_tuner t = { + .audmode = mxb->cur_mode, + }; + + tda9840_call(mxb, tuner, s_tuner, &t); +} + +static inline void tea6420_route(struct mxb *mxb, int idx) +{ + v4l2_subdev_call(mxb->tea6420_1, audio, s_routing, + TEA6420_cd[idx][0].input, TEA6420_cd[idx][0].output, 0); + v4l2_subdev_call(mxb->tea6420_2, audio, s_routing, + TEA6420_cd[idx][1].input, TEA6420_cd[idx][1].output, 0); + v4l2_subdev_call(mxb->tea6420_1, audio, s_routing, + TEA6420_line[idx][0].input, TEA6420_line[idx][0].output, 0); + v4l2_subdev_call(mxb->tea6420_2, audio, s_routing, + TEA6420_line[idx][1].input, TEA6420_line[idx][1].output, 0); +} + +static struct saa7146_extension extension; + +static int mxb_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa7146_dev *dev = container_of(ctrl->handler, + struct saa7146_dev, ctrl_handler); + struct mxb *mxb = dev->ext_priv; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + mxb->cur_mute = ctrl->val; + /* switch the audio-source */ + tea6420_route(mxb, ctrl->val ? 6 : + video_audio_connect[mxb->cur_input]); + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct v4l2_ctrl_ops mxb_ctrl_ops = { + .s_ctrl = mxb_s_ctrl, +}; + +static int mxb_probe(struct saa7146_dev *dev) +{ + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + struct mxb *mxb = NULL; + + v4l2_ctrl_new_std(hdl, &mxb_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + if (hdl->error) + return hdl->error; + mxb = kzalloc(sizeof(struct mxb), GFP_KERNEL); + if (mxb == NULL) { + DEB_D("not enough kernel memory\n"); + return -ENOMEM; + } + + + snprintf(mxb->i2c_adapter.name, sizeof(mxb->i2c_adapter.name), "mxb%d", mxb_num); + + saa7146_i2c_adapter_prepare(dev, &mxb->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&mxb->i2c_adapter) < 0) { + DEB_S("cannot register i2c-device. skipping.\n"); + kfree(mxb); + return -EFAULT; + } + + mxb->saa7111a = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "saa7111", I2C_SAA7111A, NULL); + mxb->tea6420_1 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6420", I2C_TEA6420_1, NULL); + mxb->tea6420_2 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6420", I2C_TEA6420_2, NULL); + mxb->tea6415c = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6415c", I2C_TEA6415C, NULL); + mxb->tda9840 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tda9840", I2C_TDA9840, NULL); + mxb->tuner = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tuner", I2C_TUNER, NULL); + + /* check if all devices are present */ + if (!mxb->tea6420_1 || !mxb->tea6420_2 || !mxb->tea6415c || + !mxb->tda9840 || !mxb->saa7111a || !mxb->tuner) { + pr_err("did not find all i2c devices. aborting\n"); + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + return -ENODEV; + } + + /* all devices are present, probe was successful */ + + /* we store the pointer in our private data field */ + dev->ext_priv = mxb; + + v4l2_ctrl_handler_setup(hdl); + + return 0; +} + +/* some init data for the saa7740, the so-called 'sound arena module'. + there are no specs available, so we simply use some init values */ +static struct { + int length; + char data[9]; +} mxb_saa7740_init[] = { + { 3, { 0x80, 0x00, 0x00 } },{ 3, { 0x80, 0x89, 0x00 } }, + { 3, { 0x80, 0xb0, 0x0a } },{ 3, { 0x00, 0x00, 0x00 } }, + { 3, { 0x49, 0x00, 0x00 } },{ 3, { 0x4a, 0x00, 0x00 } }, + { 3, { 0x4b, 0x00, 0x00 } },{ 3, { 0x4c, 0x00, 0x00 } }, + { 3, { 0x4d, 0x00, 0x00 } },{ 3, { 0x4e, 0x00, 0x00 } }, + { 3, { 0x4f, 0x00, 0x00 } },{ 3, { 0x50, 0x00, 0x00 } }, + { 3, { 0x51, 0x00, 0x00 } },{ 3, { 0x52, 0x00, 0x00 } }, + { 3, { 0x53, 0x00, 0x00 } },{ 3, { 0x54, 0x00, 0x00 } }, + { 3, { 0x55, 0x00, 0x00 } },{ 3, { 0x56, 0x00, 0x00 } }, + { 3, { 0x57, 0x00, 0x00 } },{ 3, { 0x58, 0x00, 0x00 } }, + { 3, { 0x59, 0x00, 0x00 } },{ 3, { 0x5a, 0x00, 0x00 } }, + { 3, { 0x5b, 0x00, 0x00 } },{ 3, { 0x5c, 0x00, 0x00 } }, + { 3, { 0x5d, 0x00, 0x00 } },{ 3, { 0x5e, 0x00, 0x00 } }, + { 3, { 0x5f, 0x00, 0x00 } },{ 3, { 0x60, 0x00, 0x00 } }, + { 3, { 0x61, 0x00, 0x00 } },{ 3, { 0x62, 0x00, 0x00 } }, + { 3, { 0x63, 0x00, 0x00 } },{ 3, { 0x64, 0x00, 0x00 } }, + { 3, { 0x65, 0x00, 0x00 } },{ 3, { 0x66, 0x00, 0x00 } }, + { 3, { 0x67, 0x00, 0x00 } },{ 3, { 0x68, 0x00, 0x00 } }, + { 3, { 0x69, 0x00, 0x00 } },{ 3, { 0x6a, 0x00, 0x00 } }, + { 3, { 0x6b, 0x00, 0x00 } },{ 3, { 0x6c, 0x00, 0x00 } }, + { 3, { 0x6d, 0x00, 0x00 } },{ 3, { 0x6e, 0x00, 0x00 } }, + { 3, { 0x6f, 0x00, 0x00 } },{ 3, { 0x70, 0x00, 0x00 } }, + { 3, { 0x71, 0x00, 0x00 } },{ 3, { 0x72, 0x00, 0x00 } }, + { 3, { 0x73, 0x00, 0x00 } },{ 3, { 0x74, 0x00, 0x00 } }, + { 3, { 0x75, 0x00, 0x00 } },{ 3, { 0x76, 0x00, 0x00 } }, + { 3, { 0x77, 0x00, 0x00 } },{ 3, { 0x41, 0x00, 0x42 } }, + { 3, { 0x42, 0x10, 0x42 } },{ 3, { 0x43, 0x20, 0x42 } }, + { 3, { 0x44, 0x30, 0x42 } },{ 3, { 0x45, 0x00, 0x01 } }, + { 3, { 0x46, 0x00, 0x01 } },{ 3, { 0x47, 0x00, 0x01 } }, + { 3, { 0x48, 0x00, 0x01 } }, + { 9, { 0x01, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x21, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x09, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x29, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x11, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x31, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x19, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x39, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x05, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x25, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x0d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x2d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x15, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x35, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x1d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 9, { 0x3d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 3, { 0x80, 0xb3, 0x0a } }, + {-1, { 0 } } +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int mxb_init_done(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + struct i2c_msg msg; + struct tuner_setup tun_setup; + v4l2_std_id std = V4L2_STD_PAL_BG; + + int i, err = 0; + + /* mute audio on tea6420s */ + tea6420_route(mxb, 6); + + /* select video mode in saa7111a */ + saa7111a_call(mxb, video, s_std, std); + + /* select tuner-output on saa7111a */ + saa7111a_call(mxb, video, s_routing, SAA7115_COMPOSITE0, + SAA7111_FMT_CCIR, 0); + + /* select a tuner type */ + tun_setup.mode_mask = T_ANALOG_TV; + tun_setup.addr = ADDR_UNSET; + tun_setup.type = TUNER_PHILIPS_PAL; + tuner_call(mxb, tuner, s_type_addr, &tun_setup); + /* tune in some frequency on tuner */ + mxb->cur_freq.tuner = 0; + mxb->cur_freq.type = V4L2_TUNER_ANALOG_TV; + mxb->cur_freq.frequency = freq; + tuner_call(mxb, tuner, s_frequency, &mxb->cur_freq); + + /* set a default video standard */ + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 1); + saa7111a_call(mxb, video, s_std, std); + tuner_call(mxb, video, s_std, std); + + /* switch to tuner-channel on tea6415c */ + tea6415c_call(mxb, video, s_routing, 3, 17, 0); + + /* select tuner-output on multicable on tea6415c */ + tea6415c_call(mxb, video, s_routing, 3, 13, 0); + + /* the rest for mxb */ + mxb->cur_input = 0; + mxb->cur_audinput = video_audio_connect[mxb->cur_input]; + mxb->cur_mute = 1; + + mxb->cur_mode = V4L2_TUNER_MODE_STEREO; + mxb_update_audmode(mxb); + + /* check if the saa7740 (aka 'sound arena module') is present + on the mxb. if so, we must initialize it. due to lack of + information about the saa7740, the values were reverse + engineered. */ + msg.addr = 0x1b; + msg.flags = 0; + msg.len = mxb_saa7740_init[0].length; + msg.buf = &mxb_saa7740_init[0].data[0]; + + err = i2c_transfer(&mxb->i2c_adapter, &msg, 1); + if (err == 1) { + /* the sound arena module is a pos, that's probably the reason + philips refuses to hand out a datasheet for the saa7740... + it seems to screw up the i2c bus, so we disable fast irq + based i2c transactions here and rely on the slow and safe + polling method ... */ + extension.flags &= ~SAA7146_USE_I2C_IRQ; + for (i = 1; ; i++) { + if (-1 == mxb_saa7740_init[i].length) + break; + + msg.len = mxb_saa7740_init[i].length; + msg.buf = &mxb_saa7740_init[i].data[0]; + err = i2c_transfer(&mxb->i2c_adapter, &msg, 1); + if (err != 1) { + DEB_D("failed to initialize 'sound arena module'\n"); + goto err; + } + } + pr_info("'sound arena module' detected\n"); + } +err: + /* the rest for saa7146: you should definitely set some basic values + for the input-port handling of the saa7146. */ + + /* ext->saa has been filled by the core driver */ + + /* some stuff is done via variables */ + saa7146_set_hps_source_and_sync(dev, input_port_selection[mxb->cur_input].hps_source, + input_port_selection[mxb->cur_input].hps_sync); + + /* some stuff is done via direct write to the registers */ + + /* this is ugly, but because of the fact that this is completely + hardware dependend, it should be done directly... */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, DD1_INIT, 0x02000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + return 0; +} + +/* interrupt-handler. this gets called when irq_mask is != 0. + it must clear the interrupt-bits in irq_mask it has handled */ +/* +void mxb_irq_bh(struct saa7146_dev* dev, u32* irq_mask) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; +} +*/ + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + DEB_EE("VIDIOC_ENUMINPUT %d\n", i->index); + if (i->index >= MXB_INPUTS) + return -EINVAL; + memcpy(i, &mxb_inputs[i->index], sizeof(struct v4l2_input)); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + *i = mxb->cur_input; + + DEB_EE("VIDIOC_G_INPUT %d\n", *i); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + int err = 0; + int i = 0; + + DEB_EE("VIDIOC_S_INPUT %d\n", input); + + if (input >= MXB_INPUTS) + return -EINVAL; + + mxb->cur_input = input; + + saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, + input_port_selection[input].hps_sync); + + /* prepare switching of tea6415c and saa7111a; + have a look at the 'background'-file for further information */ + switch (input) { + case TUNER: + i = SAA7115_COMPOSITE0; + + err = tea6415c_call(mxb, video, s_routing, 3, 17, 0); + + /* connect tuner-output always to multicable */ + if (!err) + err = tea6415c_call(mxb, video, s_routing, 3, 13, 0); + break; + case AUX3_YC: + /* nothing to be done here. aux3_yc is + directly connected to the saa711a */ + i = SAA7115_SVIDEO1; + break; + case AUX3: + /* nothing to be done here. aux3 is + directly connected to the saa711a */ + i = SAA7115_COMPOSITE1; + break; + case AUX1: + i = SAA7115_COMPOSITE0; + err = tea6415c_call(mxb, video, s_routing, 1, 17, 0); + break; + } + + if (err) + return err; + + /* switch video in saa7111a */ + if (saa7111a_call(mxb, video, s_routing, i, SAA7111_FMT_CCIR, 0)) + pr_err("VIDIOC_S_INPUT: could not address saa7111a\n"); + + mxb->cur_audinput = video_audio_connect[input]; + /* switch the audio-source only if necessary */ + if (0 == mxb->cur_mute) + tea6420_route(mxb, mxb->cur_audinput); + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *t) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (t->index) { + DEB_D("VIDIOC_G_TUNER: channel %d does not have a tuner attached\n", + t->index); + return -EINVAL; + } + + DEB_EE("VIDIOC_G_TUNER: %d\n", t->index); + + memset(t, 0, sizeof(*t)); + strscpy(t->name, "TV Tuner", sizeof(t->name)); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + t->audmode = mxb->cur_mode; + return call_all(dev, tuner, g_tuner, t); +} + +static int vidioc_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *t) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (t->index) { + DEB_D("VIDIOC_S_TUNER: channel %d does not have a tuner attached\n", + t->index); + return -EINVAL; + } + + mxb->cur_mode = t->audmode; + return call_all(dev, tuner, s_tuner, t); +} + +static int vidioc_querystd(struct file *file, void *fh, v4l2_std_id *norm) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + + return call_all(dev, video, querystd, norm); +} + +static int vidioc_g_frequency(struct file *file, void *fh, struct v4l2_frequency *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (f->tuner) + return -EINVAL; + *f = mxb->cur_freq; + + DEB_EE("VIDIOC_G_FREQ: freq:0x%08x\n", mxb->cur_freq.frequency); + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *fh, const struct v4l2_frequency *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + struct saa7146_vv *vv = dev->vv_data; + + if (f->tuner) + return -EINVAL; + + if (V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + + DEB_EE("VIDIOC_S_FREQUENCY: freq:0x%08x\n", mxb->cur_freq.frequency); + + /* tune in desired frequency */ + tuner_call(mxb, tuner, s_frequency, f); + /* let the tuner subdev clamp the frequency to the tuner range */ + mxb->cur_freq = *f; + tuner_call(mxb, tuner, g_frequency, &mxb->cur_freq); + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + + if (mxb->cur_input) + return 0; + + /* hack: changing the frequency should invalidate the vbi-counter (=> alevt) */ + spin_lock(&dev->slock); + vv->vbi_fieldcount = 0; + spin_unlock(&dev->slock); + + return 0; +} + +static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a) +{ + if (a->index >= MXB_AUDIOS) + return -EINVAL; + *a = mxb_audios[a->index]; + return 0; +} + +static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_EE("VIDIOC_G_AUDIO\n"); + *a = mxb_audios[mxb->cur_audinput]; + return 0; +} + +static int vidioc_s_audio(struct file *file, void *fh, const struct v4l2_audio *a) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_D("VIDIOC_S_AUDIO %d\n", a->index); + if (a->index >= 32 || + !(mxb_inputs[mxb->cur_input].audioset & (1 << a->index))) + return -EINVAL; + + if (mxb->cur_audinput != a->index) { + mxb->cur_audinput = a->index; + tea6420_route(mxb, a->index); + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + } + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register(struct file *file, void *fh, struct v4l2_dbg_register *reg) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + + if (reg->reg > pci_resource_len(dev->pci, 0) - 4) + return -EINVAL; + reg->val = saa7146_read(dev, reg->reg); + reg->size = 4; + return 0; +} + +static int vidioc_s_register(struct file *file, void *fh, const struct v4l2_dbg_register *reg) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + + if (reg->reg > pci_resource_len(dev->pci, 0) - 4) + return -EINVAL; + saa7146_write(dev, reg->reg, reg->val); + return 0; +} +#endif + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int mxb_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct mxb *mxb; + int ret; + + DEB_EE("dev:%p\n", dev); + + ret = saa7146_vv_init(dev, &vv_data); + if (ret) { + ERR("Error in saa7146_vv_init()"); + return ret; + } + + if (mxb_probe(dev)) { + saa7146_vv_release(dev); + return -1; + } + mxb = (struct mxb *)dev->ext_priv; + + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + vv_data.vid_ops.vidioc_querystd = vidioc_querystd; + vv_data.vid_ops.vidioc_g_tuner = vidioc_g_tuner; + vv_data.vid_ops.vidioc_s_tuner = vidioc_s_tuner; + vv_data.vid_ops.vidioc_g_frequency = vidioc_g_frequency; + vv_data.vid_ops.vidioc_s_frequency = vidioc_s_frequency; + vv_data.vid_ops.vidioc_enumaudio = vidioc_enumaudio; + vv_data.vid_ops.vidioc_g_audio = vidioc_g_audio; + vv_data.vid_ops.vidioc_s_audio = vidioc_s_audio; +#ifdef CONFIG_VIDEO_ADV_DEBUG + vv_data.vid_ops.vidioc_g_register = vidioc_g_register; + vv_data.vid_ops.vidioc_s_register = vidioc_s_register; +#endif + if (saa7146_register_device(&mxb->video_dev, dev, "mxb", VFL_TYPE_VIDEO)) { + ERR("cannot register capture v4l2 device. skipping.\n"); + saa7146_vv_release(dev); + return -1; + } + + /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ + if (MXB_BOARD_CAN_DO_VBI(dev)) { + if (saa7146_register_device(&mxb->vbi_dev, dev, "mxb", VFL_TYPE_VBI)) { + ERR("cannot register vbi v4l2 device. skipping.\n"); + } + } + + pr_info("found Multimedia eXtension Board #%d\n", mxb_num); + + mxb_num++; + mxb_init_done(dev); + return 0; +} + +static int mxb_detach(struct saa7146_dev *dev) +{ + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_EE("dev:%p\n", dev); + + /* mute audio on tea6420s */ + tea6420_route(mxb, 6); + + saa7146_unregister_device(&mxb->video_dev,dev); + if (MXB_BOARD_CAN_DO_VBI(dev)) + saa7146_unregister_device(&mxb->vbi_dev, dev); + saa7146_vv_release(dev); + + mxb_num--; + + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *standard) +{ + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (V4L2_STD_PAL_I == standard->id) { + v4l2_std_id std = V4L2_STD_PAL_I; + + DEB_D("VIDIOC_S_STD: setting mxb for PAL_I\n"); + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 0); + saa7111a_call(mxb, video, s_std, std); + if (mxb->cur_input == 0) + tuner_call(mxb, video, s_std, std); + } else { + v4l2_std_id std = V4L2_STD_PAL_BG; + + if (mxb->cur_input) + std = standard->id; + DEB_D("VIDIOC_S_STD: setting mxb for PAL/NTSC/SECAM\n"); + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 1); + saa7111a_call(mxb, video, s_std, std); + if (mxb->cur_input == 0) + tuner_call(mxb, video, s_std, std); + } + return 0; +} + +static struct saa7146_standard standard[] = { + { + .name = "PAL-BG", .id = V4L2_STD_PAL_BG, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "PAL-I", .id = V4L2_STD_PAL_I, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x16, .v_field = 240, + .h_offset = 0x06, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x14, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +static struct saa7146_pci_extension_data mxb = { + .ext_priv = "Multimedia eXtension Board", + .ext = &extension, +}; + +static const struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long)&mxb, + }, { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = MXB_INPUTS, + .capabilities = V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_AUDIO, + .stds = &standard[0], + .num_stds = ARRAY_SIZE(standard), + .std_callback = &std_callback, +}; + +static struct saa7146_extension extension = { + .name = "Multimedia eXtension Board", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .attach = mxb_attach, + .detach = mxb_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init mxb_init_module(void) +{ + if (saa7146_register_extension(&extension)) { + DEB_S("failed to register extension\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit mxb_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(mxb_init_module); +module_exit(mxb_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for the Siemens-Nixdorf 'Multimedia eXtension board'"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/ttpci/Kconfig b/drivers/media/pci/ttpci/Kconfig new file mode 100644 index 000000000000..65a6832a6b96 --- /dev/null +++ b/drivers/media/pci/ttpci/Kconfig @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_BUDGET_CORE + tristate "SAA7146 DVB cards (aka Budget, Nova-PCI)" + depends on DVB_CORE && PCI && I2C + select VIDEO_SAA7146 + select TTPCI_EEPROM + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder. + +config DVB_BUDGET + tristate "Budget cards" + depends on DVB_BUDGET_CORE && I2C + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_VES1X93 if MEDIA_SUBDRV_AUTOSELECT + select DVB_VES1820 if MEDIA_SUBDRV_AUTOSELECT + select DVB_L64781 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA8083 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1420 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10086 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA826X if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6423 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT + help + Support for simple SAA7146 based DVB cards (so called Budget- + or Nova-PCI cards) without onboard MPEG2 decoder, and without + analog inputs or an onboard Common Interface connector. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget. + +config DVB_BUDGET_CI + tristate "Budget cards with onboard CI connector" + depends on DVB_BUDGET_CORE && I2C + select DVB_STV0297 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA827X if MEDIA_SUBDRV_AUTOSELECT + depends on RC_CORE + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder, but with onboard Common Interface connector. + + Note: The Common Interface is not yet supported by this driver + due to lack of information from the vendor. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-ci. + +config DVB_BUDGET_AV + tristate "Budget cards with analog video inputs" + depends on DVB_BUDGET_CORE && I2C + select VIDEO_SAA7146_VV + depends on VIDEO_DEV # dependencies of VIDEO_SAA7146_VV + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10021 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA8261 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TUA6100 if MEDIA_SUBDRV_AUTOSELECT + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder, but with one or more analog video inputs. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-av. diff --git a/drivers/media/pci/ttpci/Makefile b/drivers/media/pci/ttpci/Makefile new file mode 100644 index 000000000000..b0708f6e40cc --- /dev/null +++ b/drivers/media/pci/ttpci/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the kernel SAA7146 FULL TS DVB device driver +# + +obj-$(CONFIG_DVB_BUDGET_CORE) += budget-core.o +obj-$(CONFIG_DVB_BUDGET) += budget.o +obj-$(CONFIG_DVB_BUDGET_AV) += budget-av.o +obj-$(CONFIG_DVB_BUDGET_CI) += budget-ci.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/ +ccflags-y += -I $(srctree)/drivers/media/tuners +ccflags-y += -I $(srctree)/drivers/media/common diff --git a/drivers/media/pci/ttpci/budget-av.c b/drivers/media/pci/ttpci/budget-av.c new file mode 100644 index 000000000000..3cb83005cf09 --- /dev/null +++ b/drivers/media/pci/ttpci/budget-av.c @@ -0,0 +1,1622 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * budget-av.c: driver for the SAA7146 based Budget DVB cards + * with analog video in + * + * Compiled from various sources by Michael Hunold <michael@mihu.de> + * + * CI interface support (c) 2004 Olivier Gournet <ogournet@anevia.com> & + * Andrew de Quincey <adq_dvb@lidskialf.net> + * + * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de> + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * the project's page is at https://linuxtv.org + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "budget.h" +#include "stv0299.h" +#include "stb0899_drv.h" +#include "stb0899_reg.h" +#include "stb0899_cfg.h" +#include "tda8261.h" +#include "tda8261_cfg.h" +#include "tda1002x.h" +#include "tda1004x.h" +#include "tua6100.h" +#include "dvb-pll.h" +#include <media/drv-intf/saa7146_vv.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/spinlock.h> + +#include <media/dvb_ca_en50221.h> + +#define DEBICICAM 0x02420000 + +#define SLOTSTATUS_NONE 1 +#define SLOTSTATUS_PRESENT 2 +#define SLOTSTATUS_RESET 4 +#define SLOTSTATUS_READY 8 +#define SLOTSTATUS_OCCUPIED (SLOTSTATUS_PRESENT|SLOTSTATUS_RESET|SLOTSTATUS_READY) + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct budget_av { + struct budget budget; + struct video_device vd; + int cur_input; + int has_saa7113; + struct tasklet_struct ciintf_irq_tasklet; + int slot_status; + struct dvb_ca_en50221 ca; + u8 reinitialise_demod:1; +}; + +static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot); + + +/* GPIO Connections: + * 0 - Vcc/Reset (Reset is controlled by capacitor). Resets the frontend *AS WELL*! + * 1 - CI memory select 0=>IO memory, 1=>Attribute Memory + * 2 - CI Card Enable (Active Low) + * 3 - CI Card Detect + */ + +/**************************************************************************** + * INITIALIZATION + ****************************************************************************/ + +static u8 i2c_readreg(struct i2c_adapter *i2c, u8 id, u8 reg) +{ + u8 mm1[] = { 0x00 }; + u8 mm2[] = { 0x00 }; + struct i2c_msg msgs[2]; + + msgs[0].flags = 0; + msgs[1].flags = I2C_M_RD; + msgs[0].addr = msgs[1].addr = id / 2; + mm1[0] = reg; + msgs[0].len = 1; + msgs[1].len = 1; + msgs[0].buf = mm1; + msgs[1].buf = mm2; + + i2c_transfer(i2c, msgs, 2); + + return mm2[0]; +} + +static int i2c_readregs(struct i2c_adapter *i2c, u8 id, u8 reg, u8 * buf, u8 len) +{ + u8 mm1[] = { reg }; + struct i2c_msg msgs[2] = { + {.addr = id / 2,.flags = 0,.buf = mm1,.len = 1}, + {.addr = id / 2,.flags = I2C_M_RD,.buf = buf,.len = len} + }; + + if (i2c_transfer(i2c, msgs, 2) != 2) + return -EIO; + + return 0; +} + +static int i2c_writereg(struct i2c_adapter *i2c, u8 id, u8 reg, u8 val) +{ + u8 msg[2] = { reg, val }; + struct i2c_msg msgs; + + msgs.flags = 0; + msgs.addr = id / 2; + msgs.len = 2; + msgs.buf = msg; + return i2c_transfer(i2c, &msgs, 1); +} + +static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTHI); + udelay(1); + + result = ttpci_budget_debiread(&budget_av->budget, DEBICICAM, address & 0xfff, 1, 0, 1); + if (result == -ETIMEDOUT) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 1\n"); + } + return result; +} + +static int ciintf_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTHI); + udelay(1); + + result = ttpci_budget_debiwrite(&budget_av->budget, DEBICICAM, address & 0xfff, 1, value, 0, 1); + if (result == -ETIMEDOUT) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 2\n"); + } + return result; +} + +static int ciintf_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + udelay(1); + + result = ttpci_budget_debiread(&budget_av->budget, DEBICICAM, address & 3, 1, 0, 0); + if (result == -ETIMEDOUT) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 3\n"); + return -ETIMEDOUT; + } + return result; +} + +static int ciintf_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + udelay(1); + + result = ttpci_budget_debiwrite(&budget_av->budget, DEBICICAM, address & 3, 1, value, 0, 0); + if (result == -ETIMEDOUT) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 5\n"); + } + return result; +} + +static int ciintf_slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ciintf_slot_reset\n"); + budget_av->slot_status = SLOTSTATUS_RESET; + + saa7146_setgpio(saa, 2, SAA7146_GPIO_OUTHI); /* disable card */ + + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTHI); /* Vcc off */ + msleep(2); + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTLO); /* Vcc on */ + msleep(20); /* 20 ms Vcc settling time */ + + saa7146_setgpio(saa, 2, SAA7146_GPIO_OUTLO); /* enable card */ + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + msleep(20); + + /* reinitialise the frontend if necessary */ + if (budget_av->reinitialise_demod) + dvb_frontend_reinitialise(budget_av->budget.dvb_frontend); + + return 0; +} + +static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ciintf_slot_shutdown\n"); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + budget_av->slot_status = SLOTSTATUS_NONE; + + return 0; +} + +static int ciintf_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ciintf_slot_ts_enable: %d\n", budget_av->slot_status); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTA); + + return 0; +} + +static int ciintf_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + int result; + + if (slot != 0) + return -EINVAL; + + /* test the card detect line - needs to be done carefully + * since it never goes high for some CAMs on this interface (e.g. topuptv) */ + if (budget_av->slot_status == SLOTSTATUS_NONE) { + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + udelay(1); + if (saa7146_read(saa, PSR) & MASK_06) { + if (budget_av->slot_status == SLOTSTATUS_NONE) { + budget_av->slot_status = SLOTSTATUS_PRESENT; + pr_info("cam inserted A\n"); + } + } + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTLO); + } + + /* We also try and read from IO memory to work round the above detection bug. If + * there is no CAM, we will get a timeout. Only done if there is no cam + * present, since this test actually breaks some cams :( + * + * if the CI interface is not open, we also do the above test since we + * don't care if the cam has problems - we'll be resetting it on open() anyway */ + if ((budget_av->slot_status == SLOTSTATUS_NONE) || (!open)) { + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + result = ttpci_budget_debiread(&budget_av->budget, DEBICICAM, 0, 1, 0, 1); + if ((result >= 0) && (budget_av->slot_status == SLOTSTATUS_NONE)) { + budget_av->slot_status = SLOTSTATUS_PRESENT; + pr_info("cam inserted B\n"); + } else if (result < 0) { + if (budget_av->slot_status != SLOTSTATUS_NONE) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 5\n"); + return 0; + } + } + } + + /* read from attribute memory in reset/ready state to know when the CAM is ready */ + if (budget_av->slot_status == SLOTSTATUS_RESET) { + result = ciintf_read_attribute_mem(ca, slot, 0); + if (result == 0x1d) { + budget_av->slot_status = SLOTSTATUS_READY; + } + } + + /* work out correct return code */ + if (budget_av->slot_status != SLOTSTATUS_NONE) { + if (budget_av->slot_status & SLOTSTATUS_READY) { + return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY; + } + return DVB_CA_EN50221_POLL_CAM_PRESENT; + } + return 0; +} + +static int ciintf_init(struct budget_av *budget_av) +{ + struct saa7146_dev *saa = budget_av->budget.dev; + int result; + + memset(&budget_av->ca, 0, sizeof(struct dvb_ca_en50221)); + + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTLO); + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTLO); + saa7146_setgpio(saa, 2, SAA7146_GPIO_OUTLO); + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTLO); + + /* Enable DEBI pins */ + saa7146_write(saa, MC1, MASK_27 | MASK_11); + + /* register CI interface */ + budget_av->ca.owner = THIS_MODULE; + budget_av->ca.read_attribute_mem = ciintf_read_attribute_mem; + budget_av->ca.write_attribute_mem = ciintf_write_attribute_mem; + budget_av->ca.read_cam_control = ciintf_read_cam_control; + budget_av->ca.write_cam_control = ciintf_write_cam_control; + budget_av->ca.slot_reset = ciintf_slot_reset; + budget_av->ca.slot_shutdown = ciintf_slot_shutdown; + budget_av->ca.slot_ts_enable = ciintf_slot_ts_enable; + budget_av->ca.poll_slot_status = ciintf_poll_slot_status; + budget_av->ca.data = budget_av; + budget_av->budget.ci_present = 1; + budget_av->slot_status = SLOTSTATUS_NONE; + + if ((result = dvb_ca_en50221_init(&budget_av->budget.dvb_adapter, + &budget_av->ca, 0, 1)) != 0) { + pr_err("ci initialisation failed\n"); + goto error; + } + + pr_info("ci interface initialised\n"); + return 0; + +error: + saa7146_write(saa, MC1, MASK_27); + return result; +} + +static void ciintf_deinit(struct budget_av *budget_av) +{ + struct saa7146_dev *saa = budget_av->budget.dev; + + saa7146_setgpio(saa, 0, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 1, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 2, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + + /* release the CA device */ + dvb_ca_en50221_release(&budget_av->ca); + + /* disable DEBI pins */ + saa7146_write(saa, MC1, MASK_27); +} + + +static const u8 saa7113_tab[] = { + 0x01, 0x08, + 0x02, 0xc0, + 0x03, 0x33, + 0x04, 0x00, + 0x05, 0x00, + 0x06, 0xeb, + 0x07, 0xe0, + 0x08, 0x28, + 0x09, 0x00, + 0x0a, 0x80, + 0x0b, 0x47, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x01, + 0x0f, 0x44, + + 0x10, 0x08, + 0x11, 0x0c, + 0x12, 0x7b, + 0x13, 0x00, + 0x15, 0x00, 0x16, 0x00, 0x17, 0x00, + + 0x57, 0xff, + 0x40, 0x82, 0x58, 0x00, 0x59, 0x54, 0x5a, 0x07, + 0x5b, 0x83, 0x5e, 0x00, + 0xff +}; + +static int saa7113_init(struct budget_av *budget_av) +{ + struct budget *budget = &budget_av->budget; + struct saa7146_dev *saa = budget->dev; + const u8 *data = saa7113_tab; + + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTHI); + msleep(200); + + if (i2c_writereg(&budget->i2c_adap, 0x4a, 0x01, 0x08) != 1) { + dprintk(1, "saa7113 not found on KNC card\n"); + return -ENODEV; + } + + dprintk(1, "saa7113 detected and initializing\n"); + + while (*data != 0xff) { + i2c_writereg(&budget->i2c_adap, 0x4a, *data, *(data + 1)); + data += 2; + } + + dprintk(1, "saa7113 status=%02x\n", i2c_readreg(&budget->i2c_adap, 0x4a, 0x1f)); + + return 0; +} + +static int saa7113_setinput(struct budget_av *budget_av, int input) +{ + struct budget *budget = &budget_av->budget; + + if (1 != budget_av->has_saa7113) + return -ENODEV; + + if (input == 1) { + i2c_writereg(&budget->i2c_adap, 0x4a, 0x02, 0xc7); + i2c_writereg(&budget->i2c_adap, 0x4a, 0x09, 0x80); + } else if (input == 0) { + i2c_writereg(&budget->i2c_adap, 0x4a, 0x02, 0xc0); + i2c_writereg(&budget->i2c_adap, 0x4a, 0x09, 0x00); + } else + return -EINVAL; + + budget_av->cur_input = input; + return 0; +} + + +static int philips_su1278_ty_ci_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + u8 m1; + + aclk = 0xb5; + if (srate < 2000000) + bclk = 0x86; + else if (srate < 5000000) + bclk = 0x89; + else if (srate < 15000000) + bclk = 0x8f; + else if (srate < 45000000) + bclk = 0x95; + + m1 = 0x14; + if (srate < 4000000) + m1 = 0x10; + + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + stv0299_writereg(fe, 0x0f, 0x80 | m1); + + return 0; +} + +static int philips_su1278_ty_ci_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 div; + u8 buf[4]; + struct budget *budget = (struct budget *) fe->dvb->priv; + struct i2c_msg msg = {.addr = 0x61,.flags = 0,.buf = buf,.len = sizeof(buf) }; + + if ((c->frequency < 950000) || (c->frequency > 2150000)) + return -EINVAL; + + div = (c->frequency + (125 - 1)) / 125; /* round correctly */ + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x80 | ((div & 0x18000) >> 10) | 4; + buf[3] = 0x20; + + if (c->symbol_rate < 4000000) + buf[3] |= 1; + + if (c->frequency < 1250000) + buf[3] |= 0; + else if (c->frequency < 1550000) + buf[3] |= 0x40; + else if (c->frequency < 2050000) + buf[3] |= 0x80; + else if (c->frequency < 2150000) + buf[3] |= 0xC0; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static u8 typhoon_cinergy1200s_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb9, + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x92, + 0xff, 0xff +}; + +static const struct stv0299_config typhoon_config = { + .demod_address = 0x68, + .inittab = typhoon_cinergy1200s_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_su1278_ty_ci_set_symbol_rate, +}; + + +static const struct stv0299_config cinergy_1200s_config = { + .demod_address = 0x68, + .inittab = typhoon_cinergy1200s_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_0, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_su1278_ty_ci_set_symbol_rate, +}; + +static const struct stv0299_config cinergy_1200s_1894_0010_config = { + .demod_address = 0x68, + .inittab = typhoon_cinergy1200s_inittab, + .mclk = 88000000UL, + .invert = 1, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_su1278_ty_ci_set_symbol_rate, +}; + +static int philips_cu1216_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = (struct budget *) fe->dvb->priv; + u8 buf[6]; + struct i2c_msg msg = {.addr = 0x60,.flags = 0,.buf = buf,.len = sizeof(buf) }; + int i; + +#define CU1216_IF 36125000 +#define TUNER_MUL 62500 + + u32 div = (c->frequency + CU1216_IF + TUNER_MUL / 2) / TUNER_MUL; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0xce; + buf[3] = (c->frequency < 150000000 ? 0x01 : + c->frequency < 445000000 ? 0x02 : 0x04); + buf[4] = 0xde; + buf[5] = 0x20; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + + /* wait for the pll lock */ + msg.flags = I2C_M_RD; + msg.len = 1; + for (i = 0; i < 20; i++) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) == 1 && (buf[0] & 0x40)) + break; + msleep(10); + } + + /* switch the charge pump to the lower current */ + msg.flags = 0; + msg.len = 2; + msg.buf = &buf[2]; + buf[2] &= ~0x40; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static struct tda1002x_config philips_cu1216_config = { + .demod_address = 0x0c, + .invert = 1, +}; + +static struct tda1002x_config philips_cu1216_config_altaddress = { + .demod_address = 0x0d, + .invert = 0, +}; + +static struct tda10023_config philips_cu1216_tda10023_config = { + .demod_address = 0x0c, + .invert = 1, +}; + +static int philips_tu1216_tuner_init(struct dvb_frontend *fe) +{ + struct budget *budget = (struct budget *) fe->dvb->priv; + static u8 tu1216_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + struct i2c_msg tuner_msg = {.addr = 0x60,.flags = 0,.buf = tu1216_init,.len = sizeof(tu1216_init) }; + + // setup PLL configuration + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + msleep(1); + + return 0; +} + +static int philips_tu1216_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = (struct budget *) fe->dvb->priv; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr = 0x60,.flags = 0,.buf = tuner_buf,.len = + sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = c->frequency + 36166000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + // determine band + if (c->frequency < 49000000) + return -EINVAL; + else if (c->frequency < 161000000) + band = 1; + else if (c->frequency < 444000000) + band = 2; + else if (c->frequency < 861000000) + band = 4; + else + return -EINVAL; + + // setup PLL filter + switch (c->bandwidth_hz) { + case 6000000: + filter = 0; + break; + + case 7000000: + filter = 0; + break; + + case 8000000: + filter = 1; + break; + + default: + return -EINVAL; + } + + // calculate divisor + // ((36166000+((1000000/6)/2)) + Finput)/(1000000/6) + tuner_frequency = (((c->frequency / 1000) * 6) + 217496) / 1000; + + // setup tuner buffer + tuner_buf[0] = (tuner_frequency >> 8) & 0x7f; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + return 0; +} + +static int philips_tu1216_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct budget *budget = (struct budget *) fe->dvb->priv; + + return request_firmware(fw, name, &budget->dev->pci->dev); +} + +static struct tda1004x_config philips_tu1216_config = { + + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 1, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .request_firmware = philips_tu1216_request_firmware, +}; + +static u8 philips_sd1878_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7d, + 0x05, 0x35, + 0x06, 0x40, + 0x07, 0x00, + 0x08, 0x43, + 0x09, 0x02, + 0x0C, 0x51, + 0x0D, 0x82, + 0x0E, 0x23, + 0x10, 0x3f, + 0x11, 0x84, + 0x12, 0xb9, + 0x15, 0xc9, + 0x16, 0x19, + 0x17, 0x8c, + 0x18, 0x59, + 0x19, 0xf8, + 0x1a, 0xfe, + 0x1c, 0x7f, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, + 0x29, 0x28, + 0x2a, 0x14, + 0x2b, 0x0f, + 0x2c, 0x09, + 0x2d, 0x09, + 0x31, 0x1f, + 0x32, 0x19, + 0x33, 0xfc, + 0x34, 0x93, + 0xff, 0xff +}; + +static int philips_sd1878_ci_set_symbol_rate(struct dvb_frontend *fe, + u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + u8 m1; + + aclk = 0xb5; + if (srate < 2000000) + bclk = 0x86; + else if (srate < 5000000) + bclk = 0x89; + else if (srate < 15000000) + bclk = 0x8f; + else if (srate < 45000000) + bclk = 0x95; + + m1 = 0x14; + if (srate < 4000000) + m1 = 0x10; + + stv0299_writereg(fe, 0x0e, 0x23); + stv0299_writereg(fe, 0x0f, 0x94); + stv0299_writereg(fe, 0x10, 0x39); + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x15, 0xc9); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + stv0299_writereg(fe, 0x0f, 0x80 | m1); + + return 0; +} + +static const struct stv0299_config philips_sd1878_config = { + .demod_address = 0x68, + .inittab = philips_sd1878_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_sd1878_ci_set_symbol_rate, +}; + +/* KNC1 DVB-S (STB0899) Inittab */ +static const struct stb0899_s1_reg knc1_stb0899_s1_init_1[] = { + + { STB0899_DEV_ID , 0x81 }, + { STB0899_DISCNTRL1 , 0x32 }, + { STB0899_DISCNTRL2 , 0x80 }, + { STB0899_DISRX_ST0 , 0x04 }, + { STB0899_DISRX_ST1 , 0x00 }, + { STB0899_DISPARITY , 0x00 }, + { STB0899_DISSTATUS , 0x20 }, + { STB0899_DISF22 , 0x8c }, + { STB0899_DISF22RX , 0x9a }, + { STB0899_SYSREG , 0x0b }, + { STB0899_ACRPRESC , 0x11 }, + { STB0899_ACRDIV1 , 0x0a }, + { STB0899_ACRDIV2 , 0x05 }, + { STB0899_DACR1 , 0x00 }, + { STB0899_DACR2 , 0x00 }, + { STB0899_OUTCFG , 0x00 }, + { STB0899_MODECFG , 0x00 }, + { STB0899_IRQSTATUS_3 , 0x30 }, + { STB0899_IRQSTATUS_2 , 0x00 }, + { STB0899_IRQSTATUS_1 , 0x00 }, + { STB0899_IRQSTATUS_0 , 0x00 }, + { STB0899_IRQMSK_3 , 0xf3 }, + { STB0899_IRQMSK_2 , 0xfc }, + { STB0899_IRQMSK_1 , 0xff }, + { STB0899_IRQMSK_0 , 0xff }, + { STB0899_IRQCFG , 0x00 }, + { STB0899_I2CCFG , 0x88 }, + { STB0899_I2CRPT , 0x58 }, /* Repeater=8, Stop=disabled */ + { STB0899_IOPVALUE5 , 0x00 }, + { STB0899_IOPVALUE4 , 0x20 }, + { STB0899_IOPVALUE3 , 0xc9 }, + { STB0899_IOPVALUE2 , 0x90 }, + { STB0899_IOPVALUE1 , 0x40 }, + { STB0899_IOPVALUE0 , 0x00 }, + { STB0899_GPIO00CFG , 0x82 }, + { STB0899_GPIO01CFG , 0x82 }, + { STB0899_GPIO02CFG , 0x82 }, + { STB0899_GPIO03CFG , 0x82 }, + { STB0899_GPIO04CFG , 0x82 }, + { STB0899_GPIO05CFG , 0x82 }, + { STB0899_GPIO06CFG , 0x82 }, + { STB0899_GPIO07CFG , 0x82 }, + { STB0899_GPIO08CFG , 0x82 }, + { STB0899_GPIO09CFG , 0x82 }, + { STB0899_GPIO10CFG , 0x82 }, + { STB0899_GPIO11CFG , 0x82 }, + { STB0899_GPIO12CFG , 0x82 }, + { STB0899_GPIO13CFG , 0x82 }, + { STB0899_GPIO14CFG , 0x82 }, + { STB0899_GPIO15CFG , 0x82 }, + { STB0899_GPIO16CFG , 0x82 }, + { STB0899_GPIO17CFG , 0x82 }, + { STB0899_GPIO18CFG , 0x82 }, + { STB0899_GPIO19CFG , 0x82 }, + { STB0899_GPIO20CFG , 0x82 }, + { STB0899_SDATCFG , 0xb8 }, + { STB0899_SCLTCFG , 0xba }, + { STB0899_AGCRFCFG , 0x08 }, /* 0x1c */ + { STB0899_GPIO22 , 0x82 }, /* AGCBB2CFG */ + { STB0899_GPIO21 , 0x91 }, /* AGCBB1CFG */ + { STB0899_DIRCLKCFG , 0x82 }, + { STB0899_CLKOUT27CFG , 0x7e }, + { STB0899_STDBYCFG , 0x82 }, + { STB0899_CS0CFG , 0x82 }, + { STB0899_CS1CFG , 0x82 }, + { STB0899_DISEQCOCFG , 0x20 }, + { STB0899_GPIO32CFG , 0x82 }, + { STB0899_GPIO33CFG , 0x82 }, + { STB0899_GPIO34CFG , 0x82 }, + { STB0899_GPIO35CFG , 0x82 }, + { STB0899_GPIO36CFG , 0x82 }, + { STB0899_GPIO37CFG , 0x82 }, + { STB0899_GPIO38CFG , 0x82 }, + { STB0899_GPIO39CFG , 0x82 }, + { STB0899_NCOARSE , 0x15 }, /* 0x15 = 27 Mhz Clock, F/3 = 198MHz, F/6 = 99MHz */ + { STB0899_SYNTCTRL , 0x02 }, /* 0x00 = CLK from CLKI, 0x02 = CLK from XTALI */ + { STB0899_FILTCTRL , 0x00 }, + { STB0899_SYSCTRL , 0x00 }, + { STB0899_STOPCLK1 , 0x20 }, + { STB0899_STOPCLK2 , 0x00 }, + { STB0899_INTBUFSTATUS , 0x00 }, + { STB0899_INTBUFCTRL , 0x0a }, + { 0xffff , 0xff }, +}; + +static const struct stb0899_s1_reg knc1_stb0899_s1_init_3[] = { + { STB0899_DEMOD , 0x00 }, + { STB0899_RCOMPC , 0xc9 }, + { STB0899_AGC1CN , 0x41 }, + { STB0899_AGC1REF , 0x08 }, + { STB0899_RTC , 0x7a }, + { STB0899_TMGCFG , 0x4e }, + { STB0899_AGC2REF , 0x33 }, + { STB0899_TLSR , 0x84 }, + { STB0899_CFD , 0xee }, + { STB0899_ACLC , 0x87 }, + { STB0899_BCLC , 0x94 }, + { STB0899_EQON , 0x41 }, + { STB0899_LDT , 0xdd }, + { STB0899_LDT2 , 0xc9 }, + { STB0899_EQUALREF , 0xb4 }, + { STB0899_TMGRAMP , 0x10 }, + { STB0899_TMGTHD , 0x30 }, + { STB0899_IDCCOMP , 0xfb }, + { STB0899_QDCCOMP , 0x03 }, + { STB0899_POWERI , 0x3b }, + { STB0899_POWERQ , 0x3d }, + { STB0899_RCOMP , 0x81 }, + { STB0899_AGCIQIN , 0x80 }, + { STB0899_AGC2I1 , 0x04 }, + { STB0899_AGC2I2 , 0xf5 }, + { STB0899_TLIR , 0x25 }, + { STB0899_RTF , 0x80 }, + { STB0899_DSTATUS , 0x00 }, + { STB0899_LDI , 0xca }, + { STB0899_CFRM , 0xf1 }, + { STB0899_CFRL , 0xf3 }, + { STB0899_NIRM , 0x2a }, + { STB0899_NIRL , 0x05 }, + { STB0899_ISYMB , 0x17 }, + { STB0899_QSYMB , 0xfa }, + { STB0899_SFRH , 0x2f }, + { STB0899_SFRM , 0x68 }, + { STB0899_SFRL , 0x40 }, + { STB0899_SFRUPH , 0x2f }, + { STB0899_SFRUPM , 0x68 }, + { STB0899_SFRUPL , 0x40 }, + { STB0899_EQUAI1 , 0xfd }, + { STB0899_EQUAQ1 , 0x04 }, + { STB0899_EQUAI2 , 0x0f }, + { STB0899_EQUAQ2 , 0xff }, + { STB0899_EQUAI3 , 0xdf }, + { STB0899_EQUAQ3 , 0xfa }, + { STB0899_EQUAI4 , 0x37 }, + { STB0899_EQUAQ4 , 0x0d }, + { STB0899_EQUAI5 , 0xbd }, + { STB0899_EQUAQ5 , 0xf7 }, + { STB0899_DSTATUS2 , 0x00 }, + { STB0899_VSTATUS , 0x00 }, + { STB0899_VERROR , 0xff }, + { STB0899_IQSWAP , 0x2a }, + { STB0899_ECNT1M , 0x00 }, + { STB0899_ECNT1L , 0x00 }, + { STB0899_ECNT2M , 0x00 }, + { STB0899_ECNT2L , 0x00 }, + { STB0899_ECNT3M , 0x00 }, + { STB0899_ECNT3L , 0x00 }, + { STB0899_FECAUTO1 , 0x06 }, + { STB0899_FECM , 0x01 }, + { STB0899_VTH12 , 0xf0 }, + { STB0899_VTH23 , 0xa0 }, + { STB0899_VTH34 , 0x78 }, + { STB0899_VTH56 , 0x4e }, + { STB0899_VTH67 , 0x48 }, + { STB0899_VTH78 , 0x38 }, + { STB0899_PRVIT , 0xff }, + { STB0899_VITSYNC , 0x19 }, + { STB0899_RSULC , 0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */ + { STB0899_TSULC , 0x42 }, + { STB0899_RSLLC , 0x40 }, + { STB0899_TSLPL , 0x12 }, + { STB0899_TSCFGH , 0x0c }, + { STB0899_TSCFGM , 0x00 }, + { STB0899_TSCFGL , 0x0c }, + { STB0899_TSOUT , 0x4d }, /* 0x0d for CAM */ + { STB0899_RSSYNCDEL , 0x00 }, + { STB0899_TSINHDELH , 0x02 }, + { STB0899_TSINHDELM , 0x00 }, + { STB0899_TSINHDELL , 0x00 }, + { STB0899_TSLLSTKM , 0x00 }, + { STB0899_TSLLSTKL , 0x00 }, + { STB0899_TSULSTKM , 0x00 }, + { STB0899_TSULSTKL , 0xab }, + { STB0899_PCKLENUL , 0x00 }, + { STB0899_PCKLENLL , 0xcc }, + { STB0899_RSPCKLEN , 0xcc }, + { STB0899_TSSTATUS , 0x80 }, + { STB0899_ERRCTRL1 , 0xb6 }, + { STB0899_ERRCTRL2 , 0x96 }, + { STB0899_ERRCTRL3 , 0x89 }, + { STB0899_DMONMSK1 , 0x27 }, + { STB0899_DMONMSK0 , 0x03 }, + { STB0899_DEMAPVIT , 0x5c }, + { STB0899_PLPARM , 0x1f }, + { STB0899_PDELCTRL , 0x48 }, + { STB0899_PDELCTRL2 , 0x00 }, + { STB0899_BBHCTRL1 , 0x00 }, + { STB0899_BBHCTRL2 , 0x00 }, + { STB0899_HYSTTHRESH , 0x77 }, + { STB0899_MATCSTM , 0x00 }, + { STB0899_MATCSTL , 0x00 }, + { STB0899_UPLCSTM , 0x00 }, + { STB0899_UPLCSTL , 0x00 }, + { STB0899_DFLCSTM , 0x00 }, + { STB0899_DFLCSTL , 0x00 }, + { STB0899_SYNCCST , 0x00 }, + { STB0899_SYNCDCSTM , 0x00 }, + { STB0899_SYNCDCSTL , 0x00 }, + { STB0899_ISI_ENTRY , 0x00 }, + { STB0899_ISI_BIT_EN , 0x00 }, + { STB0899_MATSTRM , 0x00 }, + { STB0899_MATSTRL , 0x00 }, + { STB0899_UPLSTRM , 0x00 }, + { STB0899_UPLSTRL , 0x00 }, + { STB0899_DFLSTRM , 0x00 }, + { STB0899_DFLSTRL , 0x00 }, + { STB0899_SYNCSTR , 0x00 }, + { STB0899_SYNCDSTRM , 0x00 }, + { STB0899_SYNCDSTRL , 0x00 }, + { STB0899_CFGPDELSTATUS1 , 0x10 }, + { STB0899_CFGPDELSTATUS2 , 0x00 }, + { STB0899_BBFERRORM , 0x00 }, + { STB0899_BBFERRORL , 0x00 }, + { STB0899_UPKTERRORM , 0x00 }, + { STB0899_UPKTERRORL , 0x00 }, + { 0xffff , 0xff }, +}; + +/* STB0899 demodulator config for the KNC1 and clones */ +static struct stb0899_config knc1_dvbs2_config = { + .init_dev = knc1_stb0899_s1_init_1, + .init_s2_demod = stb0899_s2_init_2, + .init_s1_demod = knc1_stb0899_s1_init_3, + .init_s2_fec = stb0899_s2_init_4, + .init_tst = stb0899_s1_init_5, + + .postproc = NULL, + + .demod_address = 0x68, +// .ts_output_mode = STB0899_OUT_PARALLEL, /* types = SERIAL/PARALLEL */ + .block_sync_mode = STB0899_SYNC_FORCED, /* DSS, SYNC_FORCED/UNSYNCED */ +// .ts_pfbit_toggle = STB0899_MPEG_NORMAL, /* DirecTV, MPEG toggling seq */ + + .xtal_freq = 27000000, + .inversion = IQ_SWAP_OFF, + + .lo_clk = 76500000, + .hi_clk = 90000000, + + .esno_ave = STB0899_DVBS2_ESNO_AVE, + .esno_quant = STB0899_DVBS2_ESNO_QUANT, + .avframes_coarse = STB0899_DVBS2_AVFRAMES_COARSE, + .avframes_fine = STB0899_DVBS2_AVFRAMES_FINE, + .miss_threshold = STB0899_DVBS2_MISS_THRESHOLD, + .uwp_threshold_acq = STB0899_DVBS2_UWP_THRESHOLD_ACQ, + .uwp_threshold_track = STB0899_DVBS2_UWP_THRESHOLD_TRACK, + .uwp_threshold_sof = STB0899_DVBS2_UWP_THRESHOLD_SOF, + .sof_search_timeout = STB0899_DVBS2_SOF_SEARCH_TIMEOUT, + + .btr_nco_bits = STB0899_DVBS2_BTR_NCO_BITS, + .btr_gain_shift_offset = STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET, + .crl_nco_bits = STB0899_DVBS2_CRL_NCO_BITS, + .ldpc_max_iter = STB0899_DVBS2_LDPC_MAX_ITER, + + .tuner_get_frequency = tda8261_get_frequency, + .tuner_set_frequency = tda8261_set_frequency, + .tuner_set_bandwidth = NULL, + .tuner_get_bandwidth = tda8261_get_bandwidth, + .tuner_set_rfsiggain = NULL +}; + +/* + * SD1878/SHA tuner config + * 1F, Single I/P, Horizontal mount, High Sensitivity + */ +static const struct tda8261_config sd1878c_config = { +// .name = "SD1878/SHA", + .addr = 0x60, + .step_size = TDA8261_STEP_1000 /* kHz */ +}; + +static u8 read_pwm(struct budget_av *budget_av) +{ + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { {.addr = 0x50,.flags = 0,.buf = &b,.len = 1}, + {.addr = 0x50,.flags = I2C_M_RD,.buf = &pwm,.len = 1} + }; + + if ((i2c_transfer(&budget_av->budget.i2c_adap, msg, 2) != 2) + || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +#define SUBID_DVBS_KNC1 0x0010 +#define SUBID_DVBS_KNC1_PLUS 0x0011 +#define SUBID_DVBS_TYPHOON 0x4f56 +#define SUBID_DVBS_CINERGY1200 0x1154 +#define SUBID_DVBS_CYNERGY1200N 0x1155 +#define SUBID_DVBS_TV_STAR 0x0014 +#define SUBID_DVBS_TV_STAR_PLUS_X4 0x0015 +#define SUBID_DVBS_TV_STAR_CI 0x0016 +#define SUBID_DVBS2_KNC1 0x0018 +#define SUBID_DVBS2_KNC1_OEM 0x0019 +#define SUBID_DVBS_EASYWATCH_1 0x001a +#define SUBID_DVBS_EASYWATCH_2 0x001b +#define SUBID_DVBS2_EASYWATCH 0x001d +#define SUBID_DVBS_EASYWATCH 0x001e + +#define SUBID_DVBC_EASYWATCH 0x002a +#define SUBID_DVBC_EASYWATCH_MK3 0x002c +#define SUBID_DVBC_KNC1 0x0020 +#define SUBID_DVBC_KNC1_PLUS 0x0021 +#define SUBID_DVBC_KNC1_MK3 0x0022 +#define SUBID_DVBC_KNC1_TDA10024 0x0028 +#define SUBID_DVBC_KNC1_PLUS_MK3 0x0023 +#define SUBID_DVBC_CINERGY1200 0x1156 +#define SUBID_DVBC_CINERGY1200_MK3 0x1176 + +#define SUBID_DVBT_EASYWATCH 0x003a +#define SUBID_DVBT_KNC1_PLUS 0x0031 +#define SUBID_DVBT_KNC1 0x0030 +#define SUBID_DVBT_CINERGY1200 0x1157 + +static void frontend_init(struct budget_av *budget_av) +{ + struct saa7146_dev * saa = budget_av->budget.dev; + struct dvb_frontend * fe = NULL; + + /* Enable / PowerON Frontend */ + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTLO); + + /* Wait for PowerON */ + msleep(100); + + /* additional setup necessary for the PLUS cards */ + switch (saa->pci->subsystem_device) { + case SUBID_DVBS_KNC1_PLUS: + case SUBID_DVBC_KNC1_PLUS: + case SUBID_DVBT_KNC1_PLUS: + case SUBID_DVBC_EASYWATCH: + case SUBID_DVBC_KNC1_PLUS_MK3: + case SUBID_DVBS2_KNC1: + case SUBID_DVBS2_KNC1_OEM: + case SUBID_DVBS2_EASYWATCH: + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTHI); + break; + } + + switch (saa->pci->subsystem_device) { + + case SUBID_DVBS_KNC1: + /* + * maybe that setting is needed for other dvb-s cards as well, + * but so far it has been only confirmed for this type + */ + budget_av->reinitialise_demod = 1; + fallthrough; + case SUBID_DVBS_KNC1_PLUS: + case SUBID_DVBS_EASYWATCH_1: + if (saa->pci->subsystem_vendor == 0x1894) { + fe = dvb_attach(stv0299_attach, &cinergy_1200s_1894_0010_config, + &budget_av->budget.i2c_adap); + if (fe) { + dvb_attach(tua6100_attach, fe, 0x60, &budget_av->budget.i2c_adap); + } + } else { + fe = dvb_attach(stv0299_attach, &typhoon_config, + &budget_av->budget.i2c_adap); + if (fe) { + fe->ops.tuner_ops.set_params = philips_su1278_ty_ci_tuner_set_params; + } + } + break; + + case SUBID_DVBS_TV_STAR: + case SUBID_DVBS_TV_STAR_PLUS_X4: + case SUBID_DVBS_TV_STAR_CI: + case SUBID_DVBS_CYNERGY1200N: + case SUBID_DVBS_EASYWATCH: + case SUBID_DVBS_EASYWATCH_2: + fe = dvb_attach(stv0299_attach, &philips_sd1878_config, + &budget_av->budget.i2c_adap); + if (fe) { + dvb_attach(dvb_pll_attach, fe, 0x60, + &budget_av->budget.i2c_adap, + DVB_PLL_PHILIPS_SD1878_TDA8261); + } + break; + + case SUBID_DVBS_TYPHOON: + fe = dvb_attach(stv0299_attach, &typhoon_config, + &budget_av->budget.i2c_adap); + if (fe) { + fe->ops.tuner_ops.set_params = philips_su1278_ty_ci_tuner_set_params; + } + break; + case SUBID_DVBS2_KNC1: + case SUBID_DVBS2_KNC1_OEM: + case SUBID_DVBS2_EASYWATCH: + budget_av->reinitialise_demod = 1; + if ((fe = dvb_attach(stb0899_attach, &knc1_dvbs2_config, &budget_av->budget.i2c_adap))) + dvb_attach(tda8261_attach, fe, &sd1878c_config, &budget_av->budget.i2c_adap); + + break; + case SUBID_DVBS_CINERGY1200: + fe = dvb_attach(stv0299_attach, &cinergy_1200s_config, + &budget_av->budget.i2c_adap); + if (fe) { + fe->ops.tuner_ops.set_params = philips_su1278_ty_ci_tuner_set_params; + } + break; + + case SUBID_DVBC_KNC1: + case SUBID_DVBC_KNC1_PLUS: + case SUBID_DVBC_CINERGY1200: + case SUBID_DVBC_EASYWATCH: + budget_av->reinitialise_demod = 1; + budget_av->budget.dev->i2c_bitrate = SAA7146_I2C_BUS_BIT_RATE_240; + fe = dvb_attach(tda10021_attach, &philips_cu1216_config, + &budget_av->budget.i2c_adap, + read_pwm(budget_av)); + if (fe == NULL) + fe = dvb_attach(tda10021_attach, &philips_cu1216_config_altaddress, + &budget_av->budget.i2c_adap, + read_pwm(budget_av)); + if (fe) { + fe->ops.tuner_ops.set_params = philips_cu1216_tuner_set_params; + } + break; + + case SUBID_DVBC_EASYWATCH_MK3: + case SUBID_DVBC_CINERGY1200_MK3: + case SUBID_DVBC_KNC1_MK3: + case SUBID_DVBC_KNC1_TDA10024: + case SUBID_DVBC_KNC1_PLUS_MK3: + budget_av->reinitialise_demod = 1; + budget_av->budget.dev->i2c_bitrate = SAA7146_I2C_BUS_BIT_RATE_240; + fe = dvb_attach(tda10023_attach, + &philips_cu1216_tda10023_config, + &budget_av->budget.i2c_adap, + read_pwm(budget_av)); + if (fe) { + fe->ops.tuner_ops.set_params = philips_cu1216_tuner_set_params; + } + break; + + case SUBID_DVBT_EASYWATCH: + case SUBID_DVBT_KNC1: + case SUBID_DVBT_KNC1_PLUS: + case SUBID_DVBT_CINERGY1200: + budget_av->reinitialise_demod = 1; + fe = dvb_attach(tda10046_attach, &philips_tu1216_config, + &budget_av->budget.i2c_adap); + if (fe) { + fe->ops.tuner_ops.init = philips_tu1216_tuner_init; + fe->ops.tuner_ops.set_params = philips_tu1216_tuner_set_params; + } + break; + } + + if (fe == NULL) { + pr_err("A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", + saa->pci->vendor, + saa->pci->device, + saa->pci->subsystem_vendor, + saa->pci->subsystem_device); + return; + } + + budget_av->budget.dvb_frontend = fe; + + if (dvb_register_frontend(&budget_av->budget.dvb_adapter, + budget_av->budget.dvb_frontend)) { + pr_err("Frontend registration failed!\n"); + dvb_frontend_detach(budget_av->budget.dvb_frontend); + budget_av->budget.dvb_frontend = NULL; + } +} + + +static void budget_av_irq(struct saa7146_dev *dev, u32 * isr) +{ + struct budget_av *budget_av = (struct budget_av *) dev->ext_priv; + + dprintk(8, "dev: %p, budget_av: %p\n", dev, budget_av); + + if (*isr & MASK_10) + ttpci_budget_irq10_handler(dev, isr); +} + +static int budget_av_detach(struct saa7146_dev *dev) +{ + struct budget_av *budget_av = (struct budget_av *) dev->ext_priv; + int err; + + dprintk(2, "dev: %p\n", dev); + + if (1 == budget_av->has_saa7113) { + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTLO); + + msleep(200); + + saa7146_unregister_device(&budget_av->vd, dev); + + saa7146_vv_release(dev); + } + + if (budget_av->budget.ci_present) + ciintf_deinit(budget_av); + + if (budget_av->budget.dvb_frontend != NULL) { + dvb_unregister_frontend(budget_av->budget.dvb_frontend); + dvb_frontend_detach(budget_av->budget.dvb_frontend); + } + err = ttpci_budget_deinit(&budget_av->budget); + + kfree(budget_av); + + return err; +} + +#define KNC1_INPUTS 2 +static struct v4l2_input knc1_inputs[KNC1_INPUTS] = { + { 0, "Composite", V4L2_INPUT_TYPE_TUNER, 1, 0, + V4L2_STD_PAL_BG | V4L2_STD_NTSC_M, 0, V4L2_IN_CAP_STD }, + { 1, "S-Video", V4L2_INPUT_TYPE_CAMERA, 2, 0, + V4L2_STD_PAL_BG | V4L2_STD_NTSC_M, 0, V4L2_IN_CAP_STD }, +}; + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + dprintk(1, "VIDIOC_ENUMINPUT %d\n", i->index); + if (i->index >= KNC1_INPUTS) + return -EINVAL; + memcpy(i, &knc1_inputs[i->index], sizeof(struct v4l2_input)); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct budget_av *budget_av = (struct budget_av *)dev->ext_priv; + + *i = budget_av->cur_input; + + dprintk(1, "VIDIOC_G_INPUT %d\n", *i); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct budget_av *budget_av = (struct budget_av *)dev->ext_priv; + + dprintk(1, "VIDIOC_S_INPUT %d\n", input); + return saa7113_setinput(budget_av, input); +} + +static struct saa7146_ext_vv vv_data; + +static int budget_av_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct budget_av *budget_av; + u8 *mac; + int err; + + dprintk(2, "dev: %p\n", dev); + + if (!(budget_av = kzalloc(sizeof(struct budget_av), GFP_KERNEL))) + return -ENOMEM; + + budget_av->has_saa7113 = 0; + budget_av->budget.ci_present = 0; + + dev->ext_priv = budget_av; + + err = ttpci_budget_init(&budget_av->budget, dev, info, THIS_MODULE, + adapter_nr); + if (err) { + kfree(budget_av); + return err; + } + + /* knc1 initialization */ + saa7146_write(dev, DD1_STREAM_B, 0x04000000); + saa7146_write(dev, DD1_INIT, 0x07000600); + saa7146_write(dev, MC2, MASK_09 | MASK_25 | MASK_10 | MASK_26); + + if (saa7113_init(budget_av) == 0) { + budget_av->has_saa7113 = 1; + err = saa7146_vv_init(dev, &vv_data); + if (err != 0) { + /* fixme: proper cleanup here */ + ERR("cannot init vv subsystem\n"); + return err; + } + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + + if ((err = saa7146_register_device(&budget_av->vd, dev, "knc1", VFL_TYPE_VIDEO))) { + /* fixme: proper cleanup here */ + ERR("cannot register capture v4l2 device\n"); + saa7146_vv_release(dev); + return err; + } + + /* beware: this modifies dev->vv ... */ + saa7146_set_hps_source_and_sync(dev, SAA7146_HPS_SOURCE_PORT_A, + SAA7146_HPS_SYNC_PORT_A); + + saa7113_setinput(budget_av, 0); + } + + /* fixme: find some sane values here... */ + saa7146_write(dev, PCI_BT_V1, 0x1c00101f); + + mac = budget_av->budget.dvb_adapter.proposed_mac; + if (i2c_readregs(&budget_av->budget.i2c_adap, 0xa0, 0x30, mac, 6)) { + pr_err("KNC1-%d: Could not read MAC from KNC1 card\n", + budget_av->budget.dvb_adapter.num); + eth_zero_addr(mac); + } else { + pr_info("KNC1-%d: MAC addr = %pM\n", + budget_av->budget.dvb_adapter.num, mac); + } + + budget_av->budget.dvb_adapter.priv = budget_av; + frontend_init(budget_av); + ciintf_init(budget_av); + + ttpci_budget_init_hooks(&budget_av->budget); + + return 0; +} + +static struct saa7146_standard standard[] = { + {.name = "PAL",.id = V4L2_STD_PAL, + .v_offset = 0x17,.v_field = 288, + .h_offset = 0x14,.h_pixels = 680, + .v_max_out = 576,.h_max_out = 768 }, + + {.name = "NTSC",.id = V4L2_STD_NTSC, + .v_offset = 0x16,.v_field = 240, + .h_offset = 0x06,.h_pixels = 708, + .v_max_out = 480,.h_max_out = 640, }, +}; + +static struct saa7146_ext_vv vv_data = { + .inputs = 2, + .capabilities = 0, // perhaps later: V4L2_CAP_VBI_CAPTURE, but that need tweaking with the saa7113 + .flags = 0, + .stds = &standard[0], + .num_stds = ARRAY_SIZE(standard), +}; + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(knc1s, "KNC1 DVB-S", BUDGET_KNC1S); +MAKE_BUDGET_INFO(knc1s2,"KNC1 DVB-S2", BUDGET_KNC1S2); +MAKE_BUDGET_INFO(sates2,"Satelco EasyWatch DVB-S2", BUDGET_KNC1S2); +MAKE_BUDGET_INFO(knc1c, "KNC1 DVB-C", BUDGET_KNC1C); +MAKE_BUDGET_INFO(knc1t, "KNC1 DVB-T", BUDGET_KNC1T); +MAKE_BUDGET_INFO(kncxs, "KNC TV STAR DVB-S", BUDGET_TVSTAR); +MAKE_BUDGET_INFO(satewpls, "Satelco EasyWatch DVB-S light", BUDGET_TVSTAR); +MAKE_BUDGET_INFO(satewpls1, "Satelco EasyWatch DVB-S light", BUDGET_KNC1S); +MAKE_BUDGET_INFO(satewps, "Satelco EasyWatch DVB-S", BUDGET_KNC1S); +MAKE_BUDGET_INFO(satewplc, "Satelco EasyWatch DVB-C", BUDGET_KNC1CP); +MAKE_BUDGET_INFO(satewcmk3, "Satelco EasyWatch DVB-C MK3", BUDGET_KNC1C_MK3); +MAKE_BUDGET_INFO(satewt, "Satelco EasyWatch DVB-T", BUDGET_KNC1T); +MAKE_BUDGET_INFO(knc1sp, "KNC1 DVB-S Plus", BUDGET_KNC1SP); +MAKE_BUDGET_INFO(knc1spx4, "KNC1 DVB-S Plus X4", BUDGET_KNC1SP); +MAKE_BUDGET_INFO(knc1cp, "KNC1 DVB-C Plus", BUDGET_KNC1CP); +MAKE_BUDGET_INFO(knc1cmk3, "KNC1 DVB-C MK3", BUDGET_KNC1C_MK3); +MAKE_BUDGET_INFO(knc1ctda10024, "KNC1 DVB-C TDA10024", BUDGET_KNC1C_TDA10024); +MAKE_BUDGET_INFO(knc1cpmk3, "KNC1 DVB-C Plus MK3", BUDGET_KNC1CP_MK3); +MAKE_BUDGET_INFO(knc1tp, "KNC1 DVB-T Plus", BUDGET_KNC1TP); +MAKE_BUDGET_INFO(cin1200s, "TerraTec Cinergy 1200 DVB-S", BUDGET_CIN1200S); +MAKE_BUDGET_INFO(cin1200sn, "TerraTec Cinergy 1200 DVB-S", BUDGET_CIN1200S); +MAKE_BUDGET_INFO(cin1200c, "Terratec Cinergy 1200 DVB-C", BUDGET_CIN1200C); +MAKE_BUDGET_INFO(cin1200cmk3, "Terratec Cinergy 1200 DVB-C MK3", BUDGET_CIN1200C_MK3); +MAKE_BUDGET_INFO(cin1200t, "Terratec Cinergy 1200 DVB-T", BUDGET_CIN1200T); + +static const struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(knc1s, 0x1131, 0x4f56), + MAKE_EXTENSION_PCI(knc1s, 0x1131, 0x0010), + MAKE_EXTENSION_PCI(knc1s, 0x1894, 0x0010), + MAKE_EXTENSION_PCI(knc1sp, 0x1131, 0x0011), + MAKE_EXTENSION_PCI(knc1sp, 0x1894, 0x0011), + MAKE_EXTENSION_PCI(kncxs, 0x1894, 0x0014), + MAKE_EXTENSION_PCI(knc1spx4, 0x1894, 0x0015), + MAKE_EXTENSION_PCI(kncxs, 0x1894, 0x0016), + MAKE_EXTENSION_PCI(knc1s2, 0x1894, 0x0018), + MAKE_EXTENSION_PCI(knc1s2, 0x1894, 0x0019), + MAKE_EXTENSION_PCI(sates2, 0x1894, 0x001d), + MAKE_EXTENSION_PCI(satewpls, 0x1894, 0x001e), + MAKE_EXTENSION_PCI(satewpls1, 0x1894, 0x001a), + MAKE_EXTENSION_PCI(satewps, 0x1894, 0x001b), + MAKE_EXTENSION_PCI(satewplc, 0x1894, 0x002a), + MAKE_EXTENSION_PCI(satewcmk3, 0x1894, 0x002c), + MAKE_EXTENSION_PCI(satewt, 0x1894, 0x003a), + MAKE_EXTENSION_PCI(knc1c, 0x1894, 0x0020), + MAKE_EXTENSION_PCI(knc1cp, 0x1894, 0x0021), + MAKE_EXTENSION_PCI(knc1cmk3, 0x1894, 0x0022), + MAKE_EXTENSION_PCI(knc1ctda10024, 0x1894, 0x0028), + MAKE_EXTENSION_PCI(knc1cpmk3, 0x1894, 0x0023), + MAKE_EXTENSION_PCI(knc1t, 0x1894, 0x0030), + MAKE_EXTENSION_PCI(knc1tp, 0x1894, 0x0031), + MAKE_EXTENSION_PCI(cin1200s, 0x153b, 0x1154), + MAKE_EXTENSION_PCI(cin1200sn, 0x153b, 0x1155), + MAKE_EXTENSION_PCI(cin1200c, 0x153b, 0x1156), + MAKE_EXTENSION_PCI(cin1200cmk3, 0x153b, 0x1176), + MAKE_EXTENSION_PCI(cin1200t, 0x153b, 0x1157), + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget_av", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = pci_tbl, + + .module = THIS_MODULE, + .attach = budget_av_attach, + .detach = budget_av_detach, + + .irq_mask = MASK_10, + .irq_func = budget_av_irq, +}; + +static int __init budget_av_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_av_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_av_init); +module_exit(budget_av_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called budget PCI DVB w/ analog input and CI-module (e.g. the KNC cards)"); diff --git a/drivers/media/pci/ttpci/budget-ci.c b/drivers/media/pci/ttpci/budget-ci.c new file mode 100644 index 000000000000..d59d18647371 --- /dev/null +++ b/drivers/media/pci/ttpci/budget-ci.c @@ -0,0 +1,1574 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * budget-ci.c: driver for the SAA7146 based Budget DVB cards + * + * Compiled from various sources by Michael Hunold <michael@mihu.de> + * + * msp430 IR support contributed by Jack Thomasson <jkt@Helius.COM> + * partially based on the Siemens DVB driver by Ralph+Marcus Metzler + * + * CI interface support (c) 2004 Andrew de Quincey <adq_dvb@lidskialf.net> + * + * the project's page is at https://linuxtv.org + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <media/rc-core.h> + +#include "budget.h" + +#include <media/dvb_ca_en50221.h> +#include "stv0299.h" +#include "stv0297.h" +#include "tda1004x.h" +#include "stb0899_drv.h" +#include "stb0899_reg.h" +#include "stb0899_cfg.h" +#include "stb6100.h" +#include "stb6100_cfg.h" +#include "lnbp21.h" +#include "bsbe1.h" +#include "bsru6.h" +#include "tda1002x.h" +#include "tda827x.h" +#include "bsbe1-d01a.h" + +#define MODULE_NAME "budget_ci" + +/* + * Regarding DEBIADDR_IR: + * Some CI modules hang if random addresses are read. + * Using address 0x4000 for the IR read means that we + * use the same address as for CI version, which should + * be a safe default. + */ +#define DEBIADDR_IR 0x4000 +#define DEBIADDR_CICONTROL 0x0000 +#define DEBIADDR_CIVERSION 0x4000 +#define DEBIADDR_IO 0x1000 +#define DEBIADDR_ATTR 0x3000 + +#define CICONTROL_RESET 0x01 +#define CICONTROL_ENABLETS 0x02 +#define CICONTROL_CAMDETECT 0x08 + +#define DEBICICTL 0x00420000 +#define DEBICICAM 0x02420000 + +#define SLOTSTATUS_NONE 1 +#define SLOTSTATUS_PRESENT 2 +#define SLOTSTATUS_RESET 4 +#define SLOTSTATUS_READY 8 +#define SLOTSTATUS_OCCUPIED (SLOTSTATUS_PRESENT|SLOTSTATUS_RESET|SLOTSTATUS_READY) + +/* RC5 device wildcard */ +#define IR_DEVICE_ANY 255 + +static int rc5_device = -1; +module_param(rc5_device, int, 0644); +MODULE_PARM_DESC(rc5_device, "only IR commands to given RC5 device (device = 0 - 31, any device = 255, default: autodetect)"); + +static int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug, "enable debugging information for IR decoding"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct budget_ci_ir { + struct rc_dev *dev; + struct tasklet_struct msp430_irq_tasklet; + char name[72]; /* 40 + 32 for (struct saa7146_dev).name */ + char phys[32]; + int rc5_device; + u32 ir_key; + bool have_command; + bool full_rc5; /* Outputs a full RC5 code */ +}; + +struct budget_ci { + struct budget budget; + struct tasklet_struct ciintf_irq_tasklet; + int slot_status; + int ci_irq; + struct dvb_ca_en50221 ca; + struct budget_ci_ir ir; + u8 tuner_pll_address; /* used for philips_tdm1316l configs */ +}; + +static void msp430_ir_interrupt(struct tasklet_struct *t) +{ + struct budget_ci_ir *ir = from_tasklet(ir, t, msp430_irq_tasklet); + struct budget_ci *budget_ci = container_of(ir, typeof(*budget_ci), ir); + struct rc_dev *dev = budget_ci->ir.dev; + u32 command = ttpci_budget_debiread(&budget_ci->budget, DEBINOSWAP, DEBIADDR_IR, 2, 1, 0) >> 8; + + /* + * The msp430 chip can generate two different bytes, command and device + * + * type1: X1CCCCCC, C = command bits (0 - 63) + * type2: X0TDDDDD, D = device bits (0 - 31), T = RC5 toggle bit + * + * Each signal from the remote control can generate one or more command + * bytes and one or more device bytes. For the repeated bytes, the + * highest bit (X) is set. The first command byte is always generated + * before the first device byte. Other than that, no specific order + * seems to apply. To make life interesting, bytes can also be lost. + * + * Only when we have a command and device byte, a keypress is + * generated. + */ + + if (ir_debug) + printk("budget_ci: received byte 0x%02x\n", command); + + /* Remove repeat bit, we use every command */ + command = command & 0x7f; + + /* Is this a RC5 command byte? */ + if (command & 0x40) { + budget_ci->ir.have_command = true; + budget_ci->ir.ir_key = command & 0x3f; + return; + } + + /* It's a RC5 device byte */ + if (!budget_ci->ir.have_command) + return; + budget_ci->ir.have_command = false; + + if (budget_ci->ir.rc5_device != IR_DEVICE_ANY && + budget_ci->ir.rc5_device != (command & 0x1f)) + return; + + if (budget_ci->ir.full_rc5) { + rc_keydown(dev, RC_PROTO_RC5, + RC_SCANCODE_RC5(budget_ci->ir.rc5_device, budget_ci->ir.ir_key), + !!(command & 0x20)); + return; + } + + /* FIXME: We should generate complete scancodes for all devices */ + rc_keydown(dev, RC_PROTO_UNKNOWN, budget_ci->ir.ir_key, + !!(command & 0x20)); +} + +static int msp430_ir_init(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + struct rc_dev *dev; + int error; + + dev = rc_allocate_device(RC_DRIVER_SCANCODE); + if (!dev) { + printk(KERN_ERR "budget_ci: IR interface initialisation failed\n"); + return -ENOMEM; + } + + snprintf(budget_ci->ir.name, sizeof(budget_ci->ir.name), + "Budget-CI dvb ir receiver %s", saa->name); + snprintf(budget_ci->ir.phys, sizeof(budget_ci->ir.phys), + "pci-%s/ir0", pci_name(saa->pci)); + + dev->driver_name = MODULE_NAME; + dev->device_name = budget_ci->ir.name; + dev->input_phys = budget_ci->ir.phys; + dev->input_id.bustype = BUS_PCI; + dev->input_id.version = 1; + if (saa->pci->subsystem_vendor) { + dev->input_id.vendor = saa->pci->subsystem_vendor; + dev->input_id.product = saa->pci->subsystem_device; + } else { + dev->input_id.vendor = saa->pci->vendor; + dev->input_id.product = saa->pci->device; + } + dev->dev.parent = &saa->pci->dev; + + if (rc5_device < 0) + budget_ci->ir.rc5_device = IR_DEVICE_ANY; + else + budget_ci->ir.rc5_device = rc5_device; + + /* Select keymap and address */ + switch (budget_ci->budget.dev->pci->subsystem_device) { + case 0x100c: + case 0x100f: + case 0x1011: + case 0x1012: + /* The hauppauge keymap is a superset of these remotes */ + dev->map_name = RC_MAP_HAUPPAUGE; + budget_ci->ir.full_rc5 = true; + + if (rc5_device < 0) + budget_ci->ir.rc5_device = 0x1f; + break; + case 0x1010: + case 0x1017: + case 0x1019: + case 0x101a: + case 0x101b: + /* for the Technotrend 1500 bundled remote */ + dev->map_name = RC_MAP_TT_1500; + break; + default: + /* unknown remote */ + dev->map_name = RC_MAP_BUDGET_CI_OLD; + break; + } + if (!budget_ci->ir.full_rc5) + dev->scancode_mask = 0xff; + + error = rc_register_device(dev); + if (error) { + printk(KERN_ERR "budget_ci: could not init driver for IR device (code %d)\n", error); + rc_free_device(dev); + return error; + } + + budget_ci->ir.dev = dev; + + tasklet_setup(&budget_ci->ir.msp430_irq_tasklet, msp430_ir_interrupt); + + SAA7146_IER_ENABLE(saa, MASK_06); + saa7146_setgpio(saa, 3, SAA7146_GPIO_IRQHI); + + return 0; +} + +static void msp430_ir_deinit(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + + SAA7146_IER_DISABLE(saa, MASK_06); + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + tasklet_kill(&budget_ci->ir.msp430_irq_tasklet); + + rc_unregister_device(budget_ci->ir.dev); +} + +static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiread(&budget_ci->budget, DEBICICAM, + DEBIADDR_ATTR | (address & 0xfff), 1, 1, 0); +} + +static int ciintf_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiwrite(&budget_ci->budget, DEBICICAM, + DEBIADDR_ATTR | (address & 0xfff), 1, value, 1, 0); +} + +static int ciintf_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiread(&budget_ci->budget, DEBICICAM, + DEBIADDR_IO | (address & 3), 1, 1, 0); +} + +static int ciintf_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiwrite(&budget_ci->budget, DEBICICAM, + DEBIADDR_IO | (address & 3), 1, value, 1, 0); +} + +static int ciintf_slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + + if (slot != 0) + return -EINVAL; + + if (budget_ci->ci_irq) { + // trigger on RISING edge during reset so we know when READY is re-asserted + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + } + budget_ci->slot_status = SLOTSTATUS_RESET; + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 0, 1, 0); + msleep(1); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI); + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + return 0; +} + +static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI); + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + return 0; +} + +static int ciintf_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + int tmp; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTLO); + + tmp = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + tmp | CICONTROL_ENABLETS, 1, 0); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTA); + return 0; +} + +static void ciintf_interrupt(struct tasklet_struct *t) +{ + struct budget_ci *budget_ci = from_tasklet(budget_ci, t, + ciintf_irq_tasklet); + struct saa7146_dev *saa = budget_ci->budget.dev; + unsigned int flags; + + // ensure we don't get spurious IRQs during initialisation + if (!budget_ci->budget.ci_present) + return; + + // read the CAM status + flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + if (flags & CICONTROL_CAMDETECT) { + + // GPIO should be set to trigger on falling edge if a CAM is present + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQLO); + + if (budget_ci->slot_status & SLOTSTATUS_NONE) { + // CAM insertion IRQ + budget_ci->slot_status = SLOTSTATUS_PRESENT; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, + DVB_CA_EN50221_CAMCHANGE_INSERTED); + + } else if (budget_ci->slot_status & SLOTSTATUS_RESET) { + // CAM ready (reset completed) + budget_ci->slot_status = SLOTSTATUS_READY; + dvb_ca_en50221_camready_irq(&budget_ci->ca, 0); + + } else if (budget_ci->slot_status & SLOTSTATUS_READY) { + // FR/DA IRQ + dvb_ca_en50221_frda_irq(&budget_ci->ca, 0); + } + } else { + + // trigger on rising edge if a CAM is not present - when a CAM is inserted, we + // only want to get the IRQ when it sets READY. If we trigger on the falling edge, + // the CAM might not actually be ready yet. + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + + // generate a CAM removal IRQ if we haven't already + if (budget_ci->slot_status & SLOTSTATUS_OCCUPIED) { + // CAM removal IRQ + budget_ci->slot_status = SLOTSTATUS_NONE; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, + DVB_CA_EN50221_CAMCHANGE_REMOVED); + } + } +} + +static int ciintf_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + unsigned int flags; + + // ensure we don't get spurious IRQs during initialisation + if (!budget_ci->budget.ci_present) + return -EINVAL; + + // read the CAM status + flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + if (flags & CICONTROL_CAMDETECT) { + // mark it as present if it wasn't before + if (budget_ci->slot_status & SLOTSTATUS_NONE) { + budget_ci->slot_status = SLOTSTATUS_PRESENT; + } + + // during a RESET, we check if we can read from IO memory to see when CAM is ready + if (budget_ci->slot_status & SLOTSTATUS_RESET) { + if (ciintf_read_attribute_mem(ca, slot, 0) == 0x1d) { + budget_ci->slot_status = SLOTSTATUS_READY; + } + } + } else { + budget_ci->slot_status = SLOTSTATUS_NONE; + } + + if (budget_ci->slot_status != SLOTSTATUS_NONE) { + if (budget_ci->slot_status & SLOTSTATUS_READY) { + return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY; + } + return DVB_CA_EN50221_POLL_CAM_PRESENT; + } + + return 0; +} + +static int ciintf_init(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + int flags; + int result; + int ci_version; + int ca_flags; + + memset(&budget_ci->ca, 0, sizeof(struct dvb_ca_en50221)); + + // enable DEBI pins + saa7146_write(saa, MC1, MASK_27 | MASK_11); + + // test if it is there + ci_version = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CIVERSION, 1, 1, 0); + if ((ci_version & 0xa0) != 0xa0) { + result = -ENODEV; + goto error; + } + + // determine whether a CAM is present or not + flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + budget_ci->slot_status = SLOTSTATUS_NONE; + if (flags & CICONTROL_CAMDETECT) + budget_ci->slot_status = SLOTSTATUS_PRESENT; + + // version 0xa2 of the CI firmware doesn't generate interrupts + if (ci_version == 0xa2) { + ca_flags = 0; + budget_ci->ci_irq = 0; + } else { + ca_flags = DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE | + DVB_CA_EN50221_FLAG_IRQ_FR | + DVB_CA_EN50221_FLAG_IRQ_DA; + budget_ci->ci_irq = 1; + } + + // register CI interface + budget_ci->ca.owner = THIS_MODULE; + budget_ci->ca.read_attribute_mem = ciintf_read_attribute_mem; + budget_ci->ca.write_attribute_mem = ciintf_write_attribute_mem; + budget_ci->ca.read_cam_control = ciintf_read_cam_control; + budget_ci->ca.write_cam_control = ciintf_write_cam_control; + budget_ci->ca.slot_reset = ciintf_slot_reset; + budget_ci->ca.slot_shutdown = ciintf_slot_shutdown; + budget_ci->ca.slot_ts_enable = ciintf_slot_ts_enable; + budget_ci->ca.poll_slot_status = ciintf_poll_slot_status; + budget_ci->ca.data = budget_ci; + if ((result = dvb_ca_en50221_init(&budget_ci->budget.dvb_adapter, + &budget_ci->ca, + ca_flags, 1)) != 0) { + printk("budget_ci: CI interface detected, but initialisation failed.\n"); + goto error; + } + + // Setup CI slot IRQ + if (budget_ci->ci_irq) { + tasklet_setup(&budget_ci->ciintf_irq_tasklet, ciintf_interrupt); + if (budget_ci->slot_status != SLOTSTATUS_NONE) { + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQLO); + } else { + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + } + SAA7146_IER_ENABLE(saa, MASK_03); + } + + // enable interface + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + // success! + printk("budget_ci: CI interface initialised\n"); + budget_ci->budget.ci_present = 1; + + // forge a fake CI IRQ so the CAM state is setup correctly + if (budget_ci->ci_irq) { + flags = DVB_CA_EN50221_CAMCHANGE_REMOVED; + if (budget_ci->slot_status != SLOTSTATUS_NONE) + flags = DVB_CA_EN50221_CAMCHANGE_INSERTED; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, flags); + } + + return 0; + +error: + saa7146_write(saa, MC1, MASK_27); + return result; +} + +static void ciintf_deinit(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + + // disable CI interrupts + if (budget_ci->ci_irq) { + SAA7146_IER_DISABLE(saa, MASK_03); + saa7146_setgpio(saa, 0, SAA7146_GPIO_INPUT); + tasklet_kill(&budget_ci->ciintf_irq_tasklet); + } + + // reset interface + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 0, 1, 0); + msleep(1); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + // disable TS data stream to CI interface + saa7146_setgpio(saa, 1, SAA7146_GPIO_INPUT); + + // release the CA device + dvb_ca_en50221_release(&budget_ci->ca); + + // disable DEBI pins + saa7146_write(saa, MC1, MASK_27); +} + +static void budget_ci_irq(struct saa7146_dev *dev, u32 * isr) +{ + struct budget_ci *budget_ci = (struct budget_ci *) dev->ext_priv; + + dprintk(8, "dev: %p, budget_ci: %p\n", dev, budget_ci); + + if (*isr & MASK_06) + tasklet_schedule(&budget_ci->ir.msp430_irq_tasklet); + + if (*isr & MASK_10) + ttpci_budget_irq10_handler(dev, isr); + + if ((*isr & MASK_03) && (budget_ci->budget.ci_present) && (budget_ci->ci_irq)) + tasklet_schedule(&budget_ci->ciintf_irq_tasklet); +} + +static u8 philips_su1278_tt_inittab[] = { + 0x01, 0x0f, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x5b, + 0x05, 0x85, + 0x06, 0x02, + 0x07, 0x00, + 0x08, 0x02, + 0x09, 0x00, + 0x0C, 0x01, + 0x0D, 0x81, + 0x0E, 0x44, + 0x0f, 0x14, + 0x10, 0x3c, + 0x11, 0x84, + 0x12, 0xda, + 0x13, 0x97, + 0x14, 0x95, + 0x15, 0xc9, + 0x16, 0x19, + 0x17, 0x8c, + 0x18, 0x59, + 0x19, 0xf8, + 0x1a, 0xfe, + 0x1c, 0x7f, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, + 0x29, 0x28, + 0x2a, 0x14, + 0x2b, 0x0f, + 0x2c, 0x09, + 0x2d, 0x09, + 0x31, 0x1f, + 0x32, 0x19, + 0x33, 0xfc, + 0x34, 0x93, + 0xff, 0xff +}; + +static int philips_su1278_tt_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio) +{ + stv0299_writereg(fe, 0x0e, 0x44); + if (srate >= 10000000) { + stv0299_writereg(fe, 0x13, 0x97); + stv0299_writereg(fe, 0x14, 0x95); + stv0299_writereg(fe, 0x15, 0xc9); + stv0299_writereg(fe, 0x17, 0x8c); + stv0299_writereg(fe, 0x1a, 0xfe); + stv0299_writereg(fe, 0x1c, 0x7f); + stv0299_writereg(fe, 0x2d, 0x09); + } else { + stv0299_writereg(fe, 0x13, 0x99); + stv0299_writereg(fe, 0x14, 0x8d); + stv0299_writereg(fe, 0x15, 0xce); + stv0299_writereg(fe, 0x17, 0x43); + stv0299_writereg(fe, 0x1a, 0x1d); + stv0299_writereg(fe, 0x1c, 0x12); + stv0299_writereg(fe, 0x2d, 0x05); + } + stv0299_writereg(fe, 0x0e, 0x23); + stv0299_writereg(fe, 0x0f, 0x94); + stv0299_writereg(fe, 0x10, 0x39); + stv0299_writereg(fe, 0x15, 0xc9); + + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + + return 0; +} + +static int philips_su1278_tt_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + u32 div; + u8 buf[4]; + struct i2c_msg msg = {.addr = 0x60,.flags = 0,.buf = buf,.len = sizeof(buf) }; + + if ((p->frequency < 950000) || (p->frequency > 2150000)) + return -EINVAL; + + div = (p->frequency + (500 - 1)) / 500; /* round correctly */ + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x80 | ((div & 0x18000) >> 10) | 2; + buf[3] = 0x20; + + if (p->symbol_rate < 4000000) + buf[3] |= 1; + + if (p->frequency < 1250000) + buf[3] |= 0; + else if (p->frequency < 1550000) + buf[3] |= 0x40; + else if (p->frequency < 2050000) + buf[3] |= 0x80; + else if (p->frequency < 2150000) + buf[3] |= 0xC0; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static const struct stv0299_config philips_su1278_tt_config = { + + .demod_address = 0x68, + .inittab = philips_su1278_tt_inittab, + .mclk = 64000000UL, + .invert = 0, + .skip_reinit = 1, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 50, + .set_symbol_rate = philips_su1278_tt_set_symbol_rate, +}; + + + +static int philips_tdm1316l_tuner_init(struct dvb_frontend *fe) +{ + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + static u8 td1316_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + static u8 disable_mc44BC374c[] = { 0x1d, 0x74, 0xa0, 0x68 }; + struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address,.flags = 0,.buf = td1316_init,.len = + sizeof(td1316_init) }; + + // setup PLL configuration + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + msleep(1); + + // disable the mc44BC374c (do not check for errors) + tuner_msg.addr = 0x65; + tuner_msg.buf = disable_mc44BC374c; + tuner_msg.len = sizeof(disable_mc44BC374c); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1); + } + + return 0; +} + +static int philips_tdm1316l_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address,.flags = 0,.buf = tuner_buf,.len = sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = p->frequency + 36130000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + // determine band + if (p->frequency < 49000000) + return -EINVAL; + else if (p->frequency < 159000000) + band = 1; + else if (p->frequency < 444000000) + band = 2; + else if (p->frequency < 861000000) + band = 4; + else + return -EINVAL; + + // setup PLL filter and TDA9889 + switch (p->bandwidth_hz) { + case 6000000: + tda1004x_writereg(fe, 0x0C, 0x14); + filter = 0; + break; + + case 7000000: + tda1004x_writereg(fe, 0x0C, 0x80); + filter = 0; + break; + + case 8000000: + tda1004x_writereg(fe, 0x0C, 0x14); + filter = 1; + break; + + default: + return -EINVAL; + } + + // calculate divisor + // ((36130000+((1000000/6)/2)) + Finput)/(1000000/6) + tuner_frequency = (((p->frequency / 1000) * 6) + 217280) / 1000; + + // setup tuner buffer + tuner_buf[0] = tuner_frequency >> 8; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + return 0; +} + +static int philips_tdm1316l_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + + return request_firmware(fw, name, &budget_ci->budget.dev->pci->dev); +} + +static struct tda1004x_config philips_tdm1316l_config = { + + .demod_address = 0x8, + .invert = 0, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .request_firmware = philips_tdm1316l_request_firmware, +}; + +static struct tda1004x_config philips_tdm1316l_config_invert = { + + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .request_firmware = philips_tdm1316l_request_firmware, +}; + +static int dvbc_philips_tdm1316l_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + u8 tuner_buf[5]; + struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address, + .flags = 0, + .buf = tuner_buf, + .len = sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = p->frequency + 36125000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) { + cp = 3; + band = 1; + } else if (tuner_frequency < 160000000) { + cp = 5; + band = 1; + } else if (tuner_frequency < 200000000) { + cp = 6; + band = 1; + } else if (tuner_frequency < 290000000) { + cp = 3; + band = 2; + } else if (tuner_frequency < 420000000) { + cp = 5; + band = 2; + } else if (tuner_frequency < 480000000) { + cp = 6; + band = 2; + } else if (tuner_frequency < 620000000) { + cp = 3; + band = 4; + } else if (tuner_frequency < 830000000) { + cp = 5; + band = 4; + } else if (tuner_frequency < 895000000) { + cp = 7; + band = 4; + } else + return -EINVAL; + + // assume PLL filter should always be 8MHz for the moment. + filter = 1; + + // calculate divisor + tuner_frequency = (p->frequency + 36125000 + (62500/2)) / 62500; + + // setup tuner buffer + tuner_buf[0] = tuner_frequency >> 8; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xc8; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + tuner_buf[4] = 0x80; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(50); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + + return 0; +} + +static u8 dvbc_philips_tdm1316l_inittab[] = { + 0x80, 0x01, + 0x80, 0x00, + 0x81, 0x01, + 0x81, 0x00, + 0x00, 0x09, + 0x01, 0x69, + 0x03, 0x00, + 0x04, 0x00, + 0x07, 0x00, + 0x08, 0x00, + 0x20, 0x00, + 0x21, 0x40, + 0x22, 0x00, + 0x23, 0x00, + 0x24, 0x40, + 0x25, 0x88, + 0x30, 0xff, + 0x31, 0x00, + 0x32, 0xff, + 0x33, 0x00, + 0x34, 0x50, + 0x35, 0x7f, + 0x36, 0x00, + 0x37, 0x20, + 0x38, 0x00, + 0x40, 0x1c, + 0x41, 0xff, + 0x42, 0x29, + 0x43, 0x20, + 0x44, 0xff, + 0x45, 0x00, + 0x46, 0x00, + 0x49, 0x04, + 0x4a, 0x00, + 0x4b, 0x7b, + 0x52, 0x30, + 0x55, 0xae, + 0x56, 0x47, + 0x57, 0xe1, + 0x58, 0x3a, + 0x5a, 0x1e, + 0x5b, 0x34, + 0x60, 0x00, + 0x63, 0x00, + 0x64, 0x00, + 0x65, 0x00, + 0x66, 0x00, + 0x67, 0x00, + 0x68, 0x00, + 0x69, 0x00, + 0x6a, 0x02, + 0x6b, 0x00, + 0x70, 0xff, + 0x71, 0x00, + 0x72, 0x00, + 0x73, 0x00, + 0x74, 0x0c, + 0x80, 0x00, + 0x81, 0x00, + 0x82, 0x00, + 0x83, 0x00, + 0x84, 0x04, + 0x85, 0x80, + 0x86, 0x24, + 0x87, 0x78, + 0x88, 0x10, + 0x89, 0x00, + 0x90, 0x01, + 0x91, 0x01, + 0xa0, 0x04, + 0xa1, 0x00, + 0xa2, 0x00, + 0xb0, 0x91, + 0xb1, 0x0b, + 0xc0, 0x53, + 0xc1, 0x70, + 0xc2, 0x12, + 0xd0, 0x00, + 0xd1, 0x00, + 0xd2, 0x00, + 0xd3, 0x00, + 0xd4, 0x00, + 0xd5, 0x00, + 0xde, 0x00, + 0xdf, 0x00, + 0x61, 0x38, + 0x62, 0x0a, + 0x53, 0x13, + 0x59, 0x08, + 0xff, 0xff, +}; + +static struct stv0297_config dvbc_philips_tdm1316l_config = { + .demod_address = 0x1c, + .inittab = dvbc_philips_tdm1316l_inittab, + .invert = 0, + .stop_during_read = 1, +}; + +static struct tda10023_config tda10023_config = { + .demod_address = 0xc, + .invert = 0, + .xtal = 16000000, + .pll_m = 11, + .pll_p = 3, + .pll_n = 1, + .deltaf = 0xa511, +}; + +static struct tda827x_config tda827x_config = { + .config = 0, +}; + +/* TT S2-3200 DVB-S (STB0899) Inittab */ +static const struct stb0899_s1_reg tt3200_stb0899_s1_init_1[] = { + + { STB0899_DEV_ID , 0x81 }, + { STB0899_DISCNTRL1 , 0x32 }, + { STB0899_DISCNTRL2 , 0x80 }, + { STB0899_DISRX_ST0 , 0x04 }, + { STB0899_DISRX_ST1 , 0x00 }, + { STB0899_DISPARITY , 0x00 }, + { STB0899_DISSTATUS , 0x20 }, + { STB0899_DISF22 , 0x8c }, + { STB0899_DISF22RX , 0x9a }, + { STB0899_SYSREG , 0x0b }, + { STB0899_ACRPRESC , 0x11 }, + { STB0899_ACRDIV1 , 0x0a }, + { STB0899_ACRDIV2 , 0x05 }, + { STB0899_DACR1 , 0x00 }, + { STB0899_DACR2 , 0x00 }, + { STB0899_OUTCFG , 0x00 }, + { STB0899_MODECFG , 0x00 }, + { STB0899_IRQSTATUS_3 , 0x30 }, + { STB0899_IRQSTATUS_2 , 0x00 }, + { STB0899_IRQSTATUS_1 , 0x00 }, + { STB0899_IRQSTATUS_0 , 0x00 }, + { STB0899_IRQMSK_3 , 0xf3 }, + { STB0899_IRQMSK_2 , 0xfc }, + { STB0899_IRQMSK_1 , 0xff }, + { STB0899_IRQMSK_0 , 0xff }, + { STB0899_IRQCFG , 0x00 }, + { STB0899_I2CCFG , 0x88 }, + { STB0899_I2CRPT , 0x48 }, /* 12k Pullup, Repeater=16, Stop=disabled */ + { STB0899_IOPVALUE5 , 0x00 }, + { STB0899_IOPVALUE4 , 0x20 }, + { STB0899_IOPVALUE3 , 0xc9 }, + { STB0899_IOPVALUE2 , 0x90 }, + { STB0899_IOPVALUE1 , 0x40 }, + { STB0899_IOPVALUE0 , 0x00 }, + { STB0899_GPIO00CFG , 0x82 }, + { STB0899_GPIO01CFG , 0x82 }, + { STB0899_GPIO02CFG , 0x82 }, + { STB0899_GPIO03CFG , 0x82 }, + { STB0899_GPIO04CFG , 0x82 }, + { STB0899_GPIO05CFG , 0x82 }, + { STB0899_GPIO06CFG , 0x82 }, + { STB0899_GPIO07CFG , 0x82 }, + { STB0899_GPIO08CFG , 0x82 }, + { STB0899_GPIO09CFG , 0x82 }, + { STB0899_GPIO10CFG , 0x82 }, + { STB0899_GPIO11CFG , 0x82 }, + { STB0899_GPIO12CFG , 0x82 }, + { STB0899_GPIO13CFG , 0x82 }, + { STB0899_GPIO14CFG , 0x82 }, + { STB0899_GPIO15CFG , 0x82 }, + { STB0899_GPIO16CFG , 0x82 }, + { STB0899_GPIO17CFG , 0x82 }, + { STB0899_GPIO18CFG , 0x82 }, + { STB0899_GPIO19CFG , 0x82 }, + { STB0899_GPIO20CFG , 0x82 }, + { STB0899_SDATCFG , 0xb8 }, + { STB0899_SCLTCFG , 0xba }, + { STB0899_AGCRFCFG , 0x1c }, /* 0x11 */ + { STB0899_GPIO22 , 0x82 }, /* AGCBB2CFG */ + { STB0899_GPIO21 , 0x91 }, /* AGCBB1CFG */ + { STB0899_DIRCLKCFG , 0x82 }, + { STB0899_CLKOUT27CFG , 0x7e }, + { STB0899_STDBYCFG , 0x82 }, + { STB0899_CS0CFG , 0x82 }, + { STB0899_CS1CFG , 0x82 }, + { STB0899_DISEQCOCFG , 0x20 }, + { STB0899_GPIO32CFG , 0x82 }, + { STB0899_GPIO33CFG , 0x82 }, + { STB0899_GPIO34CFG , 0x82 }, + { STB0899_GPIO35CFG , 0x82 }, + { STB0899_GPIO36CFG , 0x82 }, + { STB0899_GPIO37CFG , 0x82 }, + { STB0899_GPIO38CFG , 0x82 }, + { STB0899_GPIO39CFG , 0x82 }, + { STB0899_NCOARSE , 0x15 }, /* 0x15 = 27 Mhz Clock, F/3 = 198MHz, F/6 = 99MHz */ + { STB0899_SYNTCTRL , 0x02 }, /* 0x00 = CLK from CLKI, 0x02 = CLK from XTALI */ + { STB0899_FILTCTRL , 0x00 }, + { STB0899_SYSCTRL , 0x00 }, + { STB0899_STOPCLK1 , 0x20 }, + { STB0899_STOPCLK2 , 0x00 }, + { STB0899_INTBUFSTATUS , 0x00 }, + { STB0899_INTBUFCTRL , 0x0a }, + { 0xffff , 0xff }, +}; + +static const struct stb0899_s1_reg tt3200_stb0899_s1_init_3[] = { + { STB0899_DEMOD , 0x00 }, + { STB0899_RCOMPC , 0xc9 }, + { STB0899_AGC1CN , 0x41 }, + { STB0899_AGC1REF , 0x10 }, + { STB0899_RTC , 0x7a }, + { STB0899_TMGCFG , 0x4e }, + { STB0899_AGC2REF , 0x34 }, + { STB0899_TLSR , 0x84 }, + { STB0899_CFD , 0xc7 }, + { STB0899_ACLC , 0x87 }, + { STB0899_BCLC , 0x94 }, + { STB0899_EQON , 0x41 }, + { STB0899_LDT , 0xdd }, + { STB0899_LDT2 , 0xc9 }, + { STB0899_EQUALREF , 0xb4 }, + { STB0899_TMGRAMP , 0x10 }, + { STB0899_TMGTHD , 0x30 }, + { STB0899_IDCCOMP , 0xfb }, + { STB0899_QDCCOMP , 0x03 }, + { STB0899_POWERI , 0x3b }, + { STB0899_POWERQ , 0x3d }, + { STB0899_RCOMP , 0x81 }, + { STB0899_AGCIQIN , 0x80 }, + { STB0899_AGC2I1 , 0x04 }, + { STB0899_AGC2I2 , 0xf5 }, + { STB0899_TLIR , 0x25 }, + { STB0899_RTF , 0x80 }, + { STB0899_DSTATUS , 0x00 }, + { STB0899_LDI , 0xca }, + { STB0899_CFRM , 0xf1 }, + { STB0899_CFRL , 0xf3 }, + { STB0899_NIRM , 0x2a }, + { STB0899_NIRL , 0x05 }, + { STB0899_ISYMB , 0x17 }, + { STB0899_QSYMB , 0xfa }, + { STB0899_SFRH , 0x2f }, + { STB0899_SFRM , 0x68 }, + { STB0899_SFRL , 0x40 }, + { STB0899_SFRUPH , 0x2f }, + { STB0899_SFRUPM , 0x68 }, + { STB0899_SFRUPL , 0x40 }, + { STB0899_EQUAI1 , 0xfd }, + { STB0899_EQUAQ1 , 0x04 }, + { STB0899_EQUAI2 , 0x0f }, + { STB0899_EQUAQ2 , 0xff }, + { STB0899_EQUAI3 , 0xdf }, + { STB0899_EQUAQ3 , 0xfa }, + { STB0899_EQUAI4 , 0x37 }, + { STB0899_EQUAQ4 , 0x0d }, + { STB0899_EQUAI5 , 0xbd }, + { STB0899_EQUAQ5 , 0xf7 }, + { STB0899_DSTATUS2 , 0x00 }, + { STB0899_VSTATUS , 0x00 }, + { STB0899_VERROR , 0xff }, + { STB0899_IQSWAP , 0x2a }, + { STB0899_ECNT1M , 0x00 }, + { STB0899_ECNT1L , 0x00 }, + { STB0899_ECNT2M , 0x00 }, + { STB0899_ECNT2L , 0x00 }, + { STB0899_ECNT3M , 0x00 }, + { STB0899_ECNT3L , 0x00 }, + { STB0899_FECAUTO1 , 0x06 }, + { STB0899_FECM , 0x01 }, + { STB0899_VTH12 , 0xf0 }, + { STB0899_VTH23 , 0xa0 }, + { STB0899_VTH34 , 0x78 }, + { STB0899_VTH56 , 0x4e }, + { STB0899_VTH67 , 0x48 }, + { STB0899_VTH78 , 0x38 }, + { STB0899_PRVIT , 0xff }, + { STB0899_VITSYNC , 0x19 }, + { STB0899_RSULC , 0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */ + { STB0899_TSULC , 0x42 }, + { STB0899_RSLLC , 0x40 }, + { STB0899_TSLPL , 0x12 }, + { STB0899_TSCFGH , 0x0c }, + { STB0899_TSCFGM , 0x00 }, + { STB0899_TSCFGL , 0x0c }, + { STB0899_TSOUT , 0x4d }, /* 0x0d for CAM */ + { STB0899_RSSYNCDEL , 0x00 }, + { STB0899_TSINHDELH , 0x02 }, + { STB0899_TSINHDELM , 0x00 }, + { STB0899_TSINHDELL , 0x00 }, + { STB0899_TSLLSTKM , 0x00 }, + { STB0899_TSLLSTKL , 0x00 }, + { STB0899_TSULSTKM , 0x00 }, + { STB0899_TSULSTKL , 0xab }, + { STB0899_PCKLENUL , 0x00 }, + { STB0899_PCKLENLL , 0xcc }, + { STB0899_RSPCKLEN , 0xcc }, + { STB0899_TSSTATUS , 0x80 }, + { STB0899_ERRCTRL1 , 0xb6 }, + { STB0899_ERRCTRL2 , 0x96 }, + { STB0899_ERRCTRL3 , 0x89 }, + { STB0899_DMONMSK1 , 0x27 }, + { STB0899_DMONMSK0 , 0x03 }, + { STB0899_DEMAPVIT , 0x5c }, + { STB0899_PLPARM , 0x1f }, + { STB0899_PDELCTRL , 0x48 }, + { STB0899_PDELCTRL2 , 0x00 }, + { STB0899_BBHCTRL1 , 0x00 }, + { STB0899_BBHCTRL2 , 0x00 }, + { STB0899_HYSTTHRESH , 0x77 }, + { STB0899_MATCSTM , 0x00 }, + { STB0899_MATCSTL , 0x00 }, + { STB0899_UPLCSTM , 0x00 }, + { STB0899_UPLCSTL , 0x00 }, + { STB0899_DFLCSTM , 0x00 }, + { STB0899_DFLCSTL , 0x00 }, + { STB0899_SYNCCST , 0x00 }, + { STB0899_SYNCDCSTM , 0x00 }, + { STB0899_SYNCDCSTL , 0x00 }, + { STB0899_ISI_ENTRY , 0x00 }, + { STB0899_ISI_BIT_EN , 0x00 }, + { STB0899_MATSTRM , 0x00 }, + { STB0899_MATSTRL , 0x00 }, + { STB0899_UPLSTRM , 0x00 }, + { STB0899_UPLSTRL , 0x00 }, + { STB0899_DFLSTRM , 0x00 }, + { STB0899_DFLSTRL , 0x00 }, + { STB0899_SYNCSTR , 0x00 }, + { STB0899_SYNCDSTRM , 0x00 }, + { STB0899_SYNCDSTRL , 0x00 }, + { STB0899_CFGPDELSTATUS1 , 0x10 }, + { STB0899_CFGPDELSTATUS2 , 0x00 }, + { STB0899_BBFERRORM , 0x00 }, + { STB0899_BBFERRORL , 0x00 }, + { STB0899_UPKTERRORM , 0x00 }, + { STB0899_UPKTERRORL , 0x00 }, + { 0xffff , 0xff }, +}; + +static struct stb0899_config tt3200_config = { + .init_dev = tt3200_stb0899_s1_init_1, + .init_s2_demod = stb0899_s2_init_2, + .init_s1_demod = tt3200_stb0899_s1_init_3, + .init_s2_fec = stb0899_s2_init_4, + .init_tst = stb0899_s1_init_5, + + .postproc = NULL, + + .demod_address = 0x68, + + .xtal_freq = 27000000, + .inversion = IQ_SWAP_ON, + + .lo_clk = 76500000, + .hi_clk = 99000000, + + .esno_ave = STB0899_DVBS2_ESNO_AVE, + .esno_quant = STB0899_DVBS2_ESNO_QUANT, + .avframes_coarse = STB0899_DVBS2_AVFRAMES_COARSE, + .avframes_fine = STB0899_DVBS2_AVFRAMES_FINE, + .miss_threshold = STB0899_DVBS2_MISS_THRESHOLD, + .uwp_threshold_acq = STB0899_DVBS2_UWP_THRESHOLD_ACQ, + .uwp_threshold_track = STB0899_DVBS2_UWP_THRESHOLD_TRACK, + .uwp_threshold_sof = STB0899_DVBS2_UWP_THRESHOLD_SOF, + .sof_search_timeout = STB0899_DVBS2_SOF_SEARCH_TIMEOUT, + + .btr_nco_bits = STB0899_DVBS2_BTR_NCO_BITS, + .btr_gain_shift_offset = STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET, + .crl_nco_bits = STB0899_DVBS2_CRL_NCO_BITS, + .ldpc_max_iter = STB0899_DVBS2_LDPC_MAX_ITER, + + .tuner_get_frequency = stb6100_get_frequency, + .tuner_set_frequency = stb6100_set_frequency, + .tuner_set_bandwidth = stb6100_set_bandwidth, + .tuner_get_bandwidth = stb6100_get_bandwidth, + .tuner_set_rfsiggain = NULL +}; + +static struct stb6100_config tt3200_stb6100_config = { + .tuner_address = 0x60, + .refclock = 27000000, +}; + +static void frontend_init(struct budget_ci *budget_ci) +{ + switch (budget_ci->budget.dev->pci->subsystem_device) { + case 0x100c: // Hauppauge/TT Nova-CI budget (stv0299/ALPS BSRU6(tsa5059)) + budget_ci->budget.dvb_frontend = + dvb_attach(stv0299_attach, &alps_bsru6_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget_ci->budget.dvb_frontend->tuner_priv = &budget_ci->budget.i2c_adap; + break; + } + break; + + case 0x100f: // Hauppauge/TT Nova-CI budget (stv0299b/Philips su1278(tsa5059)) + budget_ci->budget.dvb_frontend = + dvb_attach(stv0299_attach, &philips_su1278_tt_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_su1278_tt_tuner_set_params; + break; + } + break; + + case 0x1010: // TT DVB-C CI budget (stv0297/Philips tdm1316l(tda6651tt)) + budget_ci->tuner_pll_address = 0x61; + budget_ci->budget.dvb_frontend = + dvb_attach(stv0297_attach, &dvbc_philips_tdm1316l_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = dvbc_philips_tdm1316l_tuner_set_params; + break; + } + break; + + case 0x1011: // Hauppauge/TT Nova-T budget (tda10045/Philips tdm1316l(tda6651tt) + TDA9889) + budget_ci->tuner_pll_address = 0x63; + budget_ci->budget.dvb_frontend = + dvb_attach(tda10045_attach, &philips_tdm1316l_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.init = philips_tdm1316l_tuner_init; + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_tdm1316l_tuner_set_params; + break; + } + break; + + case 0x1012: // TT DVB-T CI budget (tda10046/Philips tdm1316l(tda6651tt)) + budget_ci->tuner_pll_address = 0x60; + budget_ci->budget.dvb_frontend = + dvb_attach(tda10046_attach, &philips_tdm1316l_config_invert, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.init = philips_tdm1316l_tuner_init; + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_tdm1316l_tuner_set_params; + break; + } + break; + + case 0x1017: // TT S-1500 PCI + budget_ci->budget.dvb_frontend = dvb_attach(stv0299_attach, &alps_bsbe1_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = alps_bsbe1_tuner_set_params; + budget_ci->budget.dvb_frontend->tuner_priv = &budget_ci->budget.i2c_adap; + + budget_ci->budget.dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + if (dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, LNBP21_LLC, 0) == NULL) { + printk("%s: No LNBP21 found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } + break; + + case 0x101a: /* TT Budget-C-1501 (philips tda10023/philips tda8274A) */ + budget_ci->budget.dvb_frontend = dvb_attach(tda10023_attach, &tda10023_config, &budget_ci->budget.i2c_adap, 0x48); + if (budget_ci->budget.dvb_frontend) { + if (dvb_attach(tda827x_attach, budget_ci->budget.dvb_frontend, 0x61, &budget_ci->budget.i2c_adap, &tda827x_config) == NULL) { + printk(KERN_ERR "%s: No tda827x found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } + break; + + case 0x101b: /* TT S-1500B (BSBE1-D01A - STV0288/STB6000/LNBP21) */ + budget_ci->budget.dvb_frontend = dvb_attach(stv0288_attach, &stv0288_bsbe1_d01a_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + if (dvb_attach(stb6000_attach, budget_ci->budget.dvb_frontend, 0x63, &budget_ci->budget.i2c_adap)) { + if (!dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, 0, 0)) { + printk(KERN_ERR "%s: No LNBP21 found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } else { + printk(KERN_ERR "%s: No STB6000 found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } + break; + + case 0x1019: // TT S2-3200 PCI + /* + * NOTE! on some STB0899 versions, the internal PLL takes a longer time + * to settle, aka LOCK. On the older revisions of the chip, we don't see + * this, as a result on the newer chips the entire clock tree, will not + * be stable after a freshly POWER 'ed up situation. + * In this case, we should RESET the STB0899 (Active LOW) and wait for + * PLL stabilization. + * + * On the TT S2 3200 and clones, the STB0899 demodulator's RESETB is + * connected to the SAA7146 GPIO, GPIO2, Pin 142 + */ + /* Reset Demodulator */ + saa7146_setgpio(budget_ci->budget.dev, 2, SAA7146_GPIO_OUTLO); + /* Wait for everything to die */ + msleep(50); + /* Pull it up out of Reset state */ + saa7146_setgpio(budget_ci->budget.dev, 2, SAA7146_GPIO_OUTHI); + /* Wait for PLL to stabilize */ + msleep(250); + /* + * PLL state should be stable now. Ideally, we should check + * for PLL LOCK status. But well, never mind! + */ + budget_ci->budget.dvb_frontend = dvb_attach(stb0899_attach, &tt3200_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + if (dvb_attach(stb6100_attach, budget_ci->budget.dvb_frontend, &tt3200_stb6100_config, &budget_ci->budget.i2c_adap)) { + if (!dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, 0, 0)) { + printk("%s: No LNBP21 found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } else { + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } + break; + + } + + if (budget_ci->budget.dvb_frontend == NULL) { + printk("budget-ci: A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", + budget_ci->budget.dev->pci->vendor, + budget_ci->budget.dev->pci->device, + budget_ci->budget.dev->pci->subsystem_vendor, + budget_ci->budget.dev->pci->subsystem_device); + } else { + if (dvb_register_frontend + (&budget_ci->budget.dvb_adapter, budget_ci->budget.dvb_frontend)) { + printk("budget-ci: Frontend registration failed!\n"); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } +} + +static int budget_ci_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct budget_ci *budget_ci; + int err; + + budget_ci = kzalloc(sizeof(struct budget_ci), GFP_KERNEL); + if (!budget_ci) { + err = -ENOMEM; + goto out1; + } + + dprintk(2, "budget_ci: %p\n", budget_ci); + + dev->ext_priv = budget_ci; + + err = ttpci_budget_init(&budget_ci->budget, dev, info, THIS_MODULE, + adapter_nr); + if (err) + goto out2; + + err = msp430_ir_init(budget_ci); + if (err) + goto out3; + + ciintf_init(budget_ci); + + budget_ci->budget.dvb_adapter.priv = budget_ci; + frontend_init(budget_ci); + + ttpci_budget_init_hooks(&budget_ci->budget); + + return 0; + +out3: + ttpci_budget_deinit(&budget_ci->budget); +out2: + kfree(budget_ci); +out1: + return err; +} + +static int budget_ci_detach(struct saa7146_dev *dev) +{ + struct budget_ci *budget_ci = (struct budget_ci *) dev->ext_priv; + struct saa7146_dev *saa = budget_ci->budget.dev; + int err; + + if (budget_ci->budget.ci_present) + ciintf_deinit(budget_ci); + msp430_ir_deinit(budget_ci); + if (budget_ci->budget.dvb_frontend) { + dvb_unregister_frontend(budget_ci->budget.dvb_frontend); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + } + err = ttpci_budget_deinit(&budget_ci->budget); + + // disable frontend and CI interface + saa7146_setgpio(saa, 2, SAA7146_GPIO_INPUT); + + kfree(budget_ci); + + return err; +} + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(ttbs2, "TT-Budget/S-1500 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbci, "TT-Budget/WinTV-NOVA-CI PCI", BUDGET_TT_HW_DISEQC); +MAKE_BUDGET_INFO(ttbt2, "TT-Budget/WinTV-NOVA-T PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbtci, "TT-Budget-T-CI PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbcci, "TT-Budget-C-CI PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttc1501, "TT-Budget C-1501 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(tt3200, "TT-Budget S2-3200 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbs1500b, "TT-Budget S-1500B PCI", BUDGET_TT); + +static const struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(ttbci, 0x13c2, 0x100c), + MAKE_EXTENSION_PCI(ttbci, 0x13c2, 0x100f), + MAKE_EXTENSION_PCI(ttbcci, 0x13c2, 0x1010), + MAKE_EXTENSION_PCI(ttbt2, 0x13c2, 0x1011), + MAKE_EXTENSION_PCI(ttbtci, 0x13c2, 0x1012), + MAKE_EXTENSION_PCI(ttbs2, 0x13c2, 0x1017), + MAKE_EXTENSION_PCI(ttc1501, 0x13c2, 0x101a), + MAKE_EXTENSION_PCI(tt3200, 0x13c2, 0x1019), + MAKE_EXTENSION_PCI(ttbs1500b, 0x13c2, 0x101b), + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget_ci dvb", + .flags = SAA7146_USE_I2C_IRQ, + + .module = THIS_MODULE, + .pci_tbl = &pci_tbl[0], + .attach = budget_ci_attach, + .detach = budget_ci_detach, + + .irq_mask = MASK_03 | MASK_06 | MASK_10, + .irq_func = budget_ci_irq, +}; + +static int __init budget_ci_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_ci_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_ci_init); +module_exit(budget_ci_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hunold, Jack Thomasson, Andrew de Quincey, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called budget PCI DVB cards w/ CI-module produced by Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/pci/ttpci/budget-core.c b/drivers/media/pci/ttpci/budget-core.c new file mode 100644 index 000000000000..5d5796f24469 --- /dev/null +++ b/drivers/media/pci/ttpci/budget-core.c @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * budget-core.c: driver for the SAA7146 based Budget DVB cards + * + * Compiled from various sources by Michael Hunold <michael@mihu.de> + * + * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de> + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * 26feb2004 Support for FS Activy Card (Grundig tuner) by + * Michael Dreher <michael@5dot1.de>, + * Oliver Endriss <o.endriss@gmx.de>, + * Andreas 'randy' Weinberger + * + * the project's page is at https://linuxtv.org + */ + + +#include "budget.h" +#include "ttpci-eeprom.h" + +#define TS_WIDTH (2 * TS_SIZE) +#define TS_WIDTH_ACTIVY TS_SIZE +#define TS_WIDTH_DVBC TS_SIZE +#define TS_HEIGHT_MASK 0xf00 +#define TS_HEIGHT_MASK_ACTIVY 0xc00 +#define TS_HEIGHT_MASK_DVBC 0xe00 +#define TS_MIN_BUFSIZE_K 188 +#define TS_MAX_BUFSIZE_K 1410 +#define TS_MAX_BUFSIZE_K_ACTIVY 564 +#define TS_MAX_BUFSIZE_K_DVBC 1316 +#define BUFFER_WARNING_WAIT (30*HZ) + +int budget_debug; +static int dma_buffer_size = TS_MIN_BUFSIZE_K; +module_param_named(debug, budget_debug, int, 0644); +module_param_named(bufsize, dma_buffer_size, int, 0444); +MODULE_PARM_DESC(debug, "Turn on/off budget debugging (default:off)."); +MODULE_PARM_DESC(bufsize, "DMA buffer size in KB, default: 188, min: 188, max: 1410 (Activy: 564)"); + +/**************************************************************************** + * TT budget / WinTV Nova + ****************************************************************************/ + +static int stop_ts_capture(struct budget *budget) +{ + dprintk(2, "budget: %p\n", budget); + + saa7146_write(budget->dev, MC1, MASK_20); // DMA3 off + SAA7146_IER_DISABLE(budget->dev, MASK_10); + return 0; +} + +static int start_ts_capture(struct budget *budget) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + + if (!budget->feeding || !budget->fe_synced) + return 0; + + saa7146_write(dev, MC1, MASK_20); // DMA3 off + + memset(budget->grabbing, 0x00, budget->buffer_size); + + saa7146_write(dev, PCI_BT_V1, 0x001c0000 | (saa7146_read(dev, PCI_BT_V1) & ~0x001f0000)); + + budget->ttbp = 0; + + /* + * Signal path on the Activy: + * + * tuner -> SAA7146 port A -> SAA7146 BRS -> SAA7146 DMA3 -> memory + * + * Since the tuner feeds 204 bytes packets into the SAA7146, + * DMA3 is configured to strip the trailing 16 FEC bytes: + * Pitch: 188, NumBytes3: 188, NumLines3: 1024 + */ + + switch(budget->card->type) { + case BUDGET_FS_ACTIVY: + saa7146_write(dev, DD1_INIT, 0x04000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25)); + saa7146_write(dev, BRS_CTRL, 0x00000000); + break; + case BUDGET_PATCH: + saa7146_write(dev, DD1_INIT, 0x00000200); + saa7146_write(dev, MC2, (MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + break; + case BUDGET_CIN1200C_MK3: + case BUDGET_KNC1C_MK3: + case BUDGET_KNC1C_TDA10024: + case BUDGET_KNC1CP_MK3: + if (budget->video_port == BUDGET_VIDEO_PORTA) { + saa7146_write(dev, DD1_INIT, 0x06000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x00000000); + } else { + saa7146_write(dev, DD1_INIT, 0x00000600); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + } + break; + default: + if (budget->video_port == BUDGET_VIDEO_PORTA) { + saa7146_write(dev, DD1_INIT, 0x06000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x00000000); + } else { + saa7146_write(dev, DD1_INIT, 0x02000600); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + } + } + + saa7146_write(dev, MC2, (MASK_08 | MASK_24)); + mdelay(10); + + saa7146_write(dev, BASE_ODD3, 0); + if (budget->buffer_size > budget->buffer_height * budget->buffer_width) { + // using odd/even buffers + saa7146_write(dev, BASE_EVEN3, budget->buffer_height * budget->buffer_width); + } else { + // using a single buffer + saa7146_write(dev, BASE_EVEN3, 0); + } + saa7146_write(dev, PROT_ADDR3, budget->buffer_size); + saa7146_write(dev, BASE_PAGE3, budget->pt.dma | ME1 | 0x90); + + saa7146_write(dev, PITCH3, budget->buffer_width); + saa7146_write(dev, NUM_LINE_BYTE3, + (budget->buffer_height << 16) | budget->buffer_width); + + saa7146_write(dev, MC2, (MASK_04 | MASK_20)); + + SAA7146_ISR_CLEAR(budget->dev, MASK_10); /* VPE */ + SAA7146_IER_ENABLE(budget->dev, MASK_10); /* VPE */ + saa7146_write(dev, MC1, (MASK_04 | MASK_20)); /* DMA3 on */ + + return 0; +} + +static int budget_read_fe_status(struct dvb_frontend *fe, + enum fe_status *status) +{ + struct budget *budget = (struct budget *) fe->dvb->priv; + int synced; + int ret; + + if (budget->read_fe_status) + ret = budget->read_fe_status(fe, status); + else + ret = -EINVAL; + + if (!ret) { + synced = (*status & FE_HAS_LOCK); + if (synced != budget->fe_synced) { + budget->fe_synced = synced; + spin_lock(&budget->feedlock); + if (synced) + start_ts_capture(budget); + else + stop_ts_capture(budget); + spin_unlock(&budget->feedlock); + } + } + return ret; +} + +static void vpeirq(struct tasklet_struct *t) +{ + struct budget *budget = from_tasklet(budget, t, vpe_tasklet); + u8 *mem = (u8 *) (budget->grabbing); + u32 olddma = budget->ttbp; + u32 newdma = saa7146_read(budget->dev, PCI_VDP3); + u32 count; + + /* Ensure streamed PCI data is synced to CPU */ + dma_sync_sg_for_cpu(&budget->dev->pci->dev, budget->pt.slist, + budget->pt.nents, DMA_FROM_DEVICE); + + /* nearest lower position divisible by 188 */ + newdma -= newdma % 188; + + if (newdma >= budget->buffer_size) + return; + + budget->ttbp = newdma; + + if (budget->feeding == 0 || newdma == olddma) + return; + + if (newdma > olddma) { /* no wraparound, dump olddma..newdma */ + count = newdma - olddma; + dvb_dmx_swfilter_packets(&budget->demux, mem + olddma, count / 188); + } else { /* wraparound, dump olddma..buflen and 0..newdma */ + count = budget->buffer_size - olddma; + dvb_dmx_swfilter_packets(&budget->demux, mem + olddma, count / 188); + count += newdma; + dvb_dmx_swfilter_packets(&budget->demux, mem, newdma / 188); + } + + if (count > budget->buffer_warning_threshold) + budget->buffer_warnings++; + + if (budget->buffer_warnings && time_after(jiffies, budget->buffer_warning_time)) { + printk("%s %s: used %d times >80%% of buffer (%u bytes now)\n", + budget->dev->name, __func__, budget->buffer_warnings, count); + budget->buffer_warning_time = jiffies + BUFFER_WARNING_WAIT; + budget->buffer_warnings = 0; + } +} + + +static int ttpci_budget_debiread_nolock(struct budget *budget, u32 config, + int addr, int count, int nobusyloop) +{ + struct saa7146_dev *saa = budget->dev; + int result; + + result = saa7146_wait_for_debi_done(saa, nobusyloop); + if (result < 0) + return result; + + saa7146_write(saa, DEBI_COMMAND, (count << 17) | 0x10000 | (addr & 0xffff)); + saa7146_write(saa, DEBI_CONFIG, config); + saa7146_write(saa, DEBI_PAGE, 0); + saa7146_write(saa, MC2, (2 << 16) | 2); + + result = saa7146_wait_for_debi_done(saa, nobusyloop); + if (result < 0) + return result; + + result = saa7146_read(saa, DEBI_AD); + result &= (0xffffffffUL >> ((4 - count) * 8)); + return result; +} + +int ttpci_budget_debiread(struct budget *budget, u32 config, int addr, int count, + int uselocks, int nobusyloop) +{ + if (count > 4 || count <= 0) + return 0; + + if (uselocks) { + unsigned long flags; + int result; + + spin_lock_irqsave(&budget->debilock, flags); + result = ttpci_budget_debiread_nolock(budget, config, addr, + count, nobusyloop); + spin_unlock_irqrestore(&budget->debilock, flags); + return result; + } + return ttpci_budget_debiread_nolock(budget, config, addr, + count, nobusyloop); +} + +static int ttpci_budget_debiwrite_nolock(struct budget *budget, u32 config, + int addr, int count, u32 value, int nobusyloop) +{ + struct saa7146_dev *saa = budget->dev; + int result; + + result = saa7146_wait_for_debi_done(saa, nobusyloop); + if (result < 0) + return result; + + saa7146_write(saa, DEBI_COMMAND, (count << 17) | 0x00000 | (addr & 0xffff)); + saa7146_write(saa, DEBI_CONFIG, config); + saa7146_write(saa, DEBI_PAGE, 0); + saa7146_write(saa, DEBI_AD, value); + saa7146_write(saa, MC2, (2 << 16) | 2); + + result = saa7146_wait_for_debi_done(saa, nobusyloop); + return result < 0 ? result : 0; +} + +int ttpci_budget_debiwrite(struct budget *budget, u32 config, int addr, + int count, u32 value, int uselocks, int nobusyloop) +{ + if (count > 4 || count <= 0) + return 0; + + if (uselocks) { + unsigned long flags; + int result; + + spin_lock_irqsave(&budget->debilock, flags); + result = ttpci_budget_debiwrite_nolock(budget, config, addr, + count, value, nobusyloop); + spin_unlock_irqrestore(&budget->debilock, flags); + return result; + } + return ttpci_budget_debiwrite_nolock(budget, config, addr, + count, value, nobusyloop); +} + + +/**************************************************************************** + * DVB API SECTION + ****************************************************************************/ + +static int budget_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct budget *budget = (struct budget *) demux->priv; + int status = 0; + + dprintk(2, "budget: %p\n", budget); + + if (!demux->dmx.frontend) + return -EINVAL; + + spin_lock(&budget->feedlock); + feed->pusi_seen = false; /* have a clean section start */ + if (budget->feeding++ == 0) + status = start_ts_capture(budget); + spin_unlock(&budget->feedlock); + return status; +} + +static int budget_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct budget *budget = (struct budget *) demux->priv; + int status = 0; + + dprintk(2, "budget: %p\n", budget); + + spin_lock(&budget->feedlock); + if (--budget->feeding == 0) + status = stop_ts_capture(budget); + spin_unlock(&budget->feedlock); + return status; +} + +static int budget_register(struct budget *budget) +{ + struct dvb_demux *dvbdemux = &budget->demux; + int ret; + + dprintk(2, "budget: %p\n", budget); + + dvbdemux->priv = (void *) budget; + + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = budget_start_feed; + dvbdemux->stop_feed = budget_stop_feed; + dvbdemux->write_to_decoder = NULL; + + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING); + + dvb_dmx_init(&budget->demux); + + budget->dmxdev.filternum = 256; + budget->dmxdev.demux = &dvbdemux->dmx; + budget->dmxdev.capabilities = 0; + + dvb_dmxdev_init(&budget->dmxdev, &budget->dvb_adapter); + + budget->hw_frontend.source = DMX_FRONTEND_0; + + ret = dvbdemux->dmx.add_frontend(&dvbdemux->dmx, &budget->hw_frontend); + + if (ret < 0) + goto err_release_dmx; + + budget->mem_frontend.source = DMX_MEMORY_FE; + ret = dvbdemux->dmx.add_frontend(&dvbdemux->dmx, &budget->mem_frontend); + if (ret < 0) + goto err_release_dmx; + + ret = dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, &budget->hw_frontend); + if (ret < 0) + goto err_release_dmx; + + dvb_net_init(&budget->dvb_adapter, &budget->dvb_net, &dvbdemux->dmx); + + return 0; + +err_release_dmx: + dvb_dmxdev_release(&budget->dmxdev); + dvb_dmx_release(&budget->demux); + return ret; +} + +static void budget_unregister(struct budget *budget) +{ + struct dvb_demux *dvbdemux = &budget->demux; + + dprintk(2, "budget: %p\n", budget); + + dvb_net_release(&budget->dvb_net); + + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &budget->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &budget->mem_frontend); + + dvb_dmxdev_release(&budget->dmxdev); + dvb_dmx_release(&budget->demux); +} + +int ttpci_budget_init(struct budget *budget, struct saa7146_dev *dev, + struct saa7146_pci_extension_data *info, + struct module *owner, short *adapter_nums) +{ + int ret = 0; + struct budget_info *bi = info->ext_priv; + int max_bufsize; + int height_mask; + + memset(budget, 0, sizeof(struct budget)); + + dprintk(2, "dev: %p, budget: %p\n", dev, budget); + + budget->card = bi; + budget->dev = (struct saa7146_dev *) dev; + + switch(budget->card->type) { + case BUDGET_FS_ACTIVY: + budget->buffer_width = TS_WIDTH_ACTIVY; + max_bufsize = TS_MAX_BUFSIZE_K_ACTIVY; + height_mask = TS_HEIGHT_MASK_ACTIVY; + break; + + case BUDGET_KNC1C: + case BUDGET_KNC1CP: + case BUDGET_CIN1200C: + case BUDGET_KNC1C_MK3: + case BUDGET_KNC1C_TDA10024: + case BUDGET_KNC1CP_MK3: + case BUDGET_CIN1200C_MK3: + budget->buffer_width = TS_WIDTH_DVBC; + max_bufsize = TS_MAX_BUFSIZE_K_DVBC; + height_mask = TS_HEIGHT_MASK_DVBC; + break; + + default: + budget->buffer_width = TS_WIDTH; + max_bufsize = TS_MAX_BUFSIZE_K; + height_mask = TS_HEIGHT_MASK; + } + + if (dma_buffer_size < TS_MIN_BUFSIZE_K) + dma_buffer_size = TS_MIN_BUFSIZE_K; + else if (dma_buffer_size > max_bufsize) + dma_buffer_size = max_bufsize; + + budget->buffer_height = dma_buffer_size * 1024 / budget->buffer_width; + if (budget->buffer_height > 0xfff) { + budget->buffer_height /= 2; + budget->buffer_height &= height_mask; + budget->buffer_size = 2 * budget->buffer_height * budget->buffer_width; + } else { + budget->buffer_height &= height_mask; + budget->buffer_size = budget->buffer_height * budget->buffer_width; + } + budget->buffer_warning_threshold = budget->buffer_size * 80/100; + budget->buffer_warnings = 0; + budget->buffer_warning_time = jiffies; + + dprintk(2, "%s: buffer type = %s, width = %d, height = %d\n", + budget->dev->name, + budget->buffer_size > budget->buffer_width * budget->buffer_height ? "odd/even" : "single", + budget->buffer_width, budget->buffer_height); + printk("%s: dma buffer size %u\n", budget->dev->name, budget->buffer_size); + + ret = dvb_register_adapter(&budget->dvb_adapter, budget->card->name, + owner, &budget->dev->pci->dev, adapter_nums); + if (ret < 0) + return ret; + + /* set dd1 stream a & b */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25)); + saa7146_write(dev, MC2, (MASK_10 | MASK_26)); + saa7146_write(dev, DD1_INIT, 0x02000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + if (bi->type != BUDGET_FS_ACTIVY) + budget->video_port = BUDGET_VIDEO_PORTB; + else + budget->video_port = BUDGET_VIDEO_PORTA; + spin_lock_init(&budget->feedlock); + spin_lock_init(&budget->debilock); + + /* the Siemens DVB needs this if you want to have the i2c chips + get recognized before the main driver is loaded */ + if (bi->type != BUDGET_FS_ACTIVY) + saa7146_write(dev, GPIO_CTRL, 0x500000); /* GPIO 3 = 1 */ + + strscpy(budget->i2c_adap.name, budget->card->name, + sizeof(budget->i2c_adap.name)); + + saa7146_i2c_adapter_prepare(dev, &budget->i2c_adap, SAA7146_I2C_BUS_BIT_RATE_120); + strscpy(budget->i2c_adap.name, budget->card->name, + sizeof(budget->i2c_adap.name)); + + if (i2c_add_adapter(&budget->i2c_adap) < 0) { + ret = -ENOMEM; + goto err_dvb_unregister; + } + + ttpci_eeprom_parse_mac(&budget->i2c_adap, budget->dvb_adapter.proposed_mac); + + budget->grabbing = saa7146_vmalloc_build_pgtable(dev->pci, budget->buffer_size, &budget->pt); + if (NULL == budget->grabbing) { + ret = -ENOMEM; + goto err_del_i2c; + } + + saa7146_write(dev, PCI_BT_V1, 0x001c0000); + /* upload all */ + saa7146_write(dev, GPIO_CTRL, 0x000000); + + tasklet_setup(&budget->vpe_tasklet, vpeirq); + + /* frontend power on */ + if (bi->type != BUDGET_FS_ACTIVY) + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + if ((ret = budget_register(budget)) == 0) + return 0; /* Everything OK */ + + /* An error occurred, cleanup resources */ + saa7146_vfree_destroy_pgtable(dev->pci, budget->grabbing, &budget->pt); + +err_del_i2c: + i2c_del_adapter(&budget->i2c_adap); + +err_dvb_unregister: + dvb_unregister_adapter(&budget->dvb_adapter); + + return ret; +} + +void ttpci_budget_init_hooks(struct budget *budget) +{ + if (budget->dvb_frontend && !budget->read_fe_status) { + budget->read_fe_status = budget->dvb_frontend->ops.read_status; + budget->dvb_frontend->ops.read_status = budget_read_fe_status; + } +} + +int ttpci_budget_deinit(struct budget *budget) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + + budget_unregister(budget); + + tasklet_kill(&budget->vpe_tasklet); + + saa7146_vfree_destroy_pgtable(dev->pci, budget->grabbing, &budget->pt); + + i2c_del_adapter(&budget->i2c_adap); + + dvb_unregister_adapter(&budget->dvb_adapter); + + return 0; +} + +void ttpci_budget_irq10_handler(struct saa7146_dev *dev, u32 * isr) +{ + struct budget *budget = (struct budget *) dev->ext_priv; + + dprintk(8, "dev: %p, budget: %p\n", dev, budget); + + if (*isr & MASK_10) + tasklet_schedule(&budget->vpe_tasklet); +} + +void ttpci_budget_set_video_port(struct saa7146_dev *dev, int video_port) +{ + struct budget *budget = (struct budget *) dev->ext_priv; + + spin_lock(&budget->feedlock); + budget->video_port = video_port; + if (budget->feeding) { + stop_ts_capture(budget); + start_ts_capture(budget); + } + spin_unlock(&budget->feedlock); +} + +EXPORT_SYMBOL_GPL(ttpci_budget_debiread); +EXPORT_SYMBOL_GPL(ttpci_budget_debiwrite); +EXPORT_SYMBOL_GPL(ttpci_budget_init); +EXPORT_SYMBOL_GPL(ttpci_budget_init_hooks); +EXPORT_SYMBOL_GPL(ttpci_budget_deinit); +EXPORT_SYMBOL_GPL(ttpci_budget_irq10_handler); +EXPORT_SYMBOL_GPL(ttpci_budget_set_video_port); +EXPORT_SYMBOL_GPL(budget_debug); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/ttpci/budget.c b/drivers/media/pci/ttpci/budget.c new file mode 100644 index 000000000000..a88711a3ac7f --- /dev/null +++ b/drivers/media/pci/ttpci/budget.c @@ -0,0 +1,883 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * budget.c: driver for the SAA7146 based Budget DVB cards + * + * Compiled from various sources by Michael Hunold <michael@mihu.de> + * + * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de> + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * 26feb2004 Support for FS Activy Card (Grundig tuner) by + * Michael Dreher <michael@5dot1.de>, + * Oliver Endriss <o.endriss@gmx.de> and + * Andreas 'randy' Weinberger + * + * the project's page is at https://linuxtv.org + */ + +#include "budget.h" +#include "stv0299.h" +#include "ves1x93.h" +#include "ves1820.h" +#include "l64781.h" +#include "tda8083.h" +#include "s5h1420.h" +#include "tda10086.h" +#include "tda826x.h" +#include "lnbp21.h" +#include "bsru6.h" +#include "bsbe1.h" +#include "tdhd1.h" +#include "stv6110x.h" +#include "stv090x.h" +#include "isl6423.h" +#include "lnbh24.h" + + +static int diseqc_method; +module_param(diseqc_method, int, 0444); +MODULE_PARM_DESC(diseqc_method, "Select DiSEqC method for subsystem id 13c2:1003, 0: default, 1: more reliable (for newer revisions only)"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static void Set22K (struct budget *budget, int state) +{ + struct saa7146_dev *dev=budget->dev; + dprintk(2, "budget: %p\n", budget); + saa7146_setgpio(dev, 3, (state ? SAA7146_GPIO_OUTHI : SAA7146_GPIO_OUTLO)); +} + +/* Diseqc functions only for TT Budget card */ +/* taken from the Skyvision DVB driver by + Ralph Metzler <rjkm@metzlerbros.de> */ + +static void DiseqcSendBit (struct budget *budget, int data) +{ + struct saa7146_dev *dev=budget->dev; + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(data ? 500 : 1000); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + udelay(data ? 1000 : 500); +} + +static void DiseqcSendByte (struct budget *budget, int data) +{ + int i, par=1, d; + + dprintk(2, "budget: %p\n", budget); + + for (i=7; i>=0; i--) { + d = (data>>i)&1; + par ^= d; + DiseqcSendBit(budget, d); + } + + DiseqcSendBit(budget, par); +} + +static int SendDiSEqCMsg (struct budget *budget, int len, u8 *msg, unsigned long burst) +{ + struct saa7146_dev *dev=budget->dev; + int i; + + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + mdelay(16); + + for (i=0; i<len; i++) + DiseqcSendByte(budget, msg[i]); + + mdelay(16); + + if (burst!=-1) { + if (burst) + DiseqcSendByte(budget, 0xff); + else { + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + mdelay(12); + udelay(500); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + } + msleep(20); + } + + return 0; +} + +/* + * Routines for the Fujitsu Siemens Activy budget card + * 22 kHz tone and DiSEqC are handled by the frontend. + * Voltage must be set here. + * GPIO 1: LNBP EN, GPIO 2: LNBP VSEL + */ +static int SetVoltage_Activy(struct budget *budget, + enum fe_sec_voltage voltage) +{ + struct saa7146_dev *dev=budget->dev; + + dprintk(2, "budget: %p\n", budget); + + switch (voltage) { + case SEC_VOLTAGE_13: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTLO); + break; + case SEC_VOLTAGE_18: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + break; + case SEC_VOLTAGE_OFF: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTLO); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int siemens_budget_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + return SetVoltage_Activy (budget, voltage); +} + +static int budget_set_tone(struct dvb_frontend *fe, + enum fe_sec_tone_mode tone) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + switch (tone) { + case SEC_TONE_ON: + Set22K (budget, 1); + break; + + case SEC_TONE_OFF: + Set22K (budget, 0); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int budget_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + SendDiSEqCMsg (budget, cmd->msg_len, cmd->msg, 0); + + return 0; +} + +static int budget_diseqc_send_burst(struct dvb_frontend *fe, + enum fe_sec_mini_cmd minicmd) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + SendDiSEqCMsg (budget, 0, NULL, minicmd); + + return 0; +} + +static int alps_bsrv2_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget* budget = (struct budget*) fe->dvb->priv; + u8 pwr = 0; + u8 buf[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + u32 div = (c->frequency + 479500) / 125; + + if (c->frequency > 2000000) + pwr = 3; + else if (c->frequency > 1800000) + pwr = 2; + else if (c->frequency > 1600000) + pwr = 1; + else if (c->frequency > 1200000) + pwr = 0; + else if (c->frequency >= 1100000) + pwr = 1; + else pwr = 2; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = ((div & 0x18000) >> 10) | 0x95; + buf[3] = (pwr << 6) | 0x30; + + // NOTE: since we're using a prescaler of 2, we set the + // divisor frequency to 62.5kHz and divide by 125 above + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct ves1x93_config alps_bsrv2_config = +{ + .demod_address = 0x08, + .xin = 90100000UL, + .invert_pwm = 0, +}; + +static int alps_tdbe2_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x62, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (c->frequency + 35937500 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x85 | ((div >> 10) & 0x60); + data[3] = (c->frequency < 174000000 ? 0x88 : c->frequency < 470000000 ? 0x84 : 0x81); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct ves1820_config alps_tdbe2_config = { + .demod_address = 0x09, + .xin = 57840000UL, + .invert = 1, + .selagc = VES1820_SELAGC_SIGNAMPERR, +}; + +static int grundig_29504_401_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u8 *tuner_addr = fe->tuner_priv; + u32 div; + u8 cfg, cpump, band_select; + u8 data[4]; + struct i2c_msg msg = { .flags = 0, .buf = data, .len = sizeof(data) }; + + if (tuner_addr) + msg.addr = *tuner_addr; + else + msg.addr = 0x61; + + div = (36125000 + c->frequency) / 166666; + + cfg = 0x88; + + if (c->frequency < 175000000) + cpump = 2; + else if (c->frequency < 390000000) + cpump = 1; + else if (c->frequency < 470000000) + cpump = 2; + else if (c->frequency < 750000000) + cpump = 1; + else + cpump = 3; + + if (c->frequency < 175000000) + band_select = 0x0e; + else if (c->frequency < 470000000) + band_select = 0x05; + else + band_select = 0x03; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = ((div >> 10) & 0x60) | cfg; + data[3] = (cpump << 6) | band_select; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct l64781_config grundig_29504_401_config = { + .demod_address = 0x55, +}; + +static struct l64781_config grundig_29504_401_config_activy = { + .demod_address = 0x54, +}; + +static u8 tuner_address_grundig_29504_401_activy = 0x60; + +static int grundig_29504_451_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = c->frequency / 125; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x8e; + data[3] = 0x00; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct tda8083_config grundig_29504_451_config = { + .demod_address = 0x68, +}; + +static int s5h1420_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = c->frequency / 1000; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0xc2; + + if (div < 1450) + data[3] = 0x00; + else if (div < 1850) + data[3] = 0x40; + else if (div < 2000) + data[3] = 0x80; + else + data[3] = 0xc0; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + + return 0; +} + +static struct s5h1420_config s5h1420_config = { + .demod_address = 0x53, + .invert = 1, + .cdclk_polarity = 1, +}; + +static struct tda10086_config tda10086_config = { + .demod_address = 0x0e, + .invert = 0, + .diseqc_tone = 1, + .xtal_freq = TDA10086_XTAL_16M, +}; + +static const struct stv0299_config alps_bsru6_config_activy = { + .demod_address = 0x68, + .inittab = alps_bsru6_inittab, + .mclk = 88000000UL, + .invert = 1, + .op0_off = 1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsru6_set_symbol_rate, +}; + +static const struct stv0299_config alps_bsbe1_config_activy = { + .demod_address = 0x68, + .inittab = alps_bsbe1_inittab, + .mclk = 88000000UL, + .invert = 1, + .op0_off = 1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsbe1_set_symbol_rate, +}; + +static int alps_tdhd1_204_request_firmware(struct dvb_frontend *fe, const struct firmware **fw, char *name) +{ + struct budget *budget = (struct budget *)fe->dvb->priv; + + return request_firmware(fw, name, &budget->dev->pci->dev); +} + + +static int i2c_readreg(struct i2c_adapter *i2c, u8 adr, u8 reg) +{ + u8 val; + struct i2c_msg msg[] = { + { .addr = adr, .flags = 0, .buf = ®, .len = 1 }, + { .addr = adr, .flags = I2C_M_RD, .buf = &val, .len = 1 } + }; + + return (i2c_transfer(i2c, msg, 2) != 2) ? -EIO : val; +} + +static u8 read_pwm(struct budget* budget) +{ + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { { .addr = 0x50,.flags = 0,.buf = &b,.len = 1 }, + { .addr = 0x50,.flags = I2C_M_RD,.buf = &pwm,.len = 1} }; + + if ((i2c_transfer(&budget->i2c_adap, msg, 2) != 2) || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +static struct stv090x_config tt1600_stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 13500000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_DVBCI, + .ts2_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + + .repeater_level = STV090x_RPTLEVEL_16, + + .tuner_init = NULL, + .tuner_sleep = NULL, + .tuner_set_mode = NULL, + .tuner_set_frequency = NULL, + .tuner_get_frequency = NULL, + .tuner_set_bandwidth = NULL, + .tuner_get_bandwidth = NULL, + .tuner_set_bbgain = NULL, + .tuner_get_bbgain = NULL, + .tuner_set_refclk = NULL, + .tuner_get_status = NULL, +}; + +static struct stv6110x_config tt1600_stv6110x_config = { + .addr = 0x60, + .refclk = 27000000, + .clk_div = 2, +}; + +static struct isl6423_config tt1600_isl6423_config = { + .current_max = SEC_CURRENT_515m, + .curlim = SEC_CURRENT_LIM_ON, + .mod_extern = 1, + .addr = 0x08, +}; + +static void frontend_init(struct budget *budget) +{ + (void)alps_bsbe1_config; /* avoid warning */ + + switch(budget->dev->pci->subsystem_device) { + case 0x1003: // Hauppauge/TT Nova budget (stv0299/ALPS BSRU6(tsa5059) OR ves1893/ALPS BSRV2(sp5659)) + case 0x1013: + // try the ALPS BSRV2 first of all + budget->dvb_frontend = dvb_attach(ves1x93_attach, &alps_bsrv2_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsrv2_tuner_set_params; + budget->dvb_frontend->ops.diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops.diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops.set_tone = budget_set_tone; + break; + } + + // try the ALPS BSRU6 now + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + if (budget->dev->pci->subsystem_device == 0x1003 && diseqc_method == 0) { + budget->dvb_frontend->ops.diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops.diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops.set_tone = budget_set_tone; + } + break; + } + break; + + case 0x1004: // Hauppauge/TT DVB-C budget (ves1820/ALPS TDBE2(sp5659)) + + budget->dvb_frontend = dvb_attach(ves1820_attach, &alps_tdbe2_config, &budget->i2c_adap, read_pwm(budget)); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_tdbe2_tuner_set_params; + break; + } + break; + + case 0x1005: // Hauppauge/TT Nova-T budget (L64781/Grundig 29504-401(tsa5060)) + + budget->dvb_frontend = dvb_attach(l64781_attach, &grundig_29504_401_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_401_tuner_set_params; + budget->dvb_frontend->tuner_priv = NULL; + break; + } + break; + + case 0x4f52: /* Cards based on Philips Semi Sylt PCI ref. design */ + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + printk(KERN_INFO "budget: tuner ALPS BSRU6 in Philips Semi. Sylt detected\n"); + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + break; + } + break; + + case 0x4f60: /* Fujitsu Siemens Activy Budget-S PCI rev AL (stv0299/tsa5059) */ + { + int subtype = i2c_readreg(&budget->i2c_adap, 0x50, 0x67); + + if (subtype < 0) + break; + /* fixme: find a better way to identify the card */ + if (subtype < 0x36) { + /* assume ALPS BSRU6 */ + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + printk(KERN_INFO "budget: tuner ALPS BSRU6 detected\n"); + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + break; + } + } else { + /* assume ALPS BSBE1 */ + /* reset tuner */ + saa7146_setgpio(budget->dev, 3, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 3, SAA7146_GPIO_OUTHI); + msleep(250); + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsbe1_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + printk(KERN_INFO "budget: tuner ALPS BSBE1 detected\n"); + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsbe1_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + break; + } + } + break; + } + + case 0x4f61: // Fujitsu Siemens Activy Budget-S PCI rev GR (tda8083/Grundig 29504-451(tsa5522)) + budget->dvb_frontend = dvb_attach(tda8083_attach, &grundig_29504_451_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_451_tuner_set_params; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + } + break; + + case 0x5f60: /* Fujitsu Siemens Activy Budget-T PCI rev AL (tda10046/ALPS TDHD1-204A) */ + budget->dvb_frontend = dvb_attach(tda10046_attach, &alps_tdhd1_204a_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_tdhd1_204a_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + } + break; + + case 0x5f61: /* Fujitsu Siemens Activy Budget-T PCI rev GR (L64781/Grundig 29504-401(tsa5060)) */ + budget->dvb_frontend = dvb_attach(l64781_attach, &grundig_29504_401_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->tuner_priv = &tuner_address_grundig_29504_401_activy; + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_401_tuner_set_params; + } + break; + + case 0x1016: // Hauppauge/TT Nova-S SE (samsung s5h1420/????(tda8260)) + { + struct dvb_frontend *fe; + + fe = dvb_attach(s5h1420_attach, &s5h1420_config, &budget->i2c_adap); + if (fe) { + fe->ops.tuner_ops.set_params = s5h1420_tuner_set_params; + budget->dvb_frontend = fe; + if (dvb_attach(lnbp21_attach, fe, &budget->i2c_adap, + 0, 0) == NULL) { + printk("%s: No LNBP21 found!\n", __func__); + goto error_out; + } + break; + } + } + fallthrough; + case 0x1018: // TT Budget-S-1401 (philips tda10086/philips tda8262) + { + struct dvb_frontend *fe; + + // gpio2 is connected to CLB - reset it + leave it high + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(1); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(1); + + fe = dvb_attach(tda10086_attach, &tda10086_config, &budget->i2c_adap); + if (fe) { + budget->dvb_frontend = fe; + if (dvb_attach(tda826x_attach, fe, 0x60, + &budget->i2c_adap, 0) == NULL) + printk("%s: No tda826x found!\n", __func__); + if (dvb_attach(lnbp21_attach, fe, + &budget->i2c_adap, 0, 0) == NULL) { + printk("%s: No LNBP21 found!\n", __func__); + goto error_out; + } + break; + } + } + fallthrough; + + case 0x101c: { /* TT S2-1600 */ + const struct stv6110x_devctl *ctl; + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(250); + + budget->dvb_frontend = dvb_attach(stv090x_attach, + &tt1600_stv090x_config, + &budget->i2c_adap, + STV090x_DEMODULATOR_0); + + if (budget->dvb_frontend) { + + ctl = dvb_attach(stv6110x_attach, + budget->dvb_frontend, + &tt1600_stv6110x_config, + &budget->i2c_adap); + + if (ctl) { + tt1600_stv090x_config.tuner_init = ctl->tuner_init; + tt1600_stv090x_config.tuner_sleep = ctl->tuner_sleep; + tt1600_stv090x_config.tuner_set_mode = ctl->tuner_set_mode; + tt1600_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency; + tt1600_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency; + tt1600_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth; + tt1600_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth; + tt1600_stv090x_config.tuner_set_bbgain = ctl->tuner_set_bbgain; + tt1600_stv090x_config.tuner_get_bbgain = ctl->tuner_get_bbgain; + tt1600_stv090x_config.tuner_set_refclk = ctl->tuner_set_refclk; + tt1600_stv090x_config.tuner_get_status = ctl->tuner_get_status; + + /* call the init function once to initialize + tuner's clock output divider and demod's + master clock */ + if (budget->dvb_frontend->ops.init) + budget->dvb_frontend->ops.init(budget->dvb_frontend); + + if (dvb_attach(isl6423_attach, + budget->dvb_frontend, + &budget->i2c_adap, + &tt1600_isl6423_config) == NULL) { + printk(KERN_ERR "%s: No Intersil ISL6423 found!\n", __func__); + goto error_out; + } + } else { + printk(KERN_ERR "%s: No STV6110(A) Silicon Tuner found!\n", __func__); + goto error_out; + } + } + } + break; + + case 0x1020: { /* Omicom S2 */ + const struct stv6110x_devctl *ctl; + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(250); + + budget->dvb_frontend = dvb_attach(stv090x_attach, + &tt1600_stv090x_config, + &budget->i2c_adap, + STV090x_DEMODULATOR_0); + + if (budget->dvb_frontend) { + printk(KERN_INFO "budget: Omicom S2 detected\n"); + + ctl = dvb_attach(stv6110x_attach, + budget->dvb_frontend, + &tt1600_stv6110x_config, + &budget->i2c_adap); + + if (ctl) { + tt1600_stv090x_config.tuner_init = ctl->tuner_init; + tt1600_stv090x_config.tuner_sleep = ctl->tuner_sleep; + tt1600_stv090x_config.tuner_set_mode = ctl->tuner_set_mode; + tt1600_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency; + tt1600_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency; + tt1600_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth; + tt1600_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth; + tt1600_stv090x_config.tuner_set_bbgain = ctl->tuner_set_bbgain; + tt1600_stv090x_config.tuner_get_bbgain = ctl->tuner_get_bbgain; + tt1600_stv090x_config.tuner_set_refclk = ctl->tuner_set_refclk; + tt1600_stv090x_config.tuner_get_status = ctl->tuner_get_status; + + /* call the init function once to initialize + tuner's clock output divider and demod's + master clock */ + if (budget->dvb_frontend->ops.init) + budget->dvb_frontend->ops.init(budget->dvb_frontend); + + if (dvb_attach(lnbh24_attach, + budget->dvb_frontend, + &budget->i2c_adap, + LNBH24_PCL | LNBH24_TTX, + LNBH24_TEN, 0x14>>1) == NULL) { + printk(KERN_ERR + "No LNBH24 found!\n"); + goto error_out; + } + } else { + printk(KERN_ERR "%s: No STV6110(A) Silicon Tuner found!\n", __func__); + goto error_out; + } + } + } + break; + } + + if (budget->dvb_frontend == NULL) { + printk("budget: A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", + budget->dev->pci->vendor, + budget->dev->pci->device, + budget->dev->pci->subsystem_vendor, + budget->dev->pci->subsystem_device); + } else { + if (dvb_register_frontend(&budget->dvb_adapter, budget->dvb_frontend)) + goto error_out; + } + return; + +error_out: + printk("budget: Frontend registration failed!\n"); + dvb_frontend_detach(budget->dvb_frontend); + budget->dvb_frontend = NULL; + return; +} + +static int budget_attach (struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) +{ + struct budget *budget = NULL; + int err; + + budget = kmalloc(sizeof(struct budget), GFP_KERNEL); + if( NULL == budget ) { + return -ENOMEM; + } + + dprintk(2, "dev:%p, info:%p, budget:%p\n", dev, info, budget); + + dev->ext_priv = budget; + + err = ttpci_budget_init(budget, dev, info, THIS_MODULE, adapter_nr); + if (err) { + printk("==> failed\n"); + kfree (budget); + return err; + } + + budget->dvb_adapter.priv = budget; + frontend_init(budget); + + ttpci_budget_init_hooks(budget); + + return 0; +} + +static int budget_detach (struct saa7146_dev* dev) +{ + struct budget *budget = (struct budget*) dev->ext_priv; + int err; + + if (budget->dvb_frontend) { + dvb_unregister_frontend(budget->dvb_frontend); + dvb_frontend_detach(budget->dvb_frontend); + } + + err = ttpci_budget_deinit (budget); + + kfree (budget); + dev->ext_priv = NULL; + + return err; +} + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(ttbs, "TT-Budget/WinTV-NOVA-S PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbc, "TT-Budget/WinTV-NOVA-C PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbt, "TT-Budget/WinTV-NOVA-T PCI", BUDGET_TT); +MAKE_BUDGET_INFO(satel, "SATELCO Multimedia PCI", BUDGET_TT_HW_DISEQC); +MAKE_BUDGET_INFO(ttbs1401, "TT-Budget-S-1401 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(tt1600, "TT-Budget S2-1600 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(fsacs0, "Fujitsu Siemens Activy Budget-S PCI (rev GR/grundig frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsacs1, "Fujitsu Siemens Activy Budget-S PCI (rev AL/alps frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsact, "Fujitsu Siemens Activy Budget-T PCI (rev GR/Grundig frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsact1, "Fujitsu Siemens Activy Budget-T PCI (rev AL/ALPS TDHD1-204A)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(omicom, "Omicom S2 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(sylt, "Philips Semi Sylt PCI", BUDGET_TT_HW_DISEQC); + +static const struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1003), + MAKE_EXTENSION_PCI(ttbc, 0x13c2, 0x1004), + MAKE_EXTENSION_PCI(ttbt, 0x13c2, 0x1005), + MAKE_EXTENSION_PCI(satel, 0x13c2, 0x1013), + MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1016), + MAKE_EXTENSION_PCI(ttbs1401, 0x13c2, 0x1018), + MAKE_EXTENSION_PCI(tt1600, 0x13c2, 0x101c), + MAKE_EXTENSION_PCI(fsacs1,0x1131, 0x4f60), + MAKE_EXTENSION_PCI(fsacs0,0x1131, 0x4f61), + MAKE_EXTENSION_PCI(fsact1, 0x1131, 0x5f60), + MAKE_EXTENSION_PCI(fsact, 0x1131, 0x5f61), + MAKE_EXTENSION_PCI(omicom, 0x14c4, 0x1020), + MAKE_EXTENSION_PCI(sylt, 0x1131, 0x4f52), + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget dvb", + .flags = SAA7146_USE_I2C_IRQ, + + .module = THIS_MODULE, + .pci_tbl = pci_tbl, + .attach = budget_attach, + .detach = budget_detach, + + .irq_mask = MASK_10, + .irq_func = ttpci_budget_irq10_handler, +}; + +static int __init budget_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_init); +module_exit(budget_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called budget PCI DVB cards by Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/pci/ttpci/budget.h b/drivers/media/pci/ttpci/budget.h new file mode 100644 index 000000000000..bd87432e6cde --- /dev/null +++ b/drivers/media/pci/ttpci/budget.h @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __BUDGET_DVB__ +#define __BUDGET_DVB__ + +#include <media/dvb_frontend.h> +#include <media/dvbdev.h> +#include <media/demux.h> +#include <media/dvb_demux.h> +#include <media/dmxdev.h> +#include <media/dvb_net.h> + +#include <linux/module.h> +#include <linux/mutex.h> + +#include <media/drv-intf/saa7146.h> + +extern int budget_debug; + +#ifdef dprintk +#undef dprintk +#endif + +#define dprintk(level, fmt, arg...) do { \ + if (level & budget_debug) \ + printk(KERN_DEBUG KBUILD_MODNAME ": %s(): " fmt, \ + __func__, ##arg); \ +} while (0) + +#define TS_SIZE 188 + +struct budget_info { + char *name; + int type; +}; + +/* place to store all the necessary device information */ +struct budget { + + /* devices */ + struct dvb_device dvb_dev; + struct dvb_net dvb_net; + + struct saa7146_dev *dev; + + struct i2c_adapter i2c_adap; + struct budget_info *card; + + unsigned char *grabbing; + struct saa7146_pgtable pt; + + struct tasklet_struct fidb_tasklet; + struct tasklet_struct vpe_tasklet; + + struct dmxdev dmxdev; + struct dvb_demux demux; + + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + + int ci_present; + int video_port; + + u32 buffer_width; + u32 buffer_height; + u32 buffer_size; + u32 buffer_warning_threshold; + u32 buffer_warnings; + unsigned long buffer_warning_time; + + u32 ttbp; + int feeding; + + spinlock_t feedlock; + + spinlock_t debilock; + + struct dvb_adapter dvb_adapter; + struct dvb_frontend *dvb_frontend; + int (*read_fe_status)(struct dvb_frontend *fe, enum fe_status *status); + int fe_synced; + + void *priv; +}; + +#define MAKE_BUDGET_INFO(x_var,x_name,x_type) \ +static struct budget_info x_var ## _info = { \ + .name=x_name, \ + .type=x_type }; \ +static struct saa7146_pci_extension_data x_var = { \ + .ext_priv = &x_var ## _info, \ + .ext = &budget_extension }; + +#define BUDGET_TT 0 +#define BUDGET_TT_HW_DISEQC 1 +#define BUDGET_PATCH 3 +#define BUDGET_FS_ACTIVY 4 +#define BUDGET_CIN1200S 5 +#define BUDGET_CIN1200C 6 +#define BUDGET_CIN1200T 7 +#define BUDGET_KNC1S 8 +#define BUDGET_KNC1C 9 +#define BUDGET_KNC1T 10 +#define BUDGET_KNC1SP 11 +#define BUDGET_KNC1CP 12 +#define BUDGET_KNC1TP 13 +#define BUDGET_TVSTAR 14 +#define BUDGET_CIN1200C_MK3 15 +#define BUDGET_KNC1C_MK3 16 +#define BUDGET_KNC1CP_MK3 17 +#define BUDGET_KNC1S2 18 +#define BUDGET_KNC1C_TDA10024 19 + +#define BUDGET_VIDEO_PORTA 0 +#define BUDGET_VIDEO_PORTB 1 + +extern int ttpci_budget_init(struct budget *budget, struct saa7146_dev *dev, + struct saa7146_pci_extension_data *info, + struct module *owner, short *adapter_nums); +extern void ttpci_budget_init_hooks(struct budget *budget); +extern int ttpci_budget_deinit(struct budget *budget); +extern void ttpci_budget_irq10_handler(struct saa7146_dev *dev, u32 * isr); +extern void ttpci_budget_set_video_port(struct saa7146_dev *dev, int video_port); +extern int ttpci_budget_debiread(struct budget *budget, u32 config, int addr, int count, + int uselocks, int nobusyloop); +extern int ttpci_budget_debiwrite(struct budget *budget, u32 config, int addr, int count, u32 value, + int uselocks, int nobusyloop); + +#endif diff --git a/drivers/media/platform/amphion/venc.c b/drivers/media/platform/amphion/venc.c index 3cbe8ce637e5..e6e8fe45fc7c 100644 --- a/drivers/media/platform/amphion/venc.c +++ b/drivers/media/platform/amphion/venc.c @@ -250,19 +250,10 @@ static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) } if (V4L2_TYPE_IS_OUTPUT(f->type)) { - if (!vpu_color_check_primaries(pix_mp->colorspace)) { - venc->params.color.primaries = pix_mp->colorspace; - vpu_color_get_default(venc->params.color.primaries, - &venc->params.color.transfer, - &venc->params.color.matrix, - &venc->params.color.full_range); - } - if (!vpu_color_check_transfers(pix_mp->xfer_func)) - venc->params.color.transfer = pix_mp->xfer_func; - if (!vpu_color_check_matrix(pix_mp->ycbcr_enc)) - venc->params.color.matrix = pix_mp->ycbcr_enc; - if (!vpu_color_check_full_range(pix_mp->quantization)) - venc->params.color.full_range = pix_mp->quantization; + venc->params.color.primaries = pix_mp->colorspace; + venc->params.color.transfer = pix_mp->xfer_func; + venc->params.color.matrix = pix_mp->ycbcr_enc; + venc->params.color.full_range = pix_mp->quantization; } pix_mp->colorspace = venc->params.color.primaries; @@ -1281,7 +1272,6 @@ static void venc_init(struct file *file) f.fmt.pix_mp.width = 1280; f.fmt.pix_mp.height = 720; f.fmt.pix_mp.field = V4L2_FIELD_NONE; - f.fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; venc_s_fmt(file, &inst->fh, &f); memset(&f, 0, sizeof(f)); diff --git a/drivers/media/platform/amphion/vpu_color.c b/drivers/media/platform/amphion/vpu_color.c index 80b9a53fd1c1..4ae435cbc5cd 100644 --- a/drivers/media/platform/amphion/vpu_color.c +++ b/drivers/media/platform/amphion/vpu_color.c @@ -17,7 +17,7 @@ #include "vpu_helpers.h" static const u8 colorprimaries[] = { - 0, + V4L2_COLORSPACE_LAST, V4L2_COLORSPACE_REC709, /*Rec. ITU-R BT.709-6*/ 0, 0, @@ -31,7 +31,7 @@ static const u8 colorprimaries[] = { }; static const u8 colortransfers[] = { - 0, + V4L2_XFER_FUNC_LAST, V4L2_XFER_FUNC_709, /*Rec. ITU-R BT.709-6*/ 0, 0, @@ -53,7 +53,7 @@ static const u8 colortransfers[] = { }; static const u8 colormatrixcoefs[] = { - 0, + V4L2_YCBCR_ENC_LAST, V4L2_YCBCR_ENC_709, /*Rec. ITU-R BT.709-6*/ 0, 0, diff --git a/drivers/media/platform/chips-media/imx-vdoa.c b/drivers/media/platform/chips-media/imx-vdoa.c index c70871bae193..c3561fcecb98 100644 --- a/drivers/media/platform/chips-media/imx-vdoa.c +++ b/drivers/media/platform/chips-media/imx-vdoa.c @@ -324,11 +324,6 @@ static int vdoa_probe(struct platform_device *pdev) return 0; } -static int vdoa_remove(struct platform_device *pdev) -{ - return 0; -} - static const struct of_device_id vdoa_dt_ids[] = { { .compatible = "fsl,imx6q-vdoa" }, {} @@ -337,7 +332,6 @@ MODULE_DEVICE_TABLE(of, vdoa_dt_ids); static struct platform_driver vdoa_driver = { .probe = vdoa_probe, - .remove = vdoa_remove, .driver = { .name = VDOA_NAME, .of_match_table = vdoa_dt_ids, diff --git a/drivers/media/platform/marvell/mmp-driver.c b/drivers/media/platform/marvell/mmp-driver.c index df16899ab1cb..ef22bf8f276c 100644 --- a/drivers/media/platform/marvell/mmp-driver.c +++ b/drivers/media/platform/marvell/mmp-driver.c @@ -254,7 +254,7 @@ static int mmpcam_probe(struct platform_device *pdev) */ ret = mccic_register(mcam); if (ret) - return ret; + goto out; /* * Add OF clock provider. diff --git a/drivers/media/platform/mediatek/mdp3/Kconfig b/drivers/media/platform/mediatek/mdp3/Kconfig index 846e759a8f6a..602329c44750 100644 --- a/drivers/media/platform/mediatek/mdp3/Kconfig +++ b/drivers/media/platform/mediatek/mdp3/Kconfig @@ -3,14 +3,13 @@ config VIDEO_MEDIATEK_MDP3 tristate "MediaTek MDP v3 driver" depends on MTK_IOMMU || COMPILE_TEST depends on VIDEO_DEV - depends on ARCH_MEDIATEK || COMPILE_TEST depends on HAS_DMA depends on REMOTEPROC + depends on MTK_MMSYS + depends on MTK_CMDQ + depends on MTK_SCP select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV - select MTK_MMSYS - select MTK_CMDQ - select MTK_SCP default n help It is a v4l2 driver and present in MediaTek MT8183 SoC. diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c index 7bc05f42a23c..091a68685590 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c @@ -1002,7 +1002,8 @@ int mdp_comp_config(struct mdp_dev *mdp) if (!pdev) { dev_warn(dev, "can't find platform device of node:%s\n", node->name); - return -ENODEV; + ret = -ENODEV; + goto err_init_comps; } comp->comp_dev = &pdev->dev; diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c index 2d1f6ae9f080..97edcd9d1c81 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c @@ -207,8 +207,8 @@ static int mdp_probe(struct platform_device *pdev) } 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; + if (IS_ERR(mdp->mdp_mutex[i])) { + ret = PTR_ERR(mdp->mdp_mutex[i]); goto err_free_mutex; } } @@ -289,7 +289,8 @@ err_deinit_comp: mdp_comp_destroy(mdp); err_free_mutex: for (i = 0; i < MDP_PIPE_MAX; i++) - mtk_mutex_put(mdp->mdp_mutex[i]); + if (!IS_ERR_OR_NULL(mdp->mdp_mutex[i])) + mtk_mutex_put(mdp->mdp_mutex[i]); err_destroy_device: kfree(mdp); err_return: diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.c index 4305e4eb9900..777d445999e9 100644 --- a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.c +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.c @@ -72,9 +72,9 @@ static void mtk_vcodec_dec_pw_off(struct mtk_vcodec_pm *pm) { int ret; - ret = pm_runtime_put_sync(pm->dev); - if (ret) - mtk_v4l2_err("pm_runtime_put_sync fail %d", ret); + ret = pm_runtime_put(pm->dev); + if (ret && ret != -EAGAIN) + mtk_v4l2_err("pm_runtime_put fail %d", ret); } static void mtk_vcodec_dec_clock_on(struct mtk_vcodec_pm *pm) diff --git a/drivers/media/platform/mediatek/vcodec/venc/venc_h264_if.c b/drivers/media/platform/mediatek/vcodec/venc/venc_h264_if.c index 13c4f860fa69..60fd165c0d94 100644 --- a/drivers/media/platform/mediatek/vcodec/venc/venc_h264_if.c +++ b/drivers/media/platform/mediatek/vcodec/venc/venc_h264_if.c @@ -565,7 +565,7 @@ static int h264_encode_frame(struct venc_h264_inst *inst, *bs_size); ++inst->frm_cnt; ++inst->skip_frm_cnt; - return ret; + return 0; } irq_status = h264_enc_wait_venc_done(inst); @@ -580,7 +580,7 @@ static int h264_encode_frame(struct venc_h264_inst *inst, mtk_vcodec_debug(inst, "frm %d bs_size %d key_frm %d <-", inst->frm_cnt, *bs_size, inst->vpu_inst.is_key_frm); - return ret; + return 0; } static void h264_encode_filler(struct venc_h264_inst *inst, void *buf, diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index e2994d48f10c..71758ee8474b 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -32,10 +32,6 @@ #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)"); - #define ISC_IS_FORMAT_RAW(mbus_code) \ (((mbus_code) & 0xf000) == 0x3000) @@ -114,8 +110,8 @@ static int isc_buffer_prepare(struct vb2_buffer *vb) unsigned long size = isc->fmt.fmt.pix.sizeimage; if (vb2_plane_size(vb, 0) < size) { - v4l2_err(&isc->v4l2_dev, "buffer too small (%lu < %lu)\n", - vb2_plane_size(vb, 0), size); + dev_err(isc->dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); return -EINVAL; } @@ -346,15 +342,14 @@ static int isc_start_streaming(struct vb2_queue *vq, unsigned int count) /* Enable stream on the sub device */ ret = v4l2_subdev_call(isc->current_subdev->sd, video, s_stream, 1); if (ret && ret != -ENOIOCTLCMD) { - v4l2_err(&isc->v4l2_dev, "stream on failed in subdev %d\n", - ret); + dev_err(isc->dev, "stream on failed in subdev %d\n", ret); goto err_start_stream; } ret = pm_runtime_resume_and_get(isc->dev); if (ret < 0) { - v4l2_err(&isc->v4l2_dev, "RPM resume failed in subdev %d\n", - ret); + dev_err(isc->dev, "RPM resume failed in subdev %d\n", + ret); goto err_pm_get; } @@ -423,8 +418,7 @@ static void isc_stop_streaming(struct vb2_queue *vq) /* Wait until the end of the current frame */ if (isc->cur_frm && !wait_for_completion_timeout(&isc->comp, 5 * HZ)) - v4l2_err(&isc->v4l2_dev, - "Timeout waiting for end of the capture\n"); + dev_err(isc->dev, "Timeout waiting for end of the capture\n"); mutex_unlock(&isc->awb_mutex); @@ -436,7 +430,7 @@ static void isc_stop_streaming(struct vb2_queue *vq) /* Disable stream on the sub device */ ret = v4l2_subdev_call(isc->current_subdev->sd, video, s_stream, 0); if (ret && ret != -ENOIOCTLCMD) - v4l2_err(&isc->v4l2_dev, "stream off failed in subdev\n"); + dev_err(isc->dev, "stream off failed in subdev\n"); /* Release all active buffers */ spin_lock_irqsave(&isc->dma_queue_lock, flags); @@ -620,28 +614,28 @@ 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"); + dev_err(isc->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); + dev_dbg(isc->dev, + "Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n", + rgb, yuv, grey, bayer); 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"); + dev_err(isc->dev, "Cannot output RAW if we do not receive RAW.\n"); return -EINVAL; } if (grey && !ISC_IS_FORMAT_RAW(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"); + dev_err(isc->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"); + dev_err(isc->dev, "Cannot convert GREY to another format.\n"); return -EINVAL; } @@ -936,9 +930,9 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f) 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); + dev_dbg(isc->dev, "ISC set_fmt to %.4s @%dx%d\n", + (char *)&f->fmt.pix.pixelformat, + f->fmt.pix.width, f->fmt.pix.height); return 0; } @@ -973,9 +967,9 @@ static int isc_validate(struct isc_device *isc) /* 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); + dev_err(isc->dev, + "Current subdevice is streaming a media bus code that is not supported 0x%x\n", + format.format.code); return -EPIPE; } @@ -993,16 +987,16 @@ static int isc_validate(struct isc_device *isc) /* 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); + dev_err(isc->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); + dev_dbg(isc->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 && @@ -1027,7 +1021,7 @@ static int isc_validate(struct isc_device *isc) isc->config = isc->try_config; - v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n"); + dev_dbg(isc->dev, "New ISC configuration in place\n"); return 0; } @@ -1294,9 +1288,8 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max) if (!*min) *min = 1; - v4l2_dbg(1, debug, &isc->v4l2_dev, - "isc wb: hist_id %u, hist_count %u", - ctrls->hist_id, *hist_count); + dev_dbg(isc->dev, "isc wb: hist_id %u, hist_count %u", + ctrls->hist_id, *hist_count); } static void isc_wb_update(struct isc_ctrls *ctrls) @@ -1318,8 +1311,7 @@ static void isc_wb_update(struct isc_ctrls *ctrls) (u64)hist_count[ISC_HIS_CFG_MODE_GB]; avg >>= 1; - v4l2_dbg(1, debug, &isc->v4l2_dev, - "isc wb: green components average %llu\n", avg); + dev_dbg(isc->dev, "isc wb: green components average %llu\n", avg); /* Green histogram is null, nothing to do */ if (!avg) @@ -1373,9 +1365,9 @@ static void isc_wb_update(struct isc_ctrls *ctrls) else gw_gain[c] = 1 << 9; - v4l2_dbg(1, debug, &isc->v4l2_dev, - "isc wb: component %d, s_gain %u, gw_gain %u\n", - c, s_gain[c], gw_gain[c]); + dev_dbg(isc->dev, + "isc wb: component %d, s_gain %u, gw_gain %u\n", + c, s_gain[c], gw_gain[c]); /* multiply both gains and adjust for decimals */ ctrls->gain[c] = s_gain[c] * gw_gain[c]; ctrls->gain[c] >>= 9; @@ -1383,9 +1375,8 @@ static void isc_wb_update(struct isc_ctrls *ctrls) /* make sure we are not out of range */ ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0)); - v4l2_dbg(1, debug, &isc->v4l2_dev, - "isc wb: component %d, final gain %u\n", - c, ctrls->gain[c]); + dev_dbg(isc->dev, "isc wb: component %d, final gain %u\n", + c, ctrls->gain[c]); } } @@ -1406,8 +1397,8 @@ static void isc_awb_work(struct work_struct *w) isc_hist_count(isc, &min, &max); - v4l2_dbg(1, debug, &isc->v4l2_dev, - "isc wb mode %d: hist min %u , max %u\n", hist_id, min, max); + dev_dbg(isc->dev, + "isc wb mode %d: hist min %u , max %u\n", hist_id, min, max); ctrls->hist_minmax[hist_id][HIST_MIN_INDEX] = min; ctrls->hist_minmax[hist_id][HIST_MAX_INDEX] = max; @@ -1446,8 +1437,8 @@ static void isc_awb_work(struct work_struct *w) * we are basically done. */ if (ctrls->awb == ISC_WB_ONETIME) { - v4l2_info(&isc->v4l2_dev, - "Completed one time white-balance adjustment.\n"); + dev_info(isc->dev, + "Completed one time white-balance adjustment.\n"); /* update the v4l2 controls values */ isc_update_v4l2_ctrls(isc); ctrls->awb = ISC_WB_NONE; @@ -1580,8 +1571,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl) V4L2_CTRL_FLAG_INACTIVE)) { ctrls->awb = ISC_WB_ONETIME; isc_set_histogram(isc, true); - v4l2_dbg(1, debug, &isc->v4l2_dev, - "One time white-balance started.\n"); + dev_dbg(isc->dev, "One time white-balance started.\n"); } return 0; } @@ -1730,7 +1720,7 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier, int pad; if (video_is_registered(&isc->video_dev)) { - v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n"); + dev_err(isc->dev, "only supports one sub-device.\n"); return -EBUSY; } @@ -1739,8 +1729,7 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier, 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); + dev_err(isc->dev, "failed to find pad for %s\n", subdev->name); return pad; } @@ -1813,7 +1802,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = v4l2_device_register_subdev_nodes(&isc->v4l2_dev); if (ret < 0) { - v4l2_err(&isc->v4l2_dev, "Failed to register subdev nodes\n"); + dev_err(isc->dev, "Failed to register subdev nodes\n"); return ret; } @@ -1838,8 +1827,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = vb2_queue_init(q); if (ret < 0) { - v4l2_err(&isc->v4l2_dev, - "vb2_queue_init() failed: %d\n", ret); + dev_err(isc->dev, "vb2_queue_init() failed: %d\n", ret); goto isc_async_complete_err; } @@ -1850,13 +1838,13 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = isc_set_default_fmt(isc); if (ret) { - v4l2_err(&isc->v4l2_dev, "Could not set default format\n"); + dev_err(isc->dev, "Could not set default format\n"); goto isc_async_complete_err; } ret = isc_ctrl_init(isc); if (ret) { - v4l2_err(&isc->v4l2_dev, "Init isc ctrols failed: %d\n", ret); + dev_err(isc->dev, "Init isc ctrols failed: %d\n", ret); goto isc_async_complete_err; } @@ -1876,8 +1864,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret < 0) { - v4l2_err(&isc->v4l2_dev, - "video_register_device failed: %d\n", ret); + dev_err(isc->dev, "video_register_device failed: %d\n", ret); goto isc_async_complete_err; } diff --git a/drivers/media/platform/nxp/dw100/dw100.c b/drivers/media/platform/nxp/dw100/dw100.c index f6d48c36f386..189d60cd5ed1 100644 --- a/drivers/media/platform/nxp/dw100/dw100.c +++ b/drivers/media/platform/nxp/dw100/dw100.c @@ -1571,7 +1571,7 @@ static int dw100_probe(struct platform_device *pdev) dev_name(&pdev->dev), dw_dev); if (ret < 0) { dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); - return ret; + goto err_pm; } ret = v4l2_device_register(&pdev->dev, &dw_dev->v4l2_dev); diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c index 6cd015a35f7c..f085f14d676a 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -2472,19 +2472,12 @@ static int mxc_jpeg_probe(struct platform_device *pdev) jpeg->mode = mode; /* Get clocks */ - jpeg->clk_ipg = devm_clk_get(dev, "ipg"); - if (IS_ERR(jpeg->clk_ipg)) { - dev_err(dev, "failed to get clock: ipg\n"); - ret = PTR_ERR(jpeg->clk_ipg); - goto err_clk; - } - - jpeg->clk_per = devm_clk_get(dev, "per"); - if (IS_ERR(jpeg->clk_per)) { - dev_err(dev, "failed to get clock: per\n"); - ret = PTR_ERR(jpeg->clk_per); + ret = devm_clk_bulk_get_all(&pdev->dev, &jpeg->clks); + if (ret < 0) { + dev_err(dev, "failed to get clock\n"); goto err_clk; } + jpeg->num_clks = ret; ret = mxc_jpeg_attach_pm_domains(jpeg); if (ret < 0) { @@ -2581,32 +2574,20 @@ static int mxc_jpeg_runtime_resume(struct device *dev) struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); int ret; - ret = clk_prepare_enable(jpeg->clk_ipg); - if (ret < 0) { - dev_err(dev, "failed to enable clock: ipg\n"); - goto err_ipg; - } - - ret = clk_prepare_enable(jpeg->clk_per); + ret = clk_bulk_prepare_enable(jpeg->num_clks, jpeg->clks); if (ret < 0) { - dev_err(dev, "failed to enable clock: per\n"); - goto err_per; + dev_err(dev, "failed to enable clock\n"); + return ret; } return 0; - -err_per: - clk_disable_unprepare(jpeg->clk_ipg); -err_ipg: - return ret; } static int mxc_jpeg_runtime_suspend(struct device *dev) { struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); - clk_disable_unprepare(jpeg->clk_ipg); - clk_disable_unprepare(jpeg->clk_per); + clk_bulk_disable_unprepare(jpeg->num_clks, jpeg->clks); return 0; } diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h index 8fa8c0aec5a2..87157db78082 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h @@ -120,8 +120,8 @@ struct mxc_jpeg_dev { spinlock_t hw_lock; /* hardware access lock */ unsigned int mode; struct mutex lock; /* v4l2 ioctls serialization */ - struct clk *clk_ipg; - struct clk *clk_per; + struct clk_bulk_data *clks; + int num_clks; struct platform_device *pdev; struct device *dev; void __iomem *base_reg; diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c index 905072871ed2..be2768a47995 100644 --- a/drivers/media/platform/nxp/imx-mipi-csis.c +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -327,10 +327,6 @@ struct mipi_csis_device { u32 hs_settle; u32 clk_settle; - struct mutex lock; /* Protect csis_fmt and format_mbus */ - const struct csis_pix_format *csis_fmt; - struct v4l2_mbus_framefmt format_mbus[CSIS_PADS_NUM]; - spinlock_t slock; /* Protect events */ struct mipi_csis_event events[MIPI_CSIS_NUM_EVENTS]; struct dentry *debugfs_root; @@ -559,10 +555,10 @@ static void mipi_csis_system_enable(struct mipi_csis_device *csis, int on) mipi_csis_write(csis, MIPI_CSIS_DPHY_CMN_CTRL, val); } -/* Called with the csis.lock mutex held */ -static void __mipi_csis_set_format(struct mipi_csis_device *csis) +static void __mipi_csis_set_format(struct mipi_csis_device *csis, + const struct v4l2_mbus_framefmt *format, + const struct csis_pix_format *csis_fmt) { - struct v4l2_mbus_framefmt *mf = &csis->format_mbus[CSIS_PAD_SINK]; u32 val; /* Color format */ @@ -583,25 +579,26 @@ static void __mipi_csis_set_format(struct mipi_csis_device *csis) * * TODO: Verify which other formats require DUAL (or QUAD) modes. */ - if (csis->csis_fmt->data_type == MIPI_CSI2_DATA_TYPE_YUV422_8) + if (csis_fmt->data_type == MIPI_CSI2_DATA_TYPE_YUV422_8) val |= MIPI_CSIS_ISPCFG_PIXEL_MODE_DUAL; - val |= MIPI_CSIS_ISPCFG_FMT(csis->csis_fmt->data_type); + val |= MIPI_CSIS_ISPCFG_FMT(csis_fmt->data_type); mipi_csis_write(csis, MIPI_CSIS_ISP_CONFIG_CH(0), val); /* Pixel resolution */ - val = mf->width | (mf->height << 16); + val = format->width | (format->height << 16); mipi_csis_write(csis, MIPI_CSIS_ISP_RESOL_CH(0), val); } -static int mipi_csis_calculate_params(struct mipi_csis_device *csis) +static int mipi_csis_calculate_params(struct mipi_csis_device *csis, + const struct csis_pix_format *csis_fmt) { s64 link_freq; u32 lane_rate; /* Calculate the line rate from the pixel rate. */ link_freq = v4l2_get_link_freq(csis->src_sd->ctrl_handler, - csis->csis_fmt->width, + csis_fmt->width, csis->bus.num_data_lanes * 2); if (link_freq < 0) { dev_err(csis->dev, "Unable to obtain link frequency: %d\n", @@ -643,7 +640,9 @@ static int mipi_csis_calculate_params(struct mipi_csis_device *csis) return 0; } -static void mipi_csis_set_params(struct mipi_csis_device *csis) +static void mipi_csis_set_params(struct mipi_csis_device *csis, + const struct v4l2_mbus_framefmt *format, + const struct csis_pix_format *csis_fmt) { int lanes = csis->bus.num_data_lanes; u32 val; @@ -655,7 +654,7 @@ static void mipi_csis_set_params(struct mipi_csis_device *csis) val |= MIPI_CSIS_CMN_CTRL_INTER_MODE; mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, val); - __mipi_csis_set_format(csis); + __mipi_csis_set_format(csis, format, csis_fmt); mipi_csis_write(csis, MIPI_CSIS_DPHY_CMN_CTRL, MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE(csis->hs_settle) | @@ -728,10 +727,12 @@ static int mipi_csis_clk_get(struct mipi_csis_device *csis) return ret; } -static void mipi_csis_start_stream(struct mipi_csis_device *csis) +static void mipi_csis_start_stream(struct mipi_csis_device *csis, + const struct v4l2_mbus_framefmt *format, + const struct csis_pix_format *csis_fmt) { mipi_csis_sw_reset(csis); - mipi_csis_set_params(csis); + mipi_csis_set_params(csis, format, csis_fmt); mipi_csis_system_enable(csis, true); mipi_csis_enable_interrupts(csis, true); } @@ -935,120 +936,63 @@ static struct mipi_csis_device *sd_to_mipi_csis_device(struct v4l2_subdev *sdev) static int mipi_csis_s_stream(struct v4l2_subdev *sd, int enable) { struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + const struct v4l2_mbus_framefmt *format; + const struct csis_pix_format *csis_fmt; + struct v4l2_subdev_state *state; int ret; if (!enable) { - mutex_lock(&csis->lock); - v4l2_subdev_call(csis->src_sd, video, s_stream, 0); mipi_csis_stop_stream(csis); if (csis->debug.enable) mipi_csis_log_counters(csis, true); - mutex_unlock(&csis->lock); - pm_runtime_put(csis->dev); return 0; } - ret = mipi_csis_calculate_params(csis); + state = v4l2_subdev_lock_and_get_active_state(sd); + + format = v4l2_subdev_get_pad_format(sd, state, CSIS_PAD_SINK); + csis_fmt = find_csis_format(format->code); + + ret = mipi_csis_calculate_params(csis, csis_fmt); if (ret < 0) - return ret; + goto err_unlock; mipi_csis_clear_counters(csis); ret = pm_runtime_resume_and_get(csis->dev); if (ret < 0) - return ret; + goto err_unlock; - mutex_lock(&csis->lock); + mipi_csis_start_stream(csis, format, csis_fmt); - mipi_csis_start_stream(csis); ret = v4l2_subdev_call(csis->src_sd, video, s_stream, 1); if (ret < 0) - goto error; + goto err_stop; mipi_csis_log_counters(csis, true); - mutex_unlock(&csis->lock); + v4l2_subdev_unlock_state(state); return 0; -error: +err_stop: mipi_csis_stop_stream(csis); - mutex_unlock(&csis->lock); pm_runtime_put(csis->dev); +err_unlock: + v4l2_subdev_unlock_state(state); return ret; } -static struct v4l2_mbus_framefmt * -mipi_csis_get_format(struct mipi_csis_device *csis, - struct v4l2_subdev_state *sd_state, - enum v4l2_subdev_format_whence which, - unsigned int pad) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return v4l2_subdev_get_try_format(&csis->sd, sd_state, pad); - - return &csis->format_mbus[pad]; -} - -static int mipi_csis_init_cfg(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state) -{ - struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); - struct v4l2_mbus_framefmt *fmt_sink; - struct v4l2_mbus_framefmt *fmt_source; - enum v4l2_subdev_format_whence which; - - which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; - fmt_sink = mipi_csis_get_format(csis, sd_state, which, CSIS_PAD_SINK); - - fmt_sink->code = MEDIA_BUS_FMT_UYVY8_1X16; - fmt_sink->width = MIPI_CSIS_DEF_PIX_WIDTH; - fmt_sink->height = MIPI_CSIS_DEF_PIX_HEIGHT; - fmt_sink->field = V4L2_FIELD_NONE; - - fmt_sink->colorspace = V4L2_COLORSPACE_SMPTE170M; - fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace); - fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace); - fmt_sink->quantization = - V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace, - fmt_sink->ycbcr_enc); - - fmt_source = mipi_csis_get_format(csis, sd_state, which, - CSIS_PAD_SOURCE); - *fmt_source = *fmt_sink; - - return 0; -} - -static int mipi_csis_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *sdformat) -{ - struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); - struct v4l2_mbus_framefmt *fmt; - - fmt = mipi_csis_get_format(csis, sd_state, sdformat->which, - sdformat->pad); - - mutex_lock(&csis->lock); - sdformat->format = *fmt; - mutex_unlock(&csis->lock); - - return 0; -} - static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) { - struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); - /* * The CSIS can't transcode in any way, the source format is identical * to the sink format. @@ -1059,8 +1003,7 @@ static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, if (code->index > 0) return -EINVAL; - fmt = mipi_csis_get_format(csis, sd_state, code->which, - code->pad); + fmt = v4l2_subdev_get_pad_format(sd, sd_state, code->pad); code->code = fmt->code; return 0; } @@ -1080,7 +1023,6 @@ static int mipi_csis_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { - struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); struct csis_pix_format const *csis_fmt; struct v4l2_mbus_framefmt *fmt; unsigned int align; @@ -1090,7 +1032,7 @@ static int mipi_csis_set_fmt(struct v4l2_subdev *sd, * modified. */ if (sdformat->pad == CSIS_PAD_SOURCE) - return mipi_csis_get_fmt(sd, sd_state, sdformat); + return v4l2_subdev_get_fmt(sd, sd_state, sdformat); if (sdformat->pad != CSIS_PAD_SINK) return -EINVAL; @@ -1128,14 +1070,12 @@ static int mipi_csis_set_fmt(struct v4l2_subdev *sd, &sdformat->format.height, 1, CSIS_MAX_PIX_HEIGHT, 0, 0); - fmt = mipi_csis_get_format(csis, sd_state, sdformat->which, - sdformat->pad); - - mutex_lock(&csis->lock); + fmt = v4l2_subdev_get_pad_format(sd, sd_state, sdformat->pad); fmt->code = csis_fmt->code; fmt->width = sdformat->format.width; fmt->height = sdformat->format.height; + fmt->field = V4L2_FIELD_NONE; fmt->colorspace = sdformat->format.colorspace; fmt->quantization = sdformat->format.quantization; fmt->xfer_func = sdformat->format.xfer_func; @@ -1144,48 +1084,68 @@ static int mipi_csis_set_fmt(struct v4l2_subdev *sd, sdformat->format = *fmt; /* Propagate the format from sink to source. */ - fmt = mipi_csis_get_format(csis, sd_state, sdformat->which, - CSIS_PAD_SOURCE); + fmt = v4l2_subdev_get_pad_format(sd, sd_state, CSIS_PAD_SOURCE); *fmt = sdformat->format; /* The format on the source pad might change due to unpacking. */ fmt->code = csis_fmt->output; - /* Store the CSIS format descriptor for active formats. */ - if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) - csis->csis_fmt = csis_fmt; - - mutex_unlock(&csis->lock); - return 0; } static int mipi_csis_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_frame_desc *fd) { - struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); struct v4l2_mbus_frame_desc_entry *entry = &fd->entry[0]; + const struct csis_pix_format *csis_fmt; + const struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_state *state; if (pad != CSIS_PAD_SOURCE) return -EINVAL; + state = v4l2_subdev_lock_and_get_active_state(sd); + fmt = v4l2_subdev_get_pad_format(sd, state, CSIS_PAD_SOURCE); + csis_fmt = find_csis_format(fmt->code); + v4l2_subdev_unlock_state(state); + + if (!csis_fmt) + return -EPIPE; + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL; fd->num_entries = 1; memset(entry, 0, sizeof(*entry)); - mutex_lock(&csis->lock); - entry->flags = 0; - entry->pixelcode = csis->csis_fmt->code; + entry->pixelcode = csis_fmt->code; entry->bus.csi2.vc = 0; - entry->bus.csi2.dt = csis->csis_fmt->data_type; - - mutex_unlock(&csis->lock); + entry->bus.csi2.dt = csis_fmt->data_type; return 0; } +static int mipi_csis_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { + .pad = CSIS_PAD_SINK, + }; + + fmt.format.code = mipi_csis_formats[0].code; + fmt.format.width = MIPI_CSIS_DEF_PIX_WIDTH; + fmt.format.height = MIPI_CSIS_DEF_PIX_HEIGHT; + + fmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt.format.xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt.format.colorspace); + fmt.format.ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt.format.colorspace); + fmt.format.quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt.format.colorspace, + fmt.format.ycbcr_enc); + + return mipi_csis_set_fmt(sd, sd_state, &fmt); +} + static int mipi_csis_log_status(struct v4l2_subdev *sd) { struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); @@ -1208,7 +1168,7 @@ static const struct v4l2_subdev_video_ops mipi_csis_video_ops = { static const struct v4l2_subdev_pad_ops mipi_csis_pad_ops = { .init_cfg = mipi_csis_init_cfg, .enum_mbus_code = mipi_csis_enum_mbus_code, - .get_fmt = mipi_csis_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = mipi_csis_set_fmt, .get_frame_desc = mipi_csis_get_frame_desc, }; @@ -1348,40 +1308,34 @@ static int __maybe_unused mipi_csis_runtime_suspend(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); - int ret = 0; - - mutex_lock(&csis->lock); + int ret; ret = mipi_csis_phy_disable(csis); if (ret) - goto unlock; + return -EAGAIN; mipi_csis_clk_disable(csis); -unlock: - mutex_unlock(&csis->lock); - - return ret ? -EAGAIN : 0; + return 0; } static int __maybe_unused mipi_csis_runtime_resume(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); - int ret = 0; - - mutex_lock(&csis->lock); + int ret; ret = mipi_csis_phy_enable(csis); if (ret) - goto unlock; - - mipi_csis_clk_enable(csis); + return -EAGAIN; -unlock: - mutex_unlock(&csis->lock); + ret = mipi_csis_clk_enable(csis); + if (ret) { + mipi_csis_phy_disable(csis); + return ret; + } - return ret ? -EAGAIN : 0; + return 0; } static const struct dev_pm_ops mipi_csis_pm_ops = { @@ -1396,6 +1350,7 @@ static const struct dev_pm_ops mipi_csis_pm_ops = { static int mipi_csis_subdev_init(struct mipi_csis_device *csis) { struct v4l2_subdev *sd = &csis->sd; + int ret; v4l2_subdev_init(sd, &mipi_csis_subdev_ops); sd->owner = THIS_MODULE; @@ -1417,15 +1372,21 @@ static int mipi_csis_subdev_init(struct mipi_csis_device *csis) return -ENOENT; } - csis->csis_fmt = &mipi_csis_formats[0]; - mipi_csis_init_cfg(sd, NULL); - csis->pads[CSIS_PAD_SINK].flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; csis->pads[CSIS_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT; - return media_entity_pads_init(&sd->entity, CSIS_PADS_NUM, - csis->pads); + ret = media_entity_pads_init(&sd->entity, CSIS_PADS_NUM, csis->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) { + media_entity_cleanup(&sd->entity); + return ret; + } + + return 0; } static int mipi_csis_parse_dt(struct mipi_csis_device *csis) @@ -1450,7 +1411,6 @@ static int mipi_csis_probe(struct platform_device *pdev) if (!csis) return -ENOMEM; - mutex_init(&csis->lock); spin_lock_init(&csis->slock); csis->dev = dev; @@ -1496,20 +1456,20 @@ static int mipi_csis_probe(struct platform_device *pdev) dev_name(dev), csis); if (ret) { dev_err(dev, "Interrupt request failed\n"); - goto disable_clock; + goto err_disable_clock; } /* Initialize and register the subdev. */ ret = mipi_csis_subdev_init(csis); if (ret < 0) - goto disable_clock; + goto err_disable_clock; platform_set_drvdata(pdev, &csis->sd); ret = mipi_csis_async_register(csis); if (ret < 0) { dev_err(dev, "async register failed: %d\n", ret); - goto cleanup; + goto err_cleanup; } /* Initialize debugfs. */ @@ -1520,7 +1480,7 @@ static int mipi_csis_probe(struct platform_device *pdev) if (!pm_runtime_enabled(dev)) { ret = mipi_csis_runtime_resume(dev); if (ret < 0) - goto unregister_all; + goto err_unregister_all; } dev_info(dev, "lanes: %d, freq: %u\n", @@ -1528,17 +1488,17 @@ static int mipi_csis_probe(struct platform_device *pdev) return 0; -unregister_all: +err_unregister_all: mipi_csis_debugfs_exit(csis); -cleanup: +err_cleanup: + v4l2_subdev_cleanup(&csis->sd); media_entity_cleanup(&csis->sd.entity); v4l2_async_nf_unregister(&csis->notifier); v4l2_async_nf_cleanup(&csis->notifier); v4l2_async_unregister_subdev(&csis->sd); -disable_clock: +err_disable_clock: mipi_csis_clk_disable(csis); fwnode_handle_put(csis->sd.fwnode); - mutex_destroy(&csis->lock); return ret; } @@ -1556,9 +1516,9 @@ static int mipi_csis_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); mipi_csis_runtime_suspend(&pdev->dev); mipi_csis_clk_disable(csis); + v4l2_subdev_cleanup(&csis->sd); media_entity_cleanup(&csis->sd.entity); fwnode_handle_put(csis->sd.fwnode); - mutex_destroy(&csis->lock); pm_runtime_set_suspended(&pdev->dev); return 0; diff --git a/drivers/media/platform/nxp/imx-pxp.c b/drivers/media/platform/nxp/imx-pxp.c index 689ae5e6ac62..fde3c36e5e1d 100644 --- a/drivers/media/platform/nxp/imx-pxp.c +++ b/drivers/media/platform/nxp/imx-pxp.c @@ -10,6 +10,7 @@ * Pawel Osciak, <pawel@osciak.com> * Marek Szyprowski, <m.szyprowski@samsung.com> */ +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/dma-mapping.h> @@ -18,15 +19,18 @@ #include <linux/iopoll.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> #include <linux/sched.h> #include <linux/slab.h> -#include <linux/platform_device.h> -#include <media/v4l2-mem2mem.h> -#include <media/v4l2-device.h> -#include <media/v4l2-ioctl.h> +#include <media/media-device.h> #include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> #include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mem2mem.h> #include <media/videobuf2-dma-contig.h> #include "imx-pxp.h" @@ -52,6 +56,11 @@ MODULE_PARM_DESC(debug, "activates debug info"); #define MEM2MEM_HFLIP (1 << 0) #define MEM2MEM_VFLIP (1 << 1) +#define PXP_VERSION_MAJOR(version) \ + FIELD_GET(BM_PXP_VERSION_MAJOR, version) +#define PXP_VERSION_MINOR(version) \ + FIELD_GET(BM_PXP_VERSION_MINOR, version) + #define dprintk(dev, fmt, arg...) \ v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) @@ -168,14 +177,21 @@ enum { V4L2_M2M_DST = 1, }; -static struct pxp_fmt *find_format(struct v4l2_format *f) +static const struct regmap_config pxp_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = HW_PXP_VERSION, +}; + +static struct pxp_fmt *find_format(unsigned int pixelformat) { struct pxp_fmt *fmt; unsigned int k; for (k = 0; k < NUM_FORMATS; k++) { fmt = &formats[k]; - if (fmt->fourcc == f->fmt.pix.pixelformat) + if (fmt->fourcc == pixelformat) break; } @@ -185,12 +201,23 @@ static struct pxp_fmt *find_format(struct v4l2_format *f) return &formats[k]; } +struct pxp_ctx; + +struct pxp_pdata { + u32 (*data_path_ctrl0)(struct pxp_ctx *ctx); +}; + struct pxp_dev { struct v4l2_device v4l2_dev; struct video_device vfd; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device mdev; +#endif struct clk *clk; - void __iomem *mmio; + struct regmap *regmap; + + const struct pxp_pdata *pdata; atomic_t num_inst; struct mutex dev_mutex; @@ -234,6 +261,20 @@ static struct pxp_q_data *get_q_data(struct pxp_ctx *ctx, return &ctx->q_data[V4L2_M2M_DST]; } +static inline u32 pxp_read(struct pxp_dev *dev, u32 reg) +{ + u32 value; + + regmap_read(dev->regmap, reg, &value); + + return value; +} + +static inline void pxp_write(struct pxp_dev *dev, u32 reg, u32 value) +{ + regmap_write(dev->regmap, reg, value); +} + static u32 pxp_v4l2_pix_fmt_to_ps_format(u32 v4l2_pix_fmt) { switch (v4l2_pix_fmt) { @@ -486,11 +527,11 @@ static void pxp_setup_csc(struct pxp_ctx *ctx) csc1_coef = csc1_coef_smpte240m_lim; } - writel(csc1_coef[0], dev->mmio + HW_PXP_CSC1_COEF0); - writel(csc1_coef[1], dev->mmio + HW_PXP_CSC1_COEF1); - writel(csc1_coef[2], dev->mmio + HW_PXP_CSC1_COEF2); + pxp_write(dev, HW_PXP_CSC1_COEF0, csc1_coef[0]); + pxp_write(dev, HW_PXP_CSC1_COEF1, csc1_coef[1]); + pxp_write(dev, HW_PXP_CSC1_COEF2, csc1_coef[2]); } else { - writel(BM_PXP_CSC1_COEF0_BYPASS, dev->mmio + HW_PXP_CSC1_COEF0); + pxp_write(dev, HW_PXP_CSC1_COEF0, BM_PXP_CSC1_COEF0_BYPASS); } if (!pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) && @@ -706,18 +747,95 @@ static void pxp_setup_csc(struct pxp_ctx *ctx) BP_PXP_CSC2_CTRL_CSC_MODE; } - writel(csc2_ctrl, dev->mmio + HW_PXP_CSC2_CTRL); - writel(csc2_coef[0], dev->mmio + HW_PXP_CSC2_COEF0); - writel(csc2_coef[1], dev->mmio + HW_PXP_CSC2_COEF1); - writel(csc2_coef[2], dev->mmio + HW_PXP_CSC2_COEF2); - writel(csc2_coef[3], dev->mmio + HW_PXP_CSC2_COEF3); - writel(csc2_coef[4], dev->mmio + HW_PXP_CSC2_COEF4); - writel(csc2_coef[5], dev->mmio + HW_PXP_CSC2_COEF5); + pxp_write(dev, HW_PXP_CSC2_CTRL, csc2_ctrl); + pxp_write(dev, HW_PXP_CSC2_COEF0, csc2_coef[0]); + pxp_write(dev, HW_PXP_CSC2_COEF1, csc2_coef[1]); + pxp_write(dev, HW_PXP_CSC2_COEF2, csc2_coef[2]); + pxp_write(dev, HW_PXP_CSC2_COEF3, csc2_coef[3]); + pxp_write(dev, HW_PXP_CSC2_COEF4, csc2_coef[4]); + pxp_write(dev, HW_PXP_CSC2_COEF5, csc2_coef[5]); } else { - writel(BM_PXP_CSC2_CTRL_BYPASS, dev->mmio + HW_PXP_CSC2_CTRL); + pxp_write(dev, HW_PXP_CSC2_CTRL, BM_PXP_CSC2_CTRL_BYPASS); } } +static u32 pxp_imx6ull_data_path_ctrl0(struct pxp_ctx *ctx) +{ + u32 ctrl0; + + ctrl0 = 0; + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(3); + /* Bypass Dithering x3CH */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(1); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(3); + /* Select Rotation */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(0); + /* Bypass LUT */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(1); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(3); + /* Select CSC 2 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(3); + /* Bypass Rotation 2 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(3); + + return ctrl0; +} + +static u32 pxp_imx7d_data_path_ctrl0(struct pxp_ctx *ctx) +{ + u32 ctrl0; + + ctrl0 = 0; + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(3); + /* Select Rotation 0 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(3); + /* Select MUX11 for Rotation 0 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(1); + /* Bypass LUT */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(1); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(3); + /* Select CSC 2 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(3); + /* Select Composite Alpha Blending/Color Key 0 for CSC 2 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(1); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(3); + /* Bypass Rotation 1 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(3); + + return ctrl0; +} + +static void pxp_set_data_path(struct pxp_ctx *ctx) +{ + struct pxp_dev *dev = ctx->dev; + u32 ctrl0; + u32 ctrl1; + + ctrl0 = dev->pdata->data_path_ctrl0(ctx); + + ctrl1 = 0; + ctrl1 |= BF_PXP_DATA_PATH_CTRL1_MUX17_SEL(3); + ctrl1 |= BF_PXP_DATA_PATH_CTRL1_MUX16_SEL(3); + + pxp_write(dev, HW_PXP_DATA_PATH_CTRL0, ctrl0); + pxp_write(dev, HW_PXP_DATA_PATH_CTRL1, ctrl1); +} + static int pxp_start(struct pxp_ctx *ctx, struct vb2_v4l2_buffer *in_vb, struct vb2_v4l2_buffer *out_vb) { @@ -871,67 +989,48 @@ static int pxp_start(struct pxp_ctx *ctx, struct vb2_v4l2_buffer *in_vb, BF_PXP_PS_SCALE_XSCALE(xscale); ps_offset = BF_PXP_PS_OFFSET_YOFFSET(0) | BF_PXP_PS_OFFSET_XOFFSET(0); - writel(ctrl, dev->mmio + HW_PXP_CTRL); + pxp_write(dev, HW_PXP_CTRL, ctrl); /* skip STAT */ - writel(out_ctrl, dev->mmio + HW_PXP_OUT_CTRL); - writel(out_buf, dev->mmio + HW_PXP_OUT_BUF); - writel(out_buf2, dev->mmio + HW_PXP_OUT_BUF2); - writel(out_pitch, dev->mmio + HW_PXP_OUT_PITCH); - writel(out_lrc, dev->mmio + HW_PXP_OUT_LRC); - writel(out_ps_ulc, dev->mmio + HW_PXP_OUT_PS_ULC); - writel(out_ps_lrc, dev->mmio + HW_PXP_OUT_PS_LRC); - writel(as_ulc, dev->mmio + HW_PXP_OUT_AS_ULC); - writel(as_lrc, dev->mmio + HW_PXP_OUT_AS_LRC); - writel(ps_ctrl, dev->mmio + HW_PXP_PS_CTRL); - writel(ps_buf, dev->mmio + HW_PXP_PS_BUF); - writel(ps_ubuf, dev->mmio + HW_PXP_PS_UBUF); - writel(ps_vbuf, dev->mmio + HW_PXP_PS_VBUF); - writel(ps_pitch, dev->mmio + HW_PXP_PS_PITCH); - writel(0x00ffffff, dev->mmio + HW_PXP_PS_BACKGROUND_0); - writel(ps_scale, dev->mmio + HW_PXP_PS_SCALE); - writel(ps_offset, dev->mmio + HW_PXP_PS_OFFSET); + pxp_write(dev, HW_PXP_OUT_CTRL, out_ctrl); + pxp_write(dev, HW_PXP_OUT_BUF, out_buf); + pxp_write(dev, HW_PXP_OUT_BUF2, out_buf2); + pxp_write(dev, HW_PXP_OUT_PITCH, out_pitch); + pxp_write(dev, HW_PXP_OUT_LRC, out_lrc); + pxp_write(dev, HW_PXP_OUT_PS_ULC, out_ps_ulc); + pxp_write(dev, HW_PXP_OUT_PS_LRC, out_ps_lrc); + pxp_write(dev, HW_PXP_OUT_AS_ULC, as_ulc); + pxp_write(dev, HW_PXP_OUT_AS_LRC, as_lrc); + pxp_write(dev, HW_PXP_PS_CTRL, ps_ctrl); + pxp_write(dev, HW_PXP_PS_BUF, ps_buf); + pxp_write(dev, HW_PXP_PS_UBUF, ps_ubuf); + pxp_write(dev, HW_PXP_PS_VBUF, ps_vbuf); + pxp_write(dev, HW_PXP_PS_PITCH, ps_pitch); + pxp_write(dev, HW_PXP_PS_BACKGROUND_0, 0x00ffffff); + pxp_write(dev, HW_PXP_PS_SCALE, ps_scale); + pxp_write(dev, HW_PXP_PS_OFFSET, ps_offset); /* disable processed surface color keying */ - writel(0x00ffffff, dev->mmio + HW_PXP_PS_CLRKEYLOW_0); - writel(0x00000000, dev->mmio + HW_PXP_PS_CLRKEYHIGH_0); + pxp_write(dev, HW_PXP_PS_CLRKEYLOW_0, 0x00ffffff); + pxp_write(dev, HW_PXP_PS_CLRKEYHIGH_0, 0x00000000); /* disable alpha surface color keying */ - writel(0x00ffffff, dev->mmio + HW_PXP_AS_CLRKEYLOW_0); - writel(0x00000000, dev->mmio + HW_PXP_AS_CLRKEYHIGH_0); + pxp_write(dev, HW_PXP_AS_CLRKEYLOW_0, 0x00ffffff); + pxp_write(dev, HW_PXP_AS_CLRKEYHIGH_0, 0x00000000); /* setup CSC */ pxp_setup_csc(ctx); /* bypass LUT */ - writel(BM_PXP_LUT_CTRL_BYPASS, dev->mmio + HW_PXP_LUT_CTRL); - - writel(BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(1)| - BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(1)| - BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(0)| - BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(0), - dev->mmio + HW_PXP_DATA_PATH_CTRL0); - writel(BF_PXP_DATA_PATH_CTRL1_MUX17_SEL(1) | - BF_PXP_DATA_PATH_CTRL1_MUX16_SEL(1), - dev->mmio + HW_PXP_DATA_PATH_CTRL1); - - writel(0xffff, dev->mmio + HW_PXP_IRQ_MASK); + pxp_write(dev, HW_PXP_LUT_CTRL, BM_PXP_LUT_CTRL_BYPASS); + + pxp_set_data_path(ctx); + + pxp_write(dev, HW_PXP_IRQ_MASK, 0xffff); /* ungate, enable PS/AS/OUT and PXP operation */ - writel(BM_PXP_CTRL_IRQ_ENABLE, dev->mmio + HW_PXP_CTRL_SET); - writel(BM_PXP_CTRL_ENABLE | BM_PXP_CTRL_ENABLE_CSC2 | - BM_PXP_CTRL_ENABLE_LUT | BM_PXP_CTRL_ENABLE_ROTATE0 | - BM_PXP_CTRL_ENABLE_PS_AS_OUT, dev->mmio + HW_PXP_CTRL_SET); + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_IRQ_ENABLE); + pxp_write(dev, HW_PXP_CTRL_SET, + BM_PXP_CTRL_ENABLE | BM_PXP_CTRL_ENABLE_CSC2 | + BM_PXP_CTRL_ENABLE_ROTATE0 | BM_PXP_CTRL_ENABLE_PS_AS_OUT); return 0; } @@ -1004,23 +1103,23 @@ static irqreturn_t pxp_irq_handler(int irq, void *dev_id) struct pxp_dev *dev = dev_id; u32 stat; - stat = readl(dev->mmio + HW_PXP_STAT); + stat = pxp_read(dev, HW_PXP_STAT); if (stat & BM_PXP_STAT_IRQ0) { /* we expect x = 0, y = height, irq0 = 1 */ if (stat & ~(BM_PXP_STAT_BLOCKX | BM_PXP_STAT_BLOCKY | BM_PXP_STAT_IRQ0)) dprintk(dev, "%s: stat = 0x%08x\n", __func__, stat); - writel(BM_PXP_STAT_IRQ0, dev->mmio + HW_PXP_STAT_CLR); + pxp_write(dev, HW_PXP_STAT_CLR, BM_PXP_STAT_IRQ0); pxp_job_finish(dev); } else { - u32 irq = readl(dev->mmio + HW_PXP_IRQ); + u32 irq = pxp_read(dev, HW_PXP_IRQ); dprintk(dev, "%s: stat = 0x%08x\n", __func__, stat); dprintk(dev, "%s: irq = 0x%08x\n", __func__, irq); - writel(irq, dev->mmio + HW_PXP_IRQ_CLR); + pxp_write(dev, HW_PXP_IRQ_CLR, irq); } return IRQ_HANDLED; @@ -1034,8 +1133,6 @@ static int pxp_querycap(struct file *file, void *priv, { strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); strscpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); - snprintf(cap->bus_info, sizeof(cap->bus_info), - "platform:%s", MEM2MEM_NAME); return 0; } @@ -1181,10 +1278,10 @@ static int pxp_try_fmt_vid_cap(struct file *file, void *priv, struct pxp_fmt *fmt; struct pxp_ctx *ctx = file2ctx(file); - fmt = find_format(f); + fmt = find_format(f->fmt.pix.pixelformat); if (!fmt) { f->fmt.pix.pixelformat = formats[0].fourcc; - fmt = find_format(f); + fmt = find_format(f->fmt.pix.pixelformat); } if (!(fmt->types & MEM2MEM_CAPTURE)) { v4l2_err(&ctx->dev->v4l2_dev, @@ -1209,10 +1306,10 @@ static int pxp_try_fmt_vid_out(struct file *file, void *priv, struct pxp_fmt *fmt; struct pxp_ctx *ctx = file2ctx(file); - fmt = find_format(f); + fmt = find_format(f->fmt.pix.pixelformat); if (!fmt) { f->fmt.pix.pixelformat = formats[0].fourcc; - fmt = find_format(f); + fmt = find_format(f->fmt.pix.pixelformat); } if (!(fmt->types & MEM2MEM_OUTPUT)) { v4l2_err(&ctx->dev->v4l2_dev, @@ -1245,7 +1342,7 @@ static int pxp_s_fmt(struct pxp_ctx *ctx, struct v4l2_format *f) return -EBUSY; } - q_data->fmt = find_format(f); + q_data->fmt = find_format(f->fmt.pix.pixelformat); q_data->width = f->fmt.pix.width; q_data->height = f->fmt.pix.height; q_data->bytesperline = f->fmt.pix.bytesperline; @@ -1304,6 +1401,26 @@ static int pxp_s_fmt_vid_out(struct file *file, void *priv, return 0; } +static int pxp_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > 0) + return -EINVAL; + + if (!find_format(fsize->pixel_format)) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = MIN_W; + fsize->stepwise.max_width = MAX_W; + fsize->stepwise.step_width = 1 << ALIGN_W; + fsize->stepwise.min_height = MIN_H; + fsize->stepwise.max_height = MAX_H; + fsize->stepwise.step_height = 1 << ALIGN_H; + + return 0; +} + static u8 pxp_degrees_to_rot_mode(u32 degrees) { switch (degrees) { @@ -1372,6 +1489,8 @@ static const struct v4l2_ioctl_ops pxp_ioctl_ops = { .vidioc_try_fmt_vid_out = pxp_try_fmt_vid_out, .vidioc_s_fmt_vid_out = pxp_s_fmt_vid_out, + .vidioc_enum_framesizes = pxp_enum_framesizes, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, @@ -1644,18 +1763,18 @@ static int pxp_soft_reset(struct pxp_dev *dev) int ret; u32 val; - writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_CLR); - writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_CLR); + pxp_write(dev, HW_PXP_CTRL_CLR, BM_PXP_CTRL_SFTRST); + pxp_write(dev, HW_PXP_CTRL_CLR, BM_PXP_CTRL_CLKGATE); - writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_SET); + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_SFTRST); - ret = readl_poll_timeout(dev->mmio + HW_PXP_CTRL, val, - val & BM_PXP_CTRL_CLKGATE, 0, 100); + ret = regmap_read_poll_timeout(dev->regmap, HW_PXP_CTRL, val, + val & BM_PXP_CTRL_CLKGATE, 0, 100); if (ret < 0) return ret; - writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_CLR); - writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_CLR); + pxp_write(dev, HW_PXP_CTRL_CLR, BM_PXP_CTRL_SFTRST); + pxp_write(dev, HW_PXP_CTRL_CLR, BM_PXP_CTRL_CLKGATE); return 0; } @@ -1664,13 +1783,17 @@ static int pxp_probe(struct platform_device *pdev) { struct pxp_dev *dev; struct video_device *vfd; + u32 hw_version; int irq; int ret; + void __iomem *mmio; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; + dev->pdata = of_device_get_match_data(&pdev->dev); + dev->clk = devm_clk_get(&pdev->dev, "axi"); if (IS_ERR(dev->clk)) { ret = PTR_ERR(dev->clk); @@ -1678,9 +1801,11 @@ static int pxp_probe(struct platform_device *pdev) return ret; } - dev->mmio = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(dev->mmio)) - return PTR_ERR(dev->mmio); + mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mmio)) + return PTR_ERR(mmio); + dev->regmap = devm_regmap_init_mmio(&pdev->dev, mmio, + &pxp_regmap_config); irq = platform_get_irq(pdev, 0); if (irq < 0) @@ -1688,8 +1813,8 @@ static int pxp_probe(struct platform_device *pdev) spin_lock_init(&dev->irqlock); - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, pxp_irq_handler, - IRQF_ONESHOT, dev_name(&pdev->dev), dev); + ret = devm_request_irq(&pdev->dev, irq, pxp_irq_handler, 0, + dev_name(&pdev->dev), dev); if (ret < 0) { dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); return ret; @@ -1705,6 +1830,10 @@ static int pxp_probe(struct platform_device *pdev) goto err_clk; } + hw_version = pxp_read(dev, HW_PXP_VERSION); + dev_dbg(&pdev->dev, "PXP Version %u.%u\n", + PXP_VERSION_MAJOR(hw_version), PXP_VERSION_MINOR(hw_version)); + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); if (ret) goto err_clk; @@ -1737,8 +1866,34 @@ static int pxp_probe(struct platform_device *pdev) goto err_m2m; } +#ifdef CONFIG_MEDIA_CONTROLLER + dev->mdev.dev = &pdev->dev; + strscpy(dev->mdev.model, MEM2MEM_NAME, sizeof(dev->mdev.model)); + media_device_init(&dev->mdev); + dev->v4l2_dev.mdev = &dev->mdev; + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize media device\n"); + goto err_vfd; + } + + ret = media_device_register(&dev->mdev); + if (ret) { + dev_err(&pdev->dev, "Failed to register media device\n"); + goto err_m2m_mc; + } +#endif + return 0; +#ifdef CONFIG_MEDIA_CONTROLLER +err_m2m_mc: + v4l2_m2m_unregister_media_controller(dev->m2m_dev); +err_vfd: + video_unregister_device(vfd); +#endif err_m2m: v4l2_m2m_release(dev->m2m_dev); err_v4l2: @@ -1753,12 +1908,17 @@ static int pxp_remove(struct platform_device *pdev) { struct pxp_dev *dev = platform_get_drvdata(pdev); - writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_SET); - writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_SET); + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_CLKGATE); + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_SFTRST); clk_disable_unprepare(dev->clk); v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_NAME); + +#ifdef CONFIG_MEDIA_CONTROLLER + media_device_unregister(&dev->mdev); + v4l2_m2m_unregister_media_controller(dev->m2m_dev); +#endif video_unregister_device(&dev->vfd); v4l2_m2m_release(dev->m2m_dev); v4l2_device_unregister(&dev->v4l2_dev); @@ -1766,8 +1926,17 @@ static int pxp_remove(struct platform_device *pdev) return 0; } +static const struct pxp_pdata pxp_imx6ull_pdata = { + .data_path_ctrl0 = pxp_imx6ull_data_path_ctrl0, +}; + +static const struct pxp_pdata pxp_imx7d_pdata = { + .data_path_ctrl0 = pxp_imx7d_data_path_ctrl0, +}; + static const struct of_device_id pxp_dt_ids[] = { - { .compatible = "fsl,imx6ull-pxp", .data = NULL }, + { .compatible = "fsl,imx6ull-pxp", .data = &pxp_imx6ull_pdata }, + { .compatible = "fsl,imx7d-pxp", .data = &pxp_imx7d_pdata }, { }, }; MODULE_DEVICE_TABLE(of, pxp_dt_ids); diff --git a/drivers/media/platform/nxp/imx7-media-csi.c b/drivers/media/platform/nxp/imx7-media-csi.c index 886374d3a6ff..c22bf5c827e7 100644 --- a/drivers/media/platform/nxp/imx7-media-csi.c +++ b/drivers/media/platform/nxp/imx7-media-csi.c @@ -211,7 +211,6 @@ struct imx7_csi { int irq; struct clk *mclk; - struct mutex lock; /* Protects is_streaming, format_mbus, cc */ spinlock_t irqlock; /* Protects last_eof */ /* Media and V4L2 device */ @@ -227,9 +226,6 @@ struct imx7_csi { 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 */ @@ -510,7 +506,8 @@ static void imx7_csi_dma_stop(struct imx7_csi *csi) imx7_csi_hw_disable_irq(csi); } -static void imx7_csi_configure(struct imx7_csi *csi) +static void imx7_csi_configure(struct imx7_csi *csi, + struct v4l2_subdev_state *sd_state) { struct v4l2_pix_format *out_pix = &csi->vdev_fmt; int width = out_pix->width; @@ -541,12 +538,17 @@ static void imx7_csi_configure(struct imx7_csi *csi) out_pix->pixelformat == V4L2_PIX_FMT_YUYV) width *= 2; } else { + const struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt = v4l2_subdev_get_pad_format(&csi->sd, sd_state, + IMX7_CSI_PAD_SINK); + 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) { + switch (sink_fmt->code) { case MEDIA_BUS_FMT_Y8_1X8: case MEDIA_BUS_FMT_SBGGR8_1X8: case MEDIA_BUS_FMT_SGBRG8_1X8: @@ -627,7 +629,8 @@ static void imx7_csi_configure(struct imx7_csi *csi) imx7_csi_reg_write(csi, stride, CSI_CSIFBUF_PARA); } -static int imx7_csi_init(struct imx7_csi *csi) +static int imx7_csi_init(struct imx7_csi *csi, + struct v4l2_subdev_state *sd_state) { int ret; @@ -635,11 +638,13 @@ static int imx7_csi_init(struct imx7_csi *csi) if (ret < 0) return ret; - imx7_csi_configure(csi); + imx7_csi_configure(csi, sd_state); ret = imx7_csi_dma_setup(csi); - if (ret < 0) + if (ret < 0) { + clk_disable_unprepare(csi->mclk); return ret; + } return 0; } @@ -1411,14 +1416,15 @@ static void imx7_csi_video_buf_queue(struct vb2_buffer *vb) static int imx7_csi_video_validate_fmt(struct imx7_csi *csi) { - struct v4l2_subdev_format fmt_src; + struct v4l2_subdev_format fmt_src = { + .pad = IMX7_CSI_PAD_SRC, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; 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); + ret = v4l2_subdev_call_state_active(&csi->sd, pad, get_fmt, &fmt_src); if (ret) return ret; @@ -1599,17 +1605,15 @@ static struct imx7_csi_vb2_buffer *imx7_csi_video_next_buf(struct imx7_csi *csi) 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; + struct v4l2_mbus_framefmt format = { }; + + format.code = IMX7_CSI_DEF_MBUS_CODE; + format.width = IMX7_CSI_DEF_PIX_WIDTH; + 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; + imx7_csi_mbus_fmt_to_pix_fmt(&csi->vdev_fmt, &format, NULL); + csi->vdev_compose.width = format.width; + csi->vdev_compose.height = format.height; csi->vdev_cc = imx7_csi_find_pixel_format(csi->vdev_fmt.pixelformat); @@ -1728,20 +1732,13 @@ static int imx7_csi_video_init(struct imx7_csi *csi) static int imx7_csi_s_stream(struct v4l2_subdev *sd, int enable) { struct imx7_csi *csi = v4l2_get_subdevdata(sd); + struct v4l2_subdev_state *sd_state; int ret = 0; - mutex_lock(&csi->lock); - - if (!csi->src_sd) { - ret = -EPIPE; - goto out_unlock; - } - - if (csi->is_streaming == !!enable) - goto out_unlock; + sd_state = v4l2_subdev_lock_and_get_active_state(sd); if (enable) { - ret = imx7_csi_init(csi); + ret = imx7_csi_init(csi, sd_state); if (ret < 0) goto out_unlock; @@ -1763,29 +1760,14 @@ static int imx7_csi_s_stream(struct v4l2_subdev *sd, int enable) csi->is_streaming = !!enable; out_unlock: - mutex_unlock(&csi->lock); + v4l2_subdev_unlock_state(sd_state); 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; @@ -1793,7 +1775,7 @@ static int imx7_csi_init_cfg(struct v4l2_subdev *sd, for (i = 0; i < IMX7_CSI_PADS_NUM; i++) { struct v4l2_mbus_framefmt *mf = - imx7_csi_get_format(csi, sd_state, i, which); + v4l2_subdev_get_pad_format(sd, sd_state, i); mf->code = IMX7_CSI_DEF_MBUS_CODE; mf->width = IMX7_CSI_DEF_PIX_WIDTH; @@ -1805,8 +1787,6 @@ static int imx7_csi_init_cfg(struct v4l2_subdev *sd, 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; @@ -1816,59 +1796,30 @@ 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); + in_fmt = v4l2_subdev_get_pad_format(sd, sd_state, IMX7_CSI_PAD_SINK); 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; + break; } 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) { + default: ret = -EINVAL; - goto out_unlock; + break; } - sdformat->format = *fmt; - -out_unlock: - mutex_unlock(&csi->lock); - return ret; } @@ -1918,19 +1869,16 @@ static void imx7_csi_try_colorimetry(struct v4l2_mbus_framefmt *tryfmt) 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) +static void imx7_csi_try_fmt(struct v4l2_subdev *sd, + 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; + in_fmt = v4l2_subdev_get_pad_format(sd, sd_state, IMX7_CSI_PAD_SINK); switch (sdformat->pad) { case IMX7_CSI_PAD_SRC: @@ -1947,6 +1895,7 @@ static int imx7_csi_try_fmt(struct imx7_csi *csi, 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) { @@ -1958,13 +1907,9 @@ static int imx7_csi_try_fmt(struct imx7_csi *csi, 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, @@ -1977,28 +1922,13 @@ static int imx7_csi_set_fmt(struct v4l2_subdev *sd, 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; - } + if (csi->is_streaming) + return -EBUSY; - ret = imx7_csi_try_fmt(csi, sd_state, sdformat, &cc); - if (ret < 0) - goto out_unlock; + imx7_csi_try_fmt(sd, sd_state, sdformat, &cc); - fmt = imx7_csi_get_format(csi, sd_state, sdformat->pad, - sdformat->which); - if (!fmt) { - ret = -EINVAL; - goto out_unlock; - } + fmt = v4l2_subdev_get_pad_format(sd, sd_state, sdformat->pad); *fmt = sdformat->format; @@ -2007,25 +1937,14 @@ static int imx7_csi_set_fmt(struct v4l2_subdev *sd, 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; + imx7_csi_try_fmt(sd, sd_state, &format, &outcc); - if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) - csi->cc[IMX7_CSI_PAD_SRC] = outcc; + outfmt = v4l2_subdev_get_pad_format(sd, sd_state, + IMX7_CSI_PAD_SRC); + *outfmt = format.format; } - if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) - csi->cc[sdformat->pad] = cc; - -out_unlock: - mutex_unlock(&csi->lock); - - return ret; + return 0; } static int imx7_csi_pad_link_validate(struct v4l2_subdev *sd, @@ -2038,9 +1957,6 @@ static int imx7_csi_pad_link_validate(struct v4l2_subdev *sd, 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. @@ -2128,7 +2044,7 @@ static const struct v4l2_subdev_video_ops imx7_csi_video_ops = { 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, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = imx7_csi_set_fmt, .link_validate = imx7_csi_pad_link_validate, }; @@ -2201,7 +2117,7 @@ static int imx7_csi_async_register(struct imx7_csi *csi) ret = PTR_ERR(asd); /* OK if asd already exists */ if (ret != -EEXIST) - return ret; + goto error; } } @@ -2209,15 +2125,20 @@ static int imx7_csi_async_register(struct imx7_csi *csi) ret = v4l2_async_nf_register(&csi->v4l2_dev, &csi->notifier); if (ret) - return ret; + goto error; return 0; + +error: + v4l2_async_nf_cleanup(&csi->notifier); + return ret; } static void imx7_csi_media_cleanup(struct imx7_csi *csi) { v4l2_device_unregister(&csi->v4l2_dev); media_device_unregister(&csi->mdev); + v4l2_subdev_cleanup(&csi->sd); media_device_cleanup(&csi->mdev); } @@ -2285,6 +2206,10 @@ static int imx7_csi_media_init(struct imx7_csi *csi) if (ret) goto error; + ret = v4l2_subdev_init_finalize(&csi->sd); + if (ret) + goto error; + ret = v4l2_device_register_subdev(&csi->v4l2_dev, &csi->sd); if (ret) goto error; @@ -2310,27 +2235,22 @@ static int imx7_csi_probe(struct platform_device *pdev) 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; + return ret; } csi->irq = platform_get_irq(pdev, 0); - if (csi->irq < 0) { - ret = csi->irq; - goto destroy_mutex; - } + if (csi->irq < 0) + return csi->irq; csi->regbase = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(csi->regbase)) { - ret = PTR_ERR(csi->regbase); - goto destroy_mutex; - } + if (IS_ERR(csi->regbase)) + return PTR_ERR(csi->regbase); csi->model = (enum imx_csi_model)(uintptr_t)of_device_get_match_data(&pdev->dev); @@ -2338,34 +2258,23 @@ static int imx7_csi_probe(struct platform_device *pdev) (void *)csi); if (ret < 0) { dev_err(dev, "Request CSI IRQ failed.\n"); - goto destroy_mutex; + return ret; } /* 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; + return ret; ret = imx7_csi_async_register(csi); if (ret) - goto subdev_notifier_cleanup; + goto err_media_cleanup; return 0; -subdev_notifier_cleanup: - v4l2_async_nf_unregister(&csi->notifier); - v4l2_async_nf_cleanup(&csi->notifier); -media_cleanup: +err_media_cleanup: imx7_csi_media_cleanup(csi); -destroy_mutex: - mutex_destroy(&csi->lock); - return ret; } @@ -2379,8 +2288,6 @@ static int imx7_csi_remove(struct platform_device *pdev) v4l2_async_nf_cleanup(&csi->notifier); v4l2_async_unregister_subdev(&csi->sd); - mutex_destroy(&csi->lock); - return 0; } diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 451a4c9b3d30..04baa80494c6 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -429,7 +429,8 @@ static void csiphy_gen2_config_lanes(struct csiphy_device *csiphy, array_size = ARRAY_SIZE(lane_regs_sm8250[0]); break; default: - unreachable(); + WARN(1, "unknown cspi version\n"); + return; } for (l = 0; l < 5; l++) { diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c index 33e08efa3039..384fb54e219a 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c @@ -406,7 +406,7 @@ static void rzg2l_csi2_mipi_link_disable(struct rzg2l_csi2 *csi2) 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"); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index 91b57c7c2e56..e6eedd65b71d 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -404,7 +404,7 @@ void rzg2l_cru_stop_image_processing(struct rzg2l_cru_dev *cru) break; usleep_range(10, 20); - }; + } /* Notify that AXI bus can not stop here */ if (!retries) diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c index d4540684ea9a..d1d1fdce03e3 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c @@ -1131,10 +1131,12 @@ static void rkisp1_try_fmt(const struct rkisp1_capture *cap, const struct rkisp1_capture_config *config = cap->config; const struct rkisp1_capture_fmt_cfg *fmt; const struct v4l2_format_info *info; - const unsigned int max_widths[] = { RKISP1_RSZ_MP_SRC_MAX_WIDTH, - RKISP1_RSZ_SP_SRC_MAX_WIDTH }; - const unsigned int max_heights[] = { RKISP1_RSZ_MP_SRC_MAX_HEIGHT, - RKISP1_RSZ_SP_SRC_MAX_HEIGHT}; + static const unsigned int max_widths[] = { + RKISP1_RSZ_MP_SRC_MAX_WIDTH, RKISP1_RSZ_SP_SRC_MAX_WIDTH + }; + static const unsigned int max_heights[] = { + RKISP1_RSZ_MP_SRC_MAX_HEIGHT, RKISP1_RSZ_SP_SRC_MAX_HEIGHT + }; fmt = rkisp1_find_fmt_cfg(cap, pixm->pixelformat); if (!fmt) { @@ -1336,8 +1338,9 @@ void rkisp1_capture_devs_unregister(struct rkisp1_device *rkisp1) static int rkisp1_register_capture(struct rkisp1_capture *cap) { - const char * const dev_names[] = {RKISP1_MP_DEV_NAME, - RKISP1_SP_DEV_NAME}; + static const char * const dev_names[] = { + RKISP1_MP_DEV_NAME, RKISP1_SP_DEV_NAME + }; struct v4l2_device *v4l2_dev = &cap->rkisp1->v4l2_dev; struct video_device *vdev = &cap->vnode.vdev; struct rkisp1_vdev_node *node; diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is.h b/drivers/media/platform/samsung/exynos4-is/fimc-is.h index 06586e455b1d..c126b779aafc 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-is.h +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is.h @@ -14,7 +14,6 @@ #include <linux/clk.h> #include <linux/device.h> #include <linux/kernel.h> -#include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/sizes.h> #include <linux/spinlock.h> @@ -231,7 +230,6 @@ struct chain_config { /** * struct fimc_is - fimc-is data structure * @pdev: pointer to FIMC-IS platform device - * @pctrl: pointer to pinctrl structure for this device * @v4l2_dev: pointer to the top level v4l2_device * @fw: data structure describing the FIMC-IS firmware binary * @memory: memory region assigned for the FIMC-IS (firmware) @@ -262,7 +260,6 @@ struct chain_config { */ struct fimc_is { struct platform_device *pdev; - struct pinctrl *pctrl; struct v4l2_device *v4l2_dev; struct fimc_is_firmware fw; diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.h b/drivers/media/platform/samsung/exynos4-is/media-dev.h index 62ad5d7e035a..079105d88bab 100644 --- a/drivers/media/platform/samsung/exynos4-is/media-dev.h +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.h @@ -11,7 +11,6 @@ #include <linux/platform_device.h> #include <linux/mutex.h> #include <linux/of.h> -#include <linux/pinctrl/consumer.h> #include <media/media-device.h> #include <media/media-entity.h> #include <media/v4l2-device.h> diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c index f3e4cdac1ef3..9d2cce124a34 100644 --- a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c @@ -1021,8 +1021,8 @@ static __poll_t s5p_mfc_poll(struct file *file, * means either in driver already or waiting for driver to claim it * and start processing. */ - if ((!src_q->streaming || list_empty(&src_q->queued_list)) - && (!dst_q->streaming || list_empty(&dst_q->queued_list))) { + if ((!vb2_is_streaming(src_q) || list_empty(&src_q->queued_list)) && + (!vb2_is_streaming(dst_q) || list_empty(&dst_q->queued_list))) { rc = EPOLLERR; goto end; } diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c index 18e6c65f4737..86c5235a0c7a 100644 --- a/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c +++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c @@ -264,6 +264,7 @@ static int sun4i_csi_remove(struct platform_device *pdev) { struct sun4i_csi *csi = platform_get_drvdata(pdev); + pm_runtime_disable(&pdev->dev); v4l2_async_nf_unregister(&csi->notifier); v4l2_async_nf_cleanup(&csi->notifier); vb2_video_unregister_device(&csi->vdev); diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c index a3e826a755fc..95b5633b7914 100644 --- a/drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c +++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c @@ -245,7 +245,7 @@ static int sun4i_csi_start_streaming(struct vb2_queue *vq, unsigned int count) * We need a scratch buffer in case where we'll not have any * more buffer queued so that we don't error out. One of those * cases is when you end up at the last frame to capture, you - * don't havea any buffer queued any more, and yet it doesn't + * don't have any buffer queued any more, and yet it doesn't * really matter since you'll never reach the next buffer. * * Since we support the multi-planar API, we need to have a @@ -311,7 +311,7 @@ static int sun4i_csi_start_streaming(struct vb2_queue *vq, unsigned int count) writel(CSI_BUF_CTRL_DBE, csi->regs + CSI_BUF_CTRL_REG); /* Clear the pending interrupts */ - writel(CSI_INT_FRM_DONE, csi->regs + 0x34); + writel(CSI_INT_FRM_DONE, csi->regs + CSI_INT_STA_REG); /* Enable frame done interrupt */ writel(CSI_INT_FRM_DONE, csi->regs + CSI_INT_EN_REG); diff --git a/drivers/media/platform/ti/cal/cal.c b/drivers/media/platform/ti/cal/cal.c index 56b61c0583cf..1236215ec70e 100644 --- a/drivers/media/platform/ti/cal/cal.c +++ b/drivers/media/platform/ti/cal/cal.c @@ -1050,8 +1050,10 @@ static struct cal_ctx *cal_ctx_create(struct cal_dev *cal, int inst) ctx->cport = inst; ret = cal_ctx_v4l2_init(ctx); - if (ret) + if (ret) { + kfree(ctx); return NULL; + } return ctx; } diff --git a/drivers/media/platform/ti/davinci/vpif.c b/drivers/media/platform/ti/davinci/vpif.c index da27da4c165a..832489822706 100644 --- a/drivers/media/platform/ti/davinci/vpif.c +++ b/drivers/media/platform/ti/davinci/vpif.c @@ -480,7 +480,7 @@ static int vpif_probe(struct platform_device *pdev) ret = irq; goto err_put_rpm; } - res_irq = (struct resource)DEFINE_RES_IRQ_NAMED(irq, of_node_full_name(pdev->dev.of_node)); + res_irq = DEFINE_RES_IRQ_NAMED(irq, of_node_full_name(pdev->dev.of_node)); res_irq.flags |= irq_get_trigger_type(irq); pdev_capture = kzalloc(sizeof(*pdev_capture), GFP_KERNEL); diff --git a/drivers/media/platform/ti/omap3isp/isp.c b/drivers/media/platform/ti/omap3isp/isp.c index 1d40bb59ff81..e7327e38482d 100644 --- a/drivers/media/platform/ti/omap3isp/isp.c +++ b/drivers/media/platform/ti/omap3isp/isp.c @@ -2307,7 +2307,16 @@ static int isp_probe(struct platform_device *pdev) /* Regulators */ isp->isp_csiphy1.vdd = devm_regulator_get(&pdev->dev, "vdd-csiphy1"); + if (IS_ERR(isp->isp_csiphy1.vdd)) { + ret = PTR_ERR(isp->isp_csiphy1.vdd); + goto error; + } + isp->isp_csiphy2.vdd = devm_regulator_get(&pdev->dev, "vdd-csiphy2"); + if (IS_ERR(isp->isp_csiphy2.vdd)) { + ret = PTR_ERR(isp->isp_csiphy2.vdd); + goto error; + } /* Clocks * diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index 3e5348c63773..ddc7d08d4f96 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -221,22 +221,16 @@ isp_video_remote_subdev(struct isp_video *video, u32 *pad) static int isp_video_get_graph_data(struct isp_video *video, struct isp_pipeline *pipe) { - struct media_graph graph; - struct media_entity *entity = &video->video.entity; - struct media_device *mdev = entity->graph_obj.mdev; + struct media_pipeline_entity_iter iter; + struct media_entity *entity; struct isp_video *far_end = NULL; int ret; - mutex_lock(&mdev->graph_mutex); - ret = media_graph_walk_init(&graph, mdev); - if (ret) { - mutex_unlock(&mdev->graph_mutex); + ret = media_pipeline_entity_iter_init(&pipe->pipe, &iter); + if (ret) return ret; - } - media_graph_walk_start(&graph, entity); - - while ((entity = media_graph_walk_next(&graph))) { + media_pipeline_for_each_entity(&pipe->pipe, &iter, entity) { struct isp_video *__video; media_entity_enum_set(&pipe->ent_enum, entity); @@ -255,9 +249,7 @@ static int isp_video_get_graph_data(struct isp_video *video, far_end = __video; } - mutex_unlock(&mdev->graph_mutex); - - media_graph_walk_cleanup(&graph); + media_pipeline_entity_iter_cleanup(&iter); if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { pipe->input = far_end; diff --git a/drivers/media/platform/verisilicon/hantro_drv.c b/drivers/media/platform/verisilicon/hantro_drv.c index 8cb4a68c9119..b0aeedae7b65 100644 --- a/drivers/media/platform/verisilicon/hantro_drv.c +++ b/drivers/media/platform/verisilicon/hantro_drv.c @@ -1050,8 +1050,6 @@ static int hantro_probe(struct platform_device *pdev) vpu->mdev.dev = vpu->dev; strscpy(vpu->mdev.model, DRIVER_NAME, sizeof(vpu->mdev.model)); - strscpy(vpu->mdev.bus_info, "platform: " DRIVER_NAME, - sizeof(vpu->mdev.bus_info)); media_device_init(&vpu->mdev); vpu->mdev.ops = &hantro_m2m_media_ops; vpu->v4l2_dev.mdev = &vpu->mdev; diff --git a/drivers/media/platform/verisilicon/hantro_v4l2.c b/drivers/media/platform/verisilicon/hantro_v4l2.c index 2c7a805289e7..c0d427956210 100644 --- a/drivers/media/platform/verisilicon/hantro_v4l2.c +++ b/drivers/media/platform/verisilicon/hantro_v4l2.c @@ -142,8 +142,6 @@ static int vidioc_querycap(struct file *file, void *priv, strscpy(cap->driver, vpu->dev->driver->name, sizeof(cap->driver)); strscpy(cap->card, vdev->name, sizeof(cap->card)); - snprintf(cap->bus_info, sizeof(cap->bus_info), "platform: %s", - vpu->dev->driver->name); return 0; } @@ -161,8 +159,11 @@ static int vidioc_enum_framesizes(struct file *file, void *priv, } /* For non-coded formats check if postprocessing scaling is possible */ - if (fmt->codec_mode == HANTRO_MODE_NONE && hantro_needs_postproc(ctx, fmt)) { - return hanto_postproc_enum_framesizes(ctx, fsize); + if (fmt->codec_mode == HANTRO_MODE_NONE) { + if (hantro_needs_postproc(ctx, fmt)) + return hanto_postproc_enum_framesizes(ctx, fsize); + else + return -ENOTTY; } else if (fsize->index != 0) { vpu_debug(0, "invalid frame size index (expected 0, got %d)\n", fsize->index); diff --git a/drivers/media/platform/xilinx/xilinx-dma.c b/drivers/media/platform/xilinx/xilinx-dma.c index 0a7fd8642a65..fee02c8c85fd 100644 --- a/drivers/media/platform/xilinx/xilinx-dma.c +++ b/drivers/media/platform/xilinx/xilinx-dma.c @@ -173,31 +173,19 @@ done: static int xvip_pipeline_validate(struct xvip_pipeline *pipe, struct xvip_dma *start) { - struct media_graph graph; - struct media_entity *entity = &start->video.entity; - struct media_device *mdev = entity->graph_obj.mdev; + struct media_pipeline_pad_iter iter; unsigned int num_inputs = 0; unsigned int num_outputs = 0; - int ret; - - mutex_lock(&mdev->graph_mutex); - - /* Walk the graph to locate the video nodes. */ - ret = media_graph_walk_init(&graph, mdev); - if (ret) { - mutex_unlock(&mdev->graph_mutex); - return ret; - } - - media_graph_walk_start(&graph, entity); + struct media_pad *pad; - while ((entity = media_graph_walk_next(&graph))) { + /* Locate the video nodes in the pipeline. */ + media_pipeline_for_each_pad(&pipe->pipe, &iter, pad) { struct xvip_dma *dma; - if (entity->function != MEDIA_ENT_F_IO_V4L) + if (pad->entity->function != MEDIA_ENT_F_IO_V4L) continue; - dma = to_xvip_dma(media_entity_to_video_device(entity)); + dma = to_xvip_dma(media_entity_to_video_device(pad->entity)); if (dma->pad.flags & MEDIA_PAD_FL_SINK) { pipe->output = dma; @@ -207,10 +195,6 @@ static int xvip_pipeline_validate(struct xvip_pipeline *pipe, } } - mutex_unlock(&mdev->graph_mutex); - - media_graph_walk_cleanup(&graph); - /* We need exactly one output and zero or one input. */ if (num_outputs != 1 || num_inputs > 1) return -EPIPE; diff --git a/drivers/media/radio/wl128x/fmdrv_common.c b/drivers/media/radio/wl128x/fmdrv_common.c index 8a316de70e6c..cbd49dff6d74 100644 --- a/drivers/media/radio/wl128x/fmdrv_common.c +++ b/drivers/media/radio/wl128x/fmdrv_common.c @@ -1442,7 +1442,7 @@ static long fm_st_receive(void *arg, struct sk_buff *skb) { struct fmdev *fmdev; - fmdev = (struct fmdev *)arg; + fmdev = arg; if (skb == NULL) { fmerr("Invalid SKB received from ST\n"); diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index f560fc38895f..ac4172feb6f9 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -314,7 +314,7 @@ config IR_PWM_TX tristate "PWM IR transmitter" depends on LIRC depends on PWM - depends on OF || COMPILE_TEST + depends on OF help Say Y if you want to use a PWM based IR transmitter. This is more power efficient than the bit banging gpio driver. @@ -361,7 +361,7 @@ config IR_SERIAL_TRANSMITTER config IR_SPI tristate "SPI connected IR LED" depends on SPI && LIRC - depends on OF || COMPILE_TEST + depends on OF help Say Y if you want to use an IR LED connected through SPI bus. diff --git a/drivers/media/rc/ene_ir.c b/drivers/media/rc/ene_ir.c index e09270916fbc..11ee21a7db8f 100644 --- a/drivers/media/rc/ene_ir.c +++ b/drivers/media/rc/ene_ir.c @@ -1106,6 +1106,8 @@ static void ene_remove(struct pnp_dev *pnp_dev) struct ene_device *dev = pnp_get_drvdata(pnp_dev); unsigned long flags; + rc_unregister_device(dev->rdev); + del_timer_sync(&dev->tx_sim_timer); spin_lock_irqsave(&dev->hw_lock, flags); ene_rx_disable(dev); ene_rx_restore_hw_buffer(dev); @@ -1113,7 +1115,6 @@ static void ene_remove(struct pnp_dev *pnp_dev) free_irq(dev->irq, dev); release_region(dev->hw_io, ENE_IO_SIZE); - rc_unregister_device(dev->rdev); kfree(dev); } diff --git a/drivers/media/rc/gpio-ir-recv.c b/drivers/media/rc/gpio-ir-recv.c index 8f1fff7af6c9..8dbe780dae4e 100644 --- a/drivers/media/rc/gpio-ir-recv.c +++ b/drivers/media/rc/gpio-ir-recv.c @@ -126,6 +126,23 @@ static int gpio_ir_recv_probe(struct platform_device *pdev) "gpio-ir-recv-irq", gpio_dev); } +static int gpio_ir_recv_remove(struct platform_device *pdev) +{ + struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev); + struct device *pmdev = gpio_dev->pmdev; + + if (pmdev) { + pm_runtime_get_sync(pmdev); + cpu_latency_qos_remove_request(&gpio_dev->qos); + + pm_runtime_disable(pmdev); + pm_runtime_put_noidle(pmdev); + pm_runtime_set_suspended(pmdev); + } + + return 0; +} + #ifdef CONFIG_PM static int gpio_ir_recv_suspend(struct device *dev) { @@ -185,6 +202,7 @@ MODULE_DEVICE_TABLE(of, gpio_ir_recv_of_match); static struct platform_driver gpio_ir_recv_driver = { .probe = gpio_ir_recv_probe, + .remove = gpio_ir_recv_remove, .driver = { .name = KBUILD_MODNAME, .of_match_table = of_match_ptr(gpio_ir_recv_of_match), diff --git a/drivers/media/rc/ir-rx51.c b/drivers/media/rc/ir-rx51.c index 85080c3d2053..adbbe639a261 100644 --- a/drivers/media/rc/ir-rx51.c +++ b/drivers/media/rc/ir-rx51.c @@ -261,11 +261,6 @@ static int ir_rx51_probe(struct platform_device *dev) return devm_rc_register_device(&dev->dev, ir_rx51.rcdev); } -static int ir_rx51_remove(struct platform_device *dev) -{ - return 0; -} - static const struct of_device_id ir_rx51_match[] = { { .compatible = "nokia,n900-ir", @@ -276,7 +271,6 @@ MODULE_DEVICE_TABLE(of, ir_rx51_match); static struct platform_driver ir_rx51_platform_driver = { .probe = ir_rx51_probe, - .remove = ir_rx51_remove, .suspend = ir_rx51_suspend, .resume = ir_rx51_resume, .driver = { diff --git a/drivers/media/rc/pwm-ir-tx.c b/drivers/media/rc/pwm-ir-tx.c index 105a9c24f1e3..7732054c4621 100644 --- a/drivers/media/rc/pwm-ir-tx.c +++ b/drivers/media/rc/pwm-ir-tx.c @@ -120,7 +120,7 @@ static struct platform_driver pwm_ir_driver = { .probe = pwm_ir_probe, .driver = { .name = DRIVER_NAME, - .of_match_table = of_match_ptr(pwm_ir_of_match), + .of_match_table = pwm_ir_of_match, }, }; module_platform_driver(pwm_ir_driver); diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.c b/drivers/media/test-drivers/vidtv/vidtv_psi.c index a5875380ef40..ce0b7a6e92dc 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_psi.c +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.c @@ -1940,7 +1940,7 @@ u32 vidtv_psi_eit_write_into(struct vidtv_psi_eit_write_args *args) struct vidtv_psi_table_eit_event *vidtv_psi_eit_event_init(struct vidtv_psi_table_eit_event *head, u16 event_id) { - const u8 DURATION[] = {0x23, 0x59, 0x59}; /* BCD encoded */ + static const u8 DURATION[] = {0x23, 0x59, 0x59}; /* BCD encoded */ struct vidtv_psi_table_eit_event *e; struct timespec64 ts; struct tm time; diff --git a/drivers/media/test-drivers/visl/visl-video.c b/drivers/media/test-drivers/visl/visl-video.c index b08664dfbe5f..7cac6a6456eb 100644 --- a/drivers/media/test-drivers/visl/visl-video.c +++ b/drivers/media/test-drivers/visl/visl-video.c @@ -687,7 +687,7 @@ static void visl_buf_request_complete(struct vb2_buffer *vb) v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl); } -const struct vb2_ops visl_qops = { +static const struct vb2_ops visl_qops = { .queue_setup = visl_queue_setup, .buf_out_validate = visl_buf_out_validate, .buf_prepare = visl_buf_prepare, diff --git a/drivers/media/tuners/si2157.c b/drivers/media/tuners/si2157.c index 476b32c04c20..3fa3dcda917a 100644 --- a/drivers/media/tuners/si2157.c +++ b/drivers/media/tuners/si2157.c @@ -875,9 +875,9 @@ err: dev_dbg(&client->dev, "failed=%d\n", ret); } -static int si2157_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int si2157_probe(struct i2c_client *client) { + const struct i2c_device_id *id = i2c_client_get_device_id(client); struct si2157_config *cfg = client->dev.platform_data; struct dvb_frontend *fe = cfg->fe; struct si2157_dev *dev; @@ -990,7 +990,7 @@ static struct i2c_driver si2157_driver = { .name = "si2157", .suppress_bind_attrs = true, }, - .probe = si2157_probe, + .probe_new = si2157_probe, .remove = si2157_remove, .id_table = si2157_id_table, }; diff --git a/drivers/media/usb/dvb-usb-v2/af9015.c b/drivers/media/usb/dvb-usb-v2/af9015.c index d33514acc2b5..4014f7d07330 100644 --- a/drivers/media/usb/dvb-usb-v2/af9015.c +++ b/drivers/media/usb/dvb-usb-v2/af9015.c @@ -1165,7 +1165,7 @@ static int af9015_rc_query(struct dvb_usb_device *d) /* If any of these are non-zero, assume invalid data */ if (buf[1] || buf[2] || buf[3]) { dev_dbg(&intf->dev, "invalid data\n"); - return ret; + return 0; } /* Check for repeat of previous code */ @@ -1174,7 +1174,7 @@ static int af9015_rc_query(struct dvb_usb_device *d) dev_dbg(&intf->dev, "key repeated\n"); rc_repeat(d->rc_dev); state->rc_repeat = buf[6]; - return ret; + return 0; } /* Only process key if canary killed */ diff --git a/drivers/media/usb/go7007/go7007-v4l2.c b/drivers/media/usb/go7007/go7007-v4l2.c index b2edc4deaca3..13256565b034 100644 --- a/drivers/media/usb/go7007/go7007-v4l2.c +++ b/drivers/media/usb/go7007/go7007-v4l2.c @@ -404,16 +404,13 @@ static int go7007_start_streaming(struct vb2_queue *q, unsigned int count) go->next_seq = 0; go->active_buf = NULL; go->modet_event_status = 0; - q->streaming = 1; if (go7007_start_encoder(go) < 0) ret = -EIO; else ret = 0; mutex_unlock(&go->hw_lock); - if (ret) { - q->streaming = 0; + if (ret) return ret; - } call_all(&go->v4l2_dev, video, s_stream, 1); v4l2_ctrl_grab(go->mpeg_video_gop_size, true); v4l2_ctrl_grab(go->mpeg_video_gop_closure, true); @@ -430,7 +427,6 @@ static void go7007_stop_streaming(struct vb2_queue *q) struct go7007 *go = vb2_get_drv_priv(q); unsigned long flags; - q->streaming = 0; go7007_stream_stop(go); mutex_lock(&go->hw_lock); go7007_reset_encoder(go); diff --git a/drivers/media/usb/siano/smsusb.c b/drivers/media/usb/siano/smsusb.c index fe9c7b3a950e..6f443c542c6d 100644 --- a/drivers/media/usb/siano/smsusb.c +++ b/drivers/media/usb/siano/smsusb.c @@ -179,6 +179,7 @@ static void smsusb_stop_streaming(struct smsusb_device_t *dev) for (i = 0; i < MAX_URBS; i++) { usb_kill_urb(&dev->surbs[i].urb); + cancel_work_sync(&dev->surbs[i].wq); if (dev->surbs[i].cb) { smscore_putbuffer(dev->coredev, dev->surbs[i].cb); diff --git a/drivers/media/v4l2-core/v4l2-h264.c b/drivers/media/v4l2-core/v4l2-h264.c index 72bd64f65198..c00197d095e7 100644 --- a/drivers/media/v4l2-core/v4l2-h264.c +++ b/drivers/media/v4l2-core/v4l2-h264.c @@ -305,6 +305,8 @@ static const char *format_ref_list_p(const struct v4l2_h264_reflist_builder *bui int n = 0, i; *out_str = kmalloc(tmp_str_size, GFP_KERNEL); + if (!(*out_str)) + return NULL; n += snprintf(*out_str + n, tmp_str_size - n, "|"); @@ -343,6 +345,8 @@ static const char *format_ref_list_b(const struct v4l2_h264_reflist_builder *bui int n = 0, i; *out_str = kmalloc(tmp_str_size, GFP_KERNEL); + if (!(*out_str)) + return NULL; n += snprintf(*out_str + n, tmp_str_size - n, "|"); diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 2a0139c72d29..87f163a89c80 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -16,6 +16,7 @@ #include <linux/kernel.h> #include <linux/version.h> +#include <linux/v4l2-subdev.h> #include <linux/videodev2.h> #include <media/media-device.h> /* for media_set_bus_info() */ @@ -3154,6 +3155,21 @@ static int check_array_args(unsigned int cmd, void *parg, size_t *array_size, ret = 1; break; } + + case VIDIOC_SUBDEV_G_ROUTING: + case VIDIOC_SUBDEV_S_ROUTING: { + struct v4l2_subdev_routing *routing = parg; + + if (routing->num_routes > 256) + return -E2BIG; + + *user_ptr = u64_to_user_ptr(routing->routes); + *kernel_ptr = (void **)&routing->routes; + *array_size = sizeof(struct v4l2_subdev_route) + * routing->num_routes; + ret = 1; + break; + } } return ret; @@ -3400,8 +3416,15 @@ video_usercopy(struct file *file, unsigned int orig_cmd, unsigned long arg, /* * Some ioctls can return an error, but still have valid * results that must be returned. + * + * FIXME: subdev IOCTLS are partially handled here and partially in + * v4l2-subdev.c and the 'always_copy' flag can only be set for IOCTLS + * defined here as part of the 'v4l2_ioctls' array. As + * VIDIOC_SUBDEV_G_ROUTING needs to return results to applications even + * in case of failure, but it is not defined here as part of the + * 'v4l2_ioctls' array, insert an ad-hoc check to address that. */ - if (err < 0 && !always_copy) + if (err < 0 && !always_copy && cmd != VIDIOC_SUBDEV_G_ROUTING) goto out; if (has_array_args) { diff --git a/drivers/media/v4l2-core/v4l2-jpeg.c b/drivers/media/v4l2-core/v4l2-jpeg.c index c2513b775f6a..94435a7b6816 100644 --- a/drivers/media/v4l2-core/v4l2-jpeg.c +++ b/drivers/media/v4l2-core/v4l2-jpeg.c @@ -460,7 +460,7 @@ static int jpeg_parse_app14_data(struct jpeg_stream *stream, /* Check for "Adobe\0" in Ap1..6 */ if (stream->curr + 6 > stream->end || strncmp(stream->curr, "Adobe\0", 6)) - return -EINVAL; + return jpeg_skip(stream, lp - 2); /* get to Ap12 */ ret = jpeg_skip(stream, 11); @@ -474,7 +474,7 @@ static int jpeg_parse_app14_data(struct jpeg_stream *stream, *tf = ret; /* skip the rest of the segment, this ensures at least it is complete */ - skip = lp - 2 - 11; + skip = lp - 2 - 11 - 1; return jpeg_skip(stream, skip); } diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c index be7fde1ed3ea..0cc30397fbad 100644 --- a/drivers/media/v4l2-core/v4l2-mem2mem.c +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -922,9 +922,9 @@ static __poll_t v4l2_m2m_poll_for_data(struct file *file, * means either in driver already or waiting for driver to claim it * and start processing. */ - if ((!src_q->streaming || src_q->error || + if ((!vb2_is_streaming(src_q) || src_q->error || list_empty(&src_q->queued_list)) && - (!dst_q->streaming || dst_q->error || + (!vb2_is_streaming(dst_q) || dst_q->error || (list_empty(&dst_q->queued_list) && !dst_q->last_buffer_dequeued))) return EPOLLERR; diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 6630fb30bc7d..b10045c02f43 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -8,21 +8,41 @@ * Sakari Ailus <sakari.ailus@iki.fi> */ +#include <linux/export.h> #include <linux/ioctl.h> #include <linux/leds.h> #include <linux/mm.h> #include <linux/module.h> +#include <linux/overflow.h> #include <linux/slab.h> #include <linux/types.h> -#include <linux/videodev2.h> -#include <linux/export.h> #include <linux/version.h> +#include <linux/videodev2.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> -#include <media/v4l2-ioctl.h> -#include <media/v4l2-fh.h> #include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> + +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) +/* + * The Streams API is an experimental feature. To use the Streams API, set + * 'v4l2_subdev_enable_streams_api' to 1 below. + */ + +static bool v4l2_subdev_enable_streams_api; +#endif + +/* + * Maximum stream ID is 63 for now, as we use u64 bitmask to represent a set + * of streams. + * + * Note that V4L2_FRAME_DESC_ENTRY_MAX is related: V4L2_FRAME_DESC_ENTRY_MAX + * restricts the total number of streams in a pad, although the stream ID is + * not restricted. + */ +#define V4L2_SUBDEV_MAX_STREAM_ID 63 #include "v4l2-subdev-priv.h" @@ -151,8 +171,22 @@ static inline int check_pad(struct v4l2_subdev *sd, u32 pad) return 0; } -static int check_state_pads(u32 which, struct v4l2_subdev_state *state) +static int check_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, + u32 which, u32 pad, u32 stream) { + if (sd->flags & V4L2_SUBDEV_FL_STREAMS) { +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + if (!v4l2_subdev_state_get_stream_format(state, pad, stream)) + return -EINVAL; + return 0; +#else + return -EINVAL; +#endif + } + + if (stream != 0) + return -EINVAL; + if (which == V4L2_SUBDEV_FORMAT_TRY && (!state || !state->pads)) return -EINVAL; @@ -166,8 +200,11 @@ static inline int check_format(struct v4l2_subdev *sd, if (!format) return -EINVAL; + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) + format->stream = 0; + return check_which(format->which) ? : check_pad(sd, format->pad) ? : - check_state_pads(format->which, state); + check_state(sd, state, format->which, format->pad, format->stream); } static int call_get_fmt(struct v4l2_subdev *sd, @@ -193,8 +230,11 @@ static int call_enum_mbus_code(struct v4l2_subdev *sd, if (!code) return -EINVAL; + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) + code->stream = 0; + return check_which(code->which) ? : check_pad(sd, code->pad) ? : - check_state_pads(code->which, state) ? : + check_state(sd, state, code->which, code->pad, code->stream) ? : sd->ops->pad->enum_mbus_code(sd, state, code); } @@ -205,8 +245,11 @@ static int call_enum_frame_size(struct v4l2_subdev *sd, if (!fse) return -EINVAL; + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) + fse->stream = 0; + return check_which(fse->which) ? : check_pad(sd, fse->pad) ? : - check_state_pads(fse->which, state) ? : + check_state(sd, state, fse->which, fse->pad, fse->stream) ? : sd->ops->pad->enum_frame_size(sd, state, fse); } @@ -240,8 +283,11 @@ static int call_enum_frame_interval(struct v4l2_subdev *sd, if (!fie) return -EINVAL; + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) + fie->stream = 0; + return check_which(fie->which) ? : check_pad(sd, fie->pad) ? : - check_state_pads(fie->which, state) ? : + check_state(sd, state, fie->which, fie->pad, fie->stream) ? : sd->ops->pad->enum_frame_interval(sd, state, fie); } @@ -252,8 +298,11 @@ static inline int check_selection(struct v4l2_subdev *sd, if (!sel) return -EINVAL; + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) + sel->stream = 0; + return check_which(sel->which) ? : check_pad(sd, sel->pad) ? : - check_state_pads(sel->which, state); + check_state(sd, state, sel->which, sel->pad, sel->stream); } static int call_get_selection(struct v4l2_subdev *sd, @@ -444,6 +493,10 @@ subdev_ioctl_get_state(struct v4l2_subdev *sd, struct v4l2_subdev_fh *subdev_fh, case VIDIOC_SUBDEV_S_SELECTION: which = ((struct v4l2_subdev_selection *)arg)->which; break; + case VIDIOC_SUBDEV_G_ROUTING: + case VIDIOC_SUBDEV_S_ROUTING: + which = ((struct v4l2_subdev_routing *)arg)->which; + break; } return which == V4L2_SUBDEV_FORMAT_TRY ? @@ -458,6 +511,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev); struct v4l2_fh *vfh = file->private_data; bool ro_subdev = test_bit(V4L2_FL_SUBDEV_RO_DEVNODE, &vdev->flags); + bool streams_subdev = sd->flags & V4L2_SUBDEV_FL_STREAMS; int rval; switch (cmd) { @@ -466,7 +520,9 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, memset(cap->reserved, 0, sizeof(cap->reserved)); cap->version = LINUX_VERSION_CODE; - cap->capabilities = ro_subdev ? V4L2_SUBDEV_CAP_RO_SUBDEV : 0; + cap->capabilities = + (ro_subdev ? V4L2_SUBDEV_CAP_RO_SUBDEV : 0) | + (streams_subdev ? V4L2_SUBDEV_CAP_STREAMS : 0); return 0; } @@ -757,6 +813,81 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, case VIDIOC_SUBDEV_QUERYSTD: return v4l2_subdev_call(sd, video, querystd, arg); + case VIDIOC_SUBDEV_G_ROUTING: { + struct v4l2_subdev_routing *routing = arg; + struct v4l2_subdev_krouting *krouting; + + if (!v4l2_subdev_enable_streams_api) + return -ENOIOCTLCMD; + + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) + return -ENOIOCTLCMD; + + memset(routing->reserved, 0, sizeof(routing->reserved)); + + krouting = &state->routing; + + if (routing->num_routes < krouting->num_routes) { + routing->num_routes = krouting->num_routes; + return -ENOSPC; + } + + memcpy((struct v4l2_subdev_route *)(uintptr_t)routing->routes, + krouting->routes, + krouting->num_routes * sizeof(*krouting->routes)); + routing->num_routes = krouting->num_routes; + + return 0; + } + + case VIDIOC_SUBDEV_S_ROUTING: { + struct v4l2_subdev_routing *routing = arg; + struct v4l2_subdev_route *routes = + (struct v4l2_subdev_route *)(uintptr_t)routing->routes; + struct v4l2_subdev_krouting krouting = {}; + unsigned int i; + + if (!v4l2_subdev_enable_streams_api) + return -ENOIOCTLCMD; + + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) + return -ENOIOCTLCMD; + + if (routing->which != V4L2_SUBDEV_FORMAT_TRY && ro_subdev) + return -EPERM; + + memset(routing->reserved, 0, sizeof(routing->reserved)); + + for (i = 0; i < routing->num_routes; ++i) { + const struct v4l2_subdev_route *route = &routes[i]; + const struct media_pad *pads = sd->entity.pads; + + if (route->sink_stream > V4L2_SUBDEV_MAX_STREAM_ID || + route->source_stream > V4L2_SUBDEV_MAX_STREAM_ID) + return -EINVAL; + + if (route->sink_pad >= sd->entity.num_pads) + return -EINVAL; + + if (!(pads[route->sink_pad].flags & + MEDIA_PAD_FL_SINK)) + return -EINVAL; + + if (route->source_pad >= sd->entity.num_pads) + return -EINVAL; + + if (!(pads[route->source_pad].flags & + MEDIA_PAD_FL_SOURCE)) + return -EINVAL; + } + + krouting.num_routes = routing->num_routes; + krouting.routes = routes; + + return v4l2_subdev_call(sd, pad, set_routing, state, + routing->which, &krouting); + } + default: return v4l2_subdev_call(sd, core, ioctl, cmd, arg); } @@ -937,7 +1068,7 @@ int v4l2_subdev_link_validate_default(struct v4l2_subdev *sd, EXPORT_SYMBOL_GPL(v4l2_subdev_link_validate_default); static int -v4l2_subdev_link_validate_get_format(struct media_pad *pad, +v4l2_subdev_link_validate_get_format(struct media_pad *pad, u32 stream, struct v4l2_subdev_format *fmt) { if (is_media_entity_v4l2_subdev(pad->entity)) { @@ -946,7 +1077,11 @@ v4l2_subdev_link_validate_get_format(struct media_pad *pad, fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; fmt->pad = pad->index; - return v4l2_subdev_call_state_active(sd, pad, get_fmt, fmt); + fmt->stream = stream; + + return v4l2_subdev_call(sd, pad, get_fmt, + v4l2_subdev_get_locked_active_state(sd), + fmt); } WARN(pad->entity->function != MEDIA_ENT_F_IO_V4L, @@ -956,34 +1091,206 @@ v4l2_subdev_link_validate_get_format(struct media_pad *pad, return -EINVAL; } +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + +static void __v4l2_link_validate_get_streams(struct media_pad *pad, + u64 *streams_mask) +{ + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + struct v4l2_subdev *subdev; + + subdev = media_entity_to_v4l2_subdev(pad->entity); + + *streams_mask = 0; + + state = v4l2_subdev_get_locked_active_state(subdev); + if (WARN_ON(!state)) + return; + + for_each_active_route(&state->routing, route) { + u32 route_pad; + u32 route_stream; + + if (pad->flags & MEDIA_PAD_FL_SOURCE) { + route_pad = route->source_pad; + route_stream = route->source_stream; + } else { + route_pad = route->sink_pad; + route_stream = route->sink_stream; + } + + if (route_pad != pad->index) + continue; + + *streams_mask |= BIT_ULL(route_stream); + } +} + +#endif /* CONFIG_VIDEO_V4L2_SUBDEV_API */ + +static void v4l2_link_validate_get_streams(struct media_pad *pad, + u64 *streams_mask) +{ + struct v4l2_subdev *subdev = media_entity_to_v4l2_subdev(pad->entity); + + if (!(subdev->flags & V4L2_SUBDEV_FL_STREAMS)) { + /* Non-streams subdevs have an implicit stream 0 */ + *streams_mask = BIT_ULL(0); + return; + } + +#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + __v4l2_link_validate_get_streams(pad, streams_mask); +#else + /* This shouldn't happen */ + *streams_mask = 0; +#endif +} + +static int v4l2_subdev_link_validate_locked(struct media_link *link) +{ + struct v4l2_subdev *sink_subdev = + media_entity_to_v4l2_subdev(link->sink->entity); + struct device *dev = sink_subdev->entity.graph_obj.mdev->dev; + u64 source_streams_mask; + u64 sink_streams_mask; + u64 dangling_sink_streams; + u32 stream; + int ret; + + dev_dbg(dev, "validating link \"%s\":%u -> \"%s\":%u\n", + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index); + + v4l2_link_validate_get_streams(link->source, &source_streams_mask); + v4l2_link_validate_get_streams(link->sink, &sink_streams_mask); + + /* + * It is ok to have more source streams than sink streams as extra + * source streams can just be ignored by the receiver, but having extra + * sink streams is an error as streams must have a source. + */ + dangling_sink_streams = (source_streams_mask ^ sink_streams_mask) & + sink_streams_mask; + if (dangling_sink_streams) { + dev_err(dev, "Dangling sink streams: mask %#llx\n", + dangling_sink_streams); + return -EINVAL; + } + + /* Validate source and sink stream formats */ + + for (stream = 0; stream < sizeof(sink_streams_mask) * 8; ++stream) { + struct v4l2_subdev_format sink_fmt, source_fmt; + + if (!(sink_streams_mask & BIT_ULL(stream))) + continue; + + dev_dbg(dev, "validating stream \"%s\":%u:%u -> \"%s\":%u:%u\n", + link->source->entity->name, link->source->index, stream, + link->sink->entity->name, link->sink->index, stream); + + ret = v4l2_subdev_link_validate_get_format(link->source, stream, + &source_fmt); + if (ret < 0) { + dev_dbg(dev, + "Failed to get format for \"%s\":%u:%u (but that's ok)\n", + link->source->entity->name, link->source->index, + stream); + continue; + } + + ret = v4l2_subdev_link_validate_get_format(link->sink, stream, + &sink_fmt); + if (ret < 0) { + dev_dbg(dev, + "Failed to get format for \"%s\":%u:%u (but that's ok)\n", + link->sink->entity->name, link->sink->index, + stream); + continue; + } + + /* TODO: add stream number to link_validate() */ + ret = v4l2_subdev_call(sink_subdev, pad, link_validate, link, + &source_fmt, &sink_fmt); + if (!ret) + continue; + + if (ret != -ENOIOCTLCMD) + return ret; + + ret = v4l2_subdev_link_validate_default(sink_subdev, link, + &source_fmt, &sink_fmt); + + if (ret) + return ret; + } + + return 0; +} + int v4l2_subdev_link_validate(struct media_link *link) { - struct v4l2_subdev *sink; - struct v4l2_subdev_format sink_fmt, source_fmt; - int rval; + struct v4l2_subdev *source_sd, *sink_sd; + struct v4l2_subdev_state *source_state, *sink_state; + int ret; - rval = v4l2_subdev_link_validate_get_format( - link->source, &source_fmt); - if (rval < 0) - return 0; + sink_sd = media_entity_to_v4l2_subdev(link->sink->entity); + source_sd = media_entity_to_v4l2_subdev(link->source->entity); - rval = v4l2_subdev_link_validate_get_format( - link->sink, &sink_fmt); - if (rval < 0) - return 0; + sink_state = v4l2_subdev_get_unlocked_active_state(sink_sd); + source_state = v4l2_subdev_get_unlocked_active_state(source_sd); - sink = media_entity_to_v4l2_subdev(link->sink->entity); + if (sink_state) + v4l2_subdev_lock_state(sink_state); - rval = v4l2_subdev_call(sink, pad, link_validate, link, - &source_fmt, &sink_fmt); - if (rval != -ENOIOCTLCMD) - return rval; + if (source_state) + v4l2_subdev_lock_state(source_state); + + ret = v4l2_subdev_link_validate_locked(link); - return v4l2_subdev_link_validate_default( - sink, link, &source_fmt, &sink_fmt); + if (sink_state) + v4l2_subdev_unlock_state(sink_state); + + if (source_state) + v4l2_subdev_unlock_state(source_state); + + return ret; } EXPORT_SYMBOL_GPL(v4l2_subdev_link_validate); +bool v4l2_subdev_has_pad_interdep(struct media_entity *entity, + unsigned int pad0, unsigned int pad1) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct v4l2_subdev_krouting *routing; + struct v4l2_subdev_state *state; + unsigned int i; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + routing = &state->routing; + + for (i = 0; i < routing->num_routes; ++i) { + struct v4l2_subdev_route *route = &routing->routes[i]; + + if (!(route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) + continue; + + if ((route->sink_pad == pad0 && route->source_pad == pad1) || + (route->source_pad == pad0 && route->sink_pad == pad1)) { + v4l2_subdev_unlock_state(state); + return true; + } + } + + v4l2_subdev_unlock_state(state); + + return false; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_has_pad_interdep); + struct v4l2_subdev_state * __v4l2_subdev_state_alloc(struct v4l2_subdev *sd, const char *lock_name, struct lock_class_key *lock_key) @@ -1001,7 +1308,8 @@ __v4l2_subdev_state_alloc(struct v4l2_subdev *sd, const char *lock_name, else state->lock = &state->_lock; - if (sd->entity.num_pads) { + /* Drivers that support streams do not need the legacy pad config */ + if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS) && sd->entity.num_pads) { state->pads = kvcalloc(sd->entity.num_pads, sizeof(*state->pads), GFP_KERNEL); if (!state->pads) { @@ -1040,6 +1348,8 @@ void __v4l2_subdev_state_free(struct v4l2_subdev_state *state) mutex_destroy(&state->_lock); + kfree(state->routing.routes); + kvfree(state->stream_configs.configs); kvfree(state->pads); kfree(state); } @@ -1069,15 +1379,68 @@ EXPORT_SYMBOL_GPL(v4l2_subdev_cleanup); #if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) +static int +v4l2_subdev_init_stream_configs(struct v4l2_subdev_stream_configs *stream_configs, + const struct v4l2_subdev_krouting *routing) +{ + struct v4l2_subdev_stream_configs new_configs = { 0 }; + struct v4l2_subdev_route *route; + u32 idx; + + /* Count number of formats needed */ + for_each_active_route(routing, route) { + /* + * Each route needs a format on both ends of the route. + */ + new_configs.num_configs += 2; + } + + if (new_configs.num_configs) { + new_configs.configs = kvcalloc(new_configs.num_configs, + sizeof(*new_configs.configs), + GFP_KERNEL); + + if (!new_configs.configs) + return -ENOMEM; + } + + /* + * Fill in the 'pad' and stream' value for each item in the array from + * the routing table + */ + idx = 0; + + for_each_active_route(routing, route) { + new_configs.configs[idx].pad = route->sink_pad; + new_configs.configs[idx].stream = route->sink_stream; + + idx++; + + new_configs.configs[idx].pad = route->source_pad; + new_configs.configs[idx].stream = route->source_stream; + + idx++; + } + + kvfree(stream_configs->configs); + *stream_configs = new_configs; + + return 0; +} + int v4l2_subdev_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { struct v4l2_mbus_framefmt *fmt; - if (format->pad >= sd->entity.num_pads) - return -EINVAL; + if (sd->flags & V4L2_SUBDEV_FL_STREAMS) + fmt = v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + else if (format->pad < sd->entity.num_pads && format->stream == 0) + fmt = v4l2_subdev_get_pad_format(sd, state, format->pad); + else + fmt = NULL; - fmt = v4l2_subdev_get_pad_format(sd, state, format->pad); if (!fmt) return -EINVAL; @@ -1087,6 +1450,590 @@ int v4l2_subdev_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, } EXPORT_SYMBOL_GPL(v4l2_subdev_get_fmt); +int v4l2_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + const struct v4l2_subdev_krouting *routing) +{ + struct v4l2_subdev_krouting *dst = &state->routing; + const struct v4l2_subdev_krouting *src = routing; + struct v4l2_subdev_krouting new_routing = { 0 }; + size_t bytes; + int r; + + if (unlikely(check_mul_overflow((size_t)src->num_routes, + sizeof(*src->routes), &bytes))) + return -EOVERFLOW; + + lockdep_assert_held(state->lock); + + if (src->num_routes > 0) { + new_routing.routes = kmemdup(src->routes, bytes, GFP_KERNEL); + if (!new_routing.routes) + return -ENOMEM; + } + + new_routing.num_routes = src->num_routes; + + r = v4l2_subdev_init_stream_configs(&state->stream_configs, + &new_routing); + if (r) { + kfree(new_routing.routes); + return r; + } + + kfree(dst->routes); + *dst = new_routing; + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_set_routing); + +struct v4l2_subdev_route * +__v4l2_subdev_next_active_route(const struct v4l2_subdev_krouting *routing, + struct v4l2_subdev_route *route) +{ + if (route) + ++route; + else + route = &routing->routes[0]; + + for (; route < routing->routes + routing->num_routes; ++route) { + if (!(route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) + continue; + + return route; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(__v4l2_subdev_next_active_route); + +int v4l2_subdev_set_routing_with_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing, + const struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_subdev_stream_configs *stream_configs; + unsigned int i; + int ret; + + ret = v4l2_subdev_set_routing(sd, state, routing); + if (ret) + return ret; + + stream_configs = &state->stream_configs; + + for (i = 0; i < stream_configs->num_configs; ++i) + stream_configs->configs[i].fmt = *fmt; + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_set_routing_with_fmt); + +struct v4l2_mbus_framefmt * +v4l2_subdev_state_get_stream_format(struct v4l2_subdev_state *state, + unsigned int pad, u32 stream) +{ + struct v4l2_subdev_stream_configs *stream_configs; + unsigned int i; + + lockdep_assert_held(state->lock); + + stream_configs = &state->stream_configs; + + for (i = 0; i < stream_configs->num_configs; ++i) { + if (stream_configs->configs[i].pad == pad && + stream_configs->configs[i].stream == stream) + return &stream_configs->configs[i].fmt; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_state_get_stream_format); + +struct v4l2_rect * +v4l2_subdev_state_get_stream_crop(struct v4l2_subdev_state *state, + unsigned int pad, u32 stream) +{ + struct v4l2_subdev_stream_configs *stream_configs; + unsigned int i; + + lockdep_assert_held(state->lock); + + stream_configs = &state->stream_configs; + + for (i = 0; i < stream_configs->num_configs; ++i) { + if (stream_configs->configs[i].pad == pad && + stream_configs->configs[i].stream == stream) + return &stream_configs->configs[i].crop; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_state_get_stream_crop); + +struct v4l2_rect * +v4l2_subdev_state_get_stream_compose(struct v4l2_subdev_state *state, + unsigned int pad, u32 stream) +{ + struct v4l2_subdev_stream_configs *stream_configs; + unsigned int i; + + lockdep_assert_held(state->lock); + + stream_configs = &state->stream_configs; + + for (i = 0; i < stream_configs->num_configs; ++i) { + if (stream_configs->configs[i].pad == pad && + stream_configs->configs[i].stream == stream) + return &stream_configs->configs[i].compose; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_state_get_stream_compose); + +int v4l2_subdev_routing_find_opposite_end(const struct v4l2_subdev_krouting *routing, + u32 pad, u32 stream, u32 *other_pad, + u32 *other_stream) +{ + unsigned int i; + + for (i = 0; i < routing->num_routes; ++i) { + struct v4l2_subdev_route *route = &routing->routes[i]; + + if (route->source_pad == pad && + route->source_stream == stream) { + if (other_pad) + *other_pad = route->sink_pad; + if (other_stream) + *other_stream = route->sink_stream; + return 0; + } + + if (route->sink_pad == pad && route->sink_stream == stream) { + if (other_pad) + *other_pad = route->source_pad; + if (other_stream) + *other_stream = route->source_stream; + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_routing_find_opposite_end); + +struct v4l2_mbus_framefmt * +v4l2_subdev_state_get_opposite_stream_format(struct v4l2_subdev_state *state, + u32 pad, u32 stream) +{ + u32 other_pad, other_stream; + int ret; + + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, + pad, stream, + &other_pad, &other_stream); + if (ret) + return NULL; + + return v4l2_subdev_state_get_stream_format(state, other_pad, + other_stream); +} +EXPORT_SYMBOL_GPL(v4l2_subdev_state_get_opposite_stream_format); + +u64 v4l2_subdev_state_xlate_streams(const struct v4l2_subdev_state *state, + u32 pad0, u32 pad1, u64 *streams) +{ + const struct v4l2_subdev_krouting *routing = &state->routing; + struct v4l2_subdev_route *route; + u64 streams0 = 0; + u64 streams1 = 0; + + for_each_active_route(routing, route) { + if (route->sink_pad == pad0 && route->source_pad == pad1 && + (*streams & BIT_ULL(route->sink_stream))) { + streams0 |= BIT_ULL(route->sink_stream); + streams1 |= BIT_ULL(route->source_stream); + } + if (route->source_pad == pad0 && route->sink_pad == pad1 && + (*streams & BIT_ULL(route->source_stream))) { + streams0 |= BIT_ULL(route->source_stream); + streams1 |= BIT_ULL(route->sink_stream); + } + } + + *streams = streams0; + return streams1; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_state_xlate_streams); + +int v4l2_subdev_routing_validate(struct v4l2_subdev *sd, + const struct v4l2_subdev_krouting *routing, + enum v4l2_subdev_routing_restriction disallow) +{ + u32 *remote_pads = NULL; + unsigned int i, j; + int ret = -EINVAL; + + if (disallow & V4L2_SUBDEV_ROUTING_NO_STREAM_MIX) { + remote_pads = kcalloc(sd->entity.num_pads, sizeof(*remote_pads), + GFP_KERNEL); + if (!remote_pads) + return -ENOMEM; + + for (i = 0; i < sd->entity.num_pads; ++i) + remote_pads[i] = U32_MAX; + } + + for (i = 0; i < routing->num_routes; ++i) { + const struct v4l2_subdev_route *route = &routing->routes[i]; + + /* Validate the sink and source pad numbers. */ + if (route->sink_pad >= sd->entity.num_pads || + !(sd->entity.pads[route->sink_pad].flags & MEDIA_PAD_FL_SINK)) { + dev_dbg(sd->dev, "route %u sink (%u) is not a sink pad\n", + i, route->sink_pad); + goto out; + } + + if (route->source_pad >= sd->entity.num_pads || + !(sd->entity.pads[route->source_pad].flags & MEDIA_PAD_FL_SOURCE)) { + dev_dbg(sd->dev, "route %u source (%u) is not a source pad\n", + i, route->source_pad); + goto out; + } + + /* + * V4L2_SUBDEV_ROUTING_NO_STREAM_MIX: Streams on the same pad + * may not be routed to streams on different pads. + */ + if (disallow & V4L2_SUBDEV_ROUTING_NO_STREAM_MIX) { + if (remote_pads[route->sink_pad] != U32_MAX && + remote_pads[route->sink_pad] != route->source_pad) { + dev_dbg(sd->dev, + "route %u attempts to mix %s streams\n", + i, "sink"); + goto out; + } + + if (remote_pads[route->source_pad] != U32_MAX && + remote_pads[route->source_pad] != route->sink_pad) { + dev_dbg(sd->dev, + "route %u attempts to mix %s streams\n", + i, "source"); + goto out; + } + + remote_pads[route->sink_pad] = route->source_pad; + remote_pads[route->source_pad] = route->sink_pad; + } + + for (j = i + 1; j < routing->num_routes; ++j) { + const struct v4l2_subdev_route *r = &routing->routes[j]; + + /* + * V4L2_SUBDEV_ROUTING_NO_1_TO_N: No two routes can + * originate from the same (sink) stream. + */ + if ((disallow & V4L2_SUBDEV_ROUTING_NO_1_TO_N) && + route->sink_pad == r->sink_pad && + route->sink_stream == r->sink_stream) { + dev_dbg(sd->dev, + "routes %u and %u originate from same sink (%u/%u)\n", + i, j, route->sink_pad, + route->sink_stream); + goto out; + } + + /* + * V4L2_SUBDEV_ROUTING_NO_N_TO_1: No two routes can end + * at the same (source) stream. + */ + if ((disallow & V4L2_SUBDEV_ROUTING_NO_N_TO_1) && + route->source_pad == r->source_pad && + route->source_stream == r->source_stream) { + dev_dbg(sd->dev, + "routes %u and %u end at same source (%u/%u)\n", + i, j, route->source_pad, + route->source_stream); + goto out; + } + } + } + + ret = 0; + +out: + kfree(remote_pads); + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_routing_validate); + +static int v4l2_subdev_enable_streams_fallback(struct v4l2_subdev *sd, u32 pad, + u64 streams_mask) +{ + struct device *dev = sd->entity.graph_obj.mdev->dev; + unsigned int i; + int ret; + + /* + * The subdev doesn't implement pad-based stream enable, fall back + * on the .s_stream() operation. This can only be done for subdevs that + * have a single source pad, as sd->enabled_streams is global to the + * subdev. + */ + if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) + return -EOPNOTSUPP; + + for (i = 0; i < sd->entity.num_pads; ++i) { + if (i != pad && sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) + return -EOPNOTSUPP; + } + + if (sd->enabled_streams & streams_mask) { + dev_dbg(dev, "set of streams %#llx already enabled on %s:%u\n", + streams_mask, sd->entity.name, pad); + return -EALREADY; + } + + /* Start streaming when the first streams are enabled. */ + if (!sd->enabled_streams) { + ret = v4l2_subdev_call(sd, video, s_stream, 1); + if (ret) + return ret; + } + + sd->enabled_streams |= streams_mask; + + return 0; +} + +int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad, + u64 streams_mask) +{ + struct device *dev = sd->entity.graph_obj.mdev->dev; + struct v4l2_subdev_state *state; + u64 found_streams = 0; + unsigned int i; + int ret; + + /* A few basic sanity checks first. */ + if (pad >= sd->entity.num_pads) + return -EINVAL; + + if (!streams_mask) + return 0; + + /* Fallback on .s_stream() if .enable_streams() isn't available. */ + if (!sd->ops->pad || !sd->ops->pad->enable_streams) + return v4l2_subdev_enable_streams_fallback(sd, pad, + streams_mask); + + state = v4l2_subdev_lock_and_get_active_state(sd); + + /* + * Verify that the requested streams exist and that they are not + * already enabled. + */ + for (i = 0; i < state->stream_configs.num_configs; ++i) { + struct v4l2_subdev_stream_config *cfg = + &state->stream_configs.configs[i]; + + if (cfg->pad != pad || !(streams_mask & BIT_ULL(cfg->stream))) + continue; + + found_streams |= BIT_ULL(cfg->stream); + + if (cfg->enabled) { + dev_dbg(dev, "stream %u already enabled on %s:%u\n", + cfg->stream, sd->entity.name, pad); + ret = -EALREADY; + goto done; + } + } + + if (found_streams != streams_mask) { + dev_dbg(dev, "streams 0x%llx not found on %s:%u\n", + streams_mask & ~found_streams, sd->entity.name, pad); + ret = -EINVAL; + goto done; + } + + /* Call the .enable_streams() operation. */ + ret = v4l2_subdev_call(sd, pad, enable_streams, state, pad, + streams_mask); + if (ret) + goto done; + + /* Mark the streams as enabled. */ + for (i = 0; i < state->stream_configs.num_configs; ++i) { + struct v4l2_subdev_stream_config *cfg = + &state->stream_configs.configs[i]; + + if (cfg->pad == pad && (streams_mask & BIT_ULL(cfg->stream))) + cfg->enabled = true; + } + +done: + v4l2_subdev_unlock_state(state); + + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_enable_streams); + +static int v4l2_subdev_disable_streams_fallback(struct v4l2_subdev *sd, u32 pad, + u64 streams_mask) +{ + struct device *dev = sd->entity.graph_obj.mdev->dev; + unsigned int i; + int ret; + + /* + * If the subdev doesn't implement pad-based stream enable, fall back + * on the .s_stream() operation. This can only be done for subdevs that + * have a single source pad, as sd->enabled_streams is global to the + * subdev. + */ + if (!(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) + return -EOPNOTSUPP; + + for (i = 0; i < sd->entity.num_pads; ++i) { + if (i != pad && sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) + return -EOPNOTSUPP; + } + + if ((sd->enabled_streams & streams_mask) != streams_mask) { + dev_dbg(dev, "set of streams %#llx already disabled on %s:%u\n", + streams_mask, sd->entity.name, pad); + return -EALREADY; + } + + /* Stop streaming when the last streams are disabled. */ + if (!(sd->enabled_streams & ~streams_mask)) { + ret = v4l2_subdev_call(sd, video, s_stream, 0); + if (ret) + return ret; + } + + sd->enabled_streams &= ~streams_mask; + + return 0; +} + +int v4l2_subdev_disable_streams(struct v4l2_subdev *sd, u32 pad, + u64 streams_mask) +{ + struct device *dev = sd->entity.graph_obj.mdev->dev; + struct v4l2_subdev_state *state; + u64 found_streams = 0; + unsigned int i; + int ret; + + /* A few basic sanity checks first. */ + if (pad >= sd->entity.num_pads) + return -EINVAL; + + if (!streams_mask) + return 0; + + /* Fallback on .s_stream() if .disable_streams() isn't available. */ + if (!sd->ops->pad || !sd->ops->pad->disable_streams) + return v4l2_subdev_disable_streams_fallback(sd, pad, + streams_mask); + + state = v4l2_subdev_lock_and_get_active_state(sd); + + /* + * Verify that the requested streams exist and that they are not + * already disabled. + */ + for (i = 0; i < state->stream_configs.num_configs; ++i) { + struct v4l2_subdev_stream_config *cfg = + &state->stream_configs.configs[i]; + + if (cfg->pad != pad || !(streams_mask & BIT_ULL(cfg->stream))) + continue; + + found_streams |= BIT_ULL(cfg->stream); + + if (!cfg->enabled) { + dev_dbg(dev, "stream %u already disabled on %s:%u\n", + cfg->stream, sd->entity.name, pad); + ret = -EALREADY; + goto done; + } + } + + if (found_streams != streams_mask) { + dev_dbg(dev, "streams 0x%llx not found on %s:%u\n", + streams_mask & ~found_streams, sd->entity.name, pad); + ret = -EINVAL; + goto done; + } + + /* Call the .disable_streams() operation. */ + ret = v4l2_subdev_call(sd, pad, disable_streams, state, pad, + streams_mask); + if (ret) + goto done; + + /* Mark the streams as disabled. */ + for (i = 0; i < state->stream_configs.num_configs; ++i) { + struct v4l2_subdev_stream_config *cfg = + &state->stream_configs.configs[i]; + + if (cfg->pad == pad && (streams_mask & BIT_ULL(cfg->stream))) + cfg->enabled = false; + } + +done: + v4l2_subdev_unlock_state(state); + + return ret; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_disable_streams); + +int v4l2_subdev_s_stream_helper(struct v4l2_subdev *sd, int enable) +{ + struct v4l2_subdev_state *state; + struct v4l2_subdev_route *route; + struct media_pad *pad; + u64 source_mask = 0; + int pad_index = -1; + + /* + * Find the source pad. This helper is meant for subdevs that have a + * single source pad, so failures shouldn't happen, but catch them + * loudly nonetheless as they indicate a driver bug. + */ + media_entity_for_each_pad(&sd->entity, pad) { + if (pad->flags & MEDIA_PAD_FL_SOURCE) { + pad_index = pad->index; + break; + } + } + + if (WARN_ON(pad_index == -1)) + return -EINVAL; + + /* + * As there's a single source pad, just collect all the source streams. + */ + state = v4l2_subdev_lock_and_get_active_state(sd); + + for_each_active_route(&state->routing, route) + source_mask |= BIT_ULL(route->source_stream); + + v4l2_subdev_unlock_state(state); + + if (enable) + return v4l2_subdev_enable_streams(sd, pad_index, source_mask); + else + return v4l2_subdev_disable_streams(sd, pad_index, source_mask); +} +EXPORT_SYMBOL_GPL(v4l2_subdev_s_stream_helper); + #endif /* CONFIG_VIDEO_V4L2_SUBDEV_API */ #endif /* CONFIG_MEDIA_CONTROLLER */ |