123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- /*
- * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar
- *
- * Based on original driver by Krzysztof Ha?asa:
- * Copyright (C) 2015 Industrial Research Institute for Automation
- * and Measurements PIAP
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License
- * as published by the Free Software Foundation.
- *
- * Notes
- * -----
- *
- * 1. Under stress-testing, it has been observed that the PCIe link
- * goes down, without reason. Therefore, the driver takes special care
- * to allow device hot-unplugging.
- *
- * 2. TW686X devices are capable of setting a few different DMA modes,
- * including: scatter-gather, field and frame modes. However,
- * under stress testings it has been found that the machine can
- * freeze completely if DMA registers are programmed while streaming
- * is active.
- *
- * Therefore, driver implements a dma_mode called 'memcpy' which
- * avoids cycling the DMA buffers, and insteads allocates extra DMA buffers
- * and then copies into vmalloc'ed user buffers.
- *
- * In addition to this, when streaming is on, the driver tries to access
- * hardware registers as infrequently as possible. This is done by using
- * a timer to limit the rate at which DMA is reset on DMA channels error.
- */
- #include <linux/init.h>
- #include <linux/interrupt.h>
- #include <linux/delay.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/pci_ids.h>
- #include <linux/slab.h>
- #include <linux/timer.h>
- #include "tw686x.h"
- #include "tw686x-regs.h"
- /*
- * This module parameter allows to control the DMA_TIMER_INTERVAL value.
- * The DMA_TIMER_INTERVAL register controls the minimum DMA interrupt
- * time span (iow, the maximum DMA interrupt rate) thus allowing for
- * IRQ coalescing.
- *
- * The chip datasheet does not mention a time unit for this value, so
- * users wanting fine-grain control over the interrupt rate should
- * determine the desired value through testing.
- */
- static u32 dma_interval = 0x00098968;
- module_param(dma_interval, int, 0444);
- MODULE_PARM_DESC(dma_interval, "Minimum time span for DMA interrupting host");
- static unsigned int dma_mode = TW686X_DMA_MODE_MEMCPY;
- static const char *dma_mode_name(unsigned int mode)
- {
- switch (mode) {
- case TW686X_DMA_MODE_MEMCPY:
- return "memcpy";
- case TW686X_DMA_MODE_CONTIG:
- return "contig";
- case TW686X_DMA_MODE_SG:
- return "sg";
- default:
- return "unknown";
- }
- }
- static int tw686x_dma_mode_get(char *buffer, const struct kernel_param *kp)
- {
- return sprintf(buffer, "%s", dma_mode_name(dma_mode));
- }
- static int tw686x_dma_mode_set(const char *val, const struct kernel_param *kp)
- {
- if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_MEMCPY)))
- dma_mode = TW686X_DMA_MODE_MEMCPY;
- else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_CONTIG)))
- dma_mode = TW686X_DMA_MODE_CONTIG;
- else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_SG)))
- dma_mode = TW686X_DMA_MODE_SG;
- else
- return -EINVAL;
- return 0;
- }
- module_param_call(dma_mode, tw686x_dma_mode_set, tw686x_dma_mode_get,
- &dma_mode, S_IRUGO|S_IWUSR);
- MODULE_PARM_DESC(dma_mode, "DMA operation mode (memcpy/contig/sg, default=memcpy)");
- void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel)
- {
- u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
- u32 dma_cmd = reg_read(dev, DMA_CMD);
- dma_en &= ~BIT(channel);
- dma_cmd &= ~BIT(channel);
- /* Must remove it from pending too */
- dev->pending_dma_en &= ~BIT(channel);
- dev->pending_dma_cmd &= ~BIT(channel);
- /* Stop DMA if no channels are enabled */
- if (!dma_en)
- dma_cmd = 0;
- reg_write(dev, DMA_CHANNEL_ENABLE, dma_en);
- reg_write(dev, DMA_CMD, dma_cmd);
- }
- void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel)
- {
- u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
- u32 dma_cmd = reg_read(dev, DMA_CMD);
- dev->pending_dma_en |= dma_en | BIT(channel);
- dev->pending_dma_cmd |= dma_cmd | DMA_CMD_ENABLE | BIT(channel);
- }
- /*
- * The purpose of this awful hack is to avoid enabling the DMA
- * channels "too fast" which makes some TW686x devices very
- * angry and freeze the CPU (see note 1).
- */
- static void tw686x_dma_delay(struct timer_list *t)
- {
- struct tw686x_dev *dev = from_timer(dev, t, dma_delay_timer);
- unsigned long flags;
- spin_lock_irqsave(&dev->lock, flags);
- reg_write(dev, DMA_CHANNEL_ENABLE, dev->pending_dma_en);
- reg_write(dev, DMA_CMD, dev->pending_dma_cmd);
- dev->pending_dma_en = 0;
- dev->pending_dma_cmd = 0;
- spin_unlock_irqrestore(&dev->lock, flags);
- }
- static void tw686x_reset_channels(struct tw686x_dev *dev, unsigned int ch_mask)
- {
- u32 dma_en, dma_cmd;
- dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
- dma_cmd = reg_read(dev, DMA_CMD);
- /*
- * Save pending register status, the timer will
- * restore them.
- */
- dev->pending_dma_en |= dma_en;
- dev->pending_dma_cmd |= dma_cmd;
- /* Disable the reset channels */
- reg_write(dev, DMA_CHANNEL_ENABLE, dma_en & ~ch_mask);
- if ((dma_en & ~ch_mask) == 0) {
- dev_dbg(&dev->pci_dev->dev, "reset: stopping DMA\n");
- dma_cmd &= ~DMA_CMD_ENABLE;
- }
- reg_write(dev, DMA_CMD, dma_cmd & ~ch_mask);
- }
- static irqreturn_t tw686x_irq(int irq, void *dev_id)
- {
- struct tw686x_dev *dev = (struct tw686x_dev *)dev_id;
- unsigned int video_requests, audio_requests, reset_ch;
- u32 fifo_status, fifo_signal, fifo_ov, fifo_bad, fifo_errors;
- u32 int_status, dma_en, video_en, pb_status;
- unsigned long flags;
- int_status = reg_read(dev, INT_STATUS); /* cleared on read */
- fifo_status = reg_read(dev, VIDEO_FIFO_STATUS);
- /* INT_STATUS does not include FIFO_STATUS errors! */
- if (!int_status && !TW686X_FIFO_ERROR(fifo_status))
- return IRQ_NONE;
- if (int_status & INT_STATUS_DMA_TOUT) {
- dev_dbg(&dev->pci_dev->dev,
- "DMA timeout. Resetting DMA for all channels\n");
- reset_ch = ~0;
- goto reset_channels;
- }
- spin_lock_irqsave(&dev->lock, flags);
- dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
- spin_unlock_irqrestore(&dev->lock, flags);
- video_en = dma_en & 0xff;
- fifo_signal = ~(fifo_status & 0xff) & video_en;
- fifo_ov = fifo_status >> 24;
- fifo_bad = fifo_status >> 16;
- /* Mask of channels with signal and FIFO errors */
- fifo_errors = fifo_signal & (fifo_ov | fifo_bad);
- reset_ch = 0;
- pb_status = reg_read(dev, PB_STATUS);
- /* Coalesce video frame/error events */
- video_requests = (int_status & video_en) | fifo_errors;
- audio_requests = (int_status & dma_en) >> 8;
- if (video_requests)
- tw686x_video_irq(dev, video_requests, pb_status,
- fifo_status, &reset_ch);
- if (audio_requests)
- tw686x_audio_irq(dev, audio_requests, pb_status);
- reset_channels:
- if (reset_ch) {
- spin_lock_irqsave(&dev->lock, flags);
- tw686x_reset_channels(dev, reset_ch);
- spin_unlock_irqrestore(&dev->lock, flags);
- mod_timer(&dev->dma_delay_timer,
- jiffies + msecs_to_jiffies(100));
- }
- return IRQ_HANDLED;
- }
- static void tw686x_dev_release(struct v4l2_device *v4l2_dev)
- {
- struct tw686x_dev *dev = container_of(v4l2_dev, struct tw686x_dev,
- v4l2_dev);
- unsigned int ch;
- for (ch = 0; ch < max_channels(dev); ch++)
- v4l2_ctrl_handler_free(&dev->video_channels[ch].ctrl_handler);
- v4l2_device_unregister(&dev->v4l2_dev);
- kfree(dev->audio_channels);
- kfree(dev->video_channels);
- kfree(dev);
- }
- static int tw686x_probe(struct pci_dev *pci_dev,
- const struct pci_device_id *pci_id)
- {
- struct tw686x_dev *dev;
- int err;
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
- if (!dev)
- return -ENOMEM;
- dev->type = pci_id->driver_data;
- dev->dma_mode = dma_mode;
- sprintf(dev->name, "tw%04X", pci_dev->device);
- dev->video_channels = kcalloc(max_channels(dev),
- sizeof(*dev->video_channels), GFP_KERNEL);
- if (!dev->video_channels) {
- err = -ENOMEM;
- goto free_dev;
- }
- dev->audio_channels = kcalloc(max_channels(dev),
- sizeof(*dev->audio_channels), GFP_KERNEL);
- if (!dev->audio_channels) {
- err = -ENOMEM;
- goto free_video;
- }
- pr_info("%s: PCI %s, IRQ %d, MMIO 0x%lx (%s mode)\n", dev->name,
- pci_name(pci_dev), pci_dev->irq,
- (unsigned long)pci_resource_start(pci_dev, 0),
- dma_mode_name(dma_mode));
- dev->pci_dev = pci_dev;
- if (pci_enable_device(pci_dev)) {
- err = -EIO;
- goto free_audio;
- }
- pci_set_master(pci_dev);
- err = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
- if (err) {
- dev_err(&pci_dev->dev, "32-bit PCI DMA not supported\n");
- err = -EIO;
- goto disable_pci;
- }
- err = pci_request_regions(pci_dev, dev->name);
- if (err) {
- dev_err(&pci_dev->dev, "unable to request PCI region\n");
- goto disable_pci;
- }
- dev->mmio = pci_ioremap_bar(pci_dev, 0);
- if (!dev->mmio) {
- dev_err(&pci_dev->dev, "unable to remap PCI region\n");
- err = -ENOMEM;
- goto free_region;
- }
- /* Reset all subsystems */
- reg_write(dev, SYS_SOFT_RST, 0x0f);
- mdelay(1);
- reg_write(dev, SRST[0], 0x3f);
- if (max_channels(dev) > 4)
- reg_write(dev, SRST[1], 0x3f);
- /* Disable the DMA engine */
- reg_write(dev, DMA_CMD, 0);
- reg_write(dev, DMA_CHANNEL_ENABLE, 0);
- /* Enable DMA FIFO overflow and pointer check */
- reg_write(dev, DMA_CONFIG, 0xffffff04);
- reg_write(dev, DMA_CHANNEL_TIMEOUT, 0x140c8584);
- reg_write(dev, DMA_TIMER_INTERVAL, dma_interval);
- spin_lock_init(&dev->lock);
- err = request_irq(pci_dev->irq, tw686x_irq, IRQF_SHARED,
- dev->name, dev);
- if (err < 0) {
- dev_err(&pci_dev->dev, "unable to request interrupt\n");
- goto iounmap;
- }
- timer_setup(&dev->dma_delay_timer, tw686x_dma_delay, 0);
- /*
- * This must be set right before initializing v4l2_dev.
- * It's used to release resources after the last handle
- * held is released.
- */
- dev->v4l2_dev.release = tw686x_dev_release;
- err = tw686x_video_init(dev);
- if (err) {
- dev_err(&pci_dev->dev, "can't register video\n");
- goto free_irq;
- }
- err = tw686x_audio_init(dev);
- if (err)
- dev_warn(&pci_dev->dev, "can't register audio\n");
- pci_set_drvdata(pci_dev, dev);
- return 0;
- free_irq:
- free_irq(pci_dev->irq, dev);
- iounmap:
- pci_iounmap(pci_dev, dev->mmio);
- free_region:
- pci_release_regions(pci_dev);
- disable_pci:
- pci_disable_device(pci_dev);
- free_audio:
- kfree(dev->audio_channels);
- free_video:
- kfree(dev->video_channels);
- free_dev:
- kfree(dev);
- return err;
- }
- static void tw686x_remove(struct pci_dev *pci_dev)
- {
- struct tw686x_dev *dev = pci_get_drvdata(pci_dev);
- unsigned long flags;
- /* This guarantees the IRQ handler is no longer running,
- * which means we can kiss good-bye some resources.
- */
- free_irq(pci_dev->irq, dev);
- tw686x_video_free(dev);
- tw686x_audio_free(dev);
- del_timer_sync(&dev->dma_delay_timer);
- pci_iounmap(pci_dev, dev->mmio);
- pci_release_regions(pci_dev);
- pci_disable_device(pci_dev);
- /*
- * Setting pci_dev to NULL allows to detect hardware is no longer
- * available and will be used by vb2_ops. This is required because
- * the device sometimes hot-unplugs itself as the result of a PCIe
- * link down.
- * The lock is really important here.
- */
- spin_lock_irqsave(&dev->lock, flags);
- dev->pci_dev = NULL;
- spin_unlock_irqrestore(&dev->lock, flags);
- /*
- * This calls tw686x_dev_release if it's the last reference.
- * Otherwise, release is postponed until there are no users left.
- */
- v4l2_device_put(&dev->v4l2_dev);
- }
- /*
- * On TW6864 and TW6868, all channels share the pair of video DMA SG tables,
- * with 10-bit start_idx and end_idx determining start and end of frame buffer
- * for particular channel.
- * TW6868 with all its 8 channels would be problematic (only 127 SG entries per
- * channel) but we support only 4 channels on this chip anyway (the first
- * 4 channels are driven with internal video decoder, the other 4 would require
- * an external TW286x part).
- *
- * On TW6865 and TW6869, each channel has its own DMA SG table, with indexes
- * starting with 0. Both chips have complete sets of internal video decoders
- * (respectively 4 or 8-channel).
- *
- * All chips have separate SG tables for two video frames.
- */
- /* driver_data is number of A/V channels */
- static const struct pci_device_id tw686x_pci_tbl[] = {
- {
- PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6864),
- .driver_data = 4
- },
- {
- PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6865), /* not tested */
- .driver_data = 4 | TYPE_SECOND_GEN
- },
- /*
- * TW6868 supports 8 A/V channels with an external TW2865 chip;
- * not supported by the driver.
- */
- {
- PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6868), /* not tested */
- .driver_data = 4
- },
- {
- PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6869),
- .driver_data = 8 | TYPE_SECOND_GEN},
- {}
- };
- MODULE_DEVICE_TABLE(pci, tw686x_pci_tbl);
- static struct pci_driver tw686x_pci_driver = {
- .name = "tw686x",
- .id_table = tw686x_pci_tbl,
- .probe = tw686x_probe,
- .remove = tw686x_remove,
- };
- module_pci_driver(tw686x_pci_driver);
- MODULE_DESCRIPTION("Driver for video frame grabber cards based on Intersil/Techwell TW686[4589]");
- MODULE_AUTHOR("Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>");
- MODULE_AUTHOR("Krzysztof Ha?asa <khalasa@piap.pl>");
- MODULE_LICENSE("GPL v2");
|