| Author | Tokens | Token Proportion | Commits | Commit Proportion |
|---|---|---|---|---|
| Ben Hoff | 4044 | 100.00% | 1 | 100.00% |
| Total | 4044 | 1 |
// SPDX-License-Identifier: GPL-2.0-only #include <linux/pci.h> #include <linux/types.h> #include <linux/iopoll.h> #include <linux/bitfield.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kthread.h> #include <linux/interrupt.h> #include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/ktime.h> #include <linux/math64.h> #include <linux/pm.h> #include <linux/freezer.h> #include <linux/pci_regs.h> #include <media/v4l2-ctrls.h> #include "hws.h" #include "hws_reg.h" #include "hws_video.h" #include "hws_irq.h" #include "hws_v4l2_ioctl.h" #define DRV_NAME "hws" #define HWS_BUSY_POLL_DELAY_US 10 #define HWS_BUSY_POLL_TIMEOUT_US 1000000 static unsigned long long hws_elapsed_us(u64 start_ns) { return div_u64(ktime_get_mono_fast_ns() - start_ns, 1000); } /* register layout inside HWS_REG_DEVICE_INFO */ #define DEVINFO_VER GENMASK(7, 0) #define DEVINFO_SUBVER GENMASK(15, 8) #define DEVINFO_YV12 GENMASK(31, 28) #define DEVINFO_HWKEY GENMASK(27, 24) #define DEVINFO_PORTID GENMASK(25, 24) /* low 2 bits of HW-key */ #define MAKE_ENTRY(__vend, __chip, __subven, __subdev, __configptr) \ { .vendor = (__vend), \ .device = (__chip), \ .subvendor = (__subven), \ .subdevice = (__subdev), \ .driver_data = (unsigned long)(__configptr) } /* * PCI IDs for HWS family cards. * * The subsystem IDs are fixed at 0x8888:0x0007 for this family. Some boards * enumerate with vendor ID 0x8888 or 0x1f33. Exact SKU names are not fully * pinned down yet; update these comments when vendor documentation or INF * strings are available. */ static const struct pci_device_id hws_pci_table[] = { /* HWS family, SKU unknown. */ MAKE_ENTRY(0x8888, 0x9534, 0x8888, 0x0007, NULL), MAKE_ENTRY(0x1F33, 0x8534, 0x8888, 0x0007, NULL), MAKE_ENTRY(0x1F33, 0x8554, 0x8888, 0x0007, NULL), /* HWS 2x2 HDMI family. */ MAKE_ENTRY(0x8888, 0x8524, 0x8888, 0x0007, NULL), /* HWS 2x2 SDI family. */ MAKE_ENTRY(0x1F33, 0x6524, 0x8888, 0x0007, NULL), /* HWS X4 HDMI family. */ MAKE_ENTRY(0x8888, 0x8504, 0x8888, 0x0007, NULL), /* HWS X4 SDI family. */ MAKE_ENTRY(0x8888, 0x6504, 0x8888, 0x0007, NULL), /* HWS family, SKU unknown. */ MAKE_ENTRY(0x8888, 0x8532, 0x8888, 0x0007, NULL), MAKE_ENTRY(0x8888, 0x8512, 0x8888, 0x0007, NULL), MAKE_ENTRY(0x8888, 0x8501, 0x8888, 0x0007, NULL), MAKE_ENTRY(0x1F33, 0x6502, 0x8888, 0x0007, NULL), /* HWS X4 HDMI family (alternate vendor ID). */ MAKE_ENTRY(0x1F33, 0x8504, 0x8888, 0x0007, NULL), /* HWS 2x2 HDMI family (alternate vendor ID). */ MAKE_ENTRY(0x1F33, 0x8524, 0x8888, 0x0007, NULL), {} }; static void enable_pcie_relaxed_ordering(struct pci_dev *dev) { pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN); } static void hws_configure_hardware_capabilities(struct hws_pcie_dev *hdev) { u16 id = hdev->device_id; /* select per-chip channel counts */ switch (id) { case 0x9534: case 0x6524: case 0x8524: case 0x8504: case 0x6504: hdev->cur_max_video_ch = 4; break; case 0x8532: hdev->cur_max_video_ch = 2; break; case 0x8512: case 0x6502: hdev->cur_max_video_ch = 2; break; case 0x8501: hdev->cur_max_video_ch = 1; break; default: hdev->cur_max_video_ch = 4; break; } /* universal buffer capacity */ hdev->max_hw_video_buf_sz = MAX_MM_VIDEO_SIZE; /* decide hardware-version and program DMA max size if needed */ if (hdev->device_ver > 121) { if (id == 0x8501 && hdev->device_ver == 122) { hdev->hw_ver = 0; } else { hdev->hw_ver = 1; u32 dma_max = (u32)(MAX_VIDEO_SCALER_SIZE / 16); writel(dma_max, hdev->bar0_base + HWS_REG_DMA_MAX_SIZE); /* readback to flush posted MMIO write */ (void)readl(hdev->bar0_base + HWS_REG_DMA_MAX_SIZE); } } else { hdev->hw_ver = 0; } } static void hws_stop_device(struct hws_pcie_dev *hws); static void hws_log_lifecycle_snapshot(struct hws_pcie_dev *hws, const char *action, const char *phase) { struct device *dev; u32 int_en, int_status, vcap, sys_status, dec_mode; if (!hws || !hws->pdev) return; dev = &hws->pdev->dev; if (!hws->bar0_base) { dev_dbg(dev, "lifecycle:%s:%s bar0-unmapped suspended=%d start_run=%d pci_lost=%d irq=%d\n", action, phase, READ_ONCE(hws->suspended), hws->start_run, hws->pci_lost, hws->irq); return; } int_en = readl(hws->bar0_base + INT_EN_REG_BASE); int_status = readl(hws->bar0_base + HWS_REG_INT_STATUS); vcap = readl(hws->bar0_base + HWS_REG_VCAP_ENABLE); sys_status = readl(hws->bar0_base + HWS_REG_SYS_STATUS); dec_mode = readl(hws->bar0_base + HWS_REG_DEC_MODE); dev_dbg(dev, "lifecycle:%s:%s suspended=%d start_run=%d pci_lost=%d irq=%d INT_EN=0x%08x INT_STATUS=0x%08x VCAP=0x%08x SYS=0x%08x DEC=0x%08x\n", action, phase, READ_ONCE(hws->suspended), hws->start_run, hws->pci_lost, hws->irq, int_en, int_status, vcap, sys_status, dec_mode); } static int read_chip_id(struct hws_pcie_dev *hdev) { u32 reg; /* mirror PCI IDs for later switches */ hdev->device_id = hdev->pdev->device; hdev->vendor_id = hdev->pdev->vendor; reg = readl(hdev->bar0_base + HWS_REG_DEVICE_INFO); hdev->device_ver = FIELD_GET(DEVINFO_VER, reg); hdev->sub_ver = FIELD_GET(DEVINFO_SUBVER, reg); hdev->support_yv12 = FIELD_GET(DEVINFO_YV12, reg); hdev->port_id = FIELD_GET(DEVINFO_PORTID, reg); hdev->max_hw_video_buf_sz = MAX_MM_VIDEO_SIZE; hdev->max_channels = 4; hdev->buf_allocated = false; hdev->main_task = NULL; hdev->start_run = false; hdev->pci_lost = 0; writel(0x00, hdev->bar0_base + HWS_REG_DEC_MODE); writel(0x10, hdev->bar0_base + HWS_REG_DEC_MODE); hws_configure_hardware_capabilities(hdev); dev_info(&hdev->pdev->dev, "chip detected: ver=%u subver=%u port=%u yv12=%u\n", hdev->device_ver, hdev->sub_ver, hdev->port_id, hdev->support_yv12); return 0; } static int main_ks_thread_handle(void *data) { struct hws_pcie_dev *pdx = data; set_freezable(); while (!kthread_should_stop()) { /* If we're suspending, don't touch hardware; just sleep/freeze. */ if (READ_ONCE(pdx->suspended)) { try_to_freeze(); schedule_timeout_interruptible(msecs_to_jiffies(1000)); continue; } /* avoid MMIO when suspended (guarded above) */ check_video_format(pdx); try_to_freeze(); /* cooperate with freezer each loop */ /* Sleep 1s or until signaled to wake/stop */ schedule_timeout_interruptible(msecs_to_jiffies(1000)); } dev_dbg(&pdx->pdev->dev, "%s: exiting\n", __func__); return 0; } static void hws_stop_kthread_action(void *data) { struct hws_pcie_dev *hws = data; struct task_struct *t; u64 start_ns; if (!hws) return; t = READ_ONCE(hws->main_task); if (!IS_ERR_OR_NULL(t)) { start_ns = ktime_get_mono_fast_ns(); dev_dbg(&hws->pdev->dev, "lifecycle:kthread-stop:begin task=%s[%d]\n", t->comm, t->pid); WRITE_ONCE(hws->main_task, NULL); kthread_stop(t); dev_dbg(&hws->pdev->dev, "lifecycle:kthread-stop:done (%lluus)\n", hws_elapsed_us(start_ns)); } } static int hws_alloc_seed_buffers(struct hws_pcie_dev *hws) { int ch; /* 64 KiB is plenty for a safe dummy; hardware needs 64-byte alignment. */ const size_t need = ALIGN(64 * 1024, 64); for (ch = 0; ch < hws->cur_max_video_ch; ch++) { #if defined(CONFIG_HAS_DMA) /* normal on PCIe platforms */ void *cpu = dma_alloc_coherent(&hws->pdev->dev, need, &hws->scratch_vid[ch].dma, GFP_KERNEL); #else void *cpu = NULL; #endif if (!cpu) { dev_warn(&hws->pdev->dev, "scratch: dma_alloc_coherent failed ch=%d\n", ch); /* not fatal: free earlier ones and continue without seeding */ while (--ch >= 0) { if (hws->scratch_vid[ch].cpu) dma_free_coherent(&hws->pdev->dev, hws->scratch_vid[ch].size, hws->scratch_vid[ch].cpu, hws->scratch_vid[ch].dma); hws->scratch_vid[ch].cpu = NULL; hws->scratch_vid[ch].size = 0; } return -ENOMEM; } hws->scratch_vid[ch].cpu = cpu; hws->scratch_vid[ch].size = need; } return 0; } static void hws_free_seed_buffers(struct hws_pcie_dev *hws) { int ch; for (ch = 0; ch < hws->cur_max_video_ch; ch++) { if (hws->scratch_vid[ch].cpu) { dma_free_coherent(&hws->pdev->dev, hws->scratch_vid[ch].size, hws->scratch_vid[ch].cpu, hws->scratch_vid[ch].dma); hws->scratch_vid[ch].cpu = NULL; hws->scratch_vid[ch].size = 0; } } } static void hws_seed_channel(struct hws_pcie_dev *hws, int ch) { dma_addr_t paddr = hws->scratch_vid[ch].dma; u32 lo = lower_32_bits(paddr); u32 hi = upper_32_bits(paddr); u32 pci_addr = lo & PCI_E_BAR_ADD_LOWMASK; lo &= PCI_E_BAR_ADD_MASK; /* Program 64-bit BAR remap entry for this channel (table @ 0x208 + ch * 8) */ writel_relaxed(hi, hws->bar0_base + PCI_ADDR_TABLE_BASE + 0x208 + ch * 8); writel_relaxed(lo, hws->bar0_base + PCI_ADDR_TABLE_BASE + 0x208 + ch * 8 + PCIE_BARADDROFSIZE); /* Program capture engine per-channel base/half */ writel_relaxed((ch + 1) * PCIEBAR_AXI_BASE + pci_addr, hws->bar0_base + CVBS_IN_BUF_BASE + ch * PCIE_BARADDROFSIZE); /* Half size: use either the current format's half or half of scratch. */ { u32 half = hws->video[ch].pix.half_size ? hws->video[ch].pix.half_size : (u32)(hws->scratch_vid[ch].size / 2); writel_relaxed(half / 16, hws->bar0_base + CVBS_IN_BUF_BASE2 + ch * PCIE_BARADDROFSIZE); } (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); /* flush posted writes */ } static void hws_seed_all_channels(struct hws_pcie_dev *hws) { int ch; for (ch = 0; ch < hws->cur_max_video_ch; ch++) { if (hws->scratch_vid[ch].cpu) hws_seed_channel(hws, ch); } } static void hws_irq_mask_gate(struct hws_pcie_dev *hws) { writel(0x00000000, hws->bar0_base + INT_EN_REG_BASE); (void)readl(hws->bar0_base + INT_EN_REG_BASE); } static void hws_irq_unmask_gate(struct hws_pcie_dev *hws) { writel(HWS_INT_EN_MASK, hws->bar0_base + INT_EN_REG_BASE); (void)readl(hws->bar0_base + INT_EN_REG_BASE); } static void hws_irq_clear_pending(struct hws_pcie_dev *hws) { u32 st = readl(hws->bar0_base + HWS_REG_INT_STATUS); if (st) { writel(st, hws->bar0_base + HWS_REG_INT_STATUS); /* W1C */ (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); } } static void hws_block_hotpaths(struct hws_pcie_dev *hws) { WRITE_ONCE(hws->suspended, true); if (hws->irq >= 0) disable_irq(hws->irq); if (!hws->bar0_base) return; hws_irq_mask_gate(hws); hws_irq_clear_pending(hws); } static int hws_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) { struct hws_pcie_dev *hws; int i, ret, irq; unsigned long irqf = 0; bool v4l2_registered = false; /* devres-backed device object */ hws = devm_kzalloc(&pdev->dev, sizeof(*hws), GFP_KERNEL); if (!hws) return -ENOMEM; hws->pdev = pdev; hws->irq = -1; hws->suspended = false; pci_set_drvdata(pdev, hws); /* 1) Enable device + bus mastering (managed) */ ret = pcim_enable_device(pdev); if (ret) return dev_err_probe(&pdev->dev, ret, "pcim_enable_device\n"); pci_set_master(pdev); /* 2) Map BAR0 (managed) */ ret = pcim_iomap_regions(pdev, BIT(0), KBUILD_MODNAME); if (ret) return dev_err_probe(&pdev->dev, ret, "pcim_iomap_regions BAR0\n"); hws->bar0_base = pcim_iomap_table(pdev)[0]; ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (ret) { dev_warn(&pdev->dev, "64-bit DMA mask unavailable, falling back to 32-bit (%d)\n", ret); ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); if (ret) return dev_err_probe(&pdev->dev, ret, "No suitable DMA configuration\n"); } else { dev_dbg(&pdev->dev, "Using 64-bit DMA mask\n"); } /* 3) Apply optional PCIe tuning. */ enable_pcie_relaxed_ordering(pdev); #ifdef CONFIG_ARCH_TI816X pcie_set_readrq(pdev, 128); #endif /* 4) Identify chip & capabilities */ read_chip_id(hws); dev_info(&pdev->dev, "Device VID=0x%04x DID=0x%04x\n", pdev->vendor, pdev->device); hws_init_video_sys(hws, false); /* 5) Init channels (video state, locks, vb2, ctrls) */ for (i = 0; i < hws->max_channels; i++) { ret = hws_video_init_channel(hws, i); if (ret) { dev_err(&pdev->dev, "video channel init failed (ch=%d)\n", i); goto err_unwind_channels; } } /* 6) Allocate scratch DMA and seed BAR table + channel base/half (legacy SetDMAAddress) */ ret = hws_alloc_seed_buffers(hws); if (!ret) hws_seed_all_channels(hws); /* 7) Start-run sequence. */ hws_init_video_sys(hws, false); /* A) Force legacy INTx; legacy used request_irq(pdev->irq, ..., IRQF_SHARED) */ pci_intx(pdev, 1); irqf = IRQF_SHARED; irq = pdev->irq; hws->irq = irq; dev_info(&pdev->dev, "IRQ mode: legacy INTx (shared), irq=%d\n", irq); /* B) Mask the device's global/bridge gate (INT_EN_REG_BASE) */ hws_irq_mask_gate(hws); /* C) Clear any sticky pending interrupt status (W1C) before we arm the line */ hws_irq_clear_pending(hws); /* D) Request the legacy shared interrupt line (no vectors/MSI/MSI-X) */ ret = devm_request_irq(&pdev->dev, irq, hws_irq_handler, irqf, dev_name(&pdev->dev), hws); if (ret) { dev_err(&pdev->dev, "request_irq(%d) failed: %d\n", irq, ret); goto err_unwind_channels; } /* E) Set the global interrupt enable bit in main control register */ { u32 ctl_reg = readl(hws->bar0_base + HWS_REG_CTL); ctl_reg |= HWS_CTL_IRQ_ENABLE_BIT; writel(ctl_reg, hws->bar0_base + HWS_REG_CTL); (void)readl(hws->bar0_base + HWS_REG_CTL); /* flush write */ dev_info(&pdev->dev, "Global IRQ enable bit set in control register\n"); } /* F) Open the global gate just like legacy did */ hws_irq_unmask_gate(hws); dev_info(&pdev->dev, "INT_EN_GATE readback=0x%08x\n", readl(hws->bar0_base + INT_EN_REG_BASE)); /* 11) Register V4L2 */ ret = hws_video_register(hws); if (ret) { dev_err(&pdev->dev, "video_register: %d\n", ret); goto err_unwind_channels; } v4l2_registered = true; /* 12) Background monitor thread (managed) */ hws->main_task = kthread_run(main_ks_thread_handle, hws, "hws-mon"); if (IS_ERR(hws->main_task)) { ret = PTR_ERR(hws->main_task); hws->main_task = NULL; dev_err(&pdev->dev, "kthread_run: %d\n", ret); goto err_unregister_va; } ret = devm_add_action_or_reset(&pdev->dev, hws_stop_kthread_action, hws); if (ret) { dev_err(&pdev->dev, "devm_add_action kthread_stop: %d\n", ret); goto err_unregister_va; /* reset already stopped the thread */ } /* 13) Final: show the line is armed */ dev_info(&pdev->dev, "irq handler installed on irq=%d\n", irq); return 0; err_unregister_va: hws_stop_device(hws); hws_video_unregister(hws); hws_free_seed_buffers(hws); return ret; err_unwind_channels: hws_free_seed_buffers(hws); if (!v4l2_registered) { while (--i >= 0) hws_video_cleanup_channel(hws, i); } return ret; } static int hws_check_busy(struct hws_pcie_dev *pdx) { void __iomem *reg = pdx->bar0_base + HWS_REG_SYS_STATUS; u32 val; int ret; /* poll until !(val & BUSY_BIT), sleeping HWS_BUSY_POLL_DELAY_US between reads */ ret = readl_poll_timeout(reg, val, !(val & HWS_SYS_DMA_BUSY_BIT), HWS_BUSY_POLL_DELAY_US, HWS_BUSY_POLL_TIMEOUT_US); if (ret) { dev_err(&pdx->pdev->dev, "SYS_STATUS busy bit never cleared (0x%08x)\n", val); return -ETIMEDOUT; } return 0; } static void hws_stop_dsp(struct hws_pcie_dev *hws) { u32 status; /* Read the decoder mode/status register */ status = readl(hws->bar0_base + HWS_REG_DEC_MODE); dev_dbg(&hws->pdev->dev, "%s: status=0x%08x\n", __func__, status); /* If the device looks unplugged/stuck, bail out */ if (status == 0xFFFFFFFF) return; /* Tell the DSP to stop */ writel(0x10, hws->bar0_base + HWS_REG_DEC_MODE); if (hws_check_busy(hws)) dev_warn(&hws->pdev->dev, "DSP busy timeout on stop\n"); /* Disable video capture engine in the DSP */ writel(0x0, hws->bar0_base + HWS_REG_VCAP_ENABLE); } /* Publish stop so ISR/BH will not touch video buffers anymore. */ static void hws_publish_stop_flags(struct hws_pcie_dev *hws) { unsigned int i; for (i = 0; i < hws->cur_max_video_ch; ++i) { struct hws_video *v = &hws->video[i]; WRITE_ONCE(v->cap_active, false); WRITE_ONCE(v->stop_requested, true); } smp_wmb(); /* make flags visible before we touch MMIO/queues */ } /* Drain engines + ISR/BH after flags are published. */ static void hws_drain_after_stop(struct hws_pcie_dev *hws) { u32 ackmask = 0; unsigned int i; u64 start_ns = ktime_get_mono_fast_ns(); /* Mask device enables: no new DMA starts. */ writel(0x0, hws->bar0_base + HWS_REG_VCAP_ENABLE); (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); /* flush */ /* Let any in-flight DMAs finish (best-effort). */ (void)hws_check_busy(hws); /* Ack any latched VDONE. */ for (i = 0; i < hws->cur_max_video_ch; ++i) ackmask |= HWS_INT_VDONE_BIT(i); if (ackmask) { writel(ackmask, hws->bar0_base + HWS_REG_INT_STATUS); (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); } /* Ensure no hard IRQ is still running. */ if (hws->irq >= 0) synchronize_irq(hws->irq); dev_dbg(&hws->pdev->dev, "lifecycle:drain-after-stop:done (%lluus)\n", hws_elapsed_us(start_ns)); } static void hws_stop_device(struct hws_pcie_dev *hws) { u32 status = readl(hws->bar0_base + HWS_REG_SYS_STATUS); u64 start_ns = ktime_get_mono_fast_ns(); bool live = status != 0xFFFFFFFF; dev_dbg(&hws->pdev->dev, "%s: status=0x%08x\n", __func__, status); if (!live) { hws->pci_lost = true; goto out; } hws_log_lifecycle_snapshot(hws, "stop-device", "begin"); /* Make ISR/BH a no-op, then drain engines/IRQ. */ hws_publish_stop_flags(hws); hws_drain_after_stop(hws); /* 1) Stop the on-board DSP */ hws_stop_dsp(hws); out: hws->start_run = false; if (live) hws_log_lifecycle_snapshot(hws, "stop-device", "end"); else dev_dbg(&hws->pdev->dev, "lifecycle:stop-device:device-lost\n"); dev_dbg(&hws->pdev->dev, "lifecycle:stop-device:done (%lluus)\n", hws_elapsed_us(start_ns)); dev_dbg(&hws->pdev->dev, "%s: complete\n", __func__); } static int hws_quiesce_for_transition(struct hws_pcie_dev *hws, const char *action, bool stop_thread) { struct device *dev = &hws->pdev->dev; u64 start_ns = ktime_get_mono_fast_ns(); u64 step_ns; int vret; hws_log_lifecycle_snapshot(hws, action, "begin"); step_ns = ktime_get_mono_fast_ns(); hws_block_hotpaths(hws); dev_dbg(dev, "lifecycle:%s:block-hotpaths (%lluus)\n", action, hws_elapsed_us(step_ns)); hws_log_lifecycle_snapshot(hws, action, "blocked"); if (stop_thread) { step_ns = ktime_get_mono_fast_ns(); hws_stop_kthread_action(hws); dev_dbg(dev, "lifecycle:%s:stop-kthread (%lluus)\n", action, hws_elapsed_us(step_ns)); } step_ns = ktime_get_mono_fast_ns(); vret = hws_video_quiesce(hws, action); dev_dbg(dev, "lifecycle:%s:video-quiesce ret=%d (%lluus)\n", action, vret, hws_elapsed_us(step_ns)); if (vret) dev_warn(dev, "lifecycle:%s video quiesce returned %d\n", action, vret); step_ns = ktime_get_mono_fast_ns(); hws_stop_device(hws); dev_dbg(dev, "lifecycle:%s:stop-device (%lluus)\n", action, hws_elapsed_us(step_ns)); hws_log_lifecycle_snapshot(hws, action, "end"); dev_dbg(dev, "lifecycle:%s:quiesce-done ret=%d (%lluus)\n", action, vret, hws_elapsed_us(start_ns)); return vret; } static void hws_remove(struct pci_dev *pdev) { struct hws_pcie_dev *hws = pci_get_drvdata(pdev); u64 start_ns; if (!hws) return; start_ns = ktime_get_mono_fast_ns(); dev_info(&pdev->dev, "lifecycle:remove begin\n"); hws_log_lifecycle_snapshot(hws, "remove", "begin"); /* Stop the monitor thread before tearing down V4L2/vb2 objects. */ hws_block_hotpaths(hws); hws_stop_kthread_action(hws); /* Stop hardware and capture cleanly. */ hws_stop_device(hws); /* Unregister V4L2 resources. */ hws_video_unregister(hws); /* Release seeded DMA buffers */ hws_free_seed_buffers(hws); /* kthread is stopped by the devm action registered in probe. */ hws_log_lifecycle_snapshot(hws, "remove", "end"); dev_info(&pdev->dev, "lifecycle:remove done (%lluus)\n", hws_elapsed_us(start_ns)); } #ifdef CONFIG_PM_SLEEP static int hws_pm_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct hws_pcie_dev *hws = pci_get_drvdata(pdev); int vret; u64 start_ns = ktime_get_mono_fast_ns(); u64 step_ns; dev_info(dev, "lifecycle:pm_suspend begin\n"); vret = hws_quiesce_for_transition(hws, "pm_suspend", false); step_ns = ktime_get_mono_fast_ns(); pci_save_state(pdev); pci_clear_master(pdev); pci_disable_device(pdev); pci_set_power_state(pdev, PCI_D3hot); dev_dbg(dev, "lifecycle:pm_suspend:pci-d3hot (%lluus)\n", hws_elapsed_us(step_ns)); dev_info(dev, "lifecycle:pm_suspend done ret=%d (%lluus)\n", vret, hws_elapsed_us(start_ns)); return 0; } static int hws_pm_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct hws_pcie_dev *hws = pci_get_drvdata(pdev); int ret; u64 start_ns = ktime_get_mono_fast_ns(); u64 step_ns; dev_info(dev, "lifecycle:pm_resume begin\n"); /* Back to D0 and re-enable the function */ step_ns = ktime_get_mono_fast_ns(); pci_set_power_state(pdev, PCI_D0); ret = pci_enable_device(pdev); if (ret) { dev_err(dev, "pci_enable_device: %d\n", ret); return ret; } pci_restore_state(pdev); pci_set_master(pdev); dev_dbg(dev, "lifecycle:pm_resume:pci-enable (%lluus)\n", hws_elapsed_us(step_ns)); /* Reapply any PCIe tuning lost across D3 */ enable_pcie_relaxed_ordering(pdev); /* Reinitialize chip-side capabilities / registers */ step_ns = ktime_get_mono_fast_ns(); read_chip_id(hws); /* Re-seed BAR remaps/DMA windows and restart the capture core */ hws_seed_all_channels(hws); hws_init_video_sys(hws, true); hws_irq_clear_pending(hws); dev_dbg(dev, "lifecycle:pm_resume:chip-reinit (%lluus)\n", hws_elapsed_us(step_ns)); /* IRQs can be re-enabled now that MMIO is sane */ step_ns = ktime_get_mono_fast_ns(); if (hws->irq >= 0) enable_irq(hws->irq); WRITE_ONCE(hws->suspended, false); dev_dbg(dev, "lifecycle:pm_resume:irq-unsuspend (%lluus)\n", hws_elapsed_us(step_ns)); /* vb2: nothing mandatory; userspace will STREAMON again when ready */ step_ns = ktime_get_mono_fast_ns(); hws_video_pm_resume(hws); dev_dbg(dev, "lifecycle:pm_resume:video-resume (%lluus)\n", hws_elapsed_us(step_ns)); hws_log_lifecycle_snapshot(hws, "pm_resume", "end"); dev_info(dev, "lifecycle:pm_resume done (%lluus)\n", hws_elapsed_us(start_ns)); return 0; } static SIMPLE_DEV_PM_OPS(hws_pm_ops, hws_pm_suspend, hws_pm_resume); # define HWS_PM_OPS (&hws_pm_ops) #else # define HWS_PM_OPS NULL #endif static void hws_shutdown(struct pci_dev *pdev) { struct hws_pcie_dev *hws = pci_get_drvdata(pdev); int vret = 0; u64 start_ns = ktime_get_mono_fast_ns(); u64 step_ns; if (!hws) return; dev_info(&pdev->dev, "lifecycle:pci_shutdown begin\n"); vret = hws_quiesce_for_transition(hws, "pci_shutdown", true); step_ns = ktime_get_mono_fast_ns(); pci_clear_master(pdev); dev_dbg(&pdev->dev, "lifecycle:pci_shutdown:clear-master (%lluus)\n", hws_elapsed_us(step_ns)); dev_info(&pdev->dev, "lifecycle:pci_shutdown done ret=%d (%lluus)\n", vret, hws_elapsed_us(start_ns)); } static struct pci_driver hws_pci_driver = { .name = KBUILD_MODNAME, .id_table = hws_pci_table, .probe = hws_probe, .remove = hws_remove, .shutdown = hws_shutdown, .driver = { .pm = HWS_PM_OPS, }, }; MODULE_DEVICE_TABLE(pci, hws_pci_table); static int __init pcie_hws_init(void) { return pci_register_driver(&hws_pci_driver); } static void __exit pcie_hws_exit(void) { pci_unregister_driver(&hws_pci_driver); } module_init(pcie_hws_init); module_exit(pcie_hws_exit); MODULE_DESCRIPTION(DRV_NAME); MODULE_AUTHOR("Ben Hoff <hoff.benjamin.k@gmail.com>"); MODULE_AUTHOR("Sales <sales@avmatrix.com>"); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS("DMA_BUF");
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