cregit-Linux how code gets into the kernel

Release 4.11 drivers/media/pci/tw686x/tw686x-core.c

/*
 * 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"; } }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García40100.00%3100.00%
Total40100.00%3100.00%


static int tw686x_dma_mode_get(char *buffer, struct kernel_param *kp) { return sprintf(buffer, "%s", dma_mode_name(dma_mode)); }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García2692.86%150.00%
Nicolas Iooss27.14%150.00%
Total28100.00%2100.00%


static int tw686x_dma_mode_set(const char *val, 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; }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García77100.00%3100.00%
Total77100.00%3100.00%

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); }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García99100.00%1100.00%
Total99100.00%1100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García58100.00%1100.00%
Total58100.00%1100.00%

/* * 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(unsigned long data) { struct tw686x_dev *dev = (struct tw686x_dev *)data; 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); }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García80100.00%1100.00%
Total80100.00%1100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García106100.00%1100.00%
Total106100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García306100.00%1100.00%
Total306100.00%1100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García86100.00%1100.00%
Total86100.00%1100.00%


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; } setup_timer(&dev->dma_delay_timer, tw686x_dma_delay, (unsigned long) dev); /* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García607100.00%2100.00%
Total607100.00%2100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García108100.00%1100.00%
Total108100.00%1100.00%

/* * 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");

Overall Contributors

PersonTokensPropCommitsCommitProp
Ezequiel García181299.83%466.67%
Nicolas Iooss20.11%116.67%
Hans Verkuil10.06%116.67%
Total1815100.00%6100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.