Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Ezequiel García | 1796 | 98.84% | 4 | 44.44% |
Kees Cook | 16 | 0.88% | 2 | 22.22% |
Thomas Gleixner | 2 | 0.11% | 1 | 11.11% |
Nicolas Iooss | 2 | 0.11% | 1 | 11.11% |
Hans Verkuil | 1 | 0.06% | 1 | 11.11% |
Total | 1817 | 9 |
// SPDX-License-Identifier: GPL-2.0-only /* * 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 * * 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");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1